/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/**
* Class loader used by the ejbs of an application or stand alone module.
*
* This class loader also keeps cache of not found classes and resources.
* </xmp>
*
* @author Nazrul Islam
* @author Kenneth Saks
* @author Sivakumar Thyagarajan
* @since JDK 1.4
*/
public class ASURLClassLoader
extends URLClassLoader
/*
NOTE: various variables are 'final' to enjoy the JVM thread visibility guaranteed for 'final'.
These variables cannot be nulled out because of this, but the contents are cleared (for Map/Vector).
This is actually a hidden benefit, because there are places in the code where these variables
are being used but they could be nulled out while being used.
*/
/** logger for this class */
private static final Logger _logger=LogDomains.getLogger(ASURLClassLoader.class, LogDomains.LOADER_LOGGER);
/*
list of url entries of this class loader. Using LinkedHashSet instead of original ArrayList
for faster search.
*/
/** cache of not found resources */
/** cache of not found classes */
/**
State flag to track whether this instance has been shut off.
Note: 'volatile' *does not by itself eliminate a race condition* similar to null-check idiom bug.
*/
private volatile boolean doneCalled = false;
/**
snapshot of classloader state at the time done was called.
<p>
Must be 'volatile'; not all access is within 'synchronized' eg it is used in toString().
*/
/** streams opened by this loader */
private final ArrayList<ClassFileTransformer> transformers = new ArrayList<ClassFileTransformer>(1);
/**
* Constructor.
*/
public ASURLClassLoader() {
super(new URL[0]);
"ClassLoader: " + this + " is getting created.");
}
}
/**
* Constructor.
*
* @param parent parent class loader
*/
}
public boolean isDone() {
// method need not by 'synchronized' because 'doneCalled' is 'volatile'.
return doneCalled;
}
public void preDestroy() {
done();
}
/**
* This method should be called to free up the resources.
* It helps garbage collection.
*
* Must be synchronized for:
(a) visibility of variables
(b) race condition while checking 'doneCalled'
(c) only one caller should close the zip files,
(d) done should occur only once and set the flag when done.
(e) shoudl not return 'true' when a previous thread might still
be in the process of executing the method.
*/
public void done() {
// This works because 'doneCalled' is 'volatile'
if( doneCalled ) {
return;
}
// the above optimized check for 'doneCalled=true' is a race condition.
// The lock must now be acquired, and 'doneCalled' rechecked.
synchronized(this) {
if( doneCalled ) {
return;
}
// Capture the fact that the classloader is now effectively disabled.
// First create a snapshot of our state. This should be called
// before setting doneCalled = true.
+ "\n AT " + new Date() +
// Presumably OK to set this flag now while the rest of the cleanup proceeeds,
// because we've taken the snapshot.
doneCalled = true;
// closes the jar handles and sets the url entries to null
int i = 0;
try {
u.zip.reallyClose();
} catch (IOException ioe) {
ioe);
}
}
}
u = null;
i++;
}
// clears out the tables
// Clear all values. Because fields are 'final' (for thread safety), cannot null them
}
}
/**
* Adds a URL to the search list, based on the specified File.
* <p>
* This variant of the method makes sure that the URL is valid, in particular
* encoding special characters (such as blanks) in the file path.
* @param file the File to use in creating the URL
* @throws IOException in case of errors converting the file to a URL
*/
try {
} catch (MalformedURLException mue) {
"loader.asurlclassloader_malformed_url", mue);
throw ioe;
}
}
/**
* Appends the specified URL to the list of URLs to search for
* classes and resources.
*
* @param url the URL to be added to the search path of URLs
*/
}
/**
* Add a url to the list of urls we search for a class's bytecodes.
*
* @param url url to be added
*/
try {
"loader.asurlclassloader_bad_url_entry", url);
return;
}
// adds the url entry to the list
// checks the manifest if a jar
}
} else {
"[ASURLClassLoader] Ignoring duplicate URL: " + url);
/*
*Clean up the unused entry or it could hold open a jar file.
*/
try {
} catch (IOException ioe) {
ioe);
}
}
}
// clears the "not found" cache since we are adding a new url
} catch (IOException ioe) {
"loader.asurlclassloader_bad_url_entry", url);
"loader.asurlclassloader_malformed_url", ioe);
}
}
/**
* Returns the urls of this class loader.
*
* Method is 'synchronized' to avoid the thread-unsafe null-check idiom idiom, also
* protects the caller from simultaneous changes while iterating,
* by returning a URL[] (copy) rather than the original. Also protects against
* changes to 'urlSet' while iterating over it.
*
* @return the urls of this class loader or an empty array
*/
int i=0;
}
} else {
}
return url;
}
/**
* Returns all the "file" protocol resources of this ASURLClassLoader,
* concatenated to a classpath string.
*
* Notice that this method is called by the setClassPath() method of
* org.apache.catalina.loader.WebappLoader, since this ASURLClassLoader does
* not extend off of URLClassLoader.
*
* @return Classpath string containing all the "file" protocol resources
* of this ASURLClassLoader
*/
strBuf = new StringBuffer();
}
if (i > 0) {
}
}
}
}
}
/**
*Refreshes the memory of the class loader. This involves clearing the
*not-found cahces and recreating the hash tables for the URLEntries that
*record the files accessible for each.
*<p>
*Code that creates an ASURLClassLoader and then adds files to a directory
*that is in the loader's classpath should invoke this method after the new
*file(s) have been added in order to update the class loader's data
*structures which optimize class and resource searches.
*@throws IOException in case of errors refreshing the cache
*/
// for (URLEntry entry : urlSet) {
// entry.cacheItems();
// }
}
}
/**
* Create a new instance of a sibling classloader
* @return a new instance of a class loader that has the same visibility
* as this class loader
*/
return new DelegatingClassLoader(this);
}
/**
*Erases the memory of classes and resources that have been searched for
*but not found.
*/
private void clearNotFoundCaches() {
this.notFoundResources.clear();
this.notFoundClasses.clear();
}
/**
* Internal implementation of find resource.
*
* @param res url resource entry
* @param name name of the resource
*/
try {
/*
*Use a custom URL with a special stream handler to
*prevent the JDK's JarURLConnection caching from
*locking the jar file until JVM exit.
*/
// Create a new sub URL from the resource URL (i.e. res.source). To avoid double encoding
// use URL constructor instead of first creating a URI and then calling toURL().
// If the resource URL is not properly encoded, that's not our problem.
// Whoever has supplied the resource URL is at fault.
return ret;
}
"loader.excep_in_asurlclassloader",thr);
}
} else { // directory
try {
if (resourceFile.exists()) {
// If we make it this far,
// the resource is in the directory.
return resourceFile.toURL();
}
} catch (IOException e) {
"loader.excep_in_asurlclassloader",e);
}
}
return null;
} // End for -- each URL in classpath.
});
}
// quick quick that relies on 'doneCalled' being 'volatile'
if( doneCalled ) {
new Throwable());
return null;
}
// This code is dubious, because it iterates over items that could
// be changing via another thread. It appears that since 'urlSet' cannot shrink
// that the iteration at least won't go out of bounds. And it's probably OK
// if more than one thread adds the same resource to 'notFoundResources'.
//
// HOWEVER, there is still a race condition from the check for 'doneCalled' above.
// That's OK for 'notFoundResources', but it could lead to an ArrayIndexOutOfBounds
// excpetion for 'urlSet', should the set be cleared while looping.
// resource is in the not found list
return null;
}
int i = 0;
synchronized(this) {
i++;
continue;
}
i++;
}
}
// add resource to the not found list
return null;
}
/**
* Returns an enumeration of java.net.URL objects
* representing all the resources with the given name.
*
* This method is synchronized to avoid (a) race condition checking 'doneCalled',
* over them, (c) thread visibility to all of the above.
*/
public synchronized Enumeration<URL>
if( doneCalled ) {
"loader.asurlclassloader_done_already_called",
// return an empty enumeration instead of null. See issue #13096
}
// resource is in the not found list
}
}
}
// add resource to the not found list
}
}
/**
* Checks the manifest of the given jar file.
*
* @param jar the jar file that may contain manifest class path
* @param file file pointer to the jar
*
* @throws IOException if an i/o error
*/
synchronized (this) {
while (st.hasMoreTokens()) {
// add to class path of this class loader
try {
} catch (MalformedURLException ex) {
"loader.asurlclassloader_malformed_url",ex);
}
}
}
}
/**
* Internal implementation of load class.
*
* @param res url resource entry
* @param entryName name of the class
*/
try {
return classData;
}
} else { // Its a directory....
try {
return classData;
} finally {
/*
*Close the stream only if this is a directory. The stream for
*method completes.
*/
if (classStream != null) {
try {
classStream.close();
} catch (IOException closeIOE) {
}
}
}
}
}
} catch (IOException ioe) {
"loader.excep_in_asurlclassloader", ioe);
}
return null;
}
});
return (byte[]) result;
}
/** THREAD SAFETY: what happens when more than one thread requests the same class
and thus works on the same classData? Or defines the same package? Maybe
the same work just gets done twice, and that's all.
CAUTION: this method might be overriden, and subclasses must be cautious (also)
about thread safety.
*/
// Instruments the classes if the profiler's enabled
if (PreprocessorUtil.isPreprocessorEnabled()) {
// search thru the JARs for a file of the form java/lang/Object.class
}
// Define package information if necessary
if ( lastPackageSep != -1 ) {
try {
// There's a small chance that one of our parents
// could define the same package after getPackage
// returns null but before we call definePackage,
// since the parent classloader instances
// are not locked. So, just catch the exception
// that is thrown in that case and ignore it.
//
// It's unclear where we would get the info to
// set all spec and impl data for the package,
// so just use null. This is consistent will the
// JDK code that does the same.
} catch(IllegalArgumentException iae) {
// duplicate attempt to define same package.
// safe to ignore.
}
}
}
// Loop though the transformers here!!
try {
final ArrayList<ClassFileTransformer> xformers = (ArrayList<ClassFileTransformer>) transformers.clone();
// see javadocs of transform().
// as opposed to java.lang.Object
"findClass", "{0} actually got transformed",
name);
}
}
} catch (IllegalClassFormatException icfEx) {
}
try {
return clazz;
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
}
}
/**
* This method is responsible for locating the url from the class bytes
* have to be read and reading the bytes. It does not actually define
* the Class object.
* <p>
* To preclude a race condition on checking 'doneCalled', as well as transient errors
* if done() is called while running, this method is 'synchronized'.
* @param name class name in java.lang.Object format
* @return class bytes as well protection domain information
* @throws ClassNotFoundException
*/
if( doneCalled ) {
formatMsg("loader.asurlclassloader_find_class_after_done", name, this.toString()), new Throwable());
throw new ClassNotFoundException(name);
}
throw new ClassNotFoundException(name);
}
// search thru the JARs for a file of the form java/lang/Object.class
int i = 0;
i++;
continue;
}
i++;
}
// add to the not found classes list
throw new ClassNotFoundException(name);
}
/**
* Returns the byte array from the given input stream.
*
* @param istream input stream to the class or resource
*
* @throws IOException if an i/o error
*/
byte[] buf = new byte[4096];
int num = 0;
try {
}
} finally {
try {
} catch (IOException closeIOE) {
}
}
}
return bout.toByteArray();
}
return "ASURLClassLoader";
}
/**
* Returns a string representation of this class loader.
*
* @return a string representation of this class loader
*/
if( doneCalled ) {
}
} else {
}
}
/*
*Make sure not to wrap the stream if it already is a wrapper.
*/
if (! (stream instanceof SentinelInputStream)) {
}
}
return stream;
}
/**
*Looks up the key in the logger's resource bundle and substitutes any
*arguments provided into the looked-up string.
*@param key the key to look up in the resource bundle
*@param args optional arguments to plug into the string found in the bundle
*@return the formatted string
*/
}
/**
* The JarFile objects loaded in the classloader may get exposed to the
* application code (e.g. EJBs) through calls of
* ((JarURLConnection) getResource().openConnection()).getJarFile().
*
* This class protects the jar file from being closed by such an application.
*
* @author fkieviet
*/
/**
* Constructor
*
* @param file File
* @throws IOException from parent
*/
super(file);
}
/**
* Do nothing
*
* @see java.util.zip.ZipFile#close()
*/
public void close() {
// nothing
}
/**
* Really close the jar file
*
* @throws IOException from parent
*/
super.close();
}
/**
* @see java.lang.Object#finalize()
*/
reallyClose();
}
}
/**
* URL entry - keeps track of the url resources.
*/
protected static final class URLEntry {
/** the url, ensure thread visibility by making it 'final' */
/** file of the url,
ensure thread visibility by making it 'volatile' */
/** jar file if url is a jar else null,
ensure thread visibility by making it 'volatile' */
/** true if url is a jar,
ensure thread visibility by making it 'volatile' */
volatile boolean isJar = false;
/** ensure thread visibility by making it 'volatile' */
/** ProtectionDomain with signers if jar is signed,
ensure thread visibility by making it 'volatile' */
init();
}
try {
if (isJar) {
}
// cacheItems();
} catch (URISyntaxException use) {
throw ioe;
}
}
if (isJar) {
// cache entry names from jar file
}
} else {
// cache entry names from directory
}
}
}
}
}
/**
*Adds a file (or, if a directory, the directory's contents) to the table
*of files this loader knows about.
*<p>
*Invokes fillTable for subdirectories which in turn invokes processFile
*recursively.
*@param fileToProcess the File to be processed
*@param t the Hashtable that holds the files the loader knows about
*@param parentLocalName prefix to be used for the full path; should be
*non-empty only for recursive invocations
*@throws IOException in case of errors working with the fileToProcess
*/
private void processFile(File fileToProcess, Hashtable t, String parentLocalName) throws IOException {
if (fileToProcess.isFile()) {
} else if (fileToProcess.isDirectory()) {
}
}
// in the case of ejbc stub compilation, asurlclassloader is created before stubs
// gets generated, thus we need to return true for this case.
return true;
}
/*
*Even with the previous special handling, a file could be created
*in a directory after the loader was created and its table of
*URLEntry names populated. So check the table first and, if
*the target item is not there and this URLEntry is for a directory, look for
*the file. If the file is now present but was not when the loader
*was created, add an entry for the file in the table.
*/
boolean result = false;
// special handling
}
/*
*If the file exists now then it has been added to the directory since the
*loader was created. Add it to the table of files we
*know about.
*/
if (targetFile != null) {
try {
result = true;
} catch (IOException ioe) {
_logger.log(Level.SEVERE, formatMsg("loader.asurlclassloader_error_processing_file", target, file.getAbsolutePath()), ioe);
return false;
}
}
}
return result;
}
/**
*Returns a File object for the requested path within the URLEntry.
*<p>
*Runs privileged because user code could trigger invocations of this
*method.
*@param targetPath the relative path to look for
*@return File object for the requested file; null if it does not exist or
*in case of error
*/
/*
*Check for the file existence with privs, because this code can
*be invoked from user code which may not otherwise have access
*to the directories of interest.
*/
try {
if ( ! targetFile.exists()) {
targetFile = null;
}
return targetFile;
}
});
return result;
} catch (PrivilegedActionException pae) {
/*
*Log any exception and return false.
*/
_logger.log(Level.SEVERE, formatMsg("loader.asurlclassloader_error_checking_existence", targetPath, file.getAbsolutePath()), pae.getCause());
return null;
}
}
/**
* Sets ProtectionDomain with CodeSource including Signers in
* Entry for use in call to defineClass.
* @param signers the array of signer certs or null
*/
public void setProtectionDomain (ClassLoader ejbClassLoader, Certificate[] signers) throws MalformedURLException {
}
}
}
/**
* Returns true if two URL entries has equal URLs.
*
* @param obj URLEntry to compare against
* @return true if both entry has equal URL
*/
boolean tf = false;
try {
//try comparing URIs
tf = true;
}
} catch (URISyntaxException e1) {
// We should never get here, because we call init() in the constructor and
// init() would have thrown an exception if the URL could not be converted to a valid URI.
assert(false);
throw new RuntimeException(e1);
}
}
return tf;
}
/**
* Since equals is overridden, we need to override hashCode as well.
*/
public int hashCode() {
try {
} catch (URISyntaxException e) {
// We should never get here, because we call init() in the constructor and
// init() would have thrown an exception if the URL could not be converted to a valid URI.
assert(false);
throw new RuntimeException(e);
}
}
}
/**
*Returns the vector of open streams; creates it if needed.
*@return Vector<SentinelInputStream> holding open streams
*/
return streams;
}
/**
*Closes any streams that remain open, logging a warning for each.
*<p>
*This method should be invoked when the loader will no longer be used
*and the app will no longer explicitly close any streams it may have opened.
* Must be synchnronized to (a) avoid race condition checking 'streams'.
*/
private synchronized void closeOpenStreams() {
for (SentinelInputStream s : toClose) {
try {
s.closeWithWarning();
} catch (IOException ioe) {
}
}
}
}
/**
* Wraps all InputStreams returned by this class loader to report when
* a finalizer is run before the stream has been closed. This helps
* to identify where locked files could arise.
* @author vtsyganok
* @author tjquinn
*/
private volatile boolean closed = false;
/**
* Constructs new FilteredInputStream which reports InputStreams not closed properly.
* When the garbage collector runs the finalizer. If the stream is still open this class will
* report a stack trace showing where the stream was opened.
*
* @param in - InputStream to be wrapped
*/
super(in);
getStreams().add(this);
}
/**
* Closes underlying input stream.
*/
_close();
}
/**
* Invoked by Garbage Collector. If underlying InputStream was not closed properly,
* the stack trace of the constructor will be logged!
*
* 'closed' is 'volatile', but it's a race condition to check it and how this code
* relates to _close() is unclear.
*/
try {
}
catch (IOException ignored){
//Cannot do anything here.
}
//Well, give them a stack trace!
report();
}
super.finalize();
}
if ( closed ) {
return;
}
// race condition with above check, but should have no harmful effects
closed = true;
getStreams().remove(this);
super.close();
}
_close();
report();
}
/**
* Report "left-overs"!
*/
private void report(){
_logger.log(Level.WARNING, "Input stream has been finalized or forced closed without being explicitly closed; stream instantiation reported in following stack trace", this.throwable);
}
}
/**
* To properly close streams obtained through URL.getResource().getStream():
* this opens the input stream on a JarFile that is already open as part
* of the classloader, and returns a sentinel stream on it.
*
* @author fkieviet
*/
/**
* Constructor
*
* @param url the URL that is a stream for
* @param res URLEntry
* @param name String
* @throws MalformedURLException from super class
*/
throws MalformedURLException {
super(url);
}
/**
* @see java.net.JarURLConnection#getJarFile()
*/
}
/**
* @see java.net.URLConnection#connect()
*/
// Nothing
}
/**
* @see java.net.URLConnection#getInputStream()
*/
// we must throw an IOException as that's the behavior of JarURLConnection as well.
throw new IOException("no entry name specified");
}
}
}
}
/**
* To properly close streams obtained through URL.getResource().getStream():
* an instance of this class is instantiated for each and every URL object
* created by this classloader. It provides a custom JarURLConnection
* (InternalJarURLConnection) so that the stream can be obtained from an already
* open jar file.
*
* @author fkieviet
*/
/** must be 'volatile' for thread visibility */
/**
* Constructor
*
* @param res URLEntry
* @param name String
*/
}
/**
* @see java.net.URLStreamHandler#openConnection(java.net.URL)
*/
try {
+ "; foreign.url=" + u);
}
}
} catch (URISyntaxException e) {
throw new IOException(e);
}
}
/**
* Ties the URL that this handler is associated with to the handler, so
* that it can be asserted that somehow no other URLs are mangled in (this
* is theoretically impossible)
*
* @param url URL
*/
// is it OK to call this twice and whack the variable a second time?
{
throw new IllegalStateException("Setting the URL more than once not allowed" );
}
}
}
/**
* This class is used as return value of findClassIntenal method to return
* both class bytes and protection domain.
*/
private static final class ClassData {
/** must be 'volatile' to ensure thread visibility */
protected volatile byte[] classBytes;
/** must be 'final' to ensure thread visibility */
this.classBytes = classData;
}
}
/**
* This class loader only provides a new class loading namespace
* so that persistence provider can load classes in that separate
* namespace while scanning annotations.
* This class loader delegates all stream handling (i.e. reading
* It only defines the Class using the byte codes.
* Motivation behind this class is discussed at
*/
/**
* The application class loader which is used to read class data.
* Made 'final' to ensure thread visibility.
*/
/**
* Create a new instance.
* @param applicationCL is the original class loader associated
* with this application. The new class loader uses it to delegate
* stream handling operations. The new class loader also uses
* applicationCL's parent as its own parent.
*/
this.delegate = applicationCL;
}
/**
* This method uses the delegate to use class bytes and then defines
* the class using this class loader
*/
// Define package information if necessary
if ( lastPackageSep != -1 ) {
try {
// There's a small chance that one of our parents
// could define the same package after getPackage
// returns null but before we call definePackage,
// since the parent classloader instances
// are not locked. So, just catch the exception
// that is thrown in that case and ignore it.
//
// It's unclear where we would get the info to
// set all spec and impl data for the package,
// so just use null. This is consistent will the
// JDK code that does the same.
} catch(IllegalArgumentException iae) {
// duplicate attempt to define same package.
// safe to ignore.
}
}
}
try {
return clazz;
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
}
}
}
}
}
}