Return-Path: X-Original-To: apmail-tomcat-dev-archive@www.apache.org Delivered-To: apmail-tomcat-dev-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 8A6FE17764 for ; Tue, 8 Sep 2015 12:49:27 +0000 (UTC) Received: (qmail 75188 invoked by uid 500); 8 Sep 2015 12:49:25 -0000 Delivered-To: apmail-tomcat-dev-archive@tomcat.apache.org Received: (qmail 74823 invoked by uid 500); 8 Sep 2015 12:49:24 -0000 Mailing-List: contact dev-help@tomcat.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: "Tomcat Developers List" Delivered-To: mailing list dev@tomcat.apache.org Received: (qmail 74620 invoked by uid 99); 8 Sep 2015 12:49:24 -0000 Received: from eris.apache.org (HELO hades.apache.org) (140.211.11.105) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 08 Sep 2015 12:49:24 +0000 Received: from hades.apache.org (localhost [127.0.0.1]) by hades.apache.org (ASF Mail Server at hades.apache.org) with ESMTP id 9B324AC0154 for ; Tue, 8 Sep 2015 12:49:24 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1701793 [2/3] - in /tomcat/tc7.0.x/trunk: java/org/apache/catalina/core/ java/org/apache/catalina/loader/ java/org/apache/catalina/security/ java/org/apache/tomcat/ test/org/apache/catalina/loader/ test/org/apache/catalina/startup/ webapps... Date: Tue, 08 Sep 2015 12:49:24 -0000 To: dev@tomcat.apache.org From: markt@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20150908124924.9B324AC0154@hades.apache.org> Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoader.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoader.java?rev=1701793&r1=1701792&r2=1701793&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoader.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/loader/WebappClassLoader.java Tue Sep 8 12:49:24 2015 @@ -14,3773 +14,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - package org.apache.catalina.loader; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FilePermission; -import java.io.IOException; -import java.io.InputStream; -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.Charset; -import java.security.AccessControlException; -import java.security.AccessController; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Policy; -import java.security.PrivilegedAction; -import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.ConcurrentModificationException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.jar.Attributes; -import java.util.jar.Attributes.Name; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -import javax.naming.Binding; -import javax.naming.NameClassPair; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.DirContext; - -import org.apache.catalina.Globals; -import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; -import org.apache.catalina.LifecycleListener; -import org.apache.catalina.LifecycleState; -import org.apache.naming.JndiPermission; -import org.apache.naming.resources.ProxyDirContext; -import org.apache.naming.resources.Resource; -import org.apache.naming.resources.ResourceAttributes; -import org.apache.tomcat.InstrumentableClassLoader; -import org.apache.tomcat.util.ExceptionUtils; -import org.apache.tomcat.util.IntrospectionUtils; -import org.apache.tomcat.util.compat.JreVendor; -import org.apache.tomcat.util.res.StringManager; - -/** - * Specialized web application class loader. - *

- * This class loader is a full reimplementation of the - * URLClassLoader from the JDK. It is designed to be fully - * compatible with a normal URLClassLoader, although its internal - * behavior may be completely different. - *

- * IMPLEMENTATION NOTE - By default, this class loader follows - * the delegation model required by the specification. The system class - * loader will be queried first, then the local repositories, and only then - * delegation to the parent class loader will occur. This allows the web - * application to override any shared class except the classes from J2SE. - * Special handling is provided from the JAXP XML parser interfaces, the JNDI - * interfaces, and the classes from the servlet API, which are never loaded - * from the webapp repositories. The delegate property - * allows an application to modify this behavior to move the parent class loader - * ahead of the local repositories. - *

- * IMPLEMENTATION NOTE - Due to limitations in Jasper - * compilation technology, any repository which contains classes from - * the servlet API will be ignored by the class loader. - *

- * IMPLEMENTATION NOTE - The class loader generates source - * URLs which include the full JAR URL when a class is loaded from a JAR file, - * which allows setting security permission at the class level, even when a - * class is contained inside a JAR. - *

- * IMPLEMENTATION NOTE - Local repositories are searched in - * the order they are added via the initial constructor and/or any subsequent - * calls to addRepository() or addJar(). - *

- * IMPLEMENTATION NOTE - No check for sealing violations or - * security is made unless a security manager is present. - *

- * TODO: Is there any requirement to provide a proper Lifecycle implementation - * rather than the current stubbed implementation? - * IMPLEMENTATION NOTE - As of 7.0.64/8.0, this class - * loader implements {@link InstrumentableClassLoader}, permitting web - * application classes to instrument other classes in the same web - * application. It does not permit instrumentation of system or container - * classes or classes in other web apps. - * - * @author Remy Maucherat - * @author Craig R. McClanahan - */ -public class WebappClassLoader extends URLClassLoader - implements Lifecycle, InstrumentableClassLoader { - - private static final org.apache.juli.logging.Log log= - org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class ); - - private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8"); - /** - * List of ThreadGroup names to ignore when scanning for web application - * started threads that need to be shut down. - */ - private static final List JVM_THREAD_GROUP_NAMES = - new ArrayList(); - - private static final String JVM_THREAD_GROUP_SYSTEM = "system"; - - private static final String SERVICES_PREFIX = "META-INF/services/"; - private static final String CLASS_FILE_SUFFIX = ".class"; - - private static final Manifest MANIFEST_UNKNOWN = new Manifest(); +public class WebappClassLoader extends WebappClassLoaderBase { - static { - JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM); - JVM_THREAD_GROUP_NAMES.add("RMI Runtime"); - } - - protected class PrivilegedFindResourceByName - implements PrivilegedAction { - - protected String name; - protected String path; - protected boolean manifestRequired; - - PrivilegedFindResourceByName(String name, String path, boolean manifestRequired) { - this.name = name; - this.path = path; - this.manifestRequired = manifestRequired; - } - - @Override - public ResourceEntry run() { - return findResourceInternal(name, path, manifestRequired); - } - - } - - - protected static final class PrivilegedGetClassLoader - implements PrivilegedAction { - - public Class clazz; - - public PrivilegedGetClassLoader(Class clazz){ - this.clazz = clazz; - } - - @Override - public ClassLoader run() { - return clazz.getClassLoader(); - } - } - - - // ------------------------------------------------------- Static Variables - - - /** - * The set of trigger classes that will cause a proposed repository not - * to be added if this class is visible to the class loader that loaded - * this factory class. Typically, trigger classes will be listed for - * components that have been integrated into the JDK for later versions, - * but where the corresponding JAR files are required to run on - * earlier versions. - */ - protected static final String[] triggers = { - "javax.servlet.Servlet", "javax.el.Expression" // Servlet API - }; - - - /** - * Set of package names which are not allowed to be loaded from a webapp - * class loader without delegating first. - */ - protected static final String[] packageTriggers = { - }; - - - /** - * The string manager for this package. - */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); - - - /** - * Use anti JAR locking code, which does URL rerouting when accessing - * resources. - */ - boolean antiJARLocking = false; - - // ----------------------------------------------------------- Constructors - - - /** - * Construct a new ClassLoader with no defined repositories and no - * parent ClassLoader. - */ public WebappClassLoader() { - - super(new URL[0]); - - ClassLoader p = getParent(); - if (p == null) { - p = getSystemClassLoader(); - } - this.parent = p; - - ClassLoader j = String.class.getClassLoader(); - if (j == null) { - j = getSystemClassLoader(); - while (j.getParent() != null) { - j = j.getParent(); - } - } - this.j2seClassLoader = j; - - securityManager = System.getSecurityManager(); - if (securityManager != null) { - refreshPolicy(); - } + super(); } - /** - * Construct a new ClassLoader with no defined repositories and the given - * parent ClassLoader. - *

