blob: 5ae1c31253b7a1313f38d67d8b748fbd37bb6019 [file] [log] [blame]
/*
* 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());
}
}