blob: 3e1509484f4889fd60ffa8f9de5bde7adb664193 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.extensions.scripting.sandbox.loader.support;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
/**
*
*/
public abstract class AbstractThrowAwayClassLoader extends URLClassLoader
implements ThrowAwayClassLoader {
/**
* The size of the buffer we're going to use to copy the contents from a stream to a byte array.
*/
private static final int BUFFER_SIZE = 4096;
/**
* Indicates when this ClassLoader has been created.
*/
private final long _timestamp;
/**
* The name of the class that this class loader is going to load.
*/
private final String _className;
// ------------------------------------------ Constructors
public AbstractThrowAwayClassLoader(String className, ClassLoader parentClassLoader) {
super(new URL[0], parentClassLoader);
if (className == null) {
throw new IllegalArgumentException("The given class name must not be null.");
}
// Save a timestamp of the time this class loader has been created. In doing
// so, we're able to tell if this class loader is already outdated or not.
this._timestamp = System.currentTimeMillis();
this._className = className;
}
// ------------------------------------------ ThrowAwayClassLoader methods
/**
* <p>Loads the class with the specified class name. However, note that implementing
* classes are just supposed to load a single class, so if you want to load a different
* class than that, this class loader will just delegate to the parent class loader.</p>
*
* @param className the name of the class you want to load
* @param resolve if <tt>true</tt> then resolve the class
* @return the resulting Class reference
* @throws ClassNotFoundException if the class could not be found
*/
public Class loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class c;
// Note that this class loader is only supposed to load a specific Class reference,
// hence the check against the class name. Otherwise this class loader would try to
// resolve class files for dependent classes as well, which means that there would
// be different versions of the same Class reference in the system.
if (isEligibleForLoading(className)) {
// First, check if the class has already been loaded
c = findLoadedClass(className);
if (c == null) {
// Note that execution reaches this point only if we're either updating a
// dynamically loaded class or loading it for the first time. Otherwise
// this ClassLoader would have returned an already loaded class (see the
// call to findLoadedClass()).
c = findClass(className);
if (resolve) {
resolveClass(c);
}
}
}
// If this class loader isn't supposed to load the given class it doesn't
// necessarily mean, that we're not dealing with a dynamic class here.
// However, if that's the case, we really want to use the same class loader
// (i.e. the same ClassFileLoader instance) as Spring does, hence the
// delegation to the parent class loader (i.e. the ReloadingClassLoader
// again).
else {
c = super.loadClass(className, resolve);
}
return c;
}
/**
* <p>Returns <code>true</code> if the given "last modified"-timestamp is
* more recent than the time stamp of this class loader, i.e. if this class loader
* is to be destroyed as there is a newer class file available.
*
* @param lastModified the "last modified"-timestamp of the class file you want to load
* @return <code>true</code> if the given "last modified"-timestamp is
* more recent than the time stamp of this ClassLoader
*/
public boolean isOutdated(long lastModified) {
return _timestamp < lastModified;
}
// ------------------------------------------ Utility methods
/**
* <p>Determines whether this class loader is supposed to load the given class.</p>
*
* @param className the name of the class
* @return <code>true</code>, if this class loader is supposed to load the
* given class, <code>false</code> otherwise
*/
protected boolean isEligibleForLoading(String className) {
return getClassName().equals(className);
}
/**
* <p>Finds and loads the class with the specified name from the compilation path.</p>
*
* @param className the name of the class
* @return the resulting class
* @throws ClassNotFoundException if the class could not be found
*/
protected Class findClass(final String className) throws ClassNotFoundException {
if (isEligibleForLoading(className)) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws Exception {
InputStream stream = null;
try {
// Load the raw bytes of the class file into the memory ..
stream = openStreamForClass(className);
if (stream != null) {
byte[] buffer = loadClassFromStream(stream);
// .. and create an according Class object.
return defineClass(className, buffer, 0, buffer.length);
} else {
throw new ClassNotFoundException(
"Cannot find the resource that defines the class '" + className + "'.");
}
}
catch (IOException ex) {
throw new ClassNotFoundException(
"Cannot load the raw byte contents for the class '" + className + "'.", ex);
}
finally {
if (stream != null) {
stream.close();
}
}
}
});
}
catch (PrivilegedActionException e) {
throw (ClassNotFoundException) e.getException();
}
} else {
throw new ClassNotFoundException(
"This class loader only knows how to load the class '" + getClassName() + "'.");
}
}
/**
* <p>Returns the name of the class that this class loader is going to load.</p>
*
* @return the name of the class that this class loader is going to load
*/
protected String getClassName() {
return _className;
}
/**
* <p>Loads the byte array that you can use to define the given class
* afterwards using a call to {@link #defineClass}.</p>
*
* @param stream a stream referencing e.g. a .class file
* @return the byte array that you can use to define the given class
* @throws IOException if an I/O error occurs
*/
private byte[] loadClassFromStream(InputStream stream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream(BUFFER_SIZE * 5);
byte[] buffer = new byte[BUFFER_SIZE];
int readBytes;
while ((readBytes = stream.read(buffer)) != -1) {
result.write(buffer, 0, readBytes);
}
return result.toByteArray();
}
// ------------------------------------------ Abstract methods
/**
* <p>Opens a stream to the resource that defines the given class. If it
* cannot be found, return <code>null</code>.</p>
*
* @param className the class to load
* @return a stream to the resource that defines the given class
* @throws IOException if an I/O error occurs
*/
protected abstract InputStream openStreamForClass(String className) throws IOException;
}