This is an automated email from the ASF dual-hosted git repository. dblevins pushed a commit to branch redoparent in repository https://gitbox.apache.org/repos/asf/tomee-jakarta.git
commit d04637efeabb07e1dd99714a955ce9796ab1facd Author: David Blevins <[email protected]> AuthorDate: Mon Mar 22 22:52:05 2021 -0700 Delete Tomcat modifications now that we use Tomcat 10 --- .../catalina/loader/WebappClassLoaderBase.java | 2725 -------------------- .../MbeansDescriptorsIntrospectionSource.java | 392 --- 2 files changed, 3117 deletions(-) diff --git a/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java deleted file mode 100644 index 2e75d4c..0000000 --- a/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java +++ /dev/null @@ -1,2725 +0,0 @@ -/* - * 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. - * <p> - * This class loader is a full reimplementation of the - * <code>URLClassLoader</code> from the JDK. It is designed to be fully - * compatible with a normal <code>URLClassLoader</code>, although its internal - * behavior may be completely different. - * <p> - * <strong>IMPLEMENTATION NOTE</strong> - 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 <code>delegate</code> property - * allows an application to modify this behavior to move the parent class loader - * ahead of the local repositories. - * <p> - * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper - * compilation technology, any repository which contains classes from - * the servlet API will be ignored by the class loader. - * <p> - * <strong>IMPLEMENTATION NOTE</strong> - 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. - * <p> - * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in - * the order they are added via the initial constructor. - * <p> - * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or - * security is made unless a security manager is present. - * <p> - * <strong>IMPLEMENTATION NOTE</strong> - 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<String> JVM_THREAD_GROUP_NAMES = new ArrayList<>(); - - private static final String JVM_THREAD_GROUP_SYSTEM = "system"; - - private static final String CLASS_FILE_SUFFIX = ".class"; - - static { - if (!JreCompat.isGraalAvailable()) { - ClassLoader.registerAsParallelCapable(); - } - JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM); - JVM_THREAD_GROUP_NAMES.add("RMI Runtime"); - } - - protected class PrivilegedFindClassByName implements PrivilegedAction<Class<?>> { - - private final String name; - - PrivilegedFindClassByName(String name) { - this.name = name; - } - - @Override - public Class<?> run() { - return findClassInternal(name); - } - } - - - protected static final class PrivilegedGetClassLoader implements PrivilegedAction<ClassLoader> { - - private final Class<?> clazz; - - public PrivilegedGetClassLoader(Class<?> clazz){ - this.clazz = clazz; - } - - @Override - public ClassLoader run() { - return clazz.getClassLoader(); - } - } - - - protected final class PrivilegedJavaseGetResource implements PrivilegedAction<URL> { - - private final String name; - - public PrivilegedJavaseGetResource(String name) { - this.name = name; - } - - @Override - public URL run() { - return javaseClassLoader.getResource(name); - } - } - - - // ------------------------------------------------------- Static Variables - - /** - * The string manager for this package. - */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); - - - // ----------------------------------------------------------- Constructors - - /** - * Construct a new ClassLoader with no defined repositories and no - * parent ClassLoader. - */ - protected WebappClassLoaderBase() { - - 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.javaseClassLoader = j; - - securityManager = System.getSecurityManager(); - if (securityManager != null) { - refreshPolicy(); - } - } - - - /** - * Construct a new ClassLoader with no defined repositories and the given - * parent ClassLoader. - * <p> - * 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<String, ResourceEntry> resourceEntries = - new ConcurrentHashMap<>(); - - - /** - * Should this class loader delegate to the parent class loader - * <strong>before</strong> searching its own repositories (i.e. the - * usual Java2 delegation model)? If set to <code>false</code>, - * this class loader will search its own repositories first, and - * delegate to the parent only if the class or resource is not - * found locally. Note that the default, <code>false</code>, is - * the behavior called for by the servlet specification. - */ - protected boolean delegate = false; - - - private final Map<String,Long> jarModificationTimes = new HashMap<>(); - - - /** - * A list of read File Permission's required if this loader is for a web - * application context. - */ - protected final ArrayList<Permission> permissionList = new ArrayList<>(); - - - /** - * The PermissionCollection for each CodeSource for a web - * application context. - */ - protected final HashMap<String, PermissionCollection> loaderPC = new HashMap<>(); - - - /** - * Instance of the SecurityManager installed. - */ - protected final SecurityManager securityManager; - - - /** - * The parent class loader. - */ - protected final ClassLoader parent; - - - /** - * The bootstrap class loader used to load the JavaSE classes. In some - * implementations this class loader is always <code>null</code> 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; - - - /** - * 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) <code>Thread.stop()</code> 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 - * <code>false</code> 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 <code>false</code> 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 <code>true</code> 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; - - /** - * Should Tomcat skip the memory leak checks when the web application is - * stopped as part of the process of shutting down the JVM? - */ - private boolean skipMemoryLeakChecksOnJvmShutdown = false; - - /** - * 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<ClassFileTransformer> transformers = new CopyOnWriteArrayList<>(); - - - /** - * Flag that indicates that {@link #addURL(URL)} has been called which - * creates a requirement to check the super class when searching for - * resources. - */ - private boolean hasExternalRepositories = false; - - - /** - * Repositories managed by this class rather than the super class. - */ - private List<URL> localRepositories = new ArrayList<>(); - - - private volatile LifecycleState state = LifecycleState.NEW; - - - // ------------------------------------------------------------- Properties - - /** - * @return associated resources. - */ - public WebResourceRoot getResources() { - return this.resources; - } - - - /** - * Set associated resources. - * @param resources the resources from which the classloader will - * load the classes - */ - public void setResources(WebResourceRoot resources) { - this.resources = resources; - } - - - /** - * @return the context name for this class loader. - */ - public String getContextName() { - if (resources == null) { - return "Unknown"; - } else { - return resources.getContext().getBaseName(); - } - } - - - /** - * Return the "delegate first" flag for this class loader. - * @return <code>true</code> if the class lookup will delegate to - * the parent first. The default in Tomcat is <code>false</code>. - */ - 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 - * <strong>before</strong> searching its own repositories, as - * in an ordinary (non-servlet) chain of Java class loaders. - * If set to <code>false</code> (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; - } - - - public boolean getSkipMemoryLeakChecksOnJvmShutdown() { - return skipMemoryLeakChecksOnJvmShutdown; - } - - - public void setSkipMemoryLeakChecksOnJvmShutdown(boolean skipMemoryLeakChecksOnJvmShutdown) { - this.skipMemoryLeakChecksOnJvmShutdown = skipMemoryLeakChecksOnJvmShutdown; - } - - - // ------------------------------------------------------- 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 <code>true</code> if there's been a modification - */ - public boolean modified() { - - if (log.isDebugEnabled()) - log.debug("modified()"); - - for (Entry<String,ResourceEntry> entry : resourceEntries.entrySet()) { - long cachedLastModified = entry.getValue().lastModified; - long lastModified = resources.getClassLoaderResource( - entry.getKey()).getLastModified(); - if (lastModified != cachedLastModified) { - if( log.isDebugEnabled() ) - log.debug(sm.getString("webappClassLoader.resourceModified", - entry.getKey(), - new Date(cachedLastModified), - new Date(lastModified))); - return true; - } - } - - // Check if JARs have been added or removed - WebResource[] jars = resources.listResources("/WEB-INF/lib"); - // Filter out non-JAR resources - - int jarCount = 0; - for (WebResource jar : jars) { - if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { - jarCount++; - Long recordedLastModified = jarModificationTimes.get(jar.getName()); - if (recordedLastModified == null) { - // Jar has been added - log.info(sm.getString("webappClassLoader.jarsAdded", - resources.getContext().getName())); - return true; - } - if (recordedLastModified.longValue() != jar.getLastModified()) { - // Jar has been changed - log.info(sm.getString("webappClassLoader.jarsModified", - resources.getContext().getName())); - return true; - } - } - } - - if (jarCount < jarModificationTimes.size()){ - log.info(sm.getString("webappClassLoader.jarsRemoved", - resources.getContext().getName())); - return true; - } - - - // No classes have been modified - return false; - } - - - @Override - public String toString() { - - StringBuilder sb = new StringBuilder(this.getClass().getSimpleName()); - sb.append("\r\n context: "); - sb.append(getContextName()); - sb.append("\r\n delegate: "); - sb.append(delegate); - 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 - - - // Note: exposed for use by 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 <code>ClassNotFoundException</code>. - * - * @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) { - PrivilegedAction<Class<?>> dp = - new PrivilegedFindClassByName(name); - clazz = AccessController.doPrivileged(dp); - } else { - clazz = findClassInternal(name); - } - } catch(AccessControlException ace) { - log.warn(sm.getString("webappClassLoader.securityException", name, - 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) { - try { - clazz = super.findClass(name); - } catch(AccessControlException ace) { - log.warn(sm.getString("webappClassLoader.securityException", name, - 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 - * <code>URL</code> referring to it, or <code>null</code> 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) { - resourceEntries.putIfAbsent(path, entry); - } - } - - - /** - * Return an enumeration of <code>URLs</code> representing all of the - * resources with the given name. If no resources with this name are - * found, return an empty enumeration. - * - * @param name Name of the resources to be found - * - * @exception IOException if an input/output error occurs - */ - @Override - public Enumeration<URL> findResources(String name) throws IOException { - - if (log.isDebugEnabled()) - log.debug(" findResources(" + name + ")"); - - checkStateForResourceLoading(name); - - LinkedHashSet<URL> result = new LinkedHashSet<>(); - - String path = nameToPath(name); - - WebResource[] webResources = resources.getClassLoaderResources(path); - for (WebResource webResource : webResources) { - if (webResource.exists()) { - result.add(webResource.getURL()); - } - } - - // Adding the results of a call to the superclass - if (hasExternalRepositories) { - Enumeration<URL> 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 <code>null</code>. - * <p> - * This method searches according to the following algorithm, returning - * as soon as it finds the appropriate URL. If the resource cannot be - * found, returns <code>null</code>. - * <ul> - * <li>If the <code>delegate</code> property is set to <code>true</code>, - * call the <code>getResource()</code> method of the parent class - * loader, if any.</li> - * <li>Call <code>findResource()</code> to find this resource in our - * locally defined repositories.</li> - * <li>Call the <code>getResource()</code> method of the parent class - * loader, if any.</li> - * </ul> - * - * @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 Enumeration<URL> getResources(String name) throws IOException { - - Enumeration<URL> parentResources = getParent().getResources(name); - Enumeration<URL> localResources = findResources(name); - - // Need to combine these enumerations. The order in which the - // Enumerations are combined depends on how delegation is configured - boolean delegateFirst = delegate || filter(name, false); - - if (delegateFirst) { - return new CombinedEnumeration(parentResources, localResources); - } else { - return new CombinedEnumeration(localResources, parentResources); - } - } - - - /** - * 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 <code>getResource()</code>, after checking to see if the resource - * data has been previously cached. If the resource cannot be found, - * return <code>null</code>. - * - * @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 <code>loadClass(String, boolean)</code> - * with <code>false</code> 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 <code>ClassNotFoundException</code>. - * <ul> - * <li>Call <code>findLoadedClass(String)</code> to check if the - * class has already been loaded. If it has, the same - * <code>Class</code> object is returned.</li> - * <li>If the <code>delegate</code> property is set to <code>true</code>, - * call the <code>loadClass()</code> method of the parent class - * loader, if any.</li> - * <li>Call <code>findClass()</code> to find this class in our locally - * defined repositories.</li> - * <li>Call the <code>loadClass()</code> method of our parent - * class loader, if any.</li> - * </ul> - * If the class was found using the above steps, and the - * <code>resolve</code> flag is <code>true</code>, this method will then - * call <code>resolveClass(Class)</code> on the resulting Class object. - * - * @param name The binary name of the class to be loaded - * @param resolve If <code>true</code> then resolve the class - * - * @exception ClassNotFoundException if the class was not found - */ - @Override - public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - - synchronized (JreCompat.isGraalAvailable() ? this : 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 = JreCompat.isGraalAvailable() ? null : 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) { - PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName); - url = AccessController.doPrivileged(dp); - } else { - url = javaseLoader.getResource(resourceName); - } - tryLoadingFromJavaseLoader = (url != null); - } catch (Throwable t) { - // Swallow all exceptions apart from those that must be re-thrown - ExceptionUtils.handleThrowable(t); - // The getResource() trick won't work for this class. We have to - // try loading it directly and accept that we might get a - // ClassNotFoundException. - tryLoadingFromJavaseLoader = true; - } - - if (tryLoadingFromJavaseLoader) { - try { - clazz = javaseLoader.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 = sm.getString("webappClassLoader.restrictedPackage", name); - log.info(error, se); - throw new ClassNotFoundException(error, se); - } - } - } - - boolean delegateLoad = delegate || filter(name, true); - - // (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); - } - - - protected void checkStateForClassLoading(String className) throws ClassNotFoundException { - // It is not permitted to load new classes once the web application has - // been stopped. - try { - checkStateForResourceLoading(className); - } catch (IllegalStateException ise) { - throw new ClassNotFoundException(ise.getMessage(), ise); - } - } - - - protected void checkStateForResourceLoading(String resource) throws IllegalStateException { - // It is not permitted to load resources once the web application has - // been stopped. - if (!state.isAvailable()) { - String msg = sm.getString("webappClassLoader.stopped", resource); - IllegalStateException ise = new IllegalStateException(msg); - log.info(msg, ise); - throw ise; - } - } - - /** - * Get the Permissions for a CodeSource. If this instance - * of WebappClassLoaderBase is for a web application context, - * add read FilePermission for the appropriate 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) { - for (Permission p : permissionList) { - pc.add(p); - } - loaderPC.put(codeUrl,pc); - } - } - return pc; - } - - - @Override - public boolean check(Permission permission) { - if (!Globals.IS_SECURITY_ENABLED) { - return true; - } - Policy currentPolicy = Policy.getPolicy(); - if (currentPolicy != null) { - URL contextRootUrl = resources.getResource("/").getCodeBase(); - CodeSource cs = new CodeSource(contextRootUrl, (Certificate[]) null); - PermissionCollection pc = currentPolicy.getPermissions(cs); - if (pc.implies(permission)) { - return true; - } - } - return false; - } - - - /** - * {@inheritDoc} - * <p> - * 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() { - ArrayList<URL> result = new ArrayList<>(); - result.addAll(localRepositories); - result.addAll(Arrays.asList(super.getURLs())); - return result.toArray(new URL[0]); - } - - - // ------------------------------------------------------ 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 state; - } - - - /** - * {@inheritDoc} - */ - @Override - public String getStateName() { - return getState().toString(); - } - - - @Override - public void init() { - state = LifecycleState.INITIALIZED; - } - - - /** - * Start the class loader. - * - * @exception LifecycleException if a lifecycle error occurs - */ - @Override - public void start() throws LifecycleException { - - state = LifecycleState.STARTING_PREP; - - WebResource[] classesResources = resources.getResources("/WEB-INF/classes"); - for (WebResource classes : classesResources) { - if (classes.isDirectory() && classes.canRead()) { - localRepositories.add(classes.getURL()); - } - } - WebResource[] jars = resources.listResources("/WEB-INF/lib"); - for (WebResource jar : jars) { - if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { - localRepositories.add(jar.getURL()); - jarModificationTimes.put( - jar.getName(), Long.valueOf(jar.getLastModified())); - } - } - - state = LifecycleState.STARTED; - } - - - /** - * Stop the class loader. - * - * @exception LifecycleException if a lifecycle error occurs - */ - @Override - public void stop() throws LifecycleException { - - state = LifecycleState.STOPPING_PREP; - - // Clearing references should be done before setting started to - // false, due to possible side effects - clearReferences(); - - state = LifecycleState.STOPPING; - - resourceEntries.clear(); - jarModificationTimes.clear(); - resources = null; - - permissionList.clear(); - loaderPC.clear(); - - state = LifecycleState.STOPPED; - } - - - @Override - public void destroy() { - state = LifecycleState.DESTROYING; - - try { - super.close(); - } catch (IOException ioe) { - log.warn(sm.getString("webappClassLoader.superCloseFail"), ioe); - } - state = LifecycleState.DESTROYED; - } - - - // ------------------------------------------------------ Protected Methods - - protected ClassLoader getJavaseClassLoader() { - return javaseClassLoader; - } - - protected void setJavaseClassLoader(ClassLoader classLoader) { - if (classLoader == null) { - throw new IllegalArgumentException( - sm.getString("webappClassLoader.javaseClassLoaderNull")); - } - javaseClassLoader = classLoader; - } - - /** - * Clear references. - */ - protected void clearReferences() { - - // If the JVM is shutting down, skip the memory leak checks - if (skipMemoryLeakChecksOnJvmShutdown - && !resources.getContext().getParent().getState().isAvailable()) { - // During reloading / redeployment the parent is expected to be - // available. Parent is not available so this might be a JVM - // shutdown. - try { - Thread dummyHook = new Thread(); - Runtime.getRuntime().addShutdownHook(dummyHook); - Runtime.getRuntime().removeShutdownHook(dummyHook); - } catch (IllegalStateException ise) { - return; - } - } - - if (!JreCompat.isGraalAvailable()) { - // De-register any remaining JDBC drivers - clearReferencesJdbc(); - } - - // Stop any threads the web application started - clearReferencesThreads(); - - // Clear any references retained in the serialization caches - if (clearReferencesObjectStreamClassCaches && !JreCompat.isGraalAvailable()) { - clearReferencesObjectStreamClassCaches(); - } - - // Check for leaks triggered by ThreadLocals loaded by this class loader - if (clearReferencesThreadLocals && !JreCompat.isGraalAvailable()) { - checkThreadLocalsForLeaks(); - } - - // Clear RMI Targets loaded by this class loader - if (clearReferencesRmiTargets) { - clearReferencesRmiTargets(); - } - - // Clear the IntrospectionUtils cache. - IntrospectionUtils.clear(); - - // Clear the classloader reference in common-logging - if (clearReferencesLogFactoryRelease) { - org.apache.juli.logging.LogFactory.release(this); - } - - // Clear the classloader reference in the VM's bean introspector - java.beans.Introspector.flushCaches(); - - // Clear any custom URLStreamHandlers - TomcatURLStreamHandlerFactory.release(this); - } - - - /** - * 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() { - // 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 (InputStream is = getResourceAsStream( - "org/apache/catalina/loader/JdbcLeakPrevention.class")) { - 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.getConstructor().newInstance(); - @SuppressWarnings("unchecked") - List<String> driverNames = (List<String>) obj.getClass().getMethod( - "clearJdbcDriverRegistrations").invoke(obj); - for (String name : driverNames) { - log.warn(sm.getString("webappClassLoader.clearJdbc", - getContextName(), 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", getContextName()), t); - } - } - - - @SuppressWarnings("deprecation") // thread.stop() - private void clearReferencesThreads() { - Thread[] threads = getThreads(); - List<Thread> threadsToStop = 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; - } - - final String threadName = thread.getName(); - - // JVM controlled threads - ThreadGroup tg = thread.getThreadGroup(); - if (tg != null && JVM_THREAD_GROUP_NAMES.contains(tg.getName())) { - // HttpClient keep-alive threads - if (clearReferencesHttpClientKeepAliveThread && - threadName.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.warn(sm.getString("webappClassLoader.stackTraceRequestThread", - getContextName(), threadName, getStackTrace(thread))); - } else { - log.warn(sm.getString("webappClassLoader.stackTrace", - getContextName(), threadName, getStackTrace(thread))); - } - - // Don't try and 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 | NoSuchFieldException | IllegalArgumentException | - IllegalAccessException e) { - log.warn(sm.getString("webappClassLoader.stopThreadFail", - thread.getName(), getContextName()), e); - } - - // Stopping an executor automatically interrupts the - // associated threads. For non-executor threads, interrupt - // them here. - if (!usingExecutor && !thread.isInterrupted()) { - thread.interrupt(); - } - - // Threads are expected to take a short time to stop after - // being interrupted. Make a note of all threads that are - // expected to stop to enable them to be checked at the end - // of this method. - threadsToStop.add(thread); - } - } - } - - // If thread stopping is enabled, threads should have been stopped above - // when the executor was shut down or the thread was interrupted but - // that depends on the thread correctly handling the interrupt. Check - // each thread and if any are still running give all threads up to a - // total of 2 seconds to shutdown. - int count = 0; - for (Thread t : threadsToStop) { - while (t.isAlive() && count < 100) { - try { - Thread.sleep(20); - } catch (InterruptedException e) { - // Quit the while loop - break; - } - count++; - } - if (t.isAlive()) { - // This method is deprecated and for good reason. This is - // very risky code but is the only option at this point. - // A *very* good reason for apps to do this clean-up - // themselves. - t.stop(); - } - } - } - - - /* - * Look at a threads stack trace to see if it is a request thread or not. It - * isn't perfect, but it should be good-enough for most cases. - */ - private boolean isRequestThread(Thread thread) { - - StackTraceElement[] elements = thread.getStackTrace(); - - if (elements == null || elements.length == 0) { - // Must have stopped already. Too late to ignore it. Assume not a - // request processing thread. - return false; - } - - // Step through the methods in reverse order looking for calls to any - // CoyoteAdapter method. All request threads will have this unless - // Tomcat has been heavily modified - in which case there isn't much we - // can do. - for (int i = 0; i < elements.length; i++) { - StackTraceElement element = elements[elements.length - (i+1)]; - if ("org.apache.catalina.connector.CoyoteAdapter".equals( - element.getClassName())) { - return true; - } - } - return false; - } - - - private void clearReferencesStopTimerThread(Thread thread) { - - // Need to get references to: - // in Sun/Oracle JDK: - // - newTasksMayBeScheduled field (in java.util.TimerThread) - // - queue field - // - queue.clear() - // in IBM JDK, Apache Harmony: - // - cancel() method (in java.util.Timer$TimerImpl) - - try { - - try { - Field newTasksMayBeScheduledField = - thread.getClass().getDeclaredField("newTasksMayBeScheduled"); - newTasksMayBeScheduledField.setAccessible(true); - Field queueField = thread.getClass().getDeclaredField("queue"); - queueField.setAccessible(true); - - Object queue = queueField.get(thread); - - Method clearMethod = queue.getClass().getDeclaredMethod("clear"); - clearMethod.setAccessible(true); - - synchronized(queue) { - newTasksMayBeScheduledField.setBoolean(thread, false); - clearMethod.invoke(queue); - // In case queue was already empty. Should only be one - // thread waiting but use notifyAll() to be safe. - queue.notifyAll(); - } - - }catch (NoSuchFieldException nfe){ - Method cancelMethod = thread.getClass().getDeclaredMethod("cancel"); - synchronized(thread) { - cancelMethod.setAccessible(true); - cancelMethod.invoke(thread); - } - } - - log.warn(sm.getString("webappClassLoader.warnTimerThread", - getContextName(), thread.getName())); - - } catch (Exception e) { - // So many things to go wrong above... - Throwable t = ExceptionUtils.unwrapInvocationTargetException(e); - ExceptionUtils.handleThrowable(t); - log.warn(sm.getString( - "webappClassLoader.stopTimerThreadFail", - thread.getName(), getContextName()), t); - } - } - - private void checkThreadLocalsForLeaks() { - Thread[] threads = getThreads(); - - try { - // Make the fields in the Thread class that store ThreadLocals - // accessible - Field threadLocalsField = - Thread.class.getDeclaredField("threadLocals"); - threadLocalsField.setAccessible(true); - Field inheritableThreadLocalsField = - Thread.class.getDeclaredField("inheritableThreadLocals"); - inheritableThreadLocalsField.setAccessible(true); - // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects - // accessible - Class<?> tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); - Field tableField = tlmClass.getDeclaredField("table"); - tableField.setAccessible(true); - Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries"); - expungeStaleEntriesMethod.setAccessible(true); - - for (Thread thread : threads) { - Object threadLocalMap; - if (thread != null) { - - // Clear the first map - threadLocalMap = threadLocalsField.get(thread); - if (null != threadLocalMap) { - expungeStaleEntriesMethod.invoke(threadLocalMap); - checkThreadLocalMapForLeaks(threadLocalMap, tableField); - } - - // Clear the second map - threadLocalMap = inheritableThreadLocalsField.get(thread); - if (null != threadLocalMap) { - expungeStaleEntriesMethod.invoke(threadLocalMap); - checkThreadLocalMapForLeaks(threadLocalMap, tableField); - } - } - } - } catch (Throwable t) { - JreCompat jreCompat = JreCompat.getInstance(); - if (jreCompat.isInstanceOfInaccessibleObjectException(t)) { - // Must be running on Java 9 without the necessary command line - // options. - log.warn(sm.getString("webappClassLoader.addExportsThreadLocal")); - } else { - ExceptionUtils.handleThrowable(t); - log.warn(sm.getString( - "webappClassLoader.checkThreadLocalsForLeaksFail", - getContextName()), t); - } - } - } - - - /** - * Analyzes the given thread local map object. Also pass in the field that - * points to the internal table to save re-calculating it on every - * call to this method. - */ - private void checkThreadLocalMapForLeaks(Object map, - Field internalTableField) throws IllegalAccessException, - NoSuchFieldException { - if (map != null) { - Object[] table = (Object[]) internalTableField.get(map); - if (table != null) { - for (Object obj : table) { - if (obj != null) { - boolean keyLoadedByWebapp = false; - boolean valueLoadedByWebapp = false; - // Check the key - Object key = ((Reference<?>) obj).get(); - if (this.equals(key) || loadedByThisOrChild(key)) { - keyLoadedByWebapp = true; - } - // Check the value - Field valueField = - obj.getClass().getDeclaredField("value"); - valueField.setAccessible(true); - Object value = valueField.get(obj); - if (this.equals(value) || loadedByThisOrChild(value)) { - valueLoadedByWebapp = true; - } - if (keyLoadedByWebapp || valueLoadedByWebapp) { - Object[] args = new Object[5]; - args[0] = getContextName(); - if (key != null) { - args[1] = getPrettyClassName(key.getClass()); - try { - args[2] = key.toString(); - } catch (Exception e) { - log.warn(sm.getString( - "webappClassLoader.checkThreadLocalsForLeaks.badKey", - args[1]), e); - args[2] = sm.getString( - "webappClassLoader.checkThreadLocalsForLeaks.unknown"); - } - } - if (value != null) { - args[3] = getPrettyClassName(value.getClass()); - try { - args[4] = value.toString(); - } catch (Exception e) { - log.warn(sm.getString( - "webappClassLoader.checkThreadLocalsForLeaks.badValue", - args[3]), e); - args[4] = sm.getString( - "webappClassLoader.checkThreadLocalsForLeaks.unknown"); - } - } - if (valueLoadedByWebapp) { - log.error(sm.getString( - "webappClassLoader.checkThreadLocalsForLeaks", - args)); - } else if (value == null) { - if (log.isDebugEnabled()) { - log.debug(sm.getString( - "webappClassLoader.checkThreadLocalsForLeaksNull", - args)); - } - } else { - if (log.isDebugEnabled()) { - log.debug(sm.getString( - "webappClassLoader.checkThreadLocalsForLeaksNone", - args)); - } - } - } - } - } - } - } - } - - private String getPrettyClassName(Class<?> clazz) { - String name = clazz.getCanonicalName(); - if (name==null){ - name = clazz.getName(); - } - return name; - } - - private String getStackTrace(Thread thread) { - StringBuilder builder = new StringBuilder(); - for (StackTraceElement ste : thread.getStackTrace()) { - builder.append("\n ").append(ste); - } - return builder.toString(); - } - - /** - * @param o object to test, may be null - * @return <code>true</code> 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 (JreCompat.isGraalAvailable() ? this : 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 <code>true</code> 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 <code>null</code>. - * - * @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 <code>true</code> if name is a class name, - * <code>false</code> if name is a resource name - * @return <code>true</code> if the class should be filtered - */ - protected boolean filter(String name, boolean isClassName) { - - if (name == null) - return false; - - char ch; - if (name.startsWith("jakarta")) { - /* 7 == length("jakarta") */ - if (name.length() == 7) { - return false; - } - ch = name.charAt(7); - if (isClassName && ch == '.') { - /* 8 == length("jakarta.") */ - if (name.startsWith("servlet.jsp.jstl.", 8)) { - return false; - } - if (name.startsWith("el.", 8) || - name.startsWith("servlet.", 8) || - name.startsWith("websocket.", 8) || - name.startsWith("security.auth.message.", 8)) { - return true; - } - } else if (!isClassName && ch == '/') { - /* 8 == length("jakarta/") */ - if (name.startsWith("servlet/jsp/jstl/", 8)) { - return false; - } - if (name.startsWith("el/", 8) || - name.startsWith("servlet/", 8) || - name.startsWith("websocket/", 8) || - name.startsWith("security/auth/message/", 8)) { - return true; - } - } - } else 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("websocket.", 6)) { - return true; - } - } else if (!isClassName && ch == '/') { - /* 6 == length("javax/") */ - if (name.startsWith("websocket/", 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; - } - - - @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<Boolean> { - - @Override - public Boolean run() { - return Boolean.valueOf(findResource("logging.properties") != null); - } - } - - - private static class CombinedEnumeration implements Enumeration<URL> { - - private final Enumeration<URL>[] sources; - private int index = 0; - - public CombinedEnumeration(Enumeration<URL> enum1, Enumeration<URL> enum2) { - @SuppressWarnings("unchecked") - Enumeration<URL>[] sources = new Enumeration[] { enum1, enum2 }; - this.sources = sources; - } - - - @Override - public boolean hasMoreElements() { - return inc(); - } - - - @Override - public URL nextElement() { - if (inc()) { - return sources[index].nextElement(); - } - throw new NoSuchElementException(); - } - - - private boolean inc() { - while (index < sources.length) { - if (sources[index].hasMoreElements()) { - return true; - } - index++; - } - return false; - } - } -} \ No newline at end of file diff --git a/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java b/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java deleted file mode 100644 index 0c59207..0000000 --- a/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.tomcat.util.modeler.modules; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.List; -import java.util.Map.Entry; - -import javax.management.ObjectName; - -import org.apache.juli.logging.Log; -import org.apache.juli.logging.LogFactory; -import org.apache.tomcat.util.modeler.AttributeInfo; -import org.apache.tomcat.util.modeler.ManagedBean; -import org.apache.tomcat.util.modeler.OperationInfo; -import org.apache.tomcat.util.modeler.ParameterInfo; -import org.apache.tomcat.util.modeler.Registry; - -public class MbeansDescriptorsIntrospectionSource extends ModelerSource -{ - private static final Log log = LogFactory.getLog(MbeansDescriptorsIntrospectionSource.class); - - private Registry registry; - private String type; - private final List<ObjectName> mbeans = new ArrayList<>(); - - public void setRegistry(Registry reg) { - this.registry=reg; - } - - /** - * Used if a single component is loaded - * - * @param type The type - */ - public void setType( String type ) { - this.type=type; - } - - public void setSource( Object source ) { - this.source=source; - } - - @Override - public List<ObjectName> loadDescriptors(Registry registry, String type, - Object source) throws Exception { - setRegistry(registry); - setType(type); - setSource(source); - execute(); - return mbeans; - } - - public void execute() throws Exception { - if( registry==null ) registry=Registry.getRegistry(null, null); - try { - ManagedBean managed = createManagedBean(registry, null, - (Class<?>)source, type); - if( managed==null ) return; - managed.setName( type ); - - registry.addManagedBean(managed); - - } catch( Exception ex ) { - log.error(sm.getString("modules.readDescriptorsError"), ex); - } - } - - - - // ------------ Implementation for non-declared introspection classes - - private static final Hashtable<String,String> specialMethods = new Hashtable<>(); - static { - specialMethods.put( "preDeregister", ""); - specialMethods.put( "postDeregister", ""); - } - - private static final Class<?>[] supportedTypes = new Class[] { - Boolean.class, - Boolean.TYPE, - Byte.class, - Byte.TYPE, - Character.class, - Character.TYPE, - Short.class, - Short.TYPE, - Integer.class, - Integer.TYPE, - Long.class, - Long.TYPE, - Float.class, - Float.TYPE, - Double.class, - Double.TYPE, - String.class, - String[].class, - BigDecimal.class, - BigInteger.class, - ObjectName.class, - Object[].class, - java.io.File.class, - }; - - /** - * Check if this class is one of the supported types. - * If the class is supported, returns true. Otherwise, - * returns false. - * @param ret The class to check - * @return boolean True if class is supported - */ - private boolean supportedType(Class<?> ret) { - for (Class<?> supportedType : supportedTypes) { - if (ret == supportedType) { - return true; - } - } - if (isBeanCompatible(ret)) { - return true; - } - return false; - } - - /** - * Check if this class conforms to JavaBeans specifications. - * If the class is conformant, returns true. - * - * @param javaType The class to check - * @return boolean True if the class is compatible. - */ - private boolean isBeanCompatible(Class<?> javaType) { - // Must be a non-primitive and non array - if (javaType.isArray() || javaType.isPrimitive()) { - return false; - } - - // Anything in the java or javax package that - // does not have a defined mapping is excluded. - if (javaType.getName().startsWith("java.") || - javaType.getName().startsWith("javax.") || - javaType.getName().startsWith("jakarta.")) { - return false; - } - - try { - javaType.getConstructor(new Class[]{}); - } catch (java.lang.NoSuchMethodException e) { - return false; - } - - // Make sure superclass is compatible - Class<?> superClass = javaType.getSuperclass(); - if (superClass != null && - superClass != java.lang.Object.class && - superClass != java.lang.Exception.class && - superClass != java.lang.Throwable.class) { - if (!isBeanCompatible(superClass)) { - return false; - } - } - return true; - } - - /** - * Process the methods and extract 'attributes', methods, etc. - * - * @param realClass The class to process - * @param methods The methods to process - * @param attMap The attribute map (complete) - * @param getAttMap The readable attributes map - * @param setAttMap The settable attributes map - * @param invokeAttMap The invokable attributes map - */ - private void initMethods(Class<?> realClass, Method methods[], Hashtable<String,Method> attMap, - Hashtable<String,Method> getAttMap, Hashtable<String,Method> setAttMap, - Hashtable<String,Method> invokeAttMap) { - - for (Method method : methods) { - String name = method.getName(); - - if (Modifier.isStatic(method.getModifiers())) { - continue; - } - if (!Modifier.isPublic(method.getModifiers())) { - if (log.isDebugEnabled()) { - log.debug("Not public " + method); - } - continue; - } - if (method.getDeclaringClass() == Object.class) { - continue; - } - Class<?> params[] = method.getParameterTypes(); - - if (name.startsWith("get") && params.length == 0) { - Class<?> ret = method.getReturnType(); - if (!supportedType(ret)) { - if (log.isDebugEnabled()) { - log.debug("Unsupported type " + method); - } - continue; - } - name = unCapitalize(name.substring(3)); - - getAttMap.put(name, method); - // just a marker, we don't use the value - attMap.put(name, method); - } else if (name.startsWith("is") && params.length == 0) { - Class<?> ret = method.getReturnType(); - if (Boolean.TYPE != ret) { - if (log.isDebugEnabled()) { - log.debug("Unsupported type " + method + " " + ret); - } - continue; - } - name = unCapitalize(name.substring(2)); - - getAttMap.put(name, method); - // just a marker, we don't use the value - attMap.put(name, method); - - } else if (name.startsWith("set") && params.length == 1) { - if (!supportedType(params[0])) { - if (log.isDebugEnabled()) { - log.debug("Unsupported type " + method + " " + params[0]); - } - continue; - } - name = unCapitalize(name.substring(3)); - setAttMap.put(name, method); - attMap.put(name, method); - } else { - if (params.length == 0) { - if (specialMethods.get(method.getName()) != null) { - continue; - } - invokeAttMap.put(name, method); - } else { - boolean supported = true; - for (Class<?> param : params) { - if (!supportedType(param)) { - supported = false; - break; - } - } - if (supported) { - invokeAttMap.put(name, method); - } - } - } - } - } - - /** - * XXX Find if the 'className' is the name of the MBean or - * the real class ( I suppose first ) - * XXX Read (optional) descriptions from a .properties, generated - * from source - * XXX Deal with constructors - * - * @param registry The Bean registry (not used) - * @param domain The bean domain (not used) - * @param realClass The class to analyze - * @param type The bean type - * @return ManagedBean The create MBean - */ - public ManagedBean createManagedBean(Registry registry, String domain, - Class<?> realClass, String type) - { - ManagedBean mbean= new ManagedBean(); - - Method methods[]=null; - - Hashtable<String,Method> attMap = new Hashtable<>(); - // key: attribute val: getter method - Hashtable<String,Method> getAttMap = new Hashtable<>(); - // key: attribute val: setter method - Hashtable<String,Method> setAttMap = new Hashtable<>(); - // key: operation val: invoke method - Hashtable<String,Method> invokeAttMap = new Hashtable<>(); - - methods = realClass.getMethods(); - - initMethods(realClass, methods, attMap, getAttMap, setAttMap, invokeAttMap ); - - try { - - Enumeration<String> en = attMap.keys(); - while( en.hasMoreElements() ) { - String name = en.nextElement(); - AttributeInfo ai=new AttributeInfo(); - ai.setName( name ); - Method gm = getAttMap.get(name); - if( gm!=null ) { - //ai.setGetMethodObj( gm ); - ai.setGetMethod( gm.getName()); - Class<?> t=gm.getReturnType(); - if( t!=null ) - ai.setType( t.getName() ); - } - Method sm = setAttMap.get(name); - if( sm!=null ) { - //ai.setSetMethodObj(sm); - Class<?> t = sm.getParameterTypes()[0]; - if( t!=null ) - ai.setType( t.getName()); - ai.setSetMethod( sm.getName()); - } - ai.setDescription("Introspected attribute " + name); - if( log.isDebugEnabled()) log.debug("Introspected attribute " + - name + " " + gm + " " + sm); - if( gm==null ) - ai.setReadable(false); - if( sm==null ) - ai.setWriteable(false); - if( sm!=null || gm!=null ) - mbean.addAttribute(ai); - } - - // This map is populated by iterating the methods (which end up as - // values in the Map) and obtaining the key from the value. It is - // impossible for a key to be associated with a null value. - for (Entry<String,Method> entry : invokeAttMap.entrySet()) { - String name = entry.getKey(); - Method m = entry.getValue(); - - OperationInfo op=new OperationInfo(); - op.setName(name); - op.setReturnType(m.getReturnType().getName()); - op.setDescription("Introspected operation " + name); - Class<?> parms[] = m.getParameterTypes(); - for(int i=0; i<parms.length; i++ ) { - ParameterInfo pi=new ParameterInfo(); - pi.setType(parms[i].getName()); - pi.setName(("param" + i).intern()); - pi.setDescription(("Introspected parameter param" + i).intern()); - op.addParameter(pi); - } - mbean.addOperation(op); - } - - if( log.isDebugEnabled()) - log.debug("Setting name: " + type ); - mbean.setName( type ); - - return mbean; - } catch( Exception ex ) { - ex.printStackTrace(); - return null; - } - } - - - // -------------------- Utils -------------------- - /** - * Converts the first character of the given - * String into lower-case. - * - * @param name The string to convert - * @return String - */ - private static String unCapitalize(String name) { - if (name == null || name.length() == 0) { - return name; - } - char chars[] = name.toCharArray(); - chars[0] = Character.toLowerCase(chars[0]); - return new String(chars); - } - -}
