You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2013/09/27 00:55:22 UTC

svn commit: r1526730 - in /tomcat/trunk: java/org/apache/catalina/loader/ java/org/apache/tomcat/ test/org/apache/catalina/loader/ webapps/docs/

Author: markt
Date: Thu Sep 26 22:55:22 2013
New Revision: 1526730

URL: http://svn.apache.org/r1526730
Log:
Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=55317
Facilitate weaving by allowing ClassFileTransformer to be added to WebppClassLoader
Patch be Nick Williams

Added:
    tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java   (with props)
    tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java   (with props)
    tomcat/trunk/test/org/apache/catalina/loader/TesterNeverWeavedClass.java   (with props)
    tomcat/trunk/test/org/apache/catalina/loader/TesterUnweavedClass.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties
    tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties?rev=1526730&r1=1526729&r2=1526730&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/catalina/loader/LocalStrings.properties Thu Sep 26 22:55:22 2013
@@ -38,6 +38,11 @@ webappClassLoader.warnRequestThread=The 
 webappClassLoader.warnThread=The web application [{0}] appears to have started a thread named [{1}] but has failed to stop it. This is very likely to create a memory leak.
 webappClassLoader.warnTimerThread=The web application [{0}] appears to have started a TimerThread named [{1}] via the java.util.Timer API but has failed to stop it. To prevent a memory leak, the timer (and hence the associated thread) has been forcibly canceled.
 webappClassLoader.wrongVersion=(unable to load class {0})
+webappClassLoader.addTransformer.illegalArgument=The web application [{0}] attempted to add a null class file transformer.
+webappClassLoader.addTransformer.duplicate=Duplicate call to add class file transformer [{0}] to web application [{1}] ignored.
+webappClassLoader.addTransformer=Added class file transformer [{0}] to web application [{1}].
+webappClassLoader.removeTransformer=Removed class file transformer [{0}] from web application [{1}].
+webappClassLoader.transformError=Instrumentation error: could not transform class [{0}] because its class file format is not legal.
 webappLoader.addRepository=Adding repository {0}
 webappLoader.deploy=Deploying class repositories to work directory {0}
 webappLoader.jarDeploy=Deploy JAR {0} to {1}

Modified: tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java?rev=1526730&r1=1526729&r2=1526730&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java (original)
+++ tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java Thu Sep 26 22:55:22 2013
@@ -22,6 +22,8 @@ import java.io.FileOutputStream;
 import java.io.FilePermission;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
@@ -51,6 +53,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.jar.Attributes;
 import java.util.jar.Attributes.Name;
@@ -66,6 +69,7 @@ import org.apache.catalina.LifecycleStat
 import org.apache.catalina.WebResource;
 import org.apache.catalina.WebResourceRoot;
 import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
+import org.apache.tomcat.InstrumentableClassLoader;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.IntrospectionUtils;
 import org.apache.tomcat.util.res.StringManager;
@@ -105,15 +109,18 @@ import org.apache.tomcat.util.res.String
  * <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
  * @version $Id$
  */
