FileDocCategorySizeDatePackage
EJBClassLoader.javaAPI DocGlassfish v2 API50393Tue May 22 22:17:14 BST 2007com.sun.enterprise.loader

EJBClassLoader

public class EJBClassLoader extends URLClassLoader implements InstrumentableClassLoader, JasperAdapter
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

Fields Summary
static final Logger
_logger
logger for this class
private final List
urlSet
list of url entries of this class loader
private final Map
notFoundResources
cache of not found resources
private final Map
notFoundClasses
cache of not found classes
private volatile boolean
doneCalled
state flag to track whether this instance has been shut off.
private volatile String
doneSnapshot
snapshot of classloader state at the time done was called
private volatile Vector
streams
streams opened by this loader
private final ArrayList
transformers
private static final com.sun.enterprise.util.i18n.StringManager
sm
private static final String
LINE_SEP
Constructors Summary
public EJBClassLoader()
Constructor.

    
          
      
        super(new URL[0]);
        if (_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,
                        "ClassLoader: " + this + " is getting created.");
        }
    
public EJBClassLoader(ClassLoader parent)
Constructor.

param
parent parent class loader

        super(new URL[0], parent);
    
Methods Summary
public synchronized voidaddTransformer(javax.persistence.spi.ClassTransformer transformer)

        transformers.add(transformer);
    
public voidaddURL(java.net.URL url)
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

        appendURL(url);
    
public synchronized voidappendURL(java.io.File file)
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

        try {
            appendURL(file.toURI().toURL());
        } catch (MalformedURLException mue) {
            _logger.log(Level.SEVERE,
                "loader.ejbclassloader_bad_url_entry", file.toURI());

            _logger.log(Level.SEVERE,
                "loader.ejbclassloader_malformed_url", mue);
            IOException ioe = new IOException();
            ioe.initCause(mue);
            throw ioe;
        }
    
public synchronized voidappendURL(java.net.URL url)
Add a url to the list of urls we search for a class's bytecodes.

param
url url to be added


        try {
            if (url == null) {
                _logger.log(Level.INFO,
                    "loader.ejbclassloader_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,
                    "[EJB-CL] 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.ejbclassloader_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.ejbclassloader_bad_url_entry", url);

            _logger.log(Level.SEVERE,
                "loader.ejbclassloader_malformed_url", ioe);
        }
    
private voidcheckManifest(java.util.jar.JarFile jar, java.io.File file)
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


        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.ejbclassloader_malformed_url",ex);
                }
            }
        }
    
private voidclearNotFoundCaches()
Erases the memory of classes and resources that have been searched for but not found.

        this.notFoundResources.clear();
        this.notFoundClasses.clear();
    
private voidcloseOpenStreams()
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.

        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.ejbclassloader_error_closing_stream", ioe);
                }
            }
            streams.clear();
            streams = null;
        }
    
public java.lang.ClassLoadercopy()
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);
    
public voiddone()
This method should be called to free up the resources. It helps garbage collection.


        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 = "EJBClassLoader.done() called ON " + this.toString()
            + "\n AT " + new Date() +
            " \n BY :" + 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.ejbclassloader_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;
    
protected java.lang.ClassfindClass(java.lang.String name)

        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<ClassTransformer> xformers = (ArrayList<ClassTransformer>) transformers.clone();
            for (ClassTransformer 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, "EJBClassLoader",
                            "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")));
        }
    
protected com.sun.enterprise.loader.EJBClassLoader$ClassDatafindClassData(java.lang.String name)
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


        if( doneCalled ) {
            _logger.log(Level.WARNING,
                        formatMsg("loader.ejbclassloader_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);
    
public java.net.URLfindResource(java.lang.String name)


        if( doneCalled ) {
            _logger.log(Level.WARNING,
                    formatMsg("loader.ejbclassloader_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;
    
private java.net.URLfindResource0(com.sun.enterprise.loader.EJBClassLoader$URLEntry res, java.lang.String name)
Internal implementation of find resource.

param
res url resource entry
param
name name of the resource


        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_ejbclassloader",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_ejbclassloader",e);
                    }
                }

                return null;

            } // End for -- each URL in classpath.
        });

        return (URL) result;
    
