All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.loader.ASURLClassLoader Maven / Gradle / Ivy

There is a newer version: 10.0-b28
Show newest version
/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */


package com.sun.enterprise.loader;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.ClassFileTransformer;
import java.net.JarURLConnection;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import com.sun.appserv.server.util.PreprocessorUtil;
import com.sun.enterprise.util.Print;
import com.sun.logging.LogDomains;
import com.sun.enterprise.util.i18n.StringManager;

import java.util.zip.ZipEntry;

import org.glassfish.api.deployment.InstrumentableClassLoader;
import org.jvnet.hk2.component.PreDestroy;

/**
 * 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.
 * 
 *
 * @author Nazrul Islam
 * @author Kenneth Saks
 * @author Sivakumar Thyagarajan
 * @since  JDK 1.4
 */
public class ASURLClassLoader
        extends URLClassLoader
        implements JasperAdapter, InstrumentableClassLoader, PreDestroy {

    /** logger for this class */
    static Logger _logger=LogDomains.getLogger(ASURLClassLoader.class, LogDomains.LOADER_LOGGER);

    /** list of url entries of this class loader */
    private List urlSet = Collections.synchronizedList(new ArrayList());

    /** cache of not found resources */
    private Map notFoundResources   = new ConcurrentHashMap();

    /** cache of not found classes */
    private Map notFoundClasses     = new ConcurrentHashMap();

    /** state flag to track whether this instance has been shut off.  */
    private boolean doneCalled = false;
    /** snapshot of classloader state at the time done was called */
    private String doneSnapshot;

    /** streams opened by this loader */
    private Vector streams = null;

    private ArrayList transformers =
            new ArrayList(1);

    private static StringManager sm = 
        StringManager.getManager(ASURLClassLoader.class);

    /**
     * Constructor.
     */
    public ASURLClassLoader() {
        super(new URL[0]);

        if (_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,
                        "ClassLoader: " + this + " is getting created.");
        }
    }

    /**
     * Constructor.
     *
     * @param    parent    parent class loader
     */
    public ASURLClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
    }

    public boolean isDone() {
        return doneCalled;
    }


    @Override
    public void preDestroy() {
        done();
    }


    /**
     * This method should be called to free up the resources.
     * It helps garbage collection.
     */
    public void done() {

        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.
        doneSnapshot = "ASURLClassLoader.done() called ON " + this.toString()
            + "\n AT " + new Date() +
            " \n BY :" + Print.printStackTraceToString();
        doneCalled = true;

        // closes the jar handles and sets the url entries to null
        int i = 0;
        while (i < this.urlSet.size()) {
            URLEntry u = (URLEntry) this.urlSet.get(i);
            if (u.zip != null) {
                try {
                    u.zip.reallyClose();
                } catch (IOException ioe) {
                    _logger.log(Level.INFO, formatMsg("loader.asurlclassloader_exc_closing_URLEntry", u.source),
                                ioe);
                }
            }
            if (u.table != null) {
                u.table.clear();
                u.table = null;
            }
            u = null;
            i++;
        }

        closeOpenStreams();

        // clears out the tables
        if (this.urlSet != null)            { this.urlSet.clear();            }
        if (this.notFoundResources != null) { this.notFoundResources.clear(); }
        if (this.notFoundClasses != null)   { this.notFoundClasses.clear();   }

        // sets all the objects to null
        this.urlSet              = null;
        this.notFoundResources   = null;
        this.notFoundClasses     = null;
    }


    /**
     * Adds a URL to the search list, based on the specified File.
     * 

* 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 */ public synchronized void appendURL(File file) throws IOException { try { appendURL(file.toURI().toURL()); } catch (MalformedURLException mue) { _logger.log(Level.SEVERE, "loader.asurlclassloader_bad_url_entry", file.toURI()); _logger.log(Level.SEVERE, "loader.asurlclassloader_malformed_url", mue); IOException ioe = new IOException(); ioe.initCause(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 */ public void addURL(URL url) { appendURL(url); } /** * Add a url to the list of urls we search for a class's bytecodes. * * @param url url to be added */ public synchronized void appendURL(URL url) { try { if (url == null) { _logger.log(Level.INFO, "loader.asurlclassloader_bad_url_entry", url); return; } URLEntry entry = new URLEntry(url); if ( !urlSet.contains(entry) ) { entry.init(); // adds the url entry to the list this.urlSet.add(entry); if (entry.isJar) { // checks the manifest if a jar checkManifest(entry.zip, entry.file); } } else { _logger.log(Level.FINE, "[ASURLClassLoader] Ignoring duplicate URL: " + url); /* *Clean up the unused entry or it could hold open a jar file. */ if (entry.zip != null) { try { entry.zip.reallyClose(); } catch (IOException ioe) { _logger.log(Level.INFO, formatMsg("loader.asurlclassloader_exc_closing_dup_URLEntry", url), ioe); } } } // clears the "not found" cache since we are adding a new url clearNotFoundCaches(); } catch (IOException ioe) { _logger.log(Level.SEVERE, "loader.asurlclassloader_bad_url_entry", url); _logger.log(Level.SEVERE, "loader.asurlclassloader_malformed_url", ioe); } } /** * Returns the urls of this class loader. * * @return the urls of this class loader or an empty array */ public synchronized URL[] getURLs() { URL[] url = null; if (this.urlSet != null) { url = new URL[this.urlSet.size()]; for (int i=0; i 0) { strBuf.append(File.pathSeparator); } strBuf.append(urls[i].getFile()); } } } return (strBuf != null) ? strBuf.toString() : null; } /** *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. *

*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 */ public synchronized void refresh() throws IOException { clearNotFoundCaches(); // for (URLEntry entry : urlSet) { // entry.cacheItems(); // } } public synchronized void addTransformer(ClassFileTransformer transformer) { transformers.add(transformer); } /** * 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 */ public ClassLoader copy() { 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 */ private URL findResource0(final URLEntry res, final String name) { Object result = AccessController.doPrivileged(new PrivilegedAction() { public Object run() { if (res.isJar) { try { JarEntry jarEntry = res.zip.getJarEntry(name); if (jarEntry != null) { /* *Use a custom URL with a special stream handler to *prevent the JDK's JarURLConnection caching from *locking the jar file until JVM exit. */ InternalURLStreamHandler handler = new InternalURLStreamHandler(res, name); URI uri = new URI("jar", res.source + "!/" + name, null /* fragment */); URL ret = new URL(uri.toURL(), "" /* spec */, handler); handler.tieUrl(ret); return ret; } } catch (Throwable thr) { _logger.log(Level.INFO, "loader.excep_in_asurlclassloader",thr); } } else { // directory try { File resourceFile = new File(res.file.getCanonicalPath() + File.separator + name); if (resourceFile.exists()) { // If we make it this far, // the resource is in the directory. return resourceFile.toURL(); } } catch (IOException e) { _logger.log(Level.INFO, "loader.excep_in_asurlclassloader",e); } } return null; } // End for -- each URL in classpath. }); return (URL) result; } public URL findResource(String name) { if( doneCalled ) { _logger.log(Level.WARNING, formatMsg("loader.asurlclassloader_find_resource_after_done", name, this.toString()), new Throwable()); return null; } // resource is in the not found list String nf = (String) notFoundResources.get(name); if (nf != null && nf.equals(name) ) { return null; } int i = 0; while (i < this.urlSet.size()) { URLEntry u = (URLEntry) this.urlSet.get(i); if (!u.hasItem(name)) { i++; continue; } URL url = findResource0(u, name); if (url != null) return url; i++; } // add resource to the not found list notFoundResources.put(name, name); return null; } /** * Returns an enumeration of java.net.URL objects * representing all the resources with the given name. */ public Enumeration findResources(String name) throws IOException { if( doneCalled ) { _logger.log(Level.WARNING, "loader.asurlclassloader_done_already_called", new Object[] { name, doneSnapshot }); return null; } List resourcesList = new ArrayList(); // resource is in the not found list String nf = (String) notFoundResources.get(name); if (nf != null && nf.equals(name) ) { return (new Vector(resourcesList)).elements(); } for (Iterator iter = this.urlSet.iterator(); iter.hasNext();) { URLEntry urlEntry = (URLEntry) iter.next(); URL url = findResource0(urlEntry, name); if (url != null) { resourcesList.add(url); } } if (resourcesList.size() == 0) { // add resource to the not found list notFoundResources.put(name, name); } return (new Vector(resourcesList)).elements(); } /** * 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 */ private void checkManifest(JarFile jar, File file) throws IOException { if ( (jar == null) || (file == null) ) return; Manifest man = jar.getManifest(); if (man == null) return; synchronized (this) { String cp = man.getMainAttributes().getValue( Attributes.Name.CLASS_PATH); if (cp == null) return; StringTokenizer st = new StringTokenizer(cp, " "); while (st.hasMoreTokens()) { String entry = st.nextToken(); File newFile = new File(file.getParentFile(), entry); // add to class path of this class loader try { appendURL(newFile); } catch (MalformedURLException ex) { _logger.log(Level.SEVERE, "loader.asurlclassloader_malformed_url",ex); } } } } /** * Internal implementation of load class. * * @param res url resource entry * @param entryName name of the class */ private byte[] loadClassData0(final URLEntry res, final String entryName) { Object result = AccessController.doPrivileged(new PrivilegedAction() { public Object run() { InputStream classStream = null; try { if (res.isJar) { // It is a jarfile.. JarFile zip = res.zip; JarEntry entry = zip.getJarEntry(entryName); if (entry != null) { classStream = zip.getInputStream(entry); byte[] classData = getClassData(classStream); res.setProtectionDomain(ASURLClassLoader.this, entry.getCertificates()); return classData; } } else { // Its a directory.... File classFile = new File (res.file, entryName.replace('/', File.separatorChar)); if (classFile.exists()) { try { classStream = new FileInputStream(classFile); byte[] classData = getClassData(classStream); res.setProtectionDomain(ASURLClassLoader.this, null); return classData; } finally { /* *Close the stream only if this is a directory. The stream for *a jar/zip file was opened elsewhere and should remain open after this *method completes. */ if (classStream != null) { try { classStream.close(); } catch (IOException closeIOE) { _logger.log(Level.INFO, "loader.excep_in_asurlclassloader", closeIOE); } } } } } } catch (IOException ioe) { _logger.log(Level.INFO, "loader.excep_in_asurlclassloader", ioe); } return null; } }); return (byte[]) result; } protected Class findClass(String name) throws ClassNotFoundException { ClassData classData = findClassData(name); // 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 String entryName = name.replace('.', '/') + ".class"; classData.classBytes = PreprocessorUtil.processClass(entryName, classData.classBytes); } // Define package information if necessary int lastPackageSep = name.lastIndexOf('.'); if ( lastPackageSep != -1 ) { String packageName = name.substring(0, lastPackageSep); if( getPackage(packageName) == null ) { 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. definePackage(packageName, null, null, null, null, null, null, null); } catch(IllegalArgumentException iae) { // duplicate attempt to define same package. // safe to ignore. _logger.log(Level.FINE, "duplicate package " + "definition attempt for " + packageName, iae); } } } // Loop though the transformers here!! try { ArrayList xformers = (ArrayList) transformers.clone(); for (ClassFileTransformer transformer : xformers) { // see javadocs of transform(). // It expects class name as java/lang/Object // as opposed to java.lang.Object String internalClassName = name.replace('.','/'); byte[] transformedBytes = transformer.transform(this, internalClassName, null, classData.pd, classData.classBytes); if(transformedBytes!=null){ // null indicates no transformation _logger.logp(Level.INFO, "ASURLClassLoader", "findClass", "{0} actually got transformed", name); classData.classBytes = transformedBytes; } } } catch (IllegalClassFormatException icfEx) { throw new ClassNotFoundException(icfEx.toString(), icfEx); } Class clazz = null; try { clazz = defineClass(name, classData.classBytes, 0, classData.classBytes.length, classData.pd); return clazz; } catch (UnsupportedClassVersionError ucve) { throw new UnsupportedClassVersionError( sm.getString("ejbClassLoader.unsupportedVersion", name, System.getProperty("java.version"))); } } /** * 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. * @param name class name in java.lang.Object format * @return class bytes as well protection domain information * @throws ClassNotFoundException */ protected ClassData findClassData(String name) throws ClassNotFoundException { if( doneCalled ) { _logger.log(Level.WARNING, formatMsg("loader.asurlclassloader_find_class_after_done", name, this.toString()), new Throwable()); throw new ClassNotFoundException(name); } String nf = (String) notFoundClasses.get(name); if (nf != null && nf.equals(name) ) { throw new ClassNotFoundException(name); } // search thru the JARs for a file of the form java/lang/Object.class String entryName = name.replace('.', '/') + ".class"; int i = 0; while (i < urlSet.size()) { URLEntry u = (URLEntry) this.urlSet.get(i); if (!u.hasItem(entryName)) { i++; continue; } byte[] result = loadClassData0(u, entryName); if (result != null) return new ClassData(result, u.pd); i++; } // add to the not found classes list notFoundClasses.put(name, name); 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 */ private byte[] getClassData(InputStream istream) throws IOException { BufferedInputStream bstream = new BufferedInputStream(istream);; byte[] buf = new byte[4096]; ByteArrayOutputStream bout = new ByteArrayOutputStream(); int num = 0; try { while( (num = bstream.read(buf)) != -1) { bout.write(buf, 0, num); } } finally { if (bstream != null) { try { bstream.close(); } catch (IOException closeIOE) { ASURLClassLoader._logger.log(Level.INFO, "loader.excep_in_asurlclassloader", closeIOE); } } } return bout.toByteArray(); } protected String getClassLoaderName() { return "ASURLClassLoader"; } /** * Returns a string representation of this class loader. * * @return a string representation of this class loader */ public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(getClassLoaderName() + " : \n"); if( doneCalled ) { buffer.append("doneCalled = true" + "\n"); if( doneSnapshot != null ) { buffer.append("doneSnapshot = " + doneSnapshot); } } else { buffer.append("urlSet = " + this.urlSet + "\n"); buffer.append("doneCalled = false " + "\n"); } buffer.append(" Parent -> " + getParent() + "\n"); return buffer.toString(); } public InputStream getResourceAsStream(final String name) { InputStream stream = super.getResourceAsStream(name); /* *Make sure not to wrap the stream if it already is a wrapper. */ if (stream != null) { if (! (stream instanceof SentinelInputStream)) { stream = new SentinelInputStream(stream); } } 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 */ private static String formatMsg(String key, Object... args) { String fmt = _logger.getResourceBundle().getString(key); return MessageFormat.format(fmt, args); } /** * 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 */ private static class ProtectedJarFile extends JarFile { /** * Constructor * * @param file File * @throws IOException from parent */ public ProtectedJarFile(File file) throws IOException { super(file); } /** * Do nothing * * @see java.util.zip.ZipFile#close() */ public void close() { // nothing _logger.log(Level.WARNING, "Illegal call to close() detected", new Throwable()); } /** * Really close the jar file * * @throws IOException from parent */ public void reallyClose() throws IOException { super.close(); } /** * @see java.lang.Object#finalize() */ protected void finalize() throws IOException { reallyClose(); } } /** * URL entry - keeps track of the url resources. */ protected static class URLEntry { /** the url */ URL source = null; /** file of the url */ File file = null; /** jar file if url is a jar else null */ ProtectedJarFile zip = null; /** true if url is a jar */ boolean isJar = false; Hashtable table = null; /** ProtectionDomain with signers if jar is signed */ ProtectionDomain pd = null; URLEntry(URL url) { source = url; } void init() throws IOException { try { file = new File(source.toURI()); isJar = file.isFile(); if (isJar) { zip = new ProtectedJarFile(file); } table = new Hashtable(); // cacheItems(); } catch (URISyntaxException use) { IOException ioe= new IOException(); ioe.initCause(use); throw ioe; } } private void cacheItems() throws IOException { if (isJar) { // cache entry names from jar file for (Enumeration e = zip.entries(); e.hasMoreElements(); ) { ZipEntry curEntry = (ZipEntry) e.nextElement(); table.put(curEntry.getName(), curEntry.getName()); } } else { // cache entry names from directory if (file.exists()) { fillTable(file, table, ""); } } } private void fillTable(File f, Hashtable t, String parent) throws IOException { String localName = (parent.equals("")) ? "" : parent + "/"; File[] children = f.listFiles(); for (int i = 0; i < children.length; i++) { processFile(children[i], t, localName); } } /** *Adds a file (or, if a directory, the directory's contents) to the table *of files this loader knows about. *

*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 { String key = parentLocalName + fileToProcess.getName(); if (fileToProcess.isFile()) { t.put(key, key); } else if (fileToProcess.isDirectory()) { fillTable(fileToProcess, t, key); } } boolean hasItem(String item) { // in the case of ejbc stub compilation, asurlclassloader is created before stubs // gets generated, thus we need to return true for this case. if (table.size() == 0) { 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; String target = item; // special handling if (item.startsWith("./")) { target = item.substring(2, item.length()); } result = table.containsKey(target); if ( ! result && ! isJar) { /* *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. */ File targetFile = privilegedCheckForFile(target); if (targetFile != null) { try { processFile(targetFile, table, ""); 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. *

*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 */ private File privilegedCheckForFile(final String targetPath) { /* *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 { File result = (File) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { File targetFile = new File(file, targetPath); 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 { if (pd == null) { pd = new ProtectionDomain(new CodeSource(file.toURL(),signers),null, ejbClassLoader, null ); } } public String toString() { return "URLEntry : " + source.toString(); } /** * Returns true if two URL entries has equal URLs. * * @param obj URLEntry to compare against * @return true if both entry has equal URL */ public boolean equals(Object obj) { boolean tf = false; if (obj instanceof URLEntry) { URLEntry e = (URLEntry) obj; try { //try comparing URIs if (source.toURI().equals(e.source.toURI())) { tf = true; } } catch (URISyntaxException e1) { //fall back to comparing URLs if (source.equals(e.source)) { tf = true; } } } return tf; } /** * Since equals is overridden, we need to override hashCode as well. */ public int hashCode() { try { return source.toURI().hashCode(); } catch (URISyntaxException e) { //fall back to URL's hashCode return source.hashCode(); } } } /** *Returns the vector of open streams; creates it if needed. *@return Vector holding open streams */ private Vector getStreams() { if (streams == null) { streams = new Vector(); } return streams; } /** *Closes any streams that remain open, logging a warning for each. *

*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. */ private void closeOpenStreams() { if (streams != null) { SentinelInputStream[] toClose = streams.toArray(new SentinelInputStream[streams.size()]); for (SentinelInputStream s : toClose) { try { s.closeWithWarning(); } catch (IOException ioe) { _logger.log(Level.WARNING, "loader.asurlclassloader_error_closing_stream", ioe); } } streams.clear(); streams = null; } } /** * 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 */ protected class SentinelInputStream extends FilterInputStream { private boolean closed;// = false; private final Throwable throwable; /** * 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 */ protected SentinelInputStream(final InputStream in) { super(in); throwable = new Throwable(); getStreams().add(this); } /** * Closes underlying input stream. */ public void close() throws IOException { _close(); } /** * Invoked by Garbage Collector. If underlying InputStream was not closed properly, * the stack trace of the constructor will be logged! */ protected void finalize() throws Throwable { if (!closed && this.in != null){ try { in.close(); } catch (IOException ignored){ //Cannot do anything here. } //Well, give them a stack trace! report(); } super.finalize(); } private void _close() throws IOException { closed = true; getStreams().remove(this); super.close(); } private void closeWithWarning() throws IOException { _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 */ private class InternalJarURLConnection extends JarURLConnection { private URL mURL; private URLEntry mRes; private String mName; /** * Constructor * * @param url the URL that is a stream for * @param res URLEntry * @param name String * @throws MalformedURLException from super class */ public InternalJarURLConnection(URL url, URLEntry res, String name) throws MalformedURLException { super(url); mRes = res; mName = name; } /** * @see java.net.JarURLConnection#getJarFile() */ public JarFile getJarFile() throws IOException { return mRes.zip; } /** * @see java.net.URLConnection#connect() */ public void connect() throws IOException { // Nothing } /** * @see java.net.URLConnection#getInputStream() */ public InputStream getInputStream() throws IOException { ZipEntry entry = mRes.zip.getEntry(mName); return new SentinelInputStream(mRes.zip.getInputStream(entry)); } } /** * 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 */ private class InternalURLStreamHandler extends URLStreamHandler { private URL mURL; private URLEntry mRes; private String mName; /** * Constructor * * @param res URLEntry * @param name String */ public InternalURLStreamHandler(URLEntry res, String name) { mRes = res; mName = name; } /** * @see java.net.URLStreamHandler#openConnection(java.net.URL) */ protected URLConnection openConnection(URL u) throws IOException { if (u != mURL) { // ref compare on purpose // This should never happen throw new IOException("Cannot open a foreign URL; this.url=" + mURL + "; foreign.url=" + u); } return new InternalJarURLConnection(u, mRes, mName); } /** * 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 */ public void tieUrl(URL url) { mURL = url; } } /** * This class is used as return value of findClassIntenal method to return * both class bytes and protection domain. */ private static class ClassData { protected byte[] classBytes; protected ProtectionDomain pd; ClassData(byte[] classData, ProtectionDomain pd) { this.classBytes = classData; this.pd = pd; } } /** * 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 * actual class/resource data) operations to the application class loader. * It only defines the Class using the byte codes. * Motivation behind this class is discussed at * https://glassfish.dev.java.net/issues/show_bug.cgi?id=237. */ private static class DelegatingClassLoader extends SecureClassLoader { /** * The application class loader which is used to read class data. */ private ASURLClassLoader delegate = null; /** * 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. */ DelegatingClassLoader(ASURLClassLoader applicationCL) { super(applicationCL.getParent()); // normal class loading delegation this.delegate = applicationCL; } /** * This method uses the delegate to use class bytes and then defines * the class using this class loader */ protected Class findClass(String name) throws ClassNotFoundException { ClassData classData = delegate.findClassData(name); // Define package information if necessary int lastPackageSep = name.lastIndexOf('.'); if ( lastPackageSep != -1 ) { String packageName = name.substring(0, lastPackageSep); if( getPackage(packageName) == null ) { 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. definePackage(packageName, null, null, null, null, null, null, null); } catch(IllegalArgumentException iae) { // duplicate attempt to define same package. // safe to ignore. _logger.log(Level.FINE, "duplicate package " + "definition attempt for " + packageName, iae); } } } Class clazz = null; try { clazz = defineClass(name, classData.classBytes, 0, classData.classBytes.length, classData.pd); return clazz; } catch (UnsupportedClassVersionError ucve) { throw new UnsupportedClassVersionError( sm.getString("ejbClassLoader.unsupportedVersion", name, System.getProperty("java.version"))); } } protected URL findResource(String name) { return delegate.findResource(name); } protected Enumeration findResources(String name) throws IOException { return delegate.findResources(name); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy