/* * JBoss, the OpenSource J2EE webOS * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.mx.loading; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.CodeSource; import java.security.PermissionCollection; import java.security.Policy; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.HashSet; import java.util.Vector; import java.util.Collections; import java.util.Set; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import org.jboss.logging.Logger; import org.jboss.util.loading.Translator; import EDU.oswego.cs.dl.util.concurrent.ReentrantLock; import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap; /** * A RepositoryClassLoader. * * @author <a href="adrian@jboss.com">Adrian Brock</a> * @version $Revision: 1.3.4.3 $ */ public abstract class RepositoryClassLoader extends URLClassLoader { // Constants ----------------------------------------------------- /** The log */ private static final Logger log = Logger.getLogger(RepositoryClassLoader.class); /** The value returned by {@link #getURLs}. */ private static final URL[] EMPTY_URL_ARRAY = {}; // Attributes ----------------------------------------------------- /** Reference to the repository. */ protected LoaderRepository repository = null; /** The location where unregister is called from */ protected Exception unregisterTrace; /** The relative order in which this class loader was added to the respository */ private int addedOrder; /** The parent classloader */ protected ClassLoader parent = null; /** Names of classes which have resulted in CNFEs in loadClassLocally */ private Set classBlackList = Collections.synchronizedSet(new HashSet()); /** Names of resources that were not found in loadResourceLocally */ private Set resourceBlackList = Collections.synchronizedSet(new HashSet()); /** A HashMap<String, URL> for resource found in loadResourceLocally */ private ConcurrentReaderHashMap resourceCache = new ConcurrentReaderHashMap(); /** Lock */ protected ReentrantLock loadLock = new ReentrantLock(); /** A debugging variable used to track the recursive depth of loadClass() */ private int loadClassDepth; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- /** * Create a new LoaderRepositoryClassLoader * * @param urls the urls * @param parent the parent classloader */ protected RepositoryClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); this.parent = parent; } // Public -------------------------------------------------------- /** * Get the ObjectName * * @return the object name */ public abstract ObjectName getObjectName() throws MalformedObjectNameException; /** * Get the loader repository for this classloader */ public LoaderRepository getLoaderRepository() { return repository; } /** * Set the loader repository * * @param repository the repository */ public void setRepository(LoaderRepository repository) { log.debug("setRepository, repository="+repository+", cl=" + this); this.repository = repository; } /** * Get the order this classloader was added to the repository * * @return the order */ public int getAddedOrder() { return addedOrder; } /** * Set the order this classloader was added to the repository * * @param addedOrder the added order */ public void setAddedOrder(int addedOrder) { this.addedOrder = addedOrder; } /** * Called to attempt to load a class from the set of URLs associated with this classloader. */ public Class loadClassLocally(String name, boolean resolve) throws ClassNotFoundException { boolean trace = log.isTraceEnabled(); if( trace ) log.trace("loadClassLocally, " + this + " name=" + name); Class result = null; try { if (isClassBlackListed(name)) { if( trace ) log.trace("Class in blacklist, name="+name); return null; // JBOSSHACK avoid class not found exceptions } try { result = super.loadClass(name, resolve); return result; } catch (ClassNotFoundException cnfe) { addToClassBlackList(name); // If this is an array class, use Class.forName to resolve it if( name.charAt(0) == '[' ) { result = Class.forName(name, true, this); removeFromClassBlackList(name); return result; } if( trace ) log.trace("CFNE: Adding to blacklist: "+name); throw cnfe; } } finally { if (trace) { if (result != null) log.trace("loadClassLocally, " + this + " name=" + name + " class=" + result + " cl=" + result.getClassLoader()); else log.trace("loadClassLocally, " + this + " name=" + name + " not found"); } } } /** * Provides the same functionality as {@link java.net.URLClassLoader#getResource}. */ public URL getResourceLocally(String name) { URL resURL = (URL) resourceCache.get(name); if (resURL != null) return resURL; if (isResourceBlackListed(name)) return null; resURL = super.getResource(name); if( log.isTraceEnabled() == true ) log.trace("getResourceLocally("+this+"), name="+name+", resURL:"+resURL); if (resURL == null) addToResourceBlackList(name); else resourceCache.put(name, resURL); return resURL; } /** * Get the URL associated with the UCL. * * @return the url */ public URL getURL() { URL[] urls = super.getURLs(); if (urls.length > 0) return urls[0]; else return null; } public void unregister() { log.debug("Unregistering cl=" + this); if (repository != null) repository.removeClassLoader(this); clearBlackLists(); resourceCache.clear(); repository = null; this.unregisterTrace = new Exception(); } /** * This method simply invokes the super.getURLs() method to access the * list of URLs that make up the RepositoryClassLoader classpath. * * @return the urls that make up the classpath */ public URL[] getClasspath() { return super.getURLs(); } /** * Return all library URLs associated with this RepositoryClassLoader * * <p>Do not remove this method without running the WebIntegrationTestSuite */ public URL[] getAllURLs() { return repository.getURLs(); } /** * Black list a class * * @param name the name of the class */ public void addToClassBlackList(String name) { classBlackList.add(name); } /** * Remove class from black list * * @param name the name of the class */ public void removeFromClassBlackList(String name) { classBlackList.remove(name); } /** * Is the class black listed? * * @param name the name of the class * @return true when the class is black listed, false otherwise */ public boolean isClassBlackListed(String name) { return classBlackList.contains(name); } /** * Clear any class black list. */ public void clearClassBlackList() { classBlackList.clear(); } /** * Black list a resource * * @param name the name of the resource */ public void addToResourceBlackList(String name) { resourceBlackList.add(name); } /** * Remove resource from black list * * @param name the name of the resource */ public void removeFromResourceBlackList(String name) { resourceBlackList.remove(name); } /** * Is the resource black listed? * * @param name the name of the resource * @return true when the resource is black listed, false otherwise */ public boolean isResourceBlackListed(String name) { return resourceBlackList.contains(name); } /** * Clear any resource blacklist. */ public void clearResourceBlackList() { resourceBlackList.clear(); } /** * Clear all blacklists */ public void clearBlackLists() { clearClassBlackList(); clearResourceBlackList(); } // URLClassLoader overrides -------------------------------------- /** The only caller of this method should be the VM initiated * loadClassInternal() method. This method attempts to acquire the * UnifiedLoaderRepository2 lock and then asks the repository to * load the class. * * <p>Forwards request to {@link LoaderRepository}. */ public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { boolean trace = log.isTraceEnabled(); if (trace) log.trace("loadClass " + this + " name=" + name+", loadClassDepth="+loadClassDepth); Class clazz = null; try { if (repository != null) { clazz = repository.getCachedClass(name); if (clazz != null) { if( log.isTraceEnabled() ) { StringBuffer buffer = new StringBuffer("Loaded class from cache, "); ClassToStringAction.toString(clazz, buffer); log.trace(buffer.toString()); } return clazz; } } clazz = loadClassImpl(name, resolve, Integer.MAX_VALUE); return clazz; } finally { if (trace) { if (clazz != null) log.trace("loadClass " + this + " name=" + name + " class=" + clazz + " cl=" + clazz.getClassLoader()); else log.trace("loadClass " + this + " name=" + name + " not found"); } } } /** The only caller of this method should be the VM initiated * loadClassInternal() method. This method attempts to acquire the * UnifiedLoaderRepository2 lock and then asks the repository to * load the class. * * <p>Forwards request to {@link LoaderRepository}. */ public Class loadClassBefore(String name) throws ClassNotFoundException { boolean trace = log.isTraceEnabled(); if (trace) log.trace("loadClassBefore " + this + " name=" + name); Class clazz = null; try { clazz = loadClassImpl(name, false, addedOrder); return clazz; } finally { if (trace) { if (clazz != null) log.trace("loadClassBefore " + this + " name=" + name + " class=" + clazz + " cl=" + clazz.getClassLoader()); else log.trace("loadClassBefore " + this + " name=" + name + " not found"); } } } public synchronized Class loadClassImpl(String name, boolean resolve, int stopAt) throws ClassNotFoundException { loadClassDepth ++; boolean trace = log.isTraceEnabled(); if( trace ) log.trace("loadClassImpl, name="+name+", resolve="+resolve); if( repository == null ) { String msg = "Invalid use of destroyed classloader, UCL destroyed at:"; throw new ClassNotFoundException(msg, this.unregisterTrace); } if (name.endsWith(".LocalStrings")) // JBOSSHACK avoid class not found exceptions { // JBOSSHACK avoid class not found exceptions if (getResourceLocally(name.replace('.', '/') + ".class") == null) // JBOSSHACK avoid class not found exceptions { // JBOSSHACK avoid class not found exceptions return null; // JBOSSHACK avoid class not found exceptions } // JBOSSHACK avoid class not found exceptions } // JBOSSHACK avoid class not found exceptions else if (name.indexOf(".LocalStrings_") != -1) // JBOSSHACK avoid class not found exceptions { // JBOSSHACK avoid class not found exceptions if (getResourceLocally(name.replace('.', '/') + ".class") == null) // JBOSSHACK avoid class not found exceptions { // JBOSSHACK avoid class not found exceptions return null; // JBOSSHACK avoid class not found exceptions } // JBOSSHACK avoid class not found exceptions } // JBOSSHACK avoid class not found exceptions /* Since loadClass can be called from loadClassInternal with the monitor already held, we need to determine if there is a ClassLoadingTask which requires this UCL. If there is, we release the UCL monitor so that the ClassLoadingTask can use the UCL. */ boolean acquired = attempt(1); while( acquired == false ) { /* Another thread needs this UCL to load a class so release the monitor acquired by the synchronized method. We loop until we can acquire the class loading lock. */ try { if( trace ) log.trace("Waiting for loadClass lock"); this.wait(); } catch(InterruptedException ignore) { } acquired = attempt(1); } ClassLoadingTask task = null; try { Thread t = Thread.currentThread(); // Register this thread as owning this UCL if( loadLock.holds() == 1 ) LoadMgr3.registerLoaderThread(this, t); // Create a class loading task and submit it to the repository task = new ClassLoadingTask(name, this, t, stopAt); /* Process class loading tasks needing this UCL until our task has been completed by the thread owning the required UCL(s). */ UnifiedLoaderRepository3 ulr3 = (UnifiedLoaderRepository3) repository; if( LoadMgr3.beginLoadTask(task, ulr3) == false ) { while( task.threadTaskCount != 0 ) { try { LoadMgr3.nextTask(t, task, ulr3); } catch(InterruptedException e) { // Abort the load or retry? break; } } } } finally { // Unregister as the UCL owner to reschedule any remaining load tasks if( loadLock.holds() == 1 ) LoadMgr3.endLoadTask(task); // Notify any threads waiting to use this UCL this.release(); this.notifyAll(); loadClassDepth --; } if( task.loadedClass == null ) { if( task.loadException instanceof ClassNotFoundException ) throw (ClassNotFoundException) task.loadException; else if( task.loadException != null ) { if( log.isTraceEnabled() ) log.trace("Unexpected error during load of:"+name, task.loadException); String msg = "Unexpected error during load of: "+name + ", msg="+task.loadException.getMessage(); throw new ClassNotFoundException(msg); } // Assert that loadedClass is not null else throw new IllegalStateException("ClassLoadingTask.loadedTask is null, name: "+name); } return task.loadedClass; } /** * Attempts to load the resource from its URL and if not found * forwards to the request to {@link LoaderRepository}. */ public URL getResource(String name) { if (repository != null) return repository.getResource(name, this); return null; } /** Find all resource URLs for the given name. This overrides the * URLClassLoader version to look for resources in the repository. * * @param name the name of the resource * @return Enumeration<URL> * @throws java.io.IOException */ public Enumeration findResources(String name) throws IOException { Vector resURLs = new Vector(); repository.getResources(name, this, resURLs); return resURLs.elements(); } /** * Provides the same functionality as {@link java.net.URLClassLoader#findResources}. */ public Enumeration findResourcesLocally(String name) throws IOException { return super.findResources(name); } /** Called by loadClassLocally to find the requested class within this * class loaders class path. * * @param name the name of the class * @return the resulting class * @exception ClassNotFoundException if the class could not be found */ protected Class findClass(String name) throws ClassNotFoundException { boolean trace = log.isTraceEnabled(); if( trace ) log.trace("findClass, name="+name); if (isClassBlackListed(name)) { if( trace ) log.trace("Class in blacklist, name="+name); throw new ClassNotFoundException("Class Not Found(blacklist): " + name); } Translator translator = repository.getTranslator(); if (translator != null) { // Obtain the transformed class bytecode try { // Obtain the raw bytecode from the classpath URL classUrl = getClassURL(name); byte[] rawcode = loadByteCode(classUrl); URL codeSourceUrl = getCodeSourceURL(name, classUrl); ProtectionDomain pd = getProtectionDomain(codeSourceUrl); byte[] bytecode = translator.transform(this, name, null, pd, rawcode); // If there was no transform use the raw bytecode if( bytecode == null ) bytecode = rawcode; // Define the class package and instance definePackage(name); return defineClass(name, bytecode, 0, bytecode.length, pd); } catch(ClassNotFoundException e) { throw e; } catch (Throwable ex) { throw new ClassNotFoundException(name, ex); } } Class clazz = null; try { clazz = findClassLocally(name); } catch(ClassNotFoundException e) { if( trace ) log.trace("CFNE: Adding to blacklist: "+name); addToClassBlackList(name); throw e; } return clazz; } /** * Find the class * * @param name the name of the class * @return the class */ protected Class findClassLocally(String name) throws ClassNotFoundException { return super.findClass(name); } /** * Define the package for the class if not already done * * @todo this properly * @param className the class name */ protected void definePackage(String className) { int i = className.lastIndexOf('.'); if (i == -1) return; try { definePackage(className.substring(0, i), null, null, null, null, null, null, null); } catch (IllegalArgumentException alreadyDone) { } } /** Append the given url to the URLs used for class and resource loading * @param url the URL to load from */ public void addURL(URL url) { if( url == null ) throw new IllegalArgumentException("url cannot be null"); if( repository.addClassLoaderURL(this, url) == true ) { log.debug("Added url: "+url+", to ucl: "+this); // Strip any query parameters String query = url.getQuery(); if( query != null ) { String ext = url.toExternalForm(); String ext2 = ext.substring(0, ext.length() - query.length() - 1); try { url = new URL (ext2); } catch(MalformedURLException e) { log.warn("Failed to strip query from: "+url, e); } } super.addURL(url); clearBlackLists(); } else if( log.isTraceEnabled() ) { log.trace("Ignoring duplicate url: "+url+", for ucl: "+this); } } /** * Return an empty URL array to force the RMI marshalling subsystem to * use the <tt>java.server.codebase</tt> property as the annotated codebase. * * <p>Do not remove this method without discussing it on the dev list. * * @return Empty URL[] */ public URL[] getURLs() { return EMPTY_URL_ARRAY; } public Package getPackage(String name) { return super.getPackage(name); } public Package[] getPackages() { return super.getPackages(); } // Object overrides ---------------------------------------------- /** * This is here to document that this must delegate to the * super implementation to perform identity based equality. Using * URL based equality caused conflicts with the Class.forName(String, * boolean, ClassLoader). */ public final boolean equals(Object other) { return super.equals(other); } /** * This is here to document that this must delegate to the * super implementation to perform identity based hashing. Using * URL based hashing caused conflicts with the Class.forName(String, * boolean, ClassLoader). */ public final int hashCode() { return super.hashCode(); } /** * Returns a string representation. */ public String toString() { return super.toString() + "{ url=" + getURL() + " }"; } // Protected ----------------------------------------------------- /** Attempt to acquire the class loading lock. This lock must be acquired * before a thread enters the class loading task loop in loadClass. This * method maintains any interrupted state of the calling thread. *@see #loadClass(String, boolean) */ protected boolean attempt(long waitMS) { boolean acquired = false; boolean trace = log.isTraceEnabled(); // Save and clear the interrupted state of the incoming thread boolean threadWasInterrupted = Thread.interrupted(); try { acquired = loadLock.attempt(waitMS); } catch(InterruptedException e) { } finally { // Restore the interrupted state of the thread if( threadWasInterrupted ) Thread.currentThread().interrupt(); } if( trace ) log.trace("attempt("+loadLock.holds()+") was: "+acquired+" for :"+this); return acquired; } /** Acquire the class loading lock. This lock must be acquired * before a thread enters the class loading task loop in loadClass. *@see #loadClass(String, boolean) */ protected void acquire() { // Save and clear the interrupted state of the incoming thread boolean threadWasInterrupted = Thread.interrupted(); try { loadLock.acquire(); } catch(InterruptedException e) { } finally { // Restore the interrupted state of the thread if( threadWasInterrupted ) Thread.currentThread().interrupt(); } if( log.isTraceEnabled() ) log.trace("acquired("+loadLock.holds()+") for :"+this); } /** Release the class loading lock previous acquired through the acquire * method. */ protected void release() { if( log.isTraceEnabled() ) log.trace("release("+loadLock.holds()+") for :"+this); loadLock.release(); if( log.isTraceEnabled() ) log.trace("released, holds: "+loadLock.holds()); } /** Obtain the bytecode for the indicated class from this class loaders * classpath. * * @param classname * @return the bytecode array if found * @exception ClassNotFoundException - if the class resource could not * be found */ protected byte[] loadByteCode(String classname) throws ClassNotFoundException, IOException { byte[] bytecode = null; URL classURL = getClassURL(classname); // Load the class bytecode InputStream is = null; try { is = classURL.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] tmp = new byte[1024]; int read = 0; while( (read = is.read(tmp)) > 0 ) { baos.write(tmp, 0, read); } bytecode = baos.toByteArray(); } finally { if( is != null ) is.close(); } return bytecode; } /** Obtain the bytecode for the indicated class from this class loaders * classpath. * * @param classURL * @return the bytecode array if found * @exception ClassNotFoundException - if the class resource could not * be found */ protected byte[] loadByteCode(URL classURL) throws ClassNotFoundException, IOException { byte[] bytecode = null; // Load the class bytecode InputStream is = null; try { is = classURL.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] tmp = new byte[1024]; int read = 0; while( (read = is.read(tmp)) > 0 ) { baos.write(tmp, 0, read); } bytecode = baos.toByteArray(); } finally { if( is != null ) is.close(); } return bytecode; } /** * Determine the protection domain. If we are a copy of the original * deployment, use the original url as the codebase. * @return the protection domain * @todo certificates and principles? */ protected ProtectionDomain getProtectionDomain(URL codesourceUrl) { Certificate certs[] = null; CodeSource cs = new CodeSource(codesourceUrl, certs); PermissionCollection permissions = Policy.getPolicy().getPermissions(cs); if (log.isTraceEnabled()) log.trace("getProtectionDomain, url=" + codesourceUrl + " codeSource=" + cs + " permissions=" + permissions); return new ProtectionDomain(cs, permissions); } // Package Private ----------------------------------------------- // Private ------------------------------------------------------- private URL getCodeSourceURL(String classname, URL classURL) throws java.net.MalformedURLException { String classRsrcName = classname.replace('.', '/') + ".class"; String urlAsString = classURL.toString(); int idx = urlAsString.indexOf(classRsrcName); if (idx == -1) return classURL; urlAsString = urlAsString.substring(0, idx); return new URL(urlAsString); } private URL getClassURL(String classname) throws ClassNotFoundException { String classRsrcName = classname.replace('.', '/') + ".class"; URL classURL = this.getResourceLocally(classRsrcName); if( classURL == null ) { String msg = "Failed to find: "+classname+" as resource: "+classRsrcName; throw new ClassNotFoundException(msg); } return classURL; } // Inner classes ------------------------------------------------- }