/* * 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.catalina.loader; import java.io.File; 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.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; 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.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; 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.Manifest; import org.apache.catalina.Container; 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.catalina.WebResource; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; import org.apache.juli.WebappProperties; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.InstrumentableClassLoader; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.IntrospectionUtils; import org.apache.tomcat.util.compat.JreCompat; import org.apache.tomcat.util.res.StringManager; import org.apache.tomcat.util.security.PermissionCheck; /** * 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. *
* IMPLEMENTATION NOTE - No check for sealing violations or * security is made unless a security manager is present. *
* IMPLEMENTATION NOTE - As of 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 abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
private static final Log log = LogFactory.getLog(WebappClassLoaderBase.class);
/**
* List of ThreadGroup names to ignore when scanning for web application
* started threads that need to be shut down.
*/
private static final List
* Method is used via reflection -
* see {@link WebappLoader#createClassLoader()}
*
* @param parent Our parent class loader
*/
protected WebappClassLoaderBase(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.javaseClassLoader = j;
securityManager = System.getSecurityManager();
if (securityManager != null) {
refreshPolicy();
}
}
// ----------------------------------------------------- Instance Variables
/**
* Associated web resources for this webapp.
*/
protected WebResourceRoot resources = null;
/**
* The cache of ResourceEntry for classes and resources we have loaded,
* keyed by resource path, not binary name. Path is used as the key since
* resources may be requested by binary name (classes) or path (other
* resources such as property files) and the mapping from binary name to
* path is unambiguous but the reverse mapping is ambiguous.
*/
protected final Map
* This method searches according to the following algorithm, returning
* as soon as it finds the appropriate URL. If the resource cannot be
* found, returns
* Note that list of URLs returned by this method may not be complete. The
* web application class loader accesses class loader resources via the
* {@link WebResourceRoot} which supports the arbitrary mapping of
* additional files, directories and contents of JAR files under
* WEB-INF/classes. Any such resources will not be included in the URLs
* returned here.
*/
@Override
public URL[] getURLs() {
ArrayListfalse,
* 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;
private final HashMapnull and in
* those cases {@link ClassLoader#getParent()} will be called recursively on
* the system class loader and the last non-null result used.
*/
private ClassLoader javaseClassLoader;
/**
* All permission.
* @deprecated Unused. This will be removed in Tomcat 9.
*/
@Deprecated
protected final Permission allPermission = new java.security.AllPermission();
/**
* Enables the RMI Target memory leak detection to be controlled. This is
* necessary since the detection can only work on Java 9 if some of the
* modularity checks are disabled.
*/
private boolean clearReferencesRmiTargets = true;
/**
* 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(ClassLoader)} 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 ClassLoader} to
* {@link ClassLoader#getParent()} 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;
/**
* Should Tomcat attempt to clear references to classes loaded by this class
* loader from the ObjectStreamClass caches?
*/
private boolean clearReferencesObjectStreamClassCaches = true;
/**
* Should Tomcat attempt to clear references to classes loaded by this class
* loader from ThreadLocals?
*/
private boolean clearReferencesThreadLocals = true;
/**
* 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 Listtrue if the class lookup will delegate to
* the parent first. The default in Tomcat is false.
*/
public boolean getDelegate() {
return this.delegate;
}
/**
* 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.
*
* @param delegate The new "delegate first" flag
*/
public void setDelegate(boolean delegate) {
this.delegate = delegate;
}
/**
* If there is a Java SecurityManager create a read permission for the
* target of the given URL as appropriate.
*
* @param url URL for a file or directory on local system
*/
void addPermission(URL url) {
if (url == null) {
return;
}
if (securityManager != null) {
String protocol = url.getProtocol();
if ("file".equalsIgnoreCase(protocol)) {
URI uri;
File f;
String path;
try {
uri = url.toURI();
f = new File(uri);
path = f.getCanonicalPath();
} catch (IOException | URISyntaxException e) {
log.warn(sm.getString(
"webappClassLoader.addPermisionNoCanonicalFile",
url.toExternalForm()));
return;
}
if (f.isFile()) {
// Allow the file to be read
addPermission(new FilePermission(path, "read"));
} else if (f.isDirectory()) {
addPermission(new FilePermission(path, "read"));
addPermission(new FilePermission(
path + File.separator + "-", "read"));
} else {
// File does not exist - ignore (shouldn't happen)
}
} else {
// Unsupported URL protocol
log.warn(sm.getString(
"webappClassLoader.addPermisionNoProtocol",
protocol, url.toExternalForm()));
}
}
}
/**
* If there is a Java SecurityManager create a Permission.
*
* @param permission The permission
*/
void addPermission(Permission permission) {
if ((securityManager != null) && (permission != null)) {
permissionList.add(permission);
}
}
public boolean getClearReferencesRmiTargets() {
return this.clearReferencesRmiTargets;
}
public void setClearReferencesRmiTargets(boolean clearReferencesRmiTargets) {
this.clearReferencesRmiTargets = clearReferencesRmiTargets;
}
/**
* @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;
}
public boolean getClearReferencesObjectStreamClassCaches() {
return clearReferencesObjectStreamClassCaches;
}
public void setClearReferencesObjectStreamClassCaches(
boolean clearReferencesObjectStreamClassCaches) {
this.clearReferencesObjectStreamClassCaches = clearReferencesObjectStreamClassCaches;
}
public boolean getClearReferencesThreadLocals() {
return clearReferencesThreadLocals;
}
public void setClearReferencesThreadLocals(boolean clearReferencesThreadLocals) {
this.clearReferencesThreadLocals = clearReferencesThreadLocals;
}
// ------------------------------------------------------- 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()));
}
}
protected void copyStateWithoutTransformers(WebappClassLoaderBase base) {
base.resources = this.resources;
base.delegate = this.delegate;
base.state = LifecycleState.NEW;
base.clearReferencesStopThreads = this.clearReferencesStopThreads;
base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
base.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
base.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
base.jarModificationTimes.putAll(this.jarModificationTimes);
base.permissionList.addAll(this.permissionList);
base.loaderPC.putAll(this.loaderPC);
}
/**
* Have one or more classes or resources been modified so that a reload
* is appropriate?
* @return true if there's been a modification
*/
public boolean modified() {
if (log.isDebugEnabled())
log.debug("modified()");
for (EntryClassNotFoundException.
*
* @param name The binary 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 + ")");
checkStateForClassLoading(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 + ")");
try {
if (securityManager != null) {
PrivilegedActionURL 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 + ")");
checkStateForResourceLoading(name);
URL url = null;
String path = nameToPath(name);
WebResource resource = resources.getClassLoaderResource(path);
if (resource.exists()) {
url = resource.getURL();
trackLastModified(path, resource);
}
if ((url == null) && hasExternalRepositories) {
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;
}
private void trackLastModified(String path, WebResource resource) {
if (resourceEntries.containsKey(path)) {
return;
}
ResourceEntry entry = new ResourceEntry();
entry.lastModified = resource.getLastModified();
synchronized(resourceEntries) {
if (!resourceEntries.containsKey(path)) {
resourceEntries.put(path, entry);
}
}
}
/**
* 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 Enumerationnull.
* null.
*
*
*
* @param name Name of the resource to return a URL for
*/
@Override
public URL getResource(String name) {
if (log.isDebugEnabled())
log.debug("getResource(" + name + ")");
checkStateForResourceLoading(name);
URL url = null;
boolean delegateFirst = delegate || filter(name, false);
// (1) Delegate to parent if requested
if (delegateFirst) {
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) {
if (log.isDebugEnabled())
log.debug(" --> Returning '" + url.toString() + "'");
return url;
}
// (3) Delegate to parent unconditionally if not already attempted
if (!delegateFirst) {
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;
}
@Override
public Enumerationdelegate property is set to true,
* call the getResource() method of the parent class
* loader, if any.findResource() to find this resource in our
* locally defined repositories.getResource() method of the parent class
* loader, if any.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 + ")");
checkStateForResourceLoading(name);
InputStream stream = null;
boolean delegateFirst = delegate || filter(name, false);
// (1) Delegate to parent if requested
if (delegateFirst) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader " + parent);
stream = parent.getResourceAsStream(name);
if (stream != null) {
if (log.isDebugEnabled())
log.debug(" --> Returning stream from parent");
return stream;
}
}
// (2) Search local repositories
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
String path = nameToPath(name);
WebResource resource = resources.getClassLoaderResource(path);
if (resource.exists()) {
stream = resource.getInputStream();
trackLastModified(path, resource);
}
try {
if (hasExternalRepositories && stream == null) {
URL url = super.findResource(name);
if (url != null) {
stream = url.openStream();
}
}
} catch (IOException e) {
// Ignore
}
if (stream != null) {
if (log.isDebugEnabled())
log.debug(" --> Returning stream from local");
return stream;
}
// (3) Delegate to parent unconditionally
if (!delegateFirst) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader unconditionally " + parent);
stream = parent.getResourceAsStream(name);
if (stream != null) {
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 The binary 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.
*
*
* If the class was found using the above steps, and the
* findLoadedClass(String) to check if the
* class has already been loaded. If it has, the same
* Class object is returned.delegate property is set to true,
* call the loadClass() method of the parent class
* loader, if any.findClass() to find this class in our locally
* defined repositories.loadClass() method of our parent
* class loader, if any.resolve flag is true, this method will then
* call resolveClass(Class) on the resulting Class object.
*
* @param name The binary 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 Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (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 Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
URL url;
if (securityManager != null) {
PrivilegedActiontrue if o has been loaded by the current classloader
* or one of its descendants.
*/
private boolean loadedByThisOrChild(Object o) {
if (o == null) {
return false;
}
Class> clazz;
if (o instanceof Class) {
clazz = (Class>) o;
} else {
clazz = o.getClass();
}
ClassLoader cl = clazz.getClassLoader();
while (cl != null) {
if (cl == this) {
return true;
}
cl = cl.getParent();
}
if (o instanceof Collection>) {
Iterator> iter = ((Collection>) o).iterator();
try {
while (iter.hasNext()) {
Object entry = iter.next();
if (loadedByThisOrChild(entry)) {
return true;
}
}
} catch (ConcurrentModificationException e) {
log.warn(sm.getString(
"webappClassLoader.loadedByThisOrChildFail", clazz.getName(), getContextName()),
e);
}
}
return false;
}
/*
* Get the set of current threads as an array.
*/
private Thread[] getThreads() {
// Get the current thread group
ThreadGroup tg = Thread.currentThread().getThreadGroup();
// Find the root thread group
try {
while (tg.getParent() != null) {
tg = tg.getParent();
}
} catch (SecurityException se) {
String msg = sm.getString(
"webappClassLoader.getThreadGroupError", tg.getName());
if (log.isDebugEnabled()) {
log.debug(msg, se);
} else {
log.warn(msg);
}
}
int threadCountGuess = tg.activeCount() + 50;
Thread[] threads = new Thread[threadCountGuess];
int threadCountActual = tg.enumerate(threads);
// Make sure we don't miss any threads
while (threadCountActual == threadCountGuess) {
threadCountGuess *=2;
threads = new Thread[threadCountGuess];
// Note tg.enumerate(Thread[]) silently ignores any threads that
// can't fit into the array
threadCountActual = tg.enumerate(threads);
}
return threads;
}
/**
* This depends on the internals of the Sun JVM so it does everything by
* reflection.
*/
private void clearReferencesRmiTargets() {
try {
// Need access to the ccl field of sun.rmi.transport.Target to find
// the leaks
Class> objectTargetClass =
Class.forName("sun.rmi.transport.Target");
Field cclField = objectTargetClass.getDeclaredField("ccl");
cclField.setAccessible(true);
// Need access to the stub field to report the leaks
Field stubField = objectTargetClass.getDeclaredField("stub");
stubField.setAccessible(true);
// Clear the objTable map
Class> objectTableClass = Class.forName("sun.rmi.transport.ObjectTable");
Field objTableField = objectTableClass.getDeclaredField("objTable");
objTableField.setAccessible(true);
Object objTable = objTableField.get(null);
if (objTable == null) {
return;
}
Field tableLockField = objectTableClass.getDeclaredField("tableLock");
tableLockField.setAccessible(true);
Object tableLock = tableLockField.get(null);
synchronized (tableLock) {
// Iterate over the values in the table
if (objTable instanceof Map,?>) {
Iterator> iter = ((Map,?>) objTable).values().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
Object cclObject = cclField.get(obj);
if (this == cclObject) {
iter.remove();
Object stubObject = stubField.get(obj);
log.error(sm.getString("webappClassLoader.clearRmi",
stubObject.getClass().getName(), stubObject));
}
}
}
// Clear the implTable map
Field implTableField = objectTableClass.getDeclaredField("implTable");
implTableField.setAccessible(true);
Object implTable = implTableField.get(null);
if (implTable == null) {
return;
}
// Iterate over the values in the table
if (implTable instanceof Map,?>) {
Iterator> iter = ((Map,?>) implTable).values().iterator();
while (iter.hasNext()) {
Object obj = iter.next();
Object cclObject = cclField.get(obj);
if (this == cclObject) {
iter.remove();
}
}
}
}
} catch (ClassNotFoundException e) {
log.info(sm.getString("webappClassLoader.clearRmiInfo",
getContextName()), e);
} catch (SecurityException | NoSuchFieldException | IllegalArgumentException |
IllegalAccessException e) {
log.warn(sm.getString("webappClassLoader.clearRmiFail",
getContextName()), e);
} catch (Exception e) {
JreCompat jreCompat = JreCompat.getInstance();
if (jreCompat.isInstanceOfInaccessibleObjectException(e)) {
// Must be running on Java 9 without the necessary command line
// options.
log.warn(sm.getString("webappClassLoader.addExportsRmi"));
} else {
// Re-throw all other exceptions
throw e;
}
}
}
private void clearReferencesObjectStreamClassCaches() {
try {
Class> clazz = Class.forName("java.io.ObjectStreamClass$Caches");
clearCache(clazz, "localDescs");
clearCache(clazz, "reflectors");
} catch (ReflectiveOperationException | SecurityException | ClassCastException e) {
log.warn(sm.getString(
"webappClassLoader.clearObjectStreamClassCachesFail", getContextName()), e);
}
}
private void clearCache(Class> target, String mapName)
throws ReflectiveOperationException, SecurityException, ClassCastException {
Field f = target.getDeclaredField(mapName);
f.setAccessible(true);
Map,?> map = (Map,?>) f.get(null);
Iterator> keys = map.keySet().iterator();
while (keys.hasNext()) {
Object key = keys.next();
if (key instanceof Reference) {
Object clazz = ((Reference>) key).get();
if (loadedByThisOrChild(clazz)) {
keys.remove();
}
}
}
}
/**
* Find specified class in local repositories.
*
* @param name The binary name of the class to be loaded
*
* @return the loaded class, or null if the class isn't found
*/
protected Class> findClassInternal(String name) {
checkStateForResourceLoading(name);
if (name == null) {
return null;
}
String path = binaryNameToPath(name, true);
ResourceEntry entry = resourceEntries.get(path);
WebResource resource = null;
if (entry == null) {
resource = resources.getClassLoaderResource(path);
if (!resource.exists()) {
return null;
}
entry = new ResourceEntry();
entry.lastModified = resource.getLastModified();
// Add the entry in the local resource repository
synchronized (resourceEntries) {
// Ensures that all the threads which may be in a race to load
// a particular class all end up with the same ResourceEntry
// instance
ResourceEntry entry2 = resourceEntries.get(path);
if (entry2 == null) {
resourceEntries.put(path, entry);
} else {
entry = entry2;
}
}
}
Class> clazz = entry.loadedClass;
if (clazz != null)
return clazz;
synchronized (getClassLoadingLock(name)) {
clazz = entry.loadedClass;
if (clazz != null)
return clazz;
if (resource == null) {
resource = resources.getClassLoaderResource(path);
}
if (!resource.exists()) {
return null;
}
byte[] binaryContent = resource.getContent();
if (binaryContent == null) {
// Something went wrong reading the class bytes (and will have
// been logged at debug level).
return null;
}
Manifest manifest = resource.getManifest();
URL codeBase = resource.getCodeBase();
Certificate[] certificates = resource.getCertificates();
if (transformers.size() > 0) {
// If the resource is a class just being loaded, decorate it
// with any attached transformers
// Ignore leading '/' and trailing CLASS_FILE_SUFFIX
// Should be cheaper than replacing '.' by '/' in class name.
String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
for (ClassFileTransformer transformer : this.transformers) {
try {
byte[] transformed = transformer.transform(
this, internalName, null, null, binaryContent);
if (transformed != null) {
binaryContent = transformed;
}
} catch (IllegalClassFormatException e) {
log.error(sm.getString("webappClassLoader.transformError", name), e);
return null;
}
}
}
// Looking up the package
String packageName = null;
int pos = name.lastIndexOf('.');
if (pos != -1)
packageName = name.substring(0, pos);
Package pkg = null;
if (packageName != null) {
pkg = getPackage(packageName);
// Define the package (if null)
if (pkg == null) {
try {
if (manifest == null) {
definePackage(packageName, null, null, null, null, null, null, null);
} else {
definePackage(packageName, manifest, codeBase);
}
} catch (IllegalArgumentException e) {
// Ignore: normal error due to dual definition of package
}
pkg = getPackage(packageName);
}
}
if (securityManager != null) {
// Checking sealing
if (pkg != null) {
boolean sealCheck = true;
if (pkg.isSealed()) {
sealCheck = pkg.isSealed(codeBase);
} else {
sealCheck = (manifest == null) || !isPackageSealed(packageName, manifest);
}
if (!sealCheck)
throw new SecurityException
("Sealing violation loading " + name + " : Package "
+ packageName + " is sealed.");
}
}
try {
clazz = defineClass(name, binaryContent, 0,
binaryContent.length, new CodeSource(codeBase, certificates));
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
ucve.getLocalizedMessage() + " " +
sm.getString("webappClassLoader.wrongVersion",
name));
}
entry.loadedClass = clazz;
}
return clazz;
}
private String binaryNameToPath(String binaryName, boolean withLeadingSlash) {
// 1 for leading '/', 6 for ".class"
StringBuilder path = new StringBuilder(7 + binaryName.length());
if (withLeadingSlash) {
path.append('/');
}
path.append(binaryName.replace('.', '/'));
path.append(CLASS_FILE_SUFFIX);
return path.toString();
}
private String nameToPath(String name) {
if (name.startsWith("/")) {
return name;
}
StringBuilder path = new StringBuilder(
1 + name.length());
path.append('/');
path.append(name);
return path.toString();
}
/**
* Returns true if the specified package name is sealed according to the
* given manifest.
*
* @param name Path name to check
* @param man Associated manifest
* @return true if the manifest associated says it is sealed
*/
protected boolean isPackageSealed(String name, Manifest man) {
String path = name.replace('.', '/') + '/';
Attributes attr = man.getAttributes(path);
String sealed = null;
if (attr != null) {
sealed = attr.getValue(Name.SEALED);
}
if (sealed == null) {
if ((attr = man.getMainAttributes()) != null) {
sealed = attr.getValue(Name.SEALED);
}
}
return "true".equalsIgnoreCase(sealed);
}
/**
* Finds the class with the given name if it has previously been
* loaded and cached by this class loader, and return the Class object.
* If this class has not been cached, return null.
*
* @param name The binary name of the resource to return
* @return a loaded class
*/
protected Class> findLoadedClass0(String name) {
String path = binaryNameToPath(name, true);
ResourceEntry entry = resourceEntries.get(path);
if (entry != null) {
return entry.loadedClass;
}
return null;
}
/**
* Refresh the system policy file, to pick up eventual changes.
*/
protected void refreshPolicy() {
try {
// The policy file may have been modified to adjust
// permissions, so we're reloading it when loading or
// reloading a Context
Policy policy = Policy.getPolicy();
policy.refresh();
} catch (AccessControlException e) {
// Some policy files may restrict this, even for the core,
// so this exception is ignored
}
}
/**
* Filter classes.
*
* @param name class name
* @param isClassName true if name is a class name,
* false if name is a resource name
* @return true if the class should be filtered
*/
protected boolean filter(String name, boolean isClassName) {
if (name == null)
return false;
char ch;
if (name.startsWith("javax")) {
/* 5 == length("javax") */
if (name.length() == 5) {
return false;
}
ch = name.charAt(5);
if (isClassName && ch == '.') {
/* 6 == length("javax.") */
if (name.startsWith("servlet.jsp.jstl.", 6)) {
return false;
}
if (name.startsWith("el.", 6) ||
name.startsWith("servlet.", 6) ||
name.startsWith("websocket.", 6) ||
name.startsWith("security.auth.message.", 6)) {
return true;
}
} else if (!isClassName && ch == '/') {
/* 6 == length("javax/") */
if (name.startsWith("servlet/jsp/jstl/", 6)) {
return false;
}
if (name.startsWith("el/", 6) ||
name.startsWith("servlet/", 6) ||
name.startsWith("websocket/", 6) ||
name.startsWith("security/auth/message/", 6)) {
return true;
}
}
} else if (name.startsWith("org")) {
/* 3 == length("org") */
if (name.length() == 3) {
return false;
}
ch = name.charAt(3);
if (isClassName && ch == '.') {
/* 4 == length("org.") */
if (name.startsWith("apache.", 4)) {
/* 11 == length("org.apache.") */
if (name.startsWith("tomcat.jdbc.", 11)) {
return false;
}
if (name.startsWith("el.", 11) ||
name.startsWith("catalina.", 11) ||
name.startsWith("jasper.", 11) ||
name.startsWith("juli.", 11) ||
name.startsWith("tomcat.", 11) ||
name.startsWith("naming.", 11) ||
name.startsWith("coyote.", 11)) {
return true;
}
}
} else if (!isClassName && ch == '/') {
/* 4 == length("org/") */
if (name.startsWith("apache/", 4)) {
/* 11 == length("org/apache/") */
if (name.startsWith("tomcat/jdbc/", 11)) {
return false;
}
if (name.startsWith("el/", 11) ||
name.startsWith("catalina/", 11) ||
name.startsWith("jasper/", 11) ||
name.startsWith("juli/", 11) ||
name.startsWith("tomcat/", 11) ||
name.startsWith("naming/", 11) ||
name.startsWith("coyote/", 11)) {
return true;
}
}
}
}
return false;
}
/**
* Filter classes.
*
* @param name class name
* @return true if the class should be filtered
* @deprecated Use {@link #filter(String, boolean)} This will be removed in
* Tomcat 9
*/
@Deprecated
protected boolean filter(String name) {
return filter(name, true) || filter(name, false);
}
@Override
protected void addURL(URL url) {
super.addURL(url);
hasExternalRepositories = true;
}
@Override
public String getWebappName() {
return getContextName();
}
@Override
public String getHostName() {
if (resources != null) {
Container host = resources.getContext().getParent();
if (host != null) {
return host.getName();
}
}
return null;
}
@Override
public String getServiceName() {
if (resources != null) {
Container host = resources.getContext().getParent();
if (host != null) {
Container engine = host.getParent();
if (engine != null) {
return engine.getName();
}
}
}
return null;
}
@Override
public boolean hasLoggingConfig() {
if (Globals.IS_SECURITY_ENABLED) {
Boolean result = AccessController.doPrivileged(new PrivilegedHasLoggingConfig());
return result.booleanValue();
} else {
return findResource("logging.properties") != null;
}
}
private class PrivilegedHasLoggingConfig implements PrivilegedAction