| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package org.apache.tomcat.util.loader; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.Enumeration; |
| import java.util.Vector; |
| |
| /* |
| * Initially, I started with WebappClassLoader attempting to clean up and |
| * refactor it. Because of complexity and very weird ( and likely buggy ) |
| * behavior, I moved the other way, starting with URLClassLoader and adding |
| * the functionality from WebappClassLoader. |
| * |
| * The current version has a lot of unimplemented WebappClassLoader features and |
| * TODOs - all of them are needed in order to support a single/consistent loader |
| * for webapps and server/modules. |
| * |
| * - all ordering options and tricks |
| * - local loading - in case it can be made more efficient than URLCL |
| * - hook to plugin JNDI finder |
| * - ability to add extra permissions to loaded classes |
| * - ability to use work dir for anti-jar locking and generated classes ( incl getURLs) |
| * |
| * |
| * Things better kept out: |
| * - negative cache - it'll waste space with little benefit, most not found classes |
| * will not be asked multiple times, and most will be in other loaders |
| * - binaryContent cache - it's unlikely same resource will be loaded multiple |
| * times, and some may be large |
| * |
| */ |
| |
| /** |
| * Simple module class loader. Will search the repository if the class is not |
| * found locally. |
| * |
| * TODO: findResources() - merge all responses from the repo and parent. |
| * |
| * Based on StandardClassLoader and WebappClassLoader. |
| * |
| * @author Costin Manolache |
| * @author Remy Maucherat |
| * @author Craig R. McClanahan |
| */ |
| public class ModuleClassLoader |
| extends URLClassLoader |
| { |
| // Don't use commons logging or configs to debug loading - logging is dependent |
| // on loaders and drags a lot of stuff in the classpath |
| // |
| private static final boolean DEBUG=false; //LoaderProperties.getProperty("loader.debug.ModuleClassLoader") != null; |
| private static final boolean DEBUGNF=false;//LoaderProperties.getProperty("loader.debug.ModuleClassLoaderNF") != null; |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| public ModuleClassLoader(URL repositories[], ClassLoader parent) { |
| super(repositories, parent); |
| if(DEBUG) log( "NEW ModuleClassLoader " + parent + " " + repositories.length); |
| updateStamp(); |
| } |
| |
| |
| public ModuleClassLoader(URL repositories[]) { |
| super(repositories); |
| if(DEBUG) log( "NEW ModuleClassLoader -null-"+ " " + repositories.length); |
| updateStamp(); |
| } |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| protected Repository repository; |
| |
| /** |
| * Should this class loader delegate to the parent class loader |
| * <strong>before</strong> searching its own repositories (i.e. the |
| * usual Java2 delegation model)? If set to <code>false</code>, |
| * this class loader will search its own repositories first, and |
| * delegate to the parent only if the class or resource is not |
| * found locally. |
| */ |
| protected boolean delegate = false; |
| |
| /** |
| * Last time a JAR was accessed. |
| * TODO: change to last time the loader was accessed |
| */ |
| protected long lastJarAccessed = 0L; |
| |
| protected long lastModified=0L; |
| |
| /** |
| * Has this component been started? |
| */ |
| protected boolean started = false; |
| |
| protected Module module; |
| |
| // ------------------------------------------------------------- Properties |
| |
| /** |
| * Set the "delegate first" flag for this class loader. |
| * |
| * @param delegate The new "delegate first" flag |
| */ |
| void setDelegate(boolean delegate) { |
| this.delegate = delegate; |
| } |
| |
| void setRepository(Repository lg ) { |
| this.repository=lg; |
| } |
| |
| void setModule(Module webappLoader) { |
| this.module=webappLoader; |
| } |
| |
| /** Not public - his can only be called from package. |
| * To get the module from a ClassLoader you need access to the Loader |
| * instance. |
| * |
| * @return |
| */ |
| Module getModule() { |
| return module; |
| } |
| |
| void setWorkDir(File s) { |
| // TODO |
| } |
| |
| /** |
| * Add a new repository to the set of places this ClassLoader can look for |
| * classes to be loaded. |
| * |
| * @param repository Name of a source of classes to be loaded, such as a |
| * directory pathname, a JAR file pathname, or a ZIP file pathname |
| * |
| * @exception IllegalArgumentException if the specified repository is |
| * invalid or does not exist |
| */ |
| void addRepository(String repository) { |
| // Add this repository to our underlying class loader |
| try { |
| boolean mod=modified(); |
| URL url = new URL(repository); |
| super.addURL(url); |
| if( ! mod ) { |
| // don't check if it is modified, so it works |
| updateStamp(); |
| } |
| } catch (MalformedURLException e) { |
| IllegalArgumentException iae = new IllegalArgumentException |
| ("Invalid repository: " + repository); |
| iae.initCause(e); |
| //jdkCompat.chainException(iae, e); |
| throw iae; |
| } |
| } |
| |
| void updateStamp() { |
| URL cp[]=super.getURLs(); |
| if (cp != null ) { |
| for (int i = 0; i <cp.length; i++) { |
| File f=new File(cp[i].getFile()); |
| long lm=f.lastModified(); |
| if( lm > lastModified ) lastModified=lm; |
| } |
| } |
| } |
| |
| private boolean dirCheck(File dir ) { |
| //log("Checking " + dir ); |
| File subd[]=dir.listFiles(); |
| for( int i=0; i< subd.length; i++ ) { |
| long lm=subd[i].lastModified(); |
| if( lm > lastModified ) { |
| log("Modified file: " + dir + " " + subd[i] + " " + lm + " " + lastModified); |
| return true; |
| } |
| if( subd[i].isDirectory() ) { |
| return dirCheck(subd[i]); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Have one or more classes or resources been modified so that a reload |
| * is appropriate? |
| * |
| * Not public - call it via Module |
| */ |
| boolean modified() { |
| URL cp[]=super.getURLs(); |
| if (cp != null ) { |
| for (int i = 0; i <cp.length; i++) { |
| File f=new File(cp[i].getFile()); |
| long lm=f.lastModified(); |
| if( lm > lastModified ) { |
| log( "Modified file: " + f + " " + lm + " " + lastModified); |
| return true; |
| } |
| // assume dirs are used only for debug and small |
| if( f.isDirectory() ) { |
| return dirCheck(f); |
| } |
| } |
| } |
| |
| if (DEBUG) |
| log("modified() false"); |
| |
| // TODO - check at least the jars |
| return (false); |
| } |
| |
| // ---------------------------------------------------- ClassLoader Methods |
| |
| |
| /** |
| * Find the specified class in our local repositories, if possible. If |
| * not found, throw <code>ClassNotFoundException</code>. |
| * |
| * @param name Name of the class to be loaded |
| * |
| * @exception ClassNotFoundException if the class was not found |
| */ |
| public Class findClass(String name) throws ClassNotFoundException { |
| return findClass2(name, true); |
| } |
| |
| public Class findClass2(String name, boolean del2repo) throws ClassNotFoundException { |
| if( del2repo ) { |
| return ((RepositoryClassLoader)repository.getClassLoader()).findClass(name); |
| } // else |
| |
| Class clazz = null; |
| |
| try { |
| clazz = super.findClass(name); |
| } catch (RuntimeException e) { |
| if (DEBUG) |
| log("findClass() -->RuntimeException " + name, e); |
| throw e; |
| } catch( ClassNotFoundException ex ) { |
| URL cp[]=this.getURLs(); |
| if (DEBUGNF) |
| log("findClass() NOTFOUND " + name + " " + (( cp.length > 0 ) ? cp[0].toString() : "") ); |
| throw ex; |
| } |
| |
| if (clazz == null) { // does it ever happen ? |
| if (DEBUGNF) |
| log("findClass() NOTFOUND throw CNFE " + name); |
| throw new ClassNotFoundException(name); |
| } |
| |
| // Return the class we have located |
| if (DEBUG) { |
| if( clazz.getClassLoader() != this ) |
| log("findClass() FOUND " + clazz + " Loaded by " + clazz.getClassLoader()); |
| else |
| log("findClass() FOUND " + clazz ); |
| } |
| return (clazz); |
| } |
| |
| /** Same as findClass, but also checks if the class has been previously |
| * loaded. |
| * |
| * In most implementations, findClass() doesn't check with findLoadedClass(). |
| * In order to implement repository, we need to ask each loader in the group |
| * to load only from it's local resources - however this will lead to errors |
| * ( duplicated definition ) if findClass() is used. |
| * |
| * @param name |
| * @return |
| * @throws ClassNotFoundException |
| */ |
| public Class findLocalClass(String name) throws ClassNotFoundException |
| { |
| Class clazz = findLoadedClass(name); |
| if (clazz != null) { |
| if (DEBUG) |
| log("findLocalClass() - FOUND " + name); |
| return (clazz); |
| } |
| |
| return findClass(name); |
| } |
| |
| |
| |
| |
| /** |
| * Find the specified resource in our local repository, and return a |
| * <code>URL</code> refering to it, or <code>null</code> if this resource |
| * cannot be found. |
| * |
| * @param name Name of the resource to be found |
| */ |
| public URL findResource(final String name) { |
| return findResource2( name, true); |
| } |
| |
| public URL findResource2(final String name, boolean del2repo ) { |
| if( del2repo ) { |
| return ((RepositoryClassLoader)repository.getClassLoader()).findResource(name); |
| } // else: |
| |
| URL url = null; |
| |
| url = super.findResource(name); |
| |
| if(url==null) { |
| // try the repository |
| // TODO |
| } |
| |
| if (url==null && DEBUG) { |
| if (DEBUGNF) log("findResource() NOTFOUND " + name ); |
| return null; |
| } |
| |
| if (DEBUG) log("findResource() found " + name + " " + url ); |
| return (url); |
| } |
| |
| |
| /** |
| * Return an enumeration of <code>URLs</code> representing all of the |
| * resources with the given name. If no resources with this name are |
| * found, return an empty enumeration. |
| * |
| * @param name Name of the resources to be found |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| public Enumeration findResources(String name) throws IOException { |
| return findResources2(name, true); |
| } |
| |
| Enumeration findResources2(String name, boolean del2repo) throws IOException { |
| if( del2repo ) { |
| return ((RepositoryClassLoader)repository.getClassLoader()).findResources(name); |
| } else { |
| return super.findResources(name); |
| } |
| } |
| |
| // Next methods implement the search alghoritm - parent, repo, delegation, etc |
| |
| /** getResource() - modified to implement the search alghoritm |
| * |
| */ |
| public URL getResource(String name) { |
| return getResource2( name, null, true); |
| } |
| |
| /** getResource() - same thing, but don't delegate to repo if called |
| * from repo |
| * |
| */ |
| URL getResource2(String name, ClassLoader originator, boolean delegate2repo ) { |
| |
| URL url = null; |
| |
| // (1) Delegate to parent if requested |
| if (delegate) { |
| url=getResourceParentDelegate(name); |
| if(url!=null ) return url; |
| } |
| |
| // (2) Search local repositories |
| url = findResource(name); |
| if (url != null) { |
| // TODO: antijar locking - WebappClassLoader is making a copy ( is it ??) |
| if (DEBUG) |
| log("getResource() found locally " + delegate + " " + name + " " + url); |
| return (url); |
| } |
| |
| // Finally, try the group loaders ( via super() in StandardClassLoader ). |
| // not found using normal loading mechanism. load from one of the classes in the group |
| if( delegate2repo && repository!=null ) { |
| url=repository.findResource(this, name); |
| if(url!=null ) { |
| if( DEBUG ) |
| log("getResource() FOUND from group " + repository.getName() + " " + name + " " + url); |
| return url; |
| } |
| } |
| |
| // (3) Delegate to parent unconditionally if not already attempted |
| if( !delegate ) { |
| url=getResourceParentDelegate(name); |
| if(url!=null ) return url; |
| } |
| |
| |
| // (4) Resource was not found |
| if (DEBUGNF) |
| log("getResource() NOTFOUND " + delegate + " " + name + " " + url); |
| return (null); |
| |
| } |
| |
| |
| // to avoid duplication - get resource from parent, when delegating |
| private URL getResourceParentDelegate(String name) { |
| URL url=null; |
| ClassLoader loader = getParent(); |
| |
| if (loader == null) { |
| loader = getSystemClassLoader(); |
| if (url != null) { |
| if (DEBUG) |
| log("getResource() found by system " + delegate + " " + name + " " + url); |
| return (url); |
| } |
| } else { |
| url = loader.getResource(name); |
| if (url != null) { |
| if (DEBUG) |
| log("getResource() found by parent " + delegate + " " + name + " " + url); |
| return (url); |
| } |
| } |
| if( DEBUG ) log("getResource not found by parent " + loader); |
| |
| return url; |
| } |
| |
| /** |
| * Load the class with the specified name, searching using the following |
| * algorithm until it finds and returns the class. If the class cannot |
| * be found, returns <code>ClassNotFoundException</code>. |
| * <ul> |
| * <li>Call <code>findLoadedClass(String)</code> to check if the |
| * class has already been loaded. If it has, the same |
| * <code>Class</code> object is returned.</li> |
| * <li>If the <code>delegate</code> property is set to <code>true</code>, |
| * call the <code>loadClass()</code> method of the parent class |
| * loader, if any.</li> |
| * <li>Call <code>findClass()</code> to find this class in our locally |
| * defined repositories.</li> |
| * <li>Call the <code>loadClass()</code> method of our parent |
| * class loader, if any.</li> |
| * </ul> |
| * If the class was found using the above steps, and the |
| * <code>resolve</code> flag is <code>true</code>, this method will then |
| * call <code>resolveClass(Class)</code> on the resulting Class object. |
| * |
| * @param name Name of the class to be loaded |
| * @param resolve If <code>true</code> then resolve the class |
| * |
| * @exception ClassNotFoundException if the class was not found |
| */ |
| public Class loadClass(String name, boolean resolve) |
| throws ClassNotFoundException |
| { |
| return loadClass2( name, resolve, true ); |
| } |
| |
| public Class loadClass2(String name, boolean resolve, boolean del2repo) |
| throws ClassNotFoundException |
| { |
| |
| Class clazz = null; |
| |
| // Don't load classes if class loader is stopped |
| if (!started) { |
| log("Not started " + this + " " + module); |
| //throw new ThreadDeath(); |
| start(); |
| } |
| |
| // (0) Check our previously loaded local class cache |
| clazz = findLoadedClass(name); |
| if (clazz != null) { |
| if (DEBUG) |
| log("loadClass() FOUND findLoadedClass " + name + " , " + resolve); |
| if (resolve) resolveClass(clazz); |
| return (clazz); |
| } |
| |
| // (0.2) Try loading the class with the system class loader, to prevent |
| // the webapp from overriding J2SE classes |
| try { |
| clazz = getSystemClassLoader().loadClass(name); |
| if (clazz != null) { |
| // enabling this can result in ClassCircularityException |
| // if (DEBUG) |
| // log("loadClass() FOUND system " + name + " , " + resolve); |
| if (resolve) resolveClass(clazz); |
| return (clazz); |
| } |
| } catch (ClassNotFoundException e) { |
| // Ignore |
| } |
| |
| // TODO: delegate based on filter |
| boolean delegateLoad = delegate;// || filter(name); |
| |
| // (1) Delegate to our parent if requested |
| if (delegateLoad) { |
| |
| ClassLoader loader = getParent(); |
| if( loader != null ) { |
| try { |
| clazz = loader.loadClass(name); |
| if (clazz != null) { |
| if (DEBUG) |
| log("loadClass() FOUND by parent " + delegate + " " + name + " , " + resolve); |
| if (resolve) |
| resolveClass(clazz); |
| return (clazz); |
| } |
| } catch (ClassNotFoundException e) { |
| ; |
| } |
| } |
| } |
| |
| // (2) Search local repositories |
| try { |
| clazz = findClass(name); |
| if (clazz != null) { |
| if (DEBUG) |
| log("loadClass - FOUND findClass " + delegate + " " + name + " , " + resolve); |
| if (resolve) resolveClass(clazz); |
| return (clazz); |
| } |
| } catch (ClassNotFoundException e) { |
| ; |
| } |
| |
| // Finally, try the group loaders ( via super() in StandardClassLoader ). |
| // not found using normal loading mechanism. load from one of the classes in the group |
| if( del2repo && repository!=null ) { |
| Class cls=repository.findClass(this, name); |
| if(cls!=null ) { |
| if( DEBUG ) |
| log("loadClass(): FOUND from group " + repository.getName() + " " + name); |
| if (resolve) resolveClass(clazz); |
| return cls; |
| } |
| } |
| |
| // (3) Delegate to parent unconditionally |
| if (!delegateLoad) { |
| ClassLoader loader = getParent(); |
| if( loader != null ) { |
| try { |
| clazz = loader.loadClass(name); |
| if (clazz != null) { |
| if (DEBUG) |
| log("loadClass() FOUND parent " + delegate + " " + name + " , " + resolve); |
| if (resolve) resolveClass(clazz); |
| return (clazz); |
| } |
| } catch (ClassNotFoundException e) { |
| ; |
| } |
| } |
| } |
| |
| if( DEBUGNF ) log("loadClass(): NOTFOUND " + name + " xxx " + getParent() + " " + repository.getName() ); |
| throw new ClassNotFoundException(name); |
| } |
| |
| |
| // ------------------------------------------------------ Lifecycle Methods |
| |
| |
| |
| /** |
| * Start the class loader. |
| * |
| * @exception LifecycleException if a lifecycle error occurs |
| */ |
| void start() { |
| |
| started = true; |
| |
| } |
| |
| /** Support for "disabled" state. |
| * |
| * @return |
| */ |
| boolean isStarted() { |
| return started; |
| } |
| |
| |
| /** |
| * Stop the class loader. |
| * |
| * @exception LifecycleException if a lifecycle error occurs |
| */ |
| void stop() { |
| |
| started = false; |
| |
| } |
| |
| |
| |
| |
| /** |
| * Validate a classname. As per SRV.9.7.2, we must restict loading of |
| * classes from J2SE (java.*) and classes of the servlet API |
| * (javax.servlet.*). That should enhance robustness and prevent a number |
| * of user error (where an older version of servlet.jar would be present |
| * in /WEB-INF/lib). |
| * |
| * @param name class name |
| * @return true if the name is valid |
| */ |
| protected boolean validate(String name) { |
| |
| if (name == null) |
| return false; |
| if (name.startsWith("java.")) |
| return false; |
| |
| return true; |
| |
| } |
| |
| |
| // ------------------ Local methods ------------------------ |
| |
| private void log(String s ) { |
| System.err.println("ModuleClassLoader: " + s); |
| } |
| private void log(String s, Throwable t ) { |
| System.err.println("ModuleClassLoader: " + s); |
| t.printStackTrace(); |
| } |
| |
| Object debugObj=new Object(); |
| |
| /** |
| * Render a String representation of this object. |
| */ |
| public String toString() { |
| |
| StringBuffer sb = new StringBuffer("ModuleCL "); |
| sb.append(debugObj).append(" delegate: "); |
| sb.append(delegate); |
| //sb.append("\r\n"); |
| sb.append(" cp: "); |
| URL cp[]=super.getURLs(); |
| if (cp != null ) { |
| for (int i = 0; i <cp.length; i++) { |
| sb.append(" "); |
| sb.append(cp[i].getFile()); |
| } |
| } |
| if (getParent() != null) { |
| sb.append("\r\n----------> Parent: "); |
| sb.append(getParent().toString()); |
| sb.append("\r\n"); |
| } |
| return (sb.toString()); |
| } |
| } |
| |