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