- * Method is used via reflection - - * see {@link WebappLoader#createClassLoader()} - * - * @param parent Our parent class loader - */ public WebappClassLoader(ClassLoader parent) { - - super(new URL[0], parent); - - ClassLoader p = getParent(); - if (p == null) { - p = getSystemClassLoader(); - } - this.parent = p; - - ClassLoader j = String.class.getClassLoader(); - if (j == null) { - j = getSystemClassLoader(); - while (j.getParent() != null) { - j = j.getParent(); - } - } - this.j2seClassLoader = j; - - securityManager = System.getSecurityManager(); - if (securityManager != null) { - refreshPolicy(); - } - } - - - // ----------------------------------------------------- Instance Variables - - - /** - * Associated directory context giving access to the resources in this - * webapp. - */ - protected DirContext resources = null; - - - /** - * The cache of ResourceEntry for classes and resources we have loaded, - * keyed by resource name. - */ - protected HashMap resourceEntries = new HashMap(); - - - /** - * The list of not found resources. - */ - protected HashMap notFoundResources = - new LinkedHashMap() { - private static final long serialVersionUID = 1L; - @Override - protected boolean removeEldestEntry( - Map.Entry eldest) { - return size() > 1000; - } - }; - - - /** - * Should this class loader delegate to the parent class loader - * before searching its own repositories (i.e. the - * usual Java2 delegation model)? If set to false, - * this class loader will search its own repositories first, and - * delegate to the parent only if the class or resource is not - * found locally. Note that the default, false, is - * the behavior called for by the servlet specification. - */ - protected boolean delegate = false; - - - /** - * Last time a JAR was accessed. - */ - protected long lastJarAccessed = 0L; - - - /** - * The list of local repositories, in the order they should be searched - * for locally loaded classes or resources. - */ - protected String[] repositories = new String[0]; - - - /** - * Repositories URLs, used to cache the result of getURLs. - */ - protected URL[] repositoryURLs = null; - - - /** - * Repositories translated as path in the work directory (for Jasper - * originally), but which is used to generate fake URLs should getURLs be - * called. - */ - protected File[] files = new File[0]; - - - /** - * The list of JARs, in the order they should be searched - * for locally loaded classes or resources. - */ - protected JarFile[] jarFiles = new JarFile[0]; - - - /** - * The list of JARs, in the order they should be searched - * for locally loaded classes or resources. - */ - protected File[] jarRealFiles = new File[0]; - - - /** - * The path which will be monitored for added Jar files. - */ - protected String jarPath = null; - - - /** - * The list of JARs, in the order they should be searched - * for locally loaded classes or resources. - */ - protected String[] jarNames = new String[0]; - - - /** - * The list of JARs last modified dates, in the order they should be - * searched for locally loaded classes or resources. - */ - protected long[] lastModifiedDates = new long[0]; - - - /** - * The list of resources which should be checked when checking for - * modifications. - */ - protected String[] paths = new String[0]; - - - /** - * A list of read File and Jndi Permission's required if this loader - * is for a web application context. - */ - protected ArrayList permissionList = - new ArrayList(); - - - /** - * Path where resources loaded from JARs will be extracted. - */ - protected File loaderDir = null; - protected String canonicalLoaderDir = null; - - /** - * The PermissionCollection for each CodeSource for a web - * application context. - */ - protected HashMap loaderPC = new HashMap(); - - - /** - * Instance of the SecurityManager installed. - */ - protected SecurityManager securityManager = null; - - - /** - * The parent class loader. - */ - protected ClassLoader parent = null; - - - /** - * The system class loader. - */ - protected ClassLoader system = null; - - - /** - * The bootstrap class loader used to load the JavaSE classes. In some - * implementations this class loader is always null and in - * those cases {@link ClassLoader#getParent()} will be called recursively on - * the system class loader and the last non-null result used. - */ - protected ClassLoader j2seClassLoader; - - - /** - * Has this component been started? - */ - protected boolean started = false; - - - /** - * Has external repositories. - */ - protected boolean hasExternalRepositories = false; - - /** - * Search external repositories first - */ - protected boolean searchExternalFirst = false; - - /** - * need conversion for properties files - */ - protected boolean needConvert = false; - - - /** - * All permission. - */ - protected Permission allPermission = new java.security.AllPermission(); - - - /** - * Should Tomcat attempt to null out any static or final fields from loaded - * classes when a web application is stopped as a work around for apparent - * garbage collection bugs and application coding errors? There have been - * some issues reported with log4j when this option is true. Applications - * without memory leaks using recent JVMs should operate correctly with this - * option set to false. If not specified, the default value of - * false will be used. - */ - private boolean clearReferencesStatic = false; - - /** - * Should Tomcat attempt to terminate threads that have been started by the - * web application? Stopping threads is performed via the deprecated (for - * good reason) Thread.stop() method and is likely to result in - * instability. As such, enabling this should be viewed as an option of last - * resort in a development environment and is not recommended in a - * production environment. If not specified, the default value of - * false will be used. - */ - private boolean clearReferencesStopThreads = false; - - /** - * Should Tomcat attempt to terminate any {@link java.util.TimerThread}s - * that have been started by the web application? If not specified, the - * default value of false will be used. - */ - private boolean clearReferencesStopTimerThreads = false; - - /** - * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()} - * when the class loader is stopped? If not specified, the default value - * of true is used. Changing the default setting is likely to - * lead to memory leaks and other issues. - */ - private boolean clearReferencesLogFactoryRelease = true; - - /** - * If an HttpClient keep-alive timer thread has been started by this web - * application and is still running, should Tomcat change the context class - * loader from the current {@link WebappClassLoader} to - * {@link WebappClassLoader#parent} to prevent a memory leak? Note that the - * keep-alive timer thread will stop on its own once the keep-alives all - * expire however, on a busy system that might not happen for some time. - */ - private boolean clearReferencesHttpClientKeepAliveThread = true; - - /** - * Name of associated context used with logging and JMX to associate with - * the right web application. Particularly useful for the clear references - * messages. Defaults to unknown but if standard Tomcat components are used - * it will be updated during initialisation from the resources. - */ - private String contextName = "unknown"; - - /** - * Holds the class file transformers decorating this class loader. The - * CopyOnWriteArrayList is thread safe. It is expensive on writes, but - * those should be rare. It is very fast on reads, since synchronization - * is not actually used. Importantly, the ClassLoader will never block - * iterating over the transformers while loading a class. - */ - private final List transformers = new CopyOnWriteArrayList(); - - /** - * Code base to use for classes loaded from WEB-INF/classes. - */ - private URL webInfClassesCodeBase = null; - - // ------------------------------------------------------------- Properties - - - /** - * Get associated resources. - */ - public DirContext getResources() { - - return this.resources; - - } - - - /** - * Set associated resources. - */ - public void setResources(DirContext resources) { - - this.resources = resources; - - if (resources instanceof ProxyDirContext) { - contextName = ((ProxyDirContext) resources).getContextName(); - } - } - - - /** - * Return the context name for this class loader. - */ - public String getContextName() { - - return (this.contextName); - - } - - - /** - * Return the "delegate first" flag for this class loader. - */ - public boolean getDelegate() { - - return (this.delegate); - + super(parent); } /** - * Set the "delegate first" flag for this class loader. - * If this flag is true, this class loader delegates - * to the parent class loader - * before searching its own repositories, as - * in an ordinary (non-servlet) chain of Java class loaders. - * If set to false (the default), - * this class loader will search its own repositories first, and - * delegate to the parent only if the class or resource is not - * found locally, as per the servlet specification. + * Returns a copy of this class loader without any class file + * transformers. This is a tool often used by Java Persistence API + * providers to inspect entity classes in the absence of any + * instrumentation, something that can't be guaranteed within the + * context of a {@link java.lang.instrument.ClassFileTransformer}'s + * {@link java.lang.instrument.ClassFileTransformer#transform(ClassLoader, + * String, Class, java.security.ProtectionDomain, byte[]) transform} method. + *

+ * The returned class loader's resource cache will have been cleared + * so that classes already instrumented will not be retained or + * returned. * - * @param delegate The new "delegate first" flag + * @return the transformer-free copy of this class loader. */ - public void setDelegate(boolean delegate) { - - this.delegate = delegate; - - } + @Override + public WebappClassLoader copyWithoutTransformers() { + WebappClassLoader result = new WebappClassLoader(getParent()); - /** - * @return Returns the antiJARLocking. - */ - public boolean getAntiJARLocking() { - return antiJARLocking; - } + super.copyStateWithoutTransformers(result); + try { + result.start(); + } catch (LifecycleException e) { + throw new IllegalStateException(e); + } - /** - * @param antiJARLocking The antiJARLocking to set. - */ - public void setAntiJARLocking(boolean antiJARLocking) { - this.antiJARLocking = antiJARLocking; + return result; } - - /** - * @return Returns the searchExternalFirst. - */ - public boolean getSearchExternalFirst() { - return searchExternalFirst; - } - - /** - * @param searchExternalFirst Whether external repositories should be searched first - */ - public void setSearchExternalFirst(boolean searchExternalFirst) { - this.searchExternalFirst = searchExternalFirst; - } - - - /** - * If there is a Java SecurityManager create a read FilePermission - * or JndiPermission for the file directory path. - * - * @param filepath file directory path - */ - public void addPermission(String filepath) { - if (filepath == null) { - return; - } - - String path = filepath; - - if (securityManager != null) { - Permission permission = null; - if (path.startsWith("jndi:") || path.startsWith("jar:jndi:")) { - if (!path.endsWith("/")) { - path = path + "/"; - } - permission = new JndiPermission(path + "*"); - addPermission(permission); - } else { - if (!path.endsWith(File.separator)) { - permission = new FilePermission(path, "read"); - addPermission(permission); - path = path + File.separator; - } - permission = new FilePermission(path + "-", "read"); - addPermission(permission); - } - } - } - - - /** - * If there is a Java SecurityManager create a read FilePermission - * or JndiPermission for URL. - * - * @param url URL for a file or directory on local system - */ - public void addPermission(URL url) { - if (url != null) { - addPermission(url.toString()); - } - } - - - /** - * If there is a Java SecurityManager create a Permission. - * - * @param permission The permission - */ - public void addPermission(Permission permission) { - if ((securityManager != null) && (permission != null)) { - permissionList.add(permission); - } - } - - - /** - * Return the JAR path. - */ - public String getJarPath() { - - return this.jarPath; - - } - - - /** - * Change the Jar path. - */ - public void setJarPath(String jarPath) { - - this.jarPath = jarPath; - - } - - - /** - * Change the work directory. - */ - public void setWorkDir(File workDir) { - this.loaderDir = new File(workDir, "loader"); - if (loaderDir == null) { - canonicalLoaderDir = null; - } else { - try { - canonicalLoaderDir = loaderDir.getCanonicalPath(); - if (!canonicalLoaderDir.endsWith(File.separator)) { - canonicalLoaderDir += File.separator; - } - } catch (IOException ioe) { - canonicalLoaderDir = null; - } - } - } - - /** - * Utility method for use in subclasses. - * Must be called before Lifecycle methods to have any effect. - * - * @deprecated Will be removed in 8.0.x onwards. - */ - @Deprecated - protected void setParentClassLoader(ClassLoader pcl) { - parent = pcl; - } - - /** - * Return the clearReferencesStatic flag for this Context. - */ - public boolean getClearReferencesStatic() { - return (this.clearReferencesStatic); - } - - - /** - * Set the clearReferencesStatic feature for this Context. - * - * @param clearReferencesStatic The new flag value - */ - public void setClearReferencesStatic(boolean clearReferencesStatic) { - this.clearReferencesStatic = clearReferencesStatic; - } - - - /** - * Return the clearReferencesStopThreads flag for this Context. - */ - public boolean getClearReferencesStopThreads() { - return (this.clearReferencesStopThreads); - } - - - /** - * Set the clearReferencesStopThreads feature for this Context. - * - * @param clearReferencesStopThreads The new flag value - */ - public void setClearReferencesStopThreads( - boolean clearReferencesStopThreads) { - this.clearReferencesStopThreads = clearReferencesStopThreads; - } - - - /** - * Return the clearReferencesStopTimerThreads flag for this Context. - */ - public boolean getClearReferencesStopTimerThreads() { - return (this.clearReferencesStopTimerThreads); - } - - - /** - * Set the clearReferencesStopTimerThreads feature for this Context. - * - * @param clearReferencesStopTimerThreads The new flag value - */ - public void setClearReferencesStopTimerThreads( - boolean clearReferencesStopTimerThreads) { - this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads; - } - - - /** - * Return the clearReferencesLogFactoryRelease flag for this Context. - */ - public boolean getClearReferencesLogFactoryRelease() { - return (this.clearReferencesLogFactoryRelease); - } - - - /** - * Set the clearReferencesLogFactoryRelease feature for this Context. - * - * @param clearReferencesLogFactoryRelease The new flag value - */ - public void setClearReferencesLogFactoryRelease( - boolean clearReferencesLogFactoryRelease) { - this.clearReferencesLogFactoryRelease = - clearReferencesLogFactoryRelease; - } - - - /** - * Return the clearReferencesHttpClientKeepAliveThread flag for this - * Context. - */ - public boolean getClearReferencesHttpClientKeepAliveThread() { - return (this.clearReferencesHttpClientKeepAliveThread); - } - - - /** - * Set the clearReferencesHttpClientKeepAliveThread feature for this - * Context. - * - * @param clearReferencesHttpClientKeepAliveThread The new flag value - */ - public void setClearReferencesHttpClientKeepAliveThread( - boolean clearReferencesHttpClientKeepAliveThread) { - this.clearReferencesHttpClientKeepAliveThread = - clearReferencesHttpClientKeepAliveThread; - } - - - // ------------------------------------------------------- Reloader Methods - - /** - * Adds the specified class file transformer to this class loader. The - * transformer will then be able to modify the bytecode of any classes - * loaded by this class loader after the invocation of this method. - * - * @param transformer The transformer to add to the class loader - */ - @Override - public void addTransformer(ClassFileTransformer transformer) { - - if (transformer == null) { - throw new IllegalArgumentException(sm.getString( - "webappClassLoader.addTransformer.illegalArgument", getContextName())); - } - - if (this.transformers.contains(transformer)) { - // if the same instance of this transformer was already added, bail out - log.warn(sm.getString("webappClassLoader.addTransformer.duplicate", - transformer, getContextName())); - return; - } - this.transformers.add(transformer); - - log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName())); - - } - - /** - * Removes the specified class file transformer from this class loader. - * It will no longer be able to modify the byte code of any classes - * loaded by the class loader after the invocation of this method. - * However, any classes already modified by this transformer will - * remain transformed. - * - * @param transformer The transformer to remove - */ - @Override - public void removeTransformer(ClassFileTransformer transformer) { - - if (transformer == null) { - return; - } - - if (this.transformers.remove(transformer)) { - log.info(sm.getString("webappClassLoader.removeTransformer", - transformer, getContextName())); - return; - } - - } - - /** - * Returns a copy of this class loader without any class file - * transformers. This is a tool often used by Java Persistence API - * providers to inspect entity classes in the absence of any - * instrumentation, something that can't be guaranteed within the - * context of a {@link ClassFileTransformer}'s - * {@link ClassFileTransformer#transform(ClassLoader, String, Class, - * ProtectionDomain, byte[]) transform} method. - *

- * The returned class loader's resource cache will have been cleared - * so that classes already instrumented will not be retained or - * returned. - * - * @return the transformer-free copy of this class loader. - */ - @Override - public WebappClassLoader copyWithoutTransformers() { - - WebappClassLoader result = new WebappClassLoader(this.parent); - - result.antiJARLocking = this.antiJARLocking; - result.resources = this.resources; - result.files = this.files; - result.delegate = this.delegate; - result.lastJarAccessed = this.lastJarAccessed; - result.repositories = this.repositories; - result.jarPath = this.jarPath; - result.loaderDir = this.loaderDir; - result.canonicalLoaderDir = this.canonicalLoaderDir; - result.clearReferencesStatic = this.clearReferencesStatic; - result.clearReferencesStopThreads = this.clearReferencesStopThreads; - result.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads; - result.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease; - result.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread; - result.repositoryURLs = this.repositoryURLs.clone(); - result.jarFiles = this.jarFiles.clone(); - result.jarRealFiles = this.jarRealFiles.clone(); - result.jarNames = this.jarNames.clone(); - result.lastModifiedDates = this.lastModifiedDates.clone(); - result.paths = this.paths.clone(); - result.notFoundResources.putAll(this.notFoundResources); - result.permissionList.addAll(this.permissionList); - result.loaderPC.putAll(this.loaderPC); - result.contextName = this.contextName; - result.hasExternalRepositories = this.hasExternalRepositories; - result.searchExternalFirst = this.searchExternalFirst; - - try { - result.start(); - } catch (LifecycleException e) { - throw new IllegalStateException(e); - } - - return result; - } - - /** - * 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 - */ - public void addRepository(String repository) { - - // Ignore any of the standard repositories, as they are set up using - // either addJar or addRepository - if (repository.startsWith("/WEB-INF/lib") - || repository.startsWith("/WEB-INF/classes")) - return; - - // Add this repository to our underlying class loader - try { - URL url = new URL(repository); - super.addURL(url); - hasExternalRepositories = true; - repositoryURLs = null; - } catch (MalformedURLException e) { - IllegalArgumentException iae = new IllegalArgumentException - ("Invalid repository: " + repository); - iae.initCause(e); - throw iae; - } - - } - - - /** - * 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 - */ - synchronized void addRepository(String repository, File file) { - - // Note : There should be only one (of course), but I think we should - // keep this a bit generic - - if (repository == null) - return; - - if (log.isDebugEnabled()) - log.debug("addRepository(" + repository + ")"); - - int i; - - // Add this repository to our internal list - String[] result = new String[repositories.length + 1]; - for (i = 0; i < repositories.length; i++) { - result[i] = repositories[i]; - } - result[repositories.length] = repository; - repositories = result; - - // Add the file to the list - File[] result2 = new File[files.length + 1]; - for (i = 0; i < files.length; i++) { - result2[i] = files[i]; - } - result2[files.length] = file; - files = result2; - - } - - - synchronized void addJar(String jar, JarFile jarFile, File file) - throws IOException { - - if (jar == null) - return; - if (jarFile == null) - return; - if (file == null) - return; - - if (log.isDebugEnabled()) - log.debug("addJar(" + jar + ")"); - - int i; - - if ((jarPath != null) && (jar.startsWith(jarPath))) { - - String jarName = jar.substring(jarPath.length()); - while (jarName.startsWith("/")) - jarName = jarName.substring(1); - - String[] result = new String[jarNames.length + 1]; - for (i = 0; i < jarNames.length; i++) { - result[i] = jarNames[i]; - } - result[jarNames.length] = jarName; - jarNames = result; - - } - - try { - - // Register the JAR for tracking - - long lastModified = - ((ResourceAttributes) resources.getAttributes(jar)) - .getLastModified(); - - String[] result = new String[paths.length + 1]; - for (i = 0; i < paths.length; i++) { - result[i] = paths[i]; - } - result[paths.length] = jar; - paths = result; - - long[] result3 = new long[lastModifiedDates.length + 1]; - for (i = 0; i < lastModifiedDates.length; i++) { - result3[i] = lastModifiedDates[i]; - } - result3[lastModifiedDates.length] = lastModified; - lastModifiedDates = result3; - - } catch (NamingException e) { - // Ignore - } - - // If the JAR currently contains invalid classes, don't actually use it - // for classloading - if (!validateJarFile(file)) - return; - - JarFile[] result2 = new JarFile[jarFiles.length + 1]; - for (i = 0; i < jarFiles.length; i++) { - result2[i] = jarFiles[i]; - } - result2[jarFiles.length] = jarFile; - jarFiles = result2; - - // Add the file to the list - File[] result4 = new File[jarRealFiles.length + 1]; - for (i = 0; i < jarRealFiles.length; i++) { - result4[i] = jarRealFiles[i]; - } - result4[jarRealFiles.length] = file; - jarRealFiles = result4; - } - - - /** - * Return a String array of the current repositories for this class - * loader. If there are no repositories, a zero-length array is - * returned.For security reason, returns a clone of the Array (since - * String are immutable). - */ - public String[] findRepositories() { - - return (repositories.clone()); - - } - - - /** - * Have one or more classes or resources been modified so that a reload - * is appropriate? - */ - public boolean modified() { - - if (log.isDebugEnabled()) - log.debug("modified()"); - - // Checking for modified loaded resources - int length = paths.length; - - // A rare race condition can occur in the updates of the two arrays - // It's totally ok if the latest class added is not checked (it will - // be checked the next time - int length2 = lastModifiedDates.length; - if (length > length2) - length = length2; - - for (int i = 0; i < length; i++) { - try { - long lastModified = - ((ResourceAttributes) resources.getAttributes(paths[i])) - .getLastModified(); - if (lastModified != lastModifiedDates[i]) { - if( log.isDebugEnabled() ) - log.debug(" Resource '" + paths[i] - + "' was modified; Date is now: " - + new java.util.Date(lastModified) + " Was: " - + new java.util.Date(lastModifiedDates[i])); - return (true); - } - } catch (NamingException e) { - log.error(" Resource '" + paths[i] + "' is missing"); - return (true); - } - } - - length = jarNames.length; - - // Check if JARs have been added or removed - if (getJarPath() != null) { - - try { - NamingEnumeration enumeration = - resources.listBindings(getJarPath()); - int i = 0; - while (enumeration.hasMoreElements() && (i < length)) { - NameClassPair ncPair = enumeration.nextElement(); - String name = ncPair.getName(); - // Ignore non JARs present in the lib folder - if (!name.endsWith(".jar")) - continue; - if (!name.equals(jarNames[i])) { - // Missing JAR - log.info(" Additional JARs have been added : '" - + name + "'"); - return (true); - } - i++; - } - if (enumeration.hasMoreElements()) { - while (enumeration.hasMoreElements()) { - NameClassPair ncPair = enumeration.nextElement(); - String name = ncPair.getName(); - // Additional non-JAR files are allowed - if (name.endsWith(".jar")) { - // There was more JARs - log.info(" Additional JARs have been added"); - return (true); - } - } - } else if (i < jarNames.length) { - // There was less JARs - log.info(" Additional JARs have been added"); - return (true); - } - } catch (NamingException e) { - if (log.isDebugEnabled()) - log.debug(" Failed tracking modifications of '" - + getJarPath() + "'"); - } catch (ClassCastException e) { - log.error(" Failed tracking modifications of '" - + getJarPath() + "' : " + e.getMessage()); - } - - } - - // No classes have been modified - return (false); - - } - - - /** - * Render a String representation of this object. - */ - @Override - public String toString() { - - StringBuilder sb = new StringBuilder("WebappClassLoader\r\n"); - sb.append(" context: "); - sb.append(contextName); - sb.append("\r\n"); - sb.append(" delegate: "); - sb.append(delegate); - sb.append("\r\n"); - sb.append(" repositories:\r\n"); - if (repositories != null) { - for (int i = 0; i < repositories.length; i++) { - sb.append(" "); - sb.append(repositories[i]); - sb.append("\r\n"); - } - } - if (this.parent != null) { - sb.append("----------> Parent Classloader:\r\n"); - sb.append(this.parent.toString()); - sb.append("\r\n"); - } - if (this.transformers.size() > 0) { - sb.append("----------> Class file transformers:\r\n"); - for (ClassFileTransformer transformer : this.transformers) { - sb.append(transformer).append("\r\n"); - } - } - return (sb.toString()); - - } - - - // ---------------------------------------------------- ClassLoader Methods - - - /** - * Add the specified URL to the classloader. - */ - @Override - protected void addURL(URL url) { - super.addURL(url); - hasExternalRepositories = true; - repositoryURLs = null; - } - - - /** - * Expose this method for use by the unit tests. - */ - protected final Class doDefineClass(String name, byte[] b, int off, int len, - ProtectionDomain protectionDomain) { - return super.defineClass(name, b, off, len, protectionDomain); - } - - /** - * Find the specified class in our local repositories, if possible. If - * not found, throw ClassNotFoundException. - * - * @param name Name of the class to be loaded - * - * @exception ClassNotFoundException if the class was not found - */ - @Override - public Class findClass(String name) throws ClassNotFoundException { - - if (log.isDebugEnabled()) - log.debug(" findClass(" + name + ")"); - - // Cannot load anything from local repositories if class loader is stopped - if (!started) { - throw new ClassNotFoundException(name); - } - - // (1) Permission to define this class when using a SecurityManager - if (securityManager != null) { - int i = name.lastIndexOf('.'); - if (i >= 0) { - try { - if (log.isTraceEnabled()) - log.trace(" securityManager.checkPackageDefinition"); - securityManager.checkPackageDefinition(name.substring(0,i)); - } catch (Exception se) { - if (log.isTraceEnabled()) - log.trace(" -->Exception-->ClassNotFoundException", se); - throw new ClassNotFoundException(name, se); - } - } - } - - // Ask our superclass to locate this class, if possible - // (throws ClassNotFoundException if it is not found) - Class clazz = null; - try { - if (log.isTraceEnabled()) - log.trace(" findClassInternal(" + name + ")"); - if (hasExternalRepositories && searchExternalFirst) { - try { - clazz = super.findClass(name); - } catch(ClassNotFoundException cnfe) { - // Ignore - will search internal repositories next - } catch(AccessControlException ace) { - log.warn("WebappClassLoader.findClassInternal(" + name - + ") security exception: " + ace.getMessage(), ace); - throw new ClassNotFoundException(name, ace); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) - log.trace(" -->RuntimeException Rethrown", e); - throw e; - } - } - if ((clazz == null)) { - try { - clazz = findClassInternal(name); - } catch(ClassNotFoundException cnfe) { - if (!hasExternalRepositories || searchExternalFirst) { - throw cnfe; - } - } catch(AccessControlException ace) { - log.warn("WebappClassLoader.findClassInternal(" + name - + ") security exception: " + ace.getMessage(), ace); - throw new ClassNotFoundException(name, ace); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) - log.trace(" -->RuntimeException Rethrown", e); - throw e; - } - } - if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) { - try { - clazz = super.findClass(name); - } catch(AccessControlException ace) { - log.warn("WebappClassLoader.findClassInternal(" + name - + ") security exception: " + ace.getMessage(), ace); - throw new ClassNotFoundException(name, ace); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) - log.trace(" -->RuntimeException Rethrown", e); - throw e; - } - } - if (clazz == null) { - if (log.isDebugEnabled()) - log.debug(" --> Returning ClassNotFoundException"); - throw new ClassNotFoundException(name); - } - } catch (ClassNotFoundException e) { - if (log.isTraceEnabled()) - log.trace(" --> Passing on ClassNotFoundException"); - throw e; - } - - // Return the class we have located - if (log.isTraceEnabled()) - log.debug(" Returning class " + clazz); - - if (log.isTraceEnabled()) { - ClassLoader cl; - if (Globals.IS_SECURITY_ENABLED){ - cl = AccessController.doPrivileged( - new PrivilegedGetClassLoader(clazz)); - } else { - cl = clazz.getClassLoader(); - } - log.debug(" Loaded by " + cl.toString()); - } - return (clazz); - - } - - - /** - * Find the specified resource in our local repository, and return a - * URL referring to it, or null if this resource - * cannot be found. - * - * @param name Name of the resource to be found - */ - @Override - public URL findResource(final String name) { - - if (log.isDebugEnabled()) - log.debug(" findResource(" + name + ")"); - - URL url = null; - - if (hasExternalRepositories && searchExternalFirst) - url = super.findResource(name); - - if (url == null) { - ResourceEntry entry = resourceEntries.get(name); - if (entry == null) { - if (securityManager != null) { - PrivilegedAction dp = - new PrivilegedFindResourceByName(name, name, false); - entry = AccessController.doPrivileged(dp); - } else { - entry = findResourceInternal(name, name, false); - } - } - if (entry != null) { - url = entry.source; - } - } - - if ((url == null) && hasExternalRepositories && !searchExternalFirst) - url = super.findResource(name); - - if (log.isDebugEnabled()) { - if (url != null) - log.debug(" --> Returning '" + url.toString() + "'"); - else - log.debug(" --> Resource not found, returning null"); - } - return (url); - - } - - - /** - * Return an enumeration of URLs 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 - */ - @Override - public Enumeration findResources(String name) throws IOException { - - if (log.isDebugEnabled()) - log.debug(" findResources(" + name + ")"); - - //we use a LinkedHashSet instead of a Vector to avoid duplicates with virtualmappings - LinkedHashSet result = new LinkedHashSet(); - - int jarFilesLength = jarFiles.length; - int repositoriesLength = repositories.length; - - int i; - - // Adding the results of a call to the superclass - if (hasExternalRepositories && searchExternalFirst) { - - Enumeration otherResourcePaths = super.findResources(name); - - while (otherResourcePaths.hasMoreElements()) { - result.add(otherResourcePaths.nextElement()); - } - - } - // Looking at the repositories - for (i = 0; i < repositoriesLength; i++) { - try { - String fullPath = repositories[i] + name; - resources.lookup(fullPath); - // Note : Not getting an exception here means the resource was - // found - try { - result.add(getURI(new File(files[i], name))); - } catch (MalformedURLException e) { - // Ignore - } - } catch (NamingException e) { - // Ignore - } - } - - // Looking at the JAR files - synchronized (jarFiles) { - if (openJARs()) { - for (i = 0; i < jarFilesLength; i++) { - JarEntry jarEntry = jarFiles[i].getJarEntry(name); - if (jarEntry != null) { - try { - String jarFakeUrl = getURI(jarRealFiles[i]).toString(); - jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name; - result.add(new URL(jarFakeUrl)); - } catch (MalformedURLException e) { - // Ignore - } - } - } - } - } - - // Adding the results of a call to the superclass - if (hasExternalRepositories && !searchExternalFirst) { - - Enumeration otherResourcePaths = super.findResources(name); - - while (otherResourcePaths.hasMoreElements()) { - result.add(otherResourcePaths.nextElement()); - } - - } - - return Collections.enumeration(result); - } - - - /** - * Find the resource with the given name. A resource is some data - * (images, audio, text, etc.) that can be accessed by class code in a - * way that is independent of the location of the code. The name of a - * resource is a "/"-separated path name that identifies the resource. - * If the resource cannot be found, return null. - *

- * This method searches according to the following algorithm, returning - * as soon as it finds the appropriate URL. If the resource cannot be - * found, returns null. - *

    - *
  • If the delegate property is set to true, - * call the getResource() method of the parent class - * loader, if any.
  • - *
  • Call findResource() to find this resource in our - * locally defined repositories.
  • - *
  • Call the getResource() method of the parent class - * loader, if any.
  • - *
- * - * @param name Name of the resource to return a URL for - */ - @Override - public URL getResource(String name) { - - if (log.isDebugEnabled()) - log.debug("getResource(" + name + ")"); - URL url = null; - - // (1) Delegate to parent if requested - if (delegate) { - if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader " + parent); - url = parent.getResource(name); - if (url != null) { - if (log.isDebugEnabled()) - log.debug(" --> Returning '" + url.toString() + "'"); - return (url); - } - } - - // (2) Search local repositories - url = findResource(name); - if (url != null) { - // Locating the repository for special handling in the case - // of a JAR - if (antiJARLocking) { - ResourceEntry entry = resourceEntries.get(name); - try { - String repository = entry.codeBase.toString(); - if ((repository.endsWith(".jar")) - && (!(name.endsWith(CLASS_FILE_SUFFIX)))) { - // Copy binary content to the work directory if not present - File resourceFile = new File(loaderDir, name); - url = getURI(resourceFile); - } - } catch (Exception e) { - // Ignore - } - } - if (log.isDebugEnabled()) - log.debug(" --> Returning '" + url.toString() + "'"); - return (url); - } - - // (3) Delegate to parent unconditionally if not already attempted - if( !delegate ) { - url = parent.getResource(name); - if (url != null) { - if (log.isDebugEnabled()) - log.debug(" --> Returning '" + url.toString() + "'"); - return (url); - } - } - - // (4) Resource was not found - if (log.isDebugEnabled()) - log.debug(" --> Resource not found, returning null"); - return (null); - - } - - - /** - * Find the resource with the given name, and return an input stream - * that can be used for reading it. The search order is as described - * for getResource(), after checking to see if the resource - * data has been previously cached. If the resource cannot be found, - * return null. - * - * @param name Name of the resource to return an input stream for - */ - @Override - public InputStream getResourceAsStream(String name) { - - if (log.isDebugEnabled()) - log.debug("getResourceAsStream(" + name + ")"); - InputStream stream = null; - - // (0) Check for a cached copy of this resource - stream = findLoadedResource(name); - if (stream != null) { - if (log.isDebugEnabled()) - log.debug(" --> Returning stream from cache"); - return (stream); - } - - // (1) Delegate to parent if requested - if (delegate) { - if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader " + parent); - stream = parent.getResourceAsStream(name); - if (stream != null) { - // FIXME - cache??? - if (log.isDebugEnabled()) - log.debug(" --> Returning stream from parent"); - return (stream); - } - } - - // (2) Search local repositories - if (log.isDebugEnabled()) - log.debug(" Searching local repositories"); - URL url = findResource(name); - if (url != null) { - // FIXME - cache??? - if (log.isDebugEnabled()) - log.debug(" --> Returning stream from local"); - stream = findLoadedResource(name); - try { - if (hasExternalRepositories && (stream == null)) - stream = url.openStream(); - } catch (IOException e) { - // Ignore - } - if (stream != null) - return (stream); - } - - // (3) Delegate to parent unconditionally - if (!delegate) { - if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader unconditionally " + parent); - stream = parent.getResourceAsStream(name); - if (stream != null) { - // FIXME - cache??? - if (log.isDebugEnabled()) - log.debug(" --> Returning stream from parent"); - return (stream); - } - } - - // (4) Resource was not found - if (log.isDebugEnabled()) - log.debug(" --> Resource not found, returning null"); - return (null); - - } - - - /** - * Load the class with the specified name. This method searches for - * classes in the same manner as loadClass(String, boolean) - * with false as the second argument. - * - * @param name Name of the class to be loaded - * - * @exception ClassNotFoundException if the class was not found - */ - @Override - public Class loadClass(String name) throws ClassNotFoundException { - - return (loadClass(name, false)); - - } - - - /** - * 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 ClassNotFoundException. - *
    - *
  • Call findLoadedClass(String) to check if the - * class has already been loaded. If it has, the same - * Class object is returned.
  • - *
  • If the delegate property is set to true, - * call the loadClass() method of the parent class - * loader, if any.
  • - *
  • Call findClass() to find this class in our locally - * defined repositories.
  • - *
  • Call the loadClass() method of our parent - * class loader, if any.
  • - *
- * If the class was found using the above steps, and the - * resolve flag is true, this method will then - * call resolveClass(Class) on the resulting Class object. - * - * @param name Name of the class to be loaded - * @param resolve If true then resolve the class - * - * @exception ClassNotFoundException if the class was not found - */ - @Override - public synchronized Class loadClass(String name, boolean resolve) - throws ClassNotFoundException { - - if (log.isDebugEnabled()) - log.debug("loadClass(" + name + ", " + resolve + ")"); - Class clazz = null; - - // Log access to stopped classloader - if (!started) { - try { - throw new IllegalStateException(); - } catch (IllegalStateException e) { - log.info(sm.getString("webappClassLoader.stopped", name), e); - } - } - - // (0) Check our previously loaded local class cache - clazz = findLoadedClass0(name); - if (clazz != null) { - if (log.isDebugEnabled()) - log.debug(" Returning class from cache"); - if (resolve) - resolveClass(clazz); - return (clazz); - } - - // (0.1) Check our previously loaded class cache - clazz = findLoadedClass(name); - if (clazz != null) { - if (log.isDebugEnabled()) - log.debug(" Returning class from cache"); - 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 = j2seClassLoader.loadClass(name); - if (clazz != null) { - if (resolve) - resolveClass(clazz); - return (clazz); - } - } catch (ClassNotFoundException e) { - // Ignore - } - - // (0.5) Permission to access this class when using a SecurityManager - if (securityManager != null) { - int i = name.lastIndexOf('.'); - if (i >= 0) { - try { - securityManager.checkPackageAccess(name.substring(0,i)); - } catch (SecurityException se) { - String error = "Security Violation, attempt to use " + - "Restricted Class: " + name; - log.info(error, se); - throw new ClassNotFoundException(error, se); - } - } - } - - boolean delegateLoad = delegate || filter(name); - - // (1) Delegate to our parent if requested - if (delegateLoad) { - if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader1 " + parent); - try { - clazz = Class.forName(name, false, parent); - if (clazz != null) { - if (log.isDebugEnabled()) - log.debug(" Loading class from parent"); - if (resolve) - resolveClass(clazz); - return (clazz); - } - } catch (ClassNotFoundException e) { - // Ignore - } - } - - // (2) Search local repositories - if (log.isDebugEnabled()) - log.debug(" Searching local repositories"); - try { - clazz = findClass(name); - if (clazz != null) { - if (log.isDebugEnabled()) - log.debug(" Loading class from local repository"); - if (resolve) - resolveClass(clazz); - return (clazz); - } - } catch (ClassNotFoundException e) { - // Ignore - } - - // (3) Delegate to parent unconditionally - if (!delegateLoad) { - if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader at end: " + parent); - try { - clazz = Class.forName(name, false, parent); - if (clazz != null) { - if (log.isDebugEnabled()) - log.debug(" Loading class from parent"); - if (resolve) - resolveClass(clazz); - return (clazz); - } - } catch (ClassNotFoundException e) { - // Ignore - } - } - - throw new ClassNotFoundException(name); - - } - - - /** - * Get the Permissions for a CodeSource. If this instance - * of WebappClassLoader is for a web application context, - * add read FilePermission or JndiPermissions for the base - * directory (if unpacked), - * the context URL, and jar file resources. - * - * @param codeSource where the code was loaded from - * @return PermissionCollection for CodeSource - */ - @Override - protected PermissionCollection getPermissions(CodeSource codeSource) { - - String codeUrl = codeSource.getLocation().toString(); - PermissionCollection pc; - if ((pc = loaderPC.get(codeUrl)) == null) { - pc = super.getPermissions(codeSource); - if (pc != null) { - Iterator perms = permissionList.iterator(); - while (perms.hasNext()) { - Permission p = perms.next(); - pc.add(p); - } - loaderPC.put(codeUrl,pc); - } - } - return (pc); - - } - - - /** - * Returns the search path of URLs for loading classes and resources. - * This includes the original list of URLs specified to the constructor, - * along with any URLs subsequently appended by the addURL() method. - * @return the search path of URLs for loading classes and resources. - */ - @Override - public URL[] getURLs() { - - if (repositoryURLs != null) { - return repositoryURLs.clone(); - } - - URL[] external = super.getURLs(); - - int filesLength = files.length; - int jarFilesLength = jarRealFiles.length; - int externalsLength = external.length; - int off = 0; - int i; - - try { - - URL[] urls = new URL[filesLength + jarFilesLength + externalsLength]; - if (searchExternalFirst) { - for (i = 0; i < externalsLength; i++) { - urls[i] = external[i]; - } - off = externalsLength; - } - for (i = 0; i < filesLength; i++) { - urls[off + i] = getURI(files[i]); - } - off += filesLength; - for (i = 0; i < jarFilesLength; i++) { - urls[off + i] = getURI(jarRealFiles[i]); - } - off += jarFilesLength; - if (!searchExternalFirst) { - for (i = 0; i < externalsLength; i++) { - urls[off + i] = external[i]; - } - } - - repositoryURLs = urls; - - } catch (MalformedURLException e) { - repositoryURLs = new URL[0]; - } - - return repositoryURLs.clone(); - - } - - - // ------------------------------------------------------ Lifecycle Methods - - - /** - * Add a lifecycle event listener to this component. - * - * @param listener The listener to add - */ - @Override - public void addLifecycleListener(LifecycleListener listener) { - // NOOP - } - - - /** - * Get the lifecycle listeners associated with this lifecycle. If this - * Lifecycle has no listeners registered, a zero-length array is returned. - */ - @Override - public LifecycleListener[] findLifecycleListeners() { - return new LifecycleListener[0]; - } - - - /** - * Remove a lifecycle event listener from this component. - * - * @param listener The listener to remove - */ - @Override - public void removeLifecycleListener(LifecycleListener listener) { - // NOOP - } - - - /** - * Obtain the current state of the source component. - * - * @return The current state of the source component. - */ - @Override - public LifecycleState getState() { - return LifecycleState.NEW; - } - - - /** - * {@inheritDoc} - */ - @Override - public String getStateName() { - return getState().toString(); - } - - - @Override - public void init() { - // NOOP - } - - - /** - * Start the class loader. - * - * @exception LifecycleException if a lifecycle error occurs - */ - @Override - public void start() throws LifecycleException { - - started = true; - String encoding = null; - try { - encoding = System.getProperty("file.encoding"); - } catch (SecurityException e) { - return; - } - if (encoding.indexOf("EBCDIC")!=-1) { - needConvert = true; - } - - for (int i = 0; i < repositories.length; i++) { - if (repositories[i].equals("/WEB-INF/classes/")) { - try { - webInfClassesCodeBase = files[i].toURI().toURL(); - } catch (MalformedURLException e) { - // Ignore - leave it as null - } - break; - } - } - - } - - - public boolean isStarted() { - return started; - } - - /** - * Stop the class loader. - * - * @exception LifecycleException if a lifecycle error occurs - */ - @Override - public void stop() throws LifecycleException { - - // Clearing references should be done before setting started to - // false, due to possible side effects - clearReferences(); - - started = false; - - int length = files.length; - for (int i = 0; i < length; i++) { - files[i] = null; - } - - length = jarFiles.length; - for (int i = 0; i < length; i++) { - try { - if (jarFiles[i] != null) { - jarFiles[i].close(); - } - } catch (IOException e) { - // Ignore - } - jarFiles[i] = null; - } - - notFoundResources.clear(); - resourceEntries.clear(); - resources = null; - repositories = null; - repositoryURLs = null; - files = null; - jarFiles = null; - jarRealFiles = null; - jarPath = null; - jarNames = null; - lastModifiedDates = null; - paths = null; - hasExternalRepositories = false; - parent = null; - webInfClassesCodeBase = null; - - permissionList.clear(); - loaderPC.clear(); - - if (loaderDir != null) { - deleteDir(loaderDir); - } - - } - - - @Override - public void destroy() { - // NOOP - } - - - /** - * Used to periodically signal to the classloader to release - * JAR resources. - */ - public void closeJARs(boolean force) { - if (jarFiles.length > 0) { - synchronized (jarFiles) { - if (force || (System.currentTimeMillis() - > (lastJarAccessed + 90000))) { - for (int i = 0; i < jarFiles.length; i++) { - try { - if (jarFiles[i] != null) { - jarFiles[i].close(); - jarFiles[i] = null; - } - } catch (IOException e) { - if (log.isDebugEnabled()) { - log.debug("Failed to close JAR", e); - } - } - } - } - } - } - } - - - // ------------------------------------------------------ Protected Methods - - protected ClassLoader getJavaseClassLoader() { - return j2seClassLoader; - } - - protected void setJavaseClassLoader(ClassLoader classLoader) { - if (classLoader == null) { - throw new IllegalArgumentException( - sm.getString("webappClassLoader.javaseClassLoaderNull")); - } - j2seClassLoader = classLoader; - } - - /** - * Clear references. - */ - protected void clearReferences() { - - // De-register any remaining JDBC drivers - clearReferencesJdbc(); - - // Stop any threads the web application started - clearReferencesThreads(); - - // Check for leaks triggered by ThreadLocals loaded by this class loader - checkThreadLocalsForLeaks(); - - // Clear RMI Targets loaded by this class loader - clearReferencesRmiTargets(); - - // Null out any static or final fields from loaded classes, - // as a workaround for apparent garbage collection bugs - if (clearReferencesStatic) { - clearReferencesStaticFinal(); - } - - // Clear the IntrospectionUtils cache. - IntrospectionUtils.clear(); - - // Clear the classloader reference in common-logging - if (clearReferencesLogFactoryRelease) { - org.apache.juli.logging.LogFactory.release(this); - } - - // Clear the resource bundle cache - // This shouldn't be necessary, the cache uses weak references but - // it has caused leaks. Oddly, using the leak detection code in - // standard host allows the class loader to be GC'd. This has been seen - // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl? - clearReferencesResourceBundles(); - - // Clear the classloader reference in the VM's bean introspector - java.beans.Introspector.flushCaches(); - - } - - - /** - * Deregister any JDBC drivers registered by the webapp that the webapp - * forgot. This is made unnecessary complex because a) DriverManager - * checks the class loader of the calling class (it would be much easier - * if it checked the context class loader) b) using reflection would - * create a dependency on the DriverManager implementation which can, - * and has, changed. - * - * We can't just create an instance of JdbcLeakPrevention as it will be - * loaded by the common class loader (since it's .class file is in the - * $CATALINA_HOME/lib directory). This would fail DriverManager's check - * on the class loader of the calling class. So, we load the bytes via - * our parent class loader but define the class with this class loader - * so the JdbcLeakPrevention looks like a webapp class to the - * DriverManager. - * - * If only apps cleaned up after themselves... - */ - private final void clearReferencesJdbc() { - InputStream is = getResourceAsStream( - "org/apache/catalina/loader/JdbcLeakPrevention.class"); - // We know roughly how big the class will be (~ 1K) so allow 2k as a - // starting point - byte[] classBytes = new byte[2048]; - int offset = 0; - try { - int read = is.read(classBytes, offset, classBytes.length-offset); - while (read > -1) { - offset += read; - if (offset == classBytes.length) { - // Buffer full - double size - byte[] tmp = new byte[classBytes.length * 2]; - System.arraycopy(classBytes, 0, tmp, 0, classBytes.length); - classBytes = tmp; - } - read = is.read(classBytes, offset, classBytes.length-offset); - } - Class lpClass = - defineClass("org.apache.catalina.loader.JdbcLeakPrevention", - classBytes, 0, offset, this.getClass().getProtectionDomain()); - Object obj = lpClass.newInstance(); - @SuppressWarnings("unchecked") // clearJdbcDriverRegistrations() returns List - List driverNames = (List) obj.getClass().getMethod( - "clearJdbcDriverRegistrations").invoke(obj); - for (String name : driverNames) { - log.error(sm.getString("webappClassLoader.clearJdbc", - contextName, name)); - } - } catch (Exception e) { - // So many things to go wrong above... - Throwable t = ExceptionUtils.unwrapInvocationTargetException(e); - ExceptionUtils.handleThrowable(t); - log.warn(sm.getString( - "webappClassLoader.jdbcRemoveFailed", contextName), t); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException ioe) { - log.warn(sm.getString( - "webappClassLoader.jdbcRemoveStreamError", - contextName), ioe); - } - } - } - } - - - private final void clearReferencesStaticFinal() { - - @SuppressWarnings("unchecked") // resourceEntries is HashMap - Collection values = - ((HashMap) resourceEntries.clone()).values(); - Iterator loadedClasses = values.iterator(); - // - // walk through all loaded class to trigger initialization for - // any uninitialized classes, otherwise initialization of - // one class may call a previously cleared class. - while(loadedClasses.hasNext()) { - ResourceEntry entry = loadedClasses.next(); - if (entry.loadedClass != null) { - Class clazz = entry.loadedClass; - try { - Field[] fields = clazz.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - if(Modifier.isStatic(fields[i].getModifiers())) { - fields[i].get(null); - break; - } - } - } catch(Throwable t) { - // Ignore - } - } - } - loadedClasses = values.iterator(); - while (loadedClasses.hasNext()) { - ResourceEntry entry = loadedClasses.next(); - if (entry.loadedClass != null) { - Class clazz = entry.loadedClass; - try { - Field[] fields = clazz.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - int mods = field.getModifiers(); - if (field.getType().isPrimitive() - || (field.getName().indexOf("$") != -1)) { - continue; - } - if (Modifier.isStatic(mods)) { - try { - field.setAccessible(true); - if (Modifier.isFinal(mods)) { - if (!((field.getType().getName().startsWith("java.")) - || (field.getType().getName().startsWith("javax.")))) { - nullInstance(field.get(null)); - } - } else { - field.set(null, null); - if (log.isDebugEnabled()) { - log.debug("Set field " + field.getName() - + " to null in class " + clazz.getName()); - } - } - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - if (log.isDebugEnabled()) { - log.debug("Could not set field " + field.getName() - + " to null in class " + clazz.getName(), t); - } - } - } - } - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - if (log.isDebugEnabled()) { - log.debug("Could not clean fields for class " + clazz.getName(), t); - } - } - } - } - - } - - - private void nullInstance(Object instance) { - if (instance == null) { - return; - } - Field[] fields = instance.getClass().getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - int mods = field.getModifiers(); - if (field.getType().isPrimitive() - || (field.getName().indexOf("$") != -1)) { - continue; - } - try { - field.setAccessible(true); - if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) { - // Doing something recursively is too risky - continue; - } - Object value = field.get(instance); - if (null != value) { - Class valueClass = value.getClass(); - if (!loadedByThisOrChild(valueClass)) { - if (log.isDebugEnabled()) { - log.debug("Not setting field " + field.getName() + - " to null in object of class " + - instance.getClass().getName() + - " because the referenced object was of type " + - valueClass.getName() + - " which was not loaded by this WebappClassLoader."); - } - } else { - field.set(instance, null); - if (log.isDebugEnabled()) { - log.debug("Set field " + field.getName() - + " to null in class " + instance.getClass().getName()); - } - } - } - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - if (log.isDebugEnabled()) { - log.debug("Could not set field " + field.getName() - + " to null in object instance of class " - + instance.getClass().getName(), t); - } - } - } - } - - - @SuppressWarnings("deprecation") // thread.stop() - private void clearReferencesThreads() { - Thread[] threads = getThreads(); - List executorThreadsToStop = new ArrayList(); - - // Iterate over the set of threads - for (Thread thread : threads) { - if (thread != null) { - ClassLoader ccl = thread.getContextClassLoader(); - if (ccl == this) { - // Don't warn about this thread - if (thread == Thread.currentThread()) { - continue; - } - - // JVM controlled threads - ThreadGroup tg = thread.getThreadGroup(); - if (tg != null && - JVM_THREAD_GROUP_NAMES.contains(tg.getName())) { - - // HttpClient keep-alive threads - if (clearReferencesHttpClientKeepAliveThread && - thread.getName().equals("Keep-Alive-Timer")) { - thread.setContextClassLoader(parent); - log.debug(sm.getString( - "webappClassLoader.checkThreadsHttpClient")); - } - - // Don't warn about remaining JVM controlled threads - continue; - } - - // Skip threads that have already died - if (!thread.isAlive()) { - continue; - } - - // TimerThread can be stopped safely so treat separately - // "java.util.TimerThread" in Sun/Oracle JDK - // "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK - if (thread.getClass().getName().startsWith("java.util.Timer") && - clearReferencesStopTimerThreads) { - clearReferencesStopTimerThread(thread); - continue; - } - - if (isRequestThread(thread)) { - log.error(sm.getString("webappClassLoader.warnRequestThread", - contextName, thread.getName())); - } else { - log.error(sm.getString("webappClassLoader.warnThread", - contextName, thread.getName())); - } - - // Don't try an stop the threads unless explicitly - // configured to do so - if (!clearReferencesStopThreads) { - continue; - } - - // If the thread has been started via an executor, try - // shutting down the executor - boolean usingExecutor = false; - try { - - // Runnable wrapped by Thread - // "target" in Sun/Oracle JDK - // "runnable" in IBM JDK - // "action" in Apache Harmony - Object target = null; - for (String fieldName : new String[] { "target", - "runnable", "action" }) { - try { - Field targetField = thread.getClass() - .getDeclaredField(fieldName); - targetField.setAccessible(true); - target = targetField.get(thread); - break; - } catch (NoSuchFieldException nfe) { - continue; - } - } - - // "java.util.concurrent" code is in public domain, - // so all implementations are similar - if (target != null && - target.getClass().getCanonicalName() != null - && target.getClass().getCanonicalName().equals( - "java.util.concurrent.ThreadPoolExecutor.Worker")) { - Field executorField = - target.getClass().getDeclaredField("this$0"); - executorField.setAccessible(true); - Object executor = executorField.get(target); - if (executor instanceof ThreadPoolExecutor) { - ((ThreadPoolExecutor) executor).shutdownNow(); - usingExecutor = true; - } - } - } catch (SecurityException e) { - log.warn(sm.getString( - "webappClassLoader.stopThreadFail", - thread.getName(), contextName), e); - } catch (NoSuchFieldException e) { - log.warn(sm.getString( - "webappClassLoader.stopThreadFail", - thread.getName(), contextName), e); - } catch (IllegalArgumentException e) { - log.warn(sm.getString( - "webappClassLoader.stopThreadFail", - thread.getName(), contextName), e); - } catch (IllegalAccessException e) { - log.warn(sm.getString( - "webappClassLoader.stopThreadFail", - thread.getName(), contextName), e); - } - - if (usingExecutor) { - // Executor may take a short time to stop all the - // threads. Make a note of threads that should be [... 1278 lines stripped ...] --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org