ASF Bugzilla – Attachment 30838 Details for
Bug 55317
Facilitate weaving by allowing ClassFileTransformer to be added to WebppClassLoader
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Proposed implementation of this feature
instrumentable.patch (text/plain), 38.93 KB, created by
Nick Williams
on 2013-09-14 02:30:12 UTC
(
hide
)
Description:
Proposed implementation of this feature
Filename:
MIME Type:
Creator:
Nick Williams
Created:
2013-09-14 02:30:12 UTC
Size:
38.93 KB
patch
obsolete
>Index: java/org/apache/catalina/loader/LocalStrings.properties >=================================================================== >--- java/org/apache/catalina/loader/LocalStrings.properties (revision 1523171) >+++ java/org/apache/catalina/loader/LocalStrings.properties (working copy) >@@ -38,6 +38,11 @@ > 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} >Index: java/org/apache/catalina/loader/WebappClassLoader.java >=================================================================== >--- java/org/apache/catalina/loader/WebappClassLoader.java (revision 1523171) >+++ java/org/apache/catalina/loader/WebappClassLoader.java (working copy) >@@ -22,6 +22,8 @@ > 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.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; >@@ -65,6 +68,7 @@ > import org.apache.catalina.LifecycleState; > import org.apache.catalina.WebResource; > import org.apache.catalina.WebResourceRoot; >+import org.apache.tomcat.InstrumentableClassLoader; > import org.apache.tomcat.util.ExceptionUtils; > import org.apache.tomcat.util.IntrospectionUtils; > import org.apache.tomcat.util.res.StringManager; >@@ -104,15 +108,18 @@ > * <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 and 7.0.44, 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 ); >@@ -125,6 +132,8 @@ > > 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"); >@@ -461,6 +470,14 @@ > */ > 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 > >@@ -735,8 +752,109 @@ > > // ------------------------------------------------------- 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 classFileTransformer 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 classFileTransformer 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() 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. > * > * @param path Path of a source of classes to be loaded, such as a >@@ -924,6 +1042,12 @@ > 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()); > > } >@@ -1179,7 +1303,7 @@ > 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); >@@ -2546,7 +2670,7 @@ > throw new ClassNotFoundException(name); > > String tempPath = name.replace('.', '/'); >- String classPath = tempPath + ".class"; >+ String classPath = tempPath + CLASS_FILE_SUFFIX; > > ResourceEntry entry = null; > >@@ -2647,7 +2771,7 @@ > * > * @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)); >@@ -2663,7 +2787,7 @@ > > int contentLength = -1; > InputStream binaryStream = null; >- boolean isClassResource = path.endsWith(".class"); >+ boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX); > > int jarFilesLength = jarFiles.length; > >@@ -2751,7 +2875,7 @@ > } > > // 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()); >@@ -2762,7 +2886,7 @@ > JarEntry jarEntry2 = entries.nextElement(); > if (!(jarEntry2.isDirectory()) > && (!jarEntry2.getName().endsWith >- (".class"))) { >+ (CLASS_FILE_SUFFIX))) { > resourceFile = new File > (loaderDir, jarEntry2.getName()); > try { >@@ -2892,6 +3016,29 @@ > } > } > >+ if (isClassResource && entry != null && 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 >@@ -3095,7 +3242,7 @@ > } > 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); >Index: java/org/apache/tomcat/InstrumentableClassLoader.java >=================================================================== >--- java/org/apache/tomcat/InstrumentableClassLoader.java (revision 0) >+++ java/org/apache/tomcat/InstrumentableClassLoader.java (working copy) >@@ -0,0 +1,78 @@ >+/* >+ * 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() 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(); >+ >+} >Index: test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java >=================================================================== >--- test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java (revision 0) >+++ test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java (working copy) >@@ -0,0 +1,430 @@ >+/* >+ * 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.", this.loader.getAntiJARLocking(), >+ copiedLoader.getAntiJARLocking()); >+ assertEquals("getClearReferencesHttpClientKeepAliveThread did not match.", >+ this.loader.getClearReferencesHttpClientKeepAliveThread(), >+ copiedLoader.getClearReferencesHttpClientKeepAliveThread()); >+ assertEquals("getClearReferencesLogFactoryRelease did not match.", >+ this.loader.getClearReferencesLogFactoryRelease(), >+ copiedLoader.getClearReferencesLogFactoryRelease()); >+ assertEquals("getClearReferencesStatic did not match.", >+ this.loader.getClearReferencesStatic(), >+ copiedLoader.getClearReferencesStatic()); >+ assertEquals("getClearReferencesStopThreads did not match.", >+ this.loader.getClearReferencesStopThreads(), >+ copiedLoader.getClearReferencesStopThreads()); >+ assertEquals("getClearReferencesStopTimerThreads did not match.", >+ this.loader.getClearReferencesStopTimerThreads(), >+ copiedLoader.getClearReferencesStopTimerThreads()); >+ assertEquals("getContextName did not match.", this.loader.getContextName(), >+ copiedLoader.getContextName()); >+ assertEquals("getDelegate did not match.", this.loader.getDelegate(), >+ 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) throws IOException { >+ >+ 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 = Translator.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(" }"); >+ }*/ >+ >+} >Index: test/org/apache/catalina/loader/TesterNeverWeavedClass.java >=================================================================== >--- test/org/apache/catalina/loader/TesterNeverWeavedClass.java (revision 0) >+++ test/org/apache/catalina/loader/TesterNeverWeavedClass.java (working copy) >@@ -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."; >+ } >+} >Index: test/org/apache/catalina/loader/TesterUnweavedClass.java >=================================================================== >--- test/org/apache/catalina/loader/TesterUnweavedClass.java (revision 0) >+++ test/org/apache/catalina/loader/TesterUnweavedClass.java (working copy) >@@ -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!"; >+ } >+}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 55317
:
30678
|
30748
|
30749
|
30825
| 30838