FileDocCategorySizeDatePackage
ClassLoaderUtil.javaAPI DocGlassfish v2 API14300Fri May 04 22:35:26 BST 2007com.sun.appserv

ClassLoaderUtil

public class ClassLoaderUtil extends Object
Provides utility functions related to URLClassLoaders or subclasses of it. W A R N I N G This class uses undocumented, unpublished, private data structures inside java.net.URLClassLoader and sun.misc.URLClassPath. Use with extreme caution.
author
tjquinn

Fields Summary
private static boolean
isInitialized
records whether initialization has been completed
private static final String
URLCLASSLOADER_UCP_FIELD_NAME
names of classes and fields of interest for closing the loader's jar files
private static final String
URLCLASSPATH_LOADERS_FIELD_NAME
private static final String
URLCLASSPATH_URLS_FIELD_NAME
private static final String
URLCLASSPATH_LMAP_FIELD_NAME
private static final String
URLCLASSPATH_JARLOADER_INNER_CLASS_NAME
private static final String
URLCLASSPATH_JARLOADER_JARFILE_FIELD_NAME
private static Field
jcpField
private static Field
loadersField
private static Field
urlsField
private static Field
lmapField
private static Class
jarLoaderInnerClass
private static Field
jarFileField
private static boolean
initDone
Constructors Summary
Methods Summary
private static java.lang.reflect.FieldgetField(java.lang.Class cls, java.lang.String fieldName)
Retrieves a Field object for a given field on the specified class, having set it accessible.