-public class WebappClassLoader
-    extends URLClassLoader
-    implements Lifecycle
- {
+public class WebappClassLoader extends URLClassLoader
+        implements Lifecycle, InstrumentableClassLoader {
 
     private static final org.apache.juli.logging.Log log=
         org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
@@ -126,6 +133,8 @@ public class WebappClassLoader
 
     private static final String JVN_THREAD_GROUP_SYSTEM = "system";
 
+    private static final String CLASS_FILE_SUFFIX = ".class";
+
     static {
         JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
         JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
@@ -462,6 +471,14 @@ public class WebappClassLoader
      */
     private boolean clearReferencesHttpClientKeepAliveThread = true;
 
+    /**
+     * Holds the class file transformers decorating this class loader. The
+     * CopyOnWriteArrayList is thread safe. It is expensive on writes, but
+     * those should be rare. It is very fast on reads, since synchronization
+     * is not actually used. Importantly, the ClassLoader will never block
+     * iterating over the transformers while loading a class.
+     */
+    private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<>();
 
     // ------------------------------------------------------------- Properties
 
@@ -736,6 +753,108 @@ public class WebappClassLoader
 
     // ------------------------------------------------------- Reloader Methods
 
+    /**
+     * Adds the specified class file transformer to this class loader. The
+     * transformer will then be able to modify the bytecode of any classes
+     * loaded by this class loader after the invocation of this method.
+     *
+     * @param transformer The transformer to add to the class loader
+     */
+    @Override
+    public void addTransformer(ClassFileTransformer transformer) {
+
+        if (transformer == null) {
+            throw new IllegalArgumentException(sm.getString(
+                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
+        }
+
+        if (this.transformers.contains(transformer)) {
+            // if the same instance of this transformer was already added, bail out
+            log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
+                    transformer, getContextName()));
+            return;
+        }
+        this.transformers.add(transformer);
+
+        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
+
+    }
+
+    /**
+     * Removes the specified class file transformer from this class loader.
+     * It will no longer be able to modify the byte code of any classes
+     * loaded by the class loader after the invocation of this method.
+     * However, any classes already modified by this transformer will
+     * remain transformed.
+     *
+     * @param transformer The transformer to remove
+     */
+    @Override
+    public void removeTransformer(ClassFileTransformer transformer) {
+
+        if (transformer == null) {
+            return;
+        }
+
+        if (this.transformers.remove(transformer)) {
+            log.info(sm.getString("webappClassLoader.removeTransformer",
+                    transformer, getContextName()));
+            return;
+        }
+
+    }
+
+    /**
+     * Returns a copy of this class loader without any class file
+     * transformers. This is a tool often used by Java Persistence API
+     * providers to inspect entity classes in the absence of any
+     * instrumentation, something that can't be guaranteed within the
+     * context of a {@link ClassFileTransformer}'s
+     * {@link ClassFileTransformer#transform(ClassLoader, String, Class,
+     * ProtectionDomain, byte[]) transform} method.
+     * <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 loader = new WebappClassLoader(this.parent);
+
+        loader.antiJARLocking = this.antiJARLocking;
+        loader.resources = this.resources;
+        loader.delegate = this.delegate;
+        loader.lastJarAccessed = this.lastJarAccessed;
+        loader.repositoryPath = this.repositoryPath;
+        loader.repository = this.repository;
+        loader.jarPath = this.jarPath;
+        loader.loaderDir = this.loaderDir;
+        loader.canonicalLoaderDir = this.canonicalLoaderDir;
+        loader.started = this.started;
+        loader.needConvert = this.needConvert;
+        loader.clearReferencesStatic = this.clearReferencesStatic;
+        loader.clearReferencesStopThreads = this.clearReferencesStopThreads;
+        loader.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
+        loader.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
+        loader.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
+
+        loader.repositoryURLs = this.repositoryURLs.clone();
+        loader.jarFiles = this.jarFiles.clone();
+        loader.jarRealFiles = this.jarRealFiles.clone();
+        loader.jarNames = this.jarNames.clone();
+        loader.lastModifiedDates = this.lastModifiedDates.clone();
+        loader.paths = this.paths.clone();
+
+        loader.notFoundResources.putAll(this.notFoundResources);
+        loader.permissionList.addAll(this.permissionList);
+        loader.loaderPC.putAll(this.loaderPC);
+
+        return loader;
+
+    }
 
     /**
      * Set the place this ClassLoader can look for classes to be loaded.
@@ -925,6 +1044,12 @@ public class WebappClassLoader
             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());
 
     }
@@ -1180,7 +1305,7 @@ public class WebappClassLoader
                 try {
                     String repository = entry.codeBase.toString();
                     if ((repository.endsWith(".jar"))
-                            && (!(name.endsWith(".class")))) {
+                            && (!(name.endsWith(CLASS_FILE_SUFFIX)))) {
                         // Copy binary content to the work directory if not present
                         File resourceFile = new File(loaderDir, name);
                         url = getURI(resourceFile);
@@ -2549,7 +2674,7 @@ public class WebappClassLoader
             throw new ClassNotFoundException(name);
 
         String tempPath = name.replace('.', '/');
-        String classPath = tempPath + ".class";
+        String classPath = tempPath + CLASS_FILE_SUFFIX;
 
         ResourceEntry entry = null;
 
@@ -2650,7 +2775,7 @@ public class WebappClassLoader
      *
      * @return the loaded resource, or null if the resource isn't found
      */
-    protected ResourceEntry findResourceInternal(String name, String path) {
+    protected ResourceEntry findResourceInternal(final String name, final String path) {
 
         if (!started) {
             log.info(sm.getString("webappClassLoader.stopped", name));
@@ -2666,7 +2791,7 @@ public class WebappClassLoader
 
         int contentLength = -1;
         InputStream binaryStream = null;
-        boolean isClassResource = path.endsWith(".class");
+        boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
 
         int jarFilesLength = jarFiles.length;
 
@@ -2754,7 +2879,7 @@ public class WebappClassLoader
                         }
 
                         // Extract resources contained in JAR to the workdir
-                        if (antiJARLocking && !(path.endsWith(".class"))) {
+                        if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
                             byte[] buf = new byte[1024];
                             File resourceFile = new File
                                 (loaderDir, jarEntry.getName());
@@ -2765,7 +2890,7 @@ public class WebappClassLoader
                                     JarEntry jarEntry2 =  entries.nextElement();
                                     if (!(jarEntry2.isDirectory())
                                         && (!jarEntry2.getName().endsWith
-                                            (".class"))) {
+                                            (CLASS_FILE_SUFFIX))) {
                                         resourceFile = new File
                                             (loaderDir, jarEntry2.getName());
                                         try {
@@ -2895,6 +3020,29 @@ public class WebappClassLoader
             }
         }
 
+        if (isClassResource && entry.binaryContent != null &&
+                this.transformers.size() > 0) {
+            // If the resource is a class just being loaded, decorate it
+            // with any attached transformers
+            String className = name.endsWith(CLASS_FILE_SUFFIX) ?
+                    name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
+            String internalName = className.replace(".", "/");
+
+            for (ClassFileTransformer transformer : this.transformers) {
+                try {
+                    byte[] transformed = transformer.transform(
+                            this, internalName, null, null, entry.binaryContent
+                    );
+                    if (transformed != null) {
+                        entry.binaryContent = transformed;
+                    }
+                } catch (IllegalClassFormatException e) {
+                    log.error(sm.getString("webappClassLoader.transformError", name), e);
+                    return null;
+                }
+            }
+        }
+
         // Add the entry in the local resource repository
         synchronized (resourceEntries) {
             // Ensures that all the threads which may be in a race to load
@@ -3098,7 +3246,7 @@ public class WebappClassLoader
                 }
                 if (clazz == null)
                     continue;
-                String name = triggers[i].replace('.', '/') + ".class";
+                String name = triggers[i].replace('.', '/') + CLASS_FILE_SUFFIX;
                 if (log.isDebugEnabled())
                     log.debug(" Checking for " + name);
                 JarEntry jarEntry = jarFile.getJarEntry(name);

Added: tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java?rev=1526730&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java Thu Sep 26 22:55:22 2013
@@ -0,0 +1,79 @@
+/*
+ *  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;
+
+import java.lang.instrument.ClassFileTransformer;
+
+/**
+ * Specifies a class loader capable of being decorated with
+ * {@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
+ * 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
+ * manager if one is in use.
+ *
+ * @since 8.0, 7.0.44
+ */
+public interface InstrumentableClassLoader {
+
+    /**
+     * Adds the specified class file transformer to this class loader. The
+     * transformer will then be able to instrument the bytecode of any
+     * classes loaded by this class loader after the invocation of this
+     * method.
+     *
+     * @param classFileTransformer The transformer to add to the class loader
+     * @throws IllegalArgumentException if the {@literal transformer} is null.
+     */
+    void addTransformer(ClassFileTransformer transformer);
+
+    /**
+     * Removes the specified class file transformer from this class loader.
+     * It will no longer be able to instrument the byte code of any classes
+     * loaded by the class loader after the invocation of this method.
+     * However, any classes already instrumented by this transformer before
+     * this method call will remain in their instramented state.
+     *
+     * @param classFileTransformer The transformer to remove
+     */
+    void removeTransformer(ClassFileTransformer transformer);
+
+    /**
+     * 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,
+     * java.security.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.
+     */
+    ClassLoader copyWithoutTransformers();
+
+}

Propchange: tomcat/trunk/java/org/apache/tomcat/InstrumentableClassLoader.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java?rev=1526730&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java (added)
+++ tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java Thu Sep 26 22:55:22 2013
@@ -0,0 +1,429 @@
+/*
+ * 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.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.reflect.Method;
+import java.security.ProtectionDomain;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.http.fileupload.FileUtils;
+
+public class TestWebappClassLoaderWeaving extends TomcatBaseTest {
+
+    private static final String PACKAGE_PREFIX = "org/apache/catalina/loader";
+
+    private static String WEBAPP_DOC_BASE;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+
+        WEBAPP_DOC_BASE = System.getProperty("java.io.tmpdir") + "/TestWebappClassLoaderWeaving";
+        File classes = new File(WEBAPP_DOC_BASE + "/WEB-INF/classes/" + PACKAGE_PREFIX);
+        classes.mkdirs();
+
+        copyResource(PACKAGE_PREFIX + "/TesterNeverWeavedClass.class",
+                new File(classes, "TesterNeverWeavedClass.class"));
+        copyResource(PACKAGE_PREFIX + "/TesterUnweavedClass.class",
+                new File(classes, "TesterUnweavedClass.class"));
+
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+
+        FileUtils.deleteDirectory(new File(WEBAPP_DOC_BASE));
+
+    }
+
+    private Tomcat tomcat;
+    private Context context;
+    private WebappClassLoader loader;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+
+        super.setUp();
+
+        this.tomcat = getTomcatInstance();
+        this.context = this.tomcat.addContext("/weaving", WEBAPP_DOC_BASE);
+        this.tomcat.start();
+
+        ClassLoader loader = this.context.getLoader().getClassLoader();
+        assertNotNull("The class loader should not be null.", loader);
+        assertSame("The class loader is not correct.", WebappClassLoader.class, loader.getClass());
+
+        this.loader = (WebappClassLoader) loader;
+
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+
+        try {
+            this.loader = null;
+
+            this.context.stop();
+            this.tomcat.getHost().removeChild(this.context);
+            this.context = null;
+        } finally {
+            super.tearDown();
+        }
+
+    }
+
+    @Test
+    public void testNoWeaving() throws Exception {
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Unweaved World!", result);
+
+    }
+
+    @Test
+    public void testAddingNullTransformerThrowsException() throws Exception {
+
+        try {
+            this.loader.addTransformer(null);
+            fail("Expected exception IllegalArgumentException, got no exception.");
+        } catch (IllegalArgumentException ignore) {
+            // good
+        }
+
+        // class loading should still work, no weaving
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Unweaved World!", result);
+
+    }
+
+    @Test
+    public void testAddedTransformerInstrumentsClass1() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #1!", result);
+
+    }
+
+    @Test
+    public void testAddedTransformerInstrumentsClass2() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
+
+    }
+
+    @Test
+    public void testTransformersExecuteInOrderAdded1() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
+
+    }
+
+    @Test
+    public void testTransformersExecuteInOrderAdded2() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #1!", result);
+
+    }
+
+    @Test
+    public void testRemovedTransformerNoLongerInstruments1() throws Exception {
+
+        ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_1);
+        this.loader.addTransformer(removed);
+        this.loader.removeTransformer(removed);
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Unweaved World!", result);
+
+    }
+
+    @Test
+    public void testRemovedTransformerNoLongerInstruments2() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
+
+        ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_2);
+        this.loader.addTransformer(removed);
+        this.loader.removeTransformer(removed);
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #1!", result);
+
+    }
+
+    @Test
+    public void testRemovedTransformerNoLongerInstruments3() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
+
+        ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_1);
+        this.loader.addTransformer(removed);
+        this.loader.removeTransformer(removed);
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
+
+    }
+
+    @Test
+    public void testCopiedClassLoaderExcludesResourcesAndTransformers() throws Exception {
+
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
+        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
+
+        String result = invokeDoMethodOnClass(this.loader, "TesterNeverWeavedClass");
+        assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
+        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
+
+        WebappClassLoader copiedLoader = this.loader.copyWithoutTransformers();
+
+        result = invokeDoMethodOnClass(copiedLoader, "TesterNeverWeavedClass");
+        assertEquals("The third result is not correct.", "This will never be weaved.", result);
+
+        result = invokeDoMethodOnClass(copiedLoader, "TesterUnweavedClass");
+        assertEquals("The fourth result is not correct.", "Hello, Unweaved World!", result);
+
+        assertEquals("getAntiJARLocking did not match.",
+                Boolean.valueOf(this.loader.getAntiJARLocking()),
+                Boolean.valueOf(copiedLoader.getAntiJARLocking()));
+        assertEquals("getClearReferencesHttpClientKeepAliveThread did not match.",
+                Boolean.valueOf(this.loader.getClearReferencesHttpClientKeepAliveThread()),
+                Boolean.valueOf(copiedLoader.getClearReferencesHttpClientKeepAliveThread()));
+        assertEquals("getClearReferencesLogFactoryRelease did not match.",
+                Boolean.valueOf(this.loader.getClearReferencesLogFactoryRelease()),
+                Boolean.valueOf(copiedLoader.getClearReferencesLogFactoryRelease()));
+        assertEquals("getClearReferencesStatic did not match.",
+                Boolean.valueOf(this.loader.getClearReferencesStatic()),
+                Boolean.valueOf(copiedLoader.getClearReferencesStatic()));
+        assertEquals("getClearReferencesStopThreads did not match.",
+                Boolean.valueOf(this.loader.getClearReferencesStopThreads()),
+                Boolean.valueOf(copiedLoader.getClearReferencesStopThreads()));
+        assertEquals("getClearReferencesStopTimerThreads did not match.",
+                Boolean.valueOf(this.loader.getClearReferencesStopTimerThreads()),
+                Boolean.valueOf(copiedLoader.getClearReferencesStopTimerThreads()));
+        assertEquals("getContextName did not match.", this.loader.getContextName(),
+                copiedLoader.getContextName());
+        assertEquals("getDelegate did not match.",
+                Boolean.valueOf(this.loader.getDelegate()),
+                Boolean.valueOf(copiedLoader.getDelegate()));
+        assertEquals("getJarPath did not match.", this.loader.getJarPath(),
+                copiedLoader.getJarPath());
+        assertEquals("getURLs did not match.", this.loader.getURLs().length,
+                copiedLoader.getURLs().length);
+        assertSame("getParent did not match.", this.loader.getParent(), copiedLoader.getParent());
+
+    }
+
+    private static void copyResource(String name, File file) throws Exception {
+
+        InputStream is = TestWebappClassLoaderWeaving.class.getClassLoader()
+                .getResourceAsStream(name);
+        if (is == null) {
+            throw new IOException("Resource " + name + " not found on classpath.");
+        }
+
+        FileOutputStream os = new FileOutputStream(file);
+        try {
+            for (int b = is.read(); b >= 0; b = is.read()) {
+                os.write(b);
+            }
+        } finally {
+            is.close();
+            os.close();
+        }
+
+    }
+
+    private static String invokeDoMethodOnClass(WebappClassLoader loader, String className)
+            throws Exception {
+
+        Class<?> c = loader.findClass("org.apache.catalina.loader." + className);
+        assertNotNull("The loaded class should not be null.", c);
+
+        Method m = c.getMethod("doMethod");
+
+        Object o = c.newInstance();
+        return (String) m.invoke(o);
+
+    }
+
+    private static class ReplacementTransformer implements ClassFileTransformer {
+
+        private static final String CLASS_TO_WEAVE = PACKAGE_PREFIX + "/TesterUnweavedClass";
+
+        private final byte[] replacement;
+
+        ReplacementTransformer(byte[] replacement) {
+            this.replacement = replacement;
+        }
+
+        @Override
+        public byte[] transform(ClassLoader loader, String className, Class<?> x,
+                                ProtectionDomain y, byte[] b) {
+
+            if (CLASS_TO_WEAVE.equals(className)) {
+                return this.replacement;
+            }
+
+            return null;
+
+        }
+
+    }
+
+    /**
+     * Compiled version of org.apache.catalina.loader.TesterUnweavedClass, except that
+     * the doMethod method returns "Hello, Weaver #1!". Compiled with Oracle Java 1.6.0_51.
+     */
+    private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] {
+            -54, -2, -70, -66, 0, 0, 0, 50, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1,
+            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0,
+            15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 100,
+            111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97,
+            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70,
+            105, 108, 101, 1, 0, 24, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, 101,
+            100, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101,
+            108, 108, 111, 44, 32, 87, 101, 97, 118, 101, 114, 32, 35, 49, 33, 1, 0, 46, 111, 114,
+            103, 47, 97, 112, 97, 99, 104, 101, 47, 99, 97, 116, 97, 108, 105, 110, 97, 47, 108,
+            111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118,
+            101, 100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
+            79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1,
+            0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0,
+            0, 6, 0, 1, 0, 0, 0, 19, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0,
+            0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 22, 0, 1, 0, 11, 0, 0, 0,
+            2, 0, 12
+    };
+
+    /**
+     * Compiled version of org.apache.catalina.loader.TesterUnweavedClass, except that
+     * the doMethod method returns "Hello, Weaver #2!". Compiled with Oracle Java 1.6.0_51.
+     */
+    private static final byte[] WEAVED_REPLACEMENT_2 = new byte[] {
+            -54, -2, -70, -66, 0, 0, 0, 50, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1,
+            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0,
+            15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 100,
+            111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97,
+            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70,
+            105, 108, 101, 1, 0, 24, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, 101,
+            100, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101,
+            108, 108, 111, 44, 32, 87, 101, 97, 118, 101, 114, 32, 35, 50, 33, 1, 0, 46, 111, 114,
+            103, 47, 97, 112, 97, 99, 104, 101, 47, 99, 97, 116, 97, 108, 105, 110, 97, 47, 108,
+            111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118,
+            101, 100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
+            79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1,
+            0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0,
+            0, 6, 0, 1, 0, 0, 0, 19, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0,
+            0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 22, 0, 1, 0, 11, 0, 0, 0,
+            2, 0, 12
+    };
+
+    /*
+     * The WEAVED_REPLACEMENT_1 and WEAVED_REPLACEMENT_2 field contents are generated using the
+     * following code. To regenerate them, alter the TesterUnweavedClass code as desired, recompile,
+     * and run this main method.
+     */
+    public static void main(String... arguments) throws Exception {
+        InputStream input = TestWebappClassLoaderWeaving.class.getClassLoader()
+                .getResourceAsStream("org/apache/catalina/loader/TesterUnweavedClass.class");
+
+        StringBuilder builder = new StringBuilder();
+        builder.append("            ");
+
+        System.out.println("    private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] {");
+        try {
+            for (int i = 0, b = input.read(); b >= 0; i++, b = input.read()) {
+                String value = "" + ((byte)b);
+                if (builder.length() + value.length() > 97) {
+                    builder.append(",");
+                    System.out.println(builder.toString());
+                    builder = new StringBuilder();
+                    builder.append("            ").append(value);
+                } else {
+                    if (i > 0) {
+                        builder.append(", ");
+                    }
+                    builder.append(value);
+                }
+            }
+            System.out.println(builder.toString());
+        } finally {
+            input.close();
+        }
+        System.out.println("    }");
+    }
+}

Propchange: tomcat/trunk/test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/test/org/apache/catalina/loader/TesterNeverWeavedClass.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/loader/TesterNeverWeavedClass.java?rev=1526730&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/loader/TesterNeverWeavedClass.java (added)
+++ tomcat/trunk/test/org/apache/catalina/loader/TesterNeverWeavedClass.java Thu Sep 26 22:55:22 2013
@@ -0,0 +1,24 @@
+/*
+ *  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;
+
+public class TesterNeverWeavedClass {
+
+    public String doMethod() {
+        return "This will never be weaved.";
+    }
+}

Propchange: tomcat/trunk/test/org/apache/catalina/loader/TesterNeverWeavedClass.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/test/org/apache/catalina/loader/TesterUnweavedClass.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/loader/TesterUnweavedClass.java?rev=1526730&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/loader/TesterUnweavedClass.java (added)
+++ tomcat/trunk/test/org/apache/catalina/loader/TesterUnweavedClass.java Thu Sep 26 22:55:22 2013
@@ -0,0 +1,24 @@
+/*
+ *  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;
+
+public class TesterUnweavedClass {
+
+    public String doMethod() {
+        return "Hello, Unweaved World!";
+    }
+}

Propchange: tomcat/trunk/test/org/apache/catalina/loader/TesterUnweavedClass.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1526730&r1=1526729&r2=1526730&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Thu Sep 26 22:55:22 2013
@@ -169,6 +169,10 @@
         provided by Jasper. Includes removal of TldConfig lifecycle listener and
         associated Context properties. (jboynes)
       </scode>
+      <add>
+        <bug>55317</bug>: Facilitate weaving by allowing ClassFileTransformer to
+        be added to WebppClassLoader. Patch by Nick Williams. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org