Index: java/org/apache/catalina/loader/LocalStrings.properties =================================================================== --- java/org/apache/catalina/loader/LocalStrings.properties (revision 1516318) +++ 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 it 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 1516318) +++ 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; @@ -47,6 +49,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.ResourceBundle; @@ -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,6 +108,12 @@ * IMPLEMENTATION NOTE - No check for sealing violations or * security is made unless a security manager is present. *
+ * IMPLEMENTATION NOTE - As of 8.0 and 7.0.43, 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. + *
* TODO: Is there any requirement to provide a proper Lifecycle implementation
* rather than the current stubbed implementation?
*
@@ -111,10 +121,8 @@
* @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 );
@@ -127,6 +135,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");
@@ -462,7 +472,14 @@
*/
private boolean clearReferencesHttpClientKeepAliveThread = true;
+ /**
+ * Holds the class file transformers decorating this class loader. The
+ * LinkedList implementation has faster inserts and faster interator
+ * removals than an ArrayList.
+ */
+ private final LinkedList
+ * 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.repository = this.repository;
+ loader.file = this.file;
+ 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 repository Name of a source of classes to be loaded, such as a
@@ -928,6 +1056,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.getClass().getName()).append("\r\n");
+ }
+ }
return (sb.toString());
}
@@ -1183,7 +1317,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);
@@ -2552,7 +2686,7 @@
throw new ClassNotFoundException(name);
String tempPath = name.replace('.', '/');
- String classPath = tempPath + ".class";
+ String classPath = tempPath + CLASS_FILE_SUFFIX;
ResourceEntry entry = null;
@@ -2669,7 +2803,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));
@@ -2685,7 +2819,7 @@
int contentLength = -1;
InputStream binaryStream = null;
- boolean isClassResource = path.endsWith(".class");
+ boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
int jarFilesLength = jarFiles.length;
@@ -2782,7 +2916,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());
@@ -2793,7 +2927,7 @@
JarEntry jarEntry2 = entries.nextElement();
if (!(jarEntry2.isDirectory())
&& (!jarEntry2.getName().endsWith
- (".class"))) {
+ (CLASS_FILE_SUFFIX))) {
resourceFile = new File
(loaderDir, jarEntry2.getName());
try {
@@ -2923,6 +3057,31 @@
}
}
+ 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(".", "/");
+
+ synchronized (this.transformers) {
+ 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
@@ -3126,7 +3285,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.
+ *
+ * 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.43
+ */
+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.
+ *
+ * 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,413 @@
+/*
+ * 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/tomcat/unittest/weaving";
+
+ 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 + "/NeverWeavedClass.class",
+ new File(classes, "NeverWeavedClass.class"));
+ copyResource(PACKAGE_PREFIX + "/UnweavedClass.class",
+ new File(classes, "UnweavedClass.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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ 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, "NeverWeavedClass");
+ assertEquals("The first result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
+ assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
+
+ WebappClassLoader copiedLoader = this.loader.copyWithoutTransformers();
+
+ result = invokeDoMethodOnClass(copiedLoader, "NeverWeavedClass");
+ assertEquals("The third result is not correct.", "This will never be weaved.", result);
+
+ result = invokeDoMethodOnClass(copiedLoader, "UnweavedClass");
+ 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.tomcat.unittest.weaving." + 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 + "/UnweavedClass";
+
+ private final byte[] replacement;
+
+ ReplacementTransformer(byte[] replacement) throws IOException {
+
+ /*String resource = PACKAGE_PREFIX + "/" + replaceWithClass + ".class";
+ InputStream is = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(resource);
+ if (is == null) {
+ throw new IOException("Resource " + resource + " not found.");
+ }
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ try {
+ for (int b = is.read(); b >= 0; b = is.read()) {
+ os.write(b);
+ }
+ } finally {
+ is.close();
+ os.flush();
+ }
+
+ this.replacement = os.toByteArray();*/
+ 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.tomcat.unittest.weaving.UnweavedClass, 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, 18, 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, 48, 111, 114, 103, 47, 97, 112,
+ 97, 99, 104, 101, 47, 116, 111, 109, 99, 97, 116, 47, 117, 110, 105, 116, 116, 101,
+ 115, 116, 47, 119, 101, 97, 118, 105, 110, 103, 47, 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, 3, 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, 6, 0, 1, 0, 11, 0, 0, 0, 2,
+ 0, 12
+ };
+
+ /**
+ * Compiled version of org.apache.tomcat.unittest.weaving.UnweavedClass, 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, 18, 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, 48, 111, 114, 103, 47, 97, 112,
+ 97, 99, 104, 101, 47, 116, 111, 109, 99, 97, 116, 47, 117, 110, 105, 116, 116, 101,
+ 115, 116, 47, 119, 101, 97, 118, 105, 110, 103, 47, 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, 3, 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, 6, 0, 1, 0, 11, 0, 0, 0, 2,
+ 0, 12
+ };
+
+}
Index: test/org/apache/tomcat/unittest/weaving/NeverWeavedClass.java
===================================================================
--- test/org/apache/tomcat/unittest/weaving/NeverWeavedClass.java (revision 0)
+++ test/org/apache/tomcat/unittest/weaving/NeverWeavedClass.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.tomcat.unittest.weaving;
+
+public class NeverWeavedClass {
+
+ public String doMethod() {
+ return "This will never be weaved.";
+ }
+}
Index: test/org/apache/tomcat/unittest/weaving/UnweavedClass.java
===================================================================
--- test/org/apache/tomcat/unittest/weaving/UnweavedClass.java (revision 0)
+++ test/org/apache/tomcat/unittest/weaving/UnweavedClass.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.tomcat.unittest.weaving;
+
+public class UnweavedClass {
+
+ public String doMethod() {
+ return "Hello, Unweaved World!";
+ }
+}