param
cls the Class on which the field is expected to be defined
param
fieldName the name of the field of interest
throws
NoSuchFieldException in case of any error retriving information about the field

        try {
            Field field = cls.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (NoSuchFieldException nsfe) {
            NoSuchFieldException e = new NoSuchFieldException(getMessage("classloaderutil.errorGettingField", fieldName));
            e.initCause(nsfe);
            throw e;
        }

    
private static java.lang.ClassgetInnerClass(java.lang.Class cls, java.lang.String innerClassName)
Retrieves a given inner class definition from the specified outer class.

param
cls the outer Class
param
innerClassName the fully-qualified name of the inner class of interest

        Class result = null;
        Class[] innerClasses = cls.getDeclaredClasses();
        for (Class c : innerClasses) {
            if (c.getName().equals(innerClassName)) {
                result = c;
                break;
            }
        }
        return result;
    
private static java.util.logging.LoggergetLogger()
Returns the logger for the common utils component.

return
the Logger for this component

        return LogDomains.getLogger(LogDomains.CMN_LOGGER);
    
private static java.lang.StringgetMessage(java.lang.String key, java.lang.Object o)
Returns a formatted string, using the key to find the full message and substituting any parameters.

param
key the message key with which to locate the message of interest
param
o the object(s), if any, to be substituted into the message
return
a String containing the formatted message

        String msg = getLogger().getResourceBundle().getString(key);
        return MessageFormat.format(msg, o);
    
private static voidinit()
Initializes the class.

Each utility method should invoke init() before doing their own work to make sure the initialization is done.

throws
any Throwable detected during static init.

    
                                 
          
        if ( ! initDone) {
            initForClosingJars();
            initDone = true;
        }
    
private static voidinitForClosingJars()
Sets up variables used in closing a loader's jar files.

throws
NoSuchFieldException in case a field of interest is not found where expected

        jcpField = getField(URLClassLoader.class, URLCLASSLOADER_UCP_FIELD_NAME);
        loadersField = getField(URLClassPath.class, URLCLASSPATH_LOADERS_FIELD_NAME);
        urlsField = getField(URLClassPath.class, URLCLASSPATH_URLS_FIELD_NAME);
        lmapField = getField(URLClassPath.class, URLCLASSPATH_LMAP_FIELD_NAME);
        
        jarLoaderInnerClass = getInnerClass(URLClassPath.class, URLCLASSPATH_JARLOADER_INNER_CLASS_NAME);
        jarFileField = getField(jarLoaderInnerClass, URLCLASSPATH_JARLOADER_JARFILE_FIELD_NAME);
    
public static voidreleaseLoader(java.net.URLClassLoader classLoader)
Releases resources held by the URLClassLoader. Notably, close the jars opened by the loader. This does not prevent the class loader from continuing to return classes it has already resolved.

param
classLoader the instance of URLClassLoader (or a subclass)
return
array of IOExceptions reporting jars that failed to close

        releaseLoader(classLoader, null);
    
public static java.io.IOException[]releaseLoader(java.net.URLClassLoader classLoader, java.util.Vector jarsClosed)
Releases resources held by the URLClassLoader. Notably, close the jars opened by the loader. This does not prevent the class loader from continuing to return classes it has alrady resolved although that is not what we intend to happen. Initializes and updates the Vector of jars that have been successfully closed.

Any errors are logged.

param
classLoader the instance of URLClassLoader (or a subclass)
param
jarsClosed a Vector of Strings that will contain the names of jars successfully closed; can be null if the caller does not need the information returned
return
array of IOExceptions reporting jars that failed to close; null indicates that an error other than an IOException occurred attempting to release the loader; empty indicates a successful release; non-empty indicates at least one error attempting to close an open jar.

        
        IOException[] result = null;
        
        try { 
            init();

            /* Records all IOExceptions thrown while closing jar files. */
            Vector<IOException> ioExceptions = new Vector<IOException>();

            if (jarsClosed != null) {
                jarsClosed.clear();
            }

            URLClassPath ucp = (URLClassPath) jcpField.get(classLoader);
            ArrayList loaders = (ArrayList) loadersField.get(ucp);
            Stack urls = (Stack) urlsField.get(ucp);
            HashMap lmap = (HashMap) lmapField.get(ucp);

            /*
             *The urls variable in the URLClassPath object holds URLs that have not yet
             *been used to resolve a resource or load a class and, therefore, do
             *not yet have a loader associated with them.  Clear the stack so any
             *future requests that might incorrectly reach the loader cannot be 
             *resolved and cannot open a jar file after we think we've closed 
             *them all.
             */
            synchronized(urls) {
                urls.clear();
            }

            /*
             *Also clear the map of URLs to loaders so the class loader cannot use
             *previously-opened jar files - they are about to be closed.
             */
            synchronized(lmap) {
                lmap.clear();
            }

            /*
             *The URLClassPath object's path variable records the list of all URLs that are on
             *the URLClassPath's class path.  Leave that unchanged.  This might
             *help someone trying to debug why a released class loader is still used.
             *Because the stack and lmap are now clear, code that incorrectly uses a
             *the released class loader will trigger an exception if the 
             *class or resource would have been resolved by the class
             *loader (and no other) if it had not been released.  
             *
             *The list of URLs might provide some hints to the person as to where
             *in the code the class loader was set up, which might in turn suggest
             *where in the code the class loader needs to stop being used.
             *The URLClassPath does not use the path variable to open new jar 
             *files - it uses the urls Stack for that - so leaving the path variable
             *will not by itself allow the class loader to continue handling requests.
             */

            /*
             *For each loader, close the jar file associated with that loader.  
             *
             *The URLClassPath's use of loaders is sync-ed on the entire URLClassPath 
             *object.
             */
            synchronized (ucp) {
                for (Object o : loaders) {
                    if (o != null) {
                        /*
                         *If the loader is a JarLoader inner class and its jarFile
                         *field is non-null then try to close that jar file.  Add
                         *it to the list of closed files if successful.
                         */
                        if (jarLoaderInnerClass.isInstance(o)) {
                            try {
                                JarFile jarFile = (JarFile) jarFileField.get(o);
                                try {
                                    if (jarFile != null) {
                                        jarFile.close();
                                        if (jarsClosed != null) {
                                            jarsClosed.add(jarFile.getName());
                                        }
                                    }
                                } catch (IOException ioe) {
                                    /*
                                     *Wrap the IOException to identify which jar 
                                     *could not be closed and add it to the list
                                     *of IOExceptions to be returned to the caller.
                                     */
                                    String jarFileName = (jarFile == null) ? getMessage("classloaderutil.jarFileNameNotAvailable") : jarFile.getName();
                                    String msg = getMessage("classloaderutil.errorClosingJar", jarFileName);
                                    IOException newIOE = new IOException(msg);
                                    newIOE.initCause(ioe);
                                    ioExceptions.add(newIOE);
                                    
                                    /*
                                     *Log the error also.
                                     */
                                    getLogger().log(Level.WARNING, msg, ioe);
                                }
                            } catch (Throwable thr) {
                                getLogger().log(Level.WARNING, "classloaderutil.errorReleasingJarNoName", thr);
                            }
                        }
                    }
                }
                /*
                 *Now clear the loaders ArrayList.
                 */
                loaders.clear();
            }
            result = ioExceptions.toArray(new IOException[ioExceptions.size()]);
        } catch (Throwable thr) {
            getLogger().log(Level.WARNING, "classloaderutil.errorReleasingLoader", thr);
            result = null;
        }
        
        return result;