/*
* 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.assertTrue;
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 WebappClassLoaderBase 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);
assertTrue("The class loader is not correct.", loader instanceof WebappClassLoaderBase);
this.loader = (WebappClassLoaderBase) 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);
WebappClassLoaderBase copiedLoader = (WebappClassLoaderBase) 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("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("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("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 {
ClassLoader cl = TestWebappClassLoaderWeaving.class.getClassLoader();
try (InputStream is = cl.getResourceAsStream(name)) {
if (is == null) {
throw new IOException("Resource " + name + " not found on classpath.");
}
try (FileOutputStream os = new FileOutputStream(file)) {
for (int b = is.read(); b >= 0; b = is.read()) {
os.write(b);
}
}
}
}
private static String invokeDoMethodOnClass(WebappClassLoaderBase 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 {
ClassLoader cl = TestWebappClassLoaderWeaving.class.getClassLoader();
try (InputStream input = cl.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[] {");
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());
}
System.out.println(" }");
}
}