public java.util.EnumerationfindResources(java.lang.String name)
Returns an enumeration of java.net.URL objects representing all the resources with the given name.

        if( doneCalled ) {
            _logger.log(Level.WARNING,
                        "loader.ejbclassloader_done_already_called",
                        new Object[] { name, doneSnapshot });
            return null;
        }

        // resource is in the not found list
        String nf = (String) notFoundResources.get(name);
        if (nf != null && nf.equals(name) ) {
            return null;
        }

        List<URL> resourcesList = new ArrayList<URL>();

        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();
    
private static java.lang.StringformatMsg(java.lang.String key, java.lang.Object args)
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

        String fmt = _logger.getResourceBundle().getString(key);
        return MessageFormat.format(fmt, args);
    
private byte[]getClassData(java.io.InputStream istream)
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


        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) {
                    EJBClassLoader._logger.log(Level.INFO, "loader.excep_in_ejbclassloader", closeIOE);
                }
            }
        }

        return bout.toByteArray();
    
public java.lang.StringgetClasspath()
Returns all the "file" protocol resources of this EJBClassLoader, concatenated to a classpath string. Notice that this method is called by the setClassPath() method of org.apache.catalina.loader.WebappLoader, since this EJBClassLoader does not extend off of URLClassLoader.

return
Classpath string containing all the "file" protocol resources of this EJBClassLoader


        StringBuffer strBuf = null;

        URL[] urls = getURLs();
        if (urls != null) {
            for (int i=0; i<urls.length; i++) {
                if (urls[i].getProtocol().equals("file")) {
                    if (strBuf == null) {
                        strBuf = new StringBuffer();
                    }
                    if (i > 0) {
                        strBuf.append(File.pathSeparator);
                    }
                    strBuf.append(urls[i].getFile());
                }
            }
        }

        return (strBuf != null) ? strBuf.toString() : null;
    
public java.io.InputStreamgetResourceAsStream(java.lang.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;
    
private synchronized java.util.VectorgetStreams()
Returns the vector of open streams; creates it if needed.

return
Vector holding open streams

        if (streams == null) {
            streams = new Vector<SentinelInputStream>();
        }
        return streams;
    
public synchronized java.net.URL[]getURLs()
Returns the urls of this class loader.

return
the urls of this class loader or an empty array


        URL[] url  = null;

        if (this.urlSet != null) {
            url  = new URL[this.urlSet.size()];

            for (int i=0; i<url.length; i++) {
                url[i] = ((URLEntry)this.urlSet.get(i)).source;
            }
        } else {
            url = new URL[0];
        }

        return url;
    
public booleanisDone()

        return doneCalled;
    
private byte[]loadClassData0(com.sun.enterprise.loader.EJBClassLoader$URLEntry res, java.lang.String entryName)
Internal implementation of load class.

param
res url resource entry
param
entryName name of the class


        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(EJBClassLoader.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(EJBClassLoader.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_ejbclassloader", closeIOE);
                                    }
                                }
                            }
                        }
                    }
                } catch (IOException ioe) {
                    _logger.log(Level.INFO,
                                "loader.excep_in_ejbclassloader", ioe);
                }
                return null;
            }
        });
        return (byte[]) result;
    
private java.lang.StringprintStackTraceToString()
Formats the caller's stack trace to a string.

        StringBuilder sb = new StringBuilder();
        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        /*
         *Start with element 1 and not 0 to avoid displaying the stack frame
         *for the call to this method.
         */
        for (int i = 1; i < elements.length; i++) {
            sb.append(elements[i].toString()).append(LINE_SEP);
        }
        return sb.toString();
    
public synchronized voidrefresh()
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 EJBClassLoader 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

        clearNotFoundCaches();
//        for (URLEntry entry : urlSet) {
//            entry.cacheItems();
//        }
    
public java.lang.StringtoString()
Returns a string representation of this class loader.

return
a string representation of this class loader


        StringBuffer buffer = new StringBuffer();

        buffer.append("EJBClassLoader : \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();