Copied: tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java (from r1623346, tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java) URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java?p2=tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java&p1=tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java&r1=1623346&r2=1623360&rev=1623360&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java (original) +++ tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoaderBase.java Mon Sep 8 11:13:06 2014 @@ -122,11 +122,11 @@ import org.apache.tomcat.util.res.String * @author Remy Maucherat * @author Craig R. McClanahan */ -public class WebappClassLoader extends URLClassLoader +public abstract class WebappClassLoaderBase extends URLClassLoader implements Lifecycle, InstrumentableClassLoader { - private static final org.apache.juli.logging.Log log= - org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class ); + private static final org.apache.juli.logging.Log log = + org.apache.juli.logging.LogFactory.getLog(WebappClassLoaderBase.class); /** * List of ThreadGroup names to ignore when scanning for web application @@ -140,6 +140,7 @@ public class WebappClassLoader extends U private static final String SERVICES_PREFIX = "/META-INF/services/"; static { + ClassLoader.registerAsParallelCapable(); JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM); JVM_THREAD_GROUP_NAMES.add("RMI Runtime"); } @@ -215,7 +216,7 @@ public class WebappClassLoader extends U * Construct a new ClassLoader with no defined repositories and no * parent ClassLoader. */ - public WebappClassLoader() { + protected WebappClassLoaderBase() { super(new URL[0]); @@ -250,7 +251,7 @@ public class WebappClassLoader extends U * * @param parent Our parent class loader */ - public WebappClassLoader(ClassLoader parent) { + protected WebappClassLoaderBase(ClassLoader parent) { super(new URL[0], parent); @@ -397,8 +398,8 @@ public class WebappClassLoader extends U /** * If an HttpClient keep-alive timer thread has been started by this web * application and is still running, should Tomcat change the context class - * loader from the current {@link WebappClassLoader} to - * {@link WebappClassLoader#parent} to prevent a memory leak? Note that the + * 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. */ @@ -697,46 +698,19 @@ public class WebappClassLoader extends U } - /** - * Returns a copy of this class loader without any class file - * transformers. This is a tool often used by Java Persistence API - * providers to inspect entity classes in the absence of any - * instrumentation, something that can't be guaranteed within the - * context of a {@link ClassFileTransformer}'s - * {@link ClassFileTransformer#transform(ClassLoader, String, Class, - * ProtectionDomain, byte[]) transform} method. - * <p> - * The returned class loader's resource cache will have been cleared - * so that classes already instrumented will not be retained or - * returned. - * - * @return the transformer-free copy of this class loader. - */ - @Override - public WebappClassLoader copyWithoutTransformers() { - - WebappClassLoader result = new WebappClassLoader(getParent()); - - result.resources = this.resources; - result.delegate = this.delegate; - result.state = LifecycleState.NEW; - result.needConvert = this.needConvert; - result.clearReferencesStatic = this.clearReferencesStatic; - result.clearReferencesStopThreads = this.clearReferencesStopThreads; - result.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads; - result.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease; - result.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread; - result.jarModificationTimes.putAll(this.jarModificationTimes); - result.permissionList.addAll(this.permissionList); - result.loaderPC.putAll(this.loaderPC); - - try { - result.start(); - } catch (LifecycleException e) { - throw new IllegalStateException(e); - } - - return result; + protected void copyStateWithoutTransformers(WebappClassLoaderBase base) { + base.resources = this.resources; + base.delegate = this.delegate; + base.state = LifecycleState.NEW; + base.needConvert = this.needConvert; + base.clearReferencesStatic = this.clearReferencesStatic; + 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); } /** @@ -804,11 +778,10 @@ public class WebappClassLoader extends U @Override public String toString() { - StringBuilder sb = new StringBuilder("WebappClassLoader\r\n"); - sb.append(" context: "); + StringBuilder sb = new StringBuilder(this.getClass().getSimpleName()); + sb.append("\r\n context: "); sb.append(getContextName()); - sb.append("\r\n"); - sb.append(" delegate: "); + sb.append("\r\n delegate: "); sb.append(delegate); sb.append("\r\n"); if (this.parent != null) { @@ -823,7 +796,6 @@ public class WebappClassLoader extends U } } return (sb.toString()); - } @@ -1201,113 +1173,96 @@ public class WebappClassLoader extends U * @exception ClassNotFoundException if the class was not found */ @Override - public synchronized Class<?> loadClass(String name, boolean resolve) - throws ClassNotFoundException { - - if (log.isDebugEnabled()) - log.debug("loadClass(" + name + ", " + resolve + ")"); - Class<?> clazz = null; - - // Log access to stopped class loader - checkStateForClassLoading(name); + public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - // (0) Check our previously loaded local class cache - clazz = findLoadedClass0(name); - if (clazz != null) { + synchronized (getClassLoadingLock(name)) { if (log.isDebugEnabled()) - log.debug(" Returning class from cache"); - if (resolve) - resolveClass(clazz); - return (clazz); - } + log.debug("loadClass(" + name + ", " + resolve + ")"); + Class<?> clazz = null; - // (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); - } + // Log access to stopped class loader + checkStateForClassLoading(name); - // (0.2) Try loading the class with the system class loader, to prevent - // the webapp from overriding J2SE classes - String resourceName = binaryNameToPath(name, false); - ClassLoader javaseLoader = getJavaseClassLoader(); - if (javaseLoader.getResource(resourceName) != null) { - try { - clazz = javaseLoader.loadClass(name); - if (clazz != null) { - if (resolve) - resolveClass(clazz); - return (clazz); - } - } catch (ClassNotFoundException e) { - // Ignore + // (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.5) Permission to access this class when using a SecurityManager - if (securityManager != null) { - int i = name.lastIndexOf('.'); - if (i >= 0) { + // (0.1) Check our previously loaded class cache + clazz = findLoadedClass(name); + if (clazz != null) { + if (log.isDebugEnabled()) + log.debug(" Returning class from cache"); + if (resolve) + resolveClass(clazz); + return (clazz); + } + + // (0.2) Try loading the class with the system class loader, to prevent + // the webapp from overriding J2SE classes + String resourceName = binaryNameToPath(name, false); + ClassLoader javaseLoader = getJavaseClassLoader(); + if (javaseLoader.getResource(resourceName) != null) { try { - securityManager.checkPackageAccess(name.substring(0,i)); - } catch (SecurityException se) { - String error = "Security Violation, attempt to use " + - "Restricted Class: " + name; - log.info(error, se); - throw new ClassNotFoundException(error, se); + clazz = javaseLoader.loadClass(name); + if (clazz != null) { + if (resolve) + resolveClass(clazz); + return (clazz); + } + } catch (ClassNotFoundException e) { + // Ignore } } - } - boolean delegateLoad = delegate || filter(name); - - // (1) Delegate to our parent if requested - if (delegateLoad) { - if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader1 " + parent); - try { - clazz = Class.forName(name, false, parent); - if (clazz != null) { - if (log.isDebugEnabled()) - log.debug(" Loading class from parent"); - if (resolve) - resolveClass(clazz); - return (clazz); + // (0.5) Permission to access this class when using a SecurityManager + if (securityManager != null) { + int i = name.lastIndexOf('.'); + if (i >= 0) { + try { + securityManager.checkPackageAccess(name.substring(0,i)); + } catch (SecurityException se) { + String error = "Security Violation, attempt to use " + + "Restricted Class: " + name; + log.info(error, se); + throw new ClassNotFoundException(error, se); + } } - } catch (ClassNotFoundException e) { - // Ignore } - } - // (2) Search local repositories - if (log.isDebugEnabled()) - log.debug(" Searching local repositories"); - try { - clazz = findClass(name); - if (clazz != null) { + boolean delegateLoad = delegate || filter(name); + + // (1) Delegate to our parent if requested + if (delegateLoad) { if (log.isDebugEnabled()) - log.debug(" Loading class from local repository"); - if (resolve) - resolveClass(clazz); - return (clazz); + 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 + } } - } catch (ClassNotFoundException e) { - // Ignore - } - // (3) Delegate to parent unconditionally - if (!delegateLoad) { + // (2) Search local repositories if (log.isDebugEnabled()) - log.debug(" Delegating to parent classloader at end: " + parent); + log.debug(" Searching local repositories"); try { - clazz = Class.forName(name, false, parent); + clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) - log.debug(" Loading class from parent"); + log.debug(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); @@ -1315,6 +1270,24 @@ public class WebappClassLoader extends U } 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); @@ -1337,7 +1310,7 @@ public class WebappClassLoader extends U /** * Get the Permissions for a CodeSource. If this instance - * of WebappClassLoader is for a web application context, + * of WebappClassLoaderBase is for a web application context, * add read FilePermission or JndiPermissions for the base * directory (if unpacked), * the context URL, and jar file resources. @@ -1745,7 +1718,7 @@ public class WebappClassLoader extends U instance.getClass().getName() + " because the referenced object was of type " + valueClass.getName() + - " which was not loaded by this WebappClassLoader."); + " which was not loaded by this web application class loader."); } } else { field.set(instance, null); @@ -2181,7 +2154,7 @@ public class WebappClassLoader extends U } } catch (ConcurrentModificationException e) { log.warn(sm.getString( - "webappClassLoader", clazz.getName(), getContextName()), + "webappClassLoader.loadedByThisOrChildFail", clazz.getName(), getContextName()), e); } } @@ -2418,7 +2391,7 @@ public class WebappClassLoader extends U if (clazz != null) return clazz; - synchronized (this) { + synchronized (getClassLoadingLock(name)) { clazz = entry.loadedClass; if (clazz != null) return clazz;
Modified: tomcat/trunk/java/org/apache/catalina/loader/WebappLoader.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappLoader.java?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/loader/WebappLoader.java (original) +++ tomcat/trunk/java/org/apache/catalina/loader/WebappLoader.java Mon Sep 8 11:13:06 2014 @@ -92,7 +92,7 @@ public class WebappLoader extends Lifecy /** * The class loader being managed by this Loader component. */ - private WebappClassLoader classLoader = null; + private WebappClassLoaderBase classLoader = null; /** @@ -110,11 +110,10 @@ public class WebappLoader extends Lifecy /** * The Java class name of the ClassLoader implementation to be used. - * This class should extend WebappClassLoader, otherwise, a different + * This class should extend WebappClassLoaderBase, otherwise, a different * loader implementation must be used. */ - private String loaderClass = - "org.apache.catalina.loader.WebappClassLoader"; + private String loaderClass = WebappClassLoader.class.getName(); /** @@ -406,9 +405,9 @@ public class WebappLoader extends Lifecy if (!contextName.startsWith("/")) { contextName = "/" + contextName; } - ObjectName cloname = new ObjectName(context.getDomain() + - ":type=WebappClassLoader,host=" + context.getParent().getName() + - ",context=" + contextName); + ObjectName cloname = new ObjectName(context.getDomain() + ":type=" + + classLoader.getClass().getSimpleName() + ",host=" + + context.getParent().getName() + ",context=" + contextName); Registry.getRegistry(null, null) .registerComponent(classLoader, cloname, null); @@ -456,9 +455,9 @@ public class WebappLoader extends Lifecy if (!contextName.startsWith("/")) { contextName = "/" + contextName; } - ObjectName cloname = new ObjectName(context.getDomain() + - ":type=WebappClassLoader,host=" + context.getParent().getName() + - ",context=" + contextName); + ObjectName cloname = new ObjectName(context.getDomain() + ":type=" + + classLoader.getClass().getSimpleName() + ",host=" + + context.getParent().getName() + ",context=" + contextName); Registry.getRegistry(null, null).unregisterComponent(cloname); } catch (Exception e) { log.error("LifecycleException ", e); @@ -501,11 +500,11 @@ public class WebappLoader extends Lifecy /** * Create associated classLoader. */ - private WebappClassLoader createClassLoader() + private WebappClassLoaderBase createClassLoader() throws Exception { Class<?> clazz = Class.forName(loaderClass); - WebappClassLoader classLoader = null; + WebappClassLoaderBase classLoader = null; if (parentClassLoader == null) { parentClassLoader = context.getParentClassLoader(); @@ -513,7 +512,7 @@ public class WebappLoader extends Lifecy Class<?>[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor<?> constr = clazz.getConstructor(argTypes); - classLoader = (WebappClassLoader) constr.newInstance(args); + classLoader = (WebappClassLoaderBase) constr.newInstance(args); return classLoader; } Modified: tomcat/trunk/java/org/apache/catalina/loader/mbeans-descriptors.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/mbeans-descriptors.xml?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/loader/mbeans-descriptors.xml (original) +++ tomcat/trunk/java/org/apache/catalina/loader/mbeans-descriptors.xml Mon Sep 8 11:13:06 2014 @@ -90,4 +90,35 @@ </mbean> + + <mbean name="ParallelWebappClassLoader" + description="Classloader implementation which is specialized for handling web applications and is capable of loading classes in parallel" + domain="Catalina" + group="Loader" + type="org.apache.catalina.loader.ParallelWebappClassLoader"> + + <attribute name="className" + description="Fully qualified class name of the managed object" + type="java.lang.String" + writeable="false"/> + + <attribute name="contextName" + description="Name of the webapp context" + type="java.lang.String" + writeable="false"/> + + <attribute name="delegate" + description="The 'follow standard delegation model' flag that will be used to configure our ClassLoader" + type="boolean"/> + + <attribute name="stateName" + description="The name of the LifecycleState that this component is currently in" + type="java.lang.String" + writeable="false"/> + + <attribute name="URLs" + description="The URLs of this loader" + type="[Ljava.net.URL;"/> + + </mbean> </mbeans-descriptors> Modified: tomcat/trunk/java/org/apache/catalina/security/SecurityClassLoad.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/security/SecurityClassLoad.java?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/security/SecurityClassLoad.java (original) +++ tomcat/trunk/java/org/apache/catalina/security/SecurityClassLoad.java Mon Sep 8 11:13:06 2014 @@ -109,7 +109,7 @@ public final class SecurityClassLoad { final String basePackage = "org.apache.catalina.loader."; loader.loadClass (basePackage + - "WebappClassLoader$PrivilegedFindResourceByName"); + "WebappClassLoaderBase$PrivilegedFindResourceByName"); } Modified: tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java (original) +++ tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java Mon Sep 8 11:13:06 2014 @@ -23,14 +23,14 @@ import java.lang.instrument.ClassFileTra * {@link ClassFileTransformer}s. These transformers can instrument * (or weave) the byte code of classes loaded through this class loader * to alter their behavior. Currently only - * {@link org.apache.catalina.loader.WebappClassLoader} implements this + * {@link org.apache.catalina.loader.WebappClassLoaderBase} implements this * interface. This allows web application frameworks or JPA providers * bundled with a web application to instrument web application classes * as necessary. * <p> * You should always program against the methods of this interface * (whether using reflection or otherwise). The methods in - * {@code WebappClassLoader} are protected by the default security + * {@code WebappClassLoaderBase} are protected by the default security * manager if one is in use. * * @since 8.0, 7.0.44 Modified: tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderMemoryLeak.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderMemoryLeak.java?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderMemoryLeak.java (original) +++ tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderMemoryLeak.java Mon Sep 8 11:13:06 2014 @@ -77,7 +77,7 @@ public class TestWebappClassLoaderMemory /* * Get the set of current threads as an array. - * Copied from WebappClassLoader + * Copied from WebappClassLoaderBase */ private Thread[] getThreads() { // Get the current thread group Modified: tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java (original) +++ tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderThreadLocalMemoryLeak.java Mon Sep 8 11:13:06 2014 @@ -60,7 +60,7 @@ public class TestWebappClassLoaderThread LogValidationFilter f = new LogValidationFilter( "The web application [] created a ThreadLocal with key of"); LogManager.getLogManager().getLogger( - "org.apache.catalina.loader.WebappClassLoader").setFilter(f); + "org.apache.catalina.loader.WebappClassLoaderBase").setFilter(f); // Need to force loading of all web application classes via the web // application class loader @@ -114,7 +114,7 @@ public class TestWebappClassLoaderThread LogValidationFilter f = new LogValidationFilter( "The web application [] created a ThreadLocal with key of"); LogManager.getLogManager().getLogger( - "org.apache.catalina.loader.WebappClassLoader").setFilter(f); + "org.apache.catalina.loader.WebappClassLoaderBase").setFilter(f); // Need to force loading of all web application classes via the web // application class loader Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Mon Sep 8 11:13:06 2014 @@ -63,6 +63,10 @@ a cookie value. The new RFC6265 based cookie parser must be enabled to correctly handle these cookies. (markt) </fix> + <add> + <bug>56530</bug>: Add a web application class loader implementation that + supports the parallel loading of web application classes. (markt) + </add> <fix> <bug>56900</bug>: Fix some potential resource leaks when reading property files reported by Coverity Scan. Based on patches provided by Modified: tomcat/trunk/webapps/docs/config/context.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/context.xml?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/context.xml (original) +++ tomcat/trunk/webapps/docs/config/context.xml Mon Sep 8 11:13:06 2014 @@ -641,12 +641,11 @@ <p>If <code>true</code> and an <code>sun.net.www.http.HttpClient</code> keep-alive timer thread has been started by this web application and is still running, Tomcat will change the context class loader for that - thread from the current <code>WebappClassLoader</code> to - <code>WebappClassLoader#parent</code> 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. If not specified, the default value of - <code>true</code> will be used.</p> + thread from the web application class loader to the the parentof the web + application class loader 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. If + not specified, the default value of <code>true</code> will be used.</p> </attribute> <attribute name="clearReferencesStatic" required = "false"> Modified: tomcat/trunk/webapps/docs/config/loader.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/loader.xml?rev=1623360&r1=1623359&r2=1623360&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/loader.xml (original) +++ tomcat/trunk/webapps/docs/config/loader.xml Mon Sep 8 11:13:06 2014 @@ -125,7 +125,11 @@ implementation class to use. If not specified, the default value is <code>org.apache.catalina.loader.WebappClassLoader</code>. Custom <strong>loaderClass</strong> implementations must extend - <code>org.apache.catalina.loader.WebappClassLoader</code>.</p> + <code>org.apache.catalina.loader.WebappClassLoaderBase</code>. The + default <strong>loaderClass</strong> is not parallel capable. A parallel + capable <strong>loaderClass</strong> is available and can be used by + specifying + <code>org.apache.catalina.loader.ParallelWebappClassLoader</code>.</p> </attribute> <attribute name="searchExternalFirst" required="false"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org