View | Details | Raw Unified | Return to bug 55317
Collapse All | Expand All

(-)java/org/apache/catalina/loader/LocalStrings.properties (+5 lines)
Lines 38-43 Link Here
38
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.
38
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.
39
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.
39
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.
40
webappClassLoader.wrongVersion=(unable to load class {0})
40
webappClassLoader.wrongVersion=(unable to load class {0})
41
webappClassLoader.addTransformer.illegalArgument=The web application {0} attempted to add a null class file transformer.
42
webappClassLoader.addTransformer.duplicate=Duplicate call to add class file transformer {0} to web application {1} ignored.
43
webappClassLoader.addTransformer=Added class file transformer {0} to web application {1}.
44
webappClassLoader.removeTransformer=Removed class file transformer {0} from web application {1}.
45
webappClassLoader.transformError=Instrumentation error: could not transform class {0} because it its class file format is not legal.
41
webappLoader.addRepository=Adding repository {0}
46
webappLoader.addRepository=Adding repository {0}
42
webappLoader.deploy=Deploying class repositories to work directory {0}
47
webappLoader.deploy=Deploying class repositories to work directory {0}
43
webappLoader.jarDeploy=Deploy JAR {0} to {1}
48
webappLoader.jarDeploy=Deploy JAR {0} to {1}
(-)java/org/apache/catalina/loader/WebappClassLoader.java (-11 / +170 lines)
Lines 22-27 Link Here
22
import java.io.FilePermission;
22
import java.io.FilePermission;
23
import java.io.IOException;
23
import java.io.IOException;
24
import java.io.InputStream;
24
import java.io.InputStream;
25
import java.lang.instrument.ClassFileTransformer;
26
import java.lang.instrument.IllegalClassFormatException;
25
import java.lang.ref.Reference;
27
import java.lang.ref.Reference;
26
import java.lang.ref.WeakReference;
28
import java.lang.ref.WeakReference;
27
import java.lang.reflect.Field;
29
import java.lang.reflect.Field;
Lines 47-52 Link Here
47
import java.util.Iterator;
49
import java.util.Iterator;
48
import java.util.LinkedHashMap;
50
import java.util.LinkedHashMap;
49
import java.util.LinkedHashSet;
51
import java.util.LinkedHashSet;
52
import java.util.LinkedList;
50
import java.util.List;
53
import java.util.List;
51
import java.util.Map;
54
import java.util.Map;
52
import java.util.ResourceBundle;
55
import java.util.ResourceBundle;
Lines 65-70 Link Here
65
import org.apache.catalina.LifecycleState;
68
import org.apache.catalina.LifecycleState;
66
import org.apache.catalina.WebResource;
69
import org.apache.catalina.WebResource;
67
import org.apache.catalina.WebResourceRoot;
70
import org.apache.catalina.WebResourceRoot;
71
import org.apache.tomcat.InstrumentableClassLoader;
68
import org.apache.tomcat.util.ExceptionUtils;
72
import org.apache.tomcat.util.ExceptionUtils;
69
import org.apache.tomcat.util.IntrospectionUtils;
73
import org.apache.tomcat.util.IntrospectionUtils;
70
import org.apache.tomcat.util.res.StringManager;
74
import org.apache.tomcat.util.res.StringManager;
Lines 104-109 Link Here
104
 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
108
 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
105
 * security is made unless a security manager is present.
109
 * security is made unless a security manager is present.
106
 * <p>
110
 * <p>
111
 * <strong>IMPLEMENTATION NOTE</strong> - As of 8.0 and 7.0.43, this class
112
 * loader implements {@link InstrumentableClassLoader}, permitting web
113
 * application classes to instrument other classes in the same web
114
 * application. It does not permit instrumentation of system or container
115
 * classes or classes in other web apps.
116
 * <p>
107
 * TODO: Is there any requirement to provide a proper Lifecycle implementation
117
 * TODO: Is there any requirement to provide a proper Lifecycle implementation
108
 *       rather than the current stubbed implementation?
118
 *       rather than the current stubbed implementation?
109
 *
119
 *
Lines 111-120 Link Here
111
 * @author Craig R. McClanahan
121
 * @author Craig R. McClanahan
112
 * @version $Id$
122
 * @version $Id$
113
 */
123
 */
114
public class WebappClassLoader
124
public class WebappClassLoader extends URLClassLoader
115
    extends URLClassLoader
125
        implements Lifecycle, InstrumentableClassLoader {
116
    implements Lifecycle
117
 {
118
126
119
    private static final org.apache.juli.logging.Log log=
127
    private static final org.apache.juli.logging.Log log=
120
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
128
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
Lines 127-132 Link Here
127
135
128
    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
136
    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
129
137
138
    private static final String CLASS_FILE_SUFFIX = ".class";
139
130
    static {
140
    static {
131
        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
141
        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
132
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
142
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
Lines 462-468 Link Here
462
     */
472
     */
463
    private boolean clearReferencesHttpClientKeepAliveThread = true;
473
    private boolean clearReferencesHttpClientKeepAliveThread = true;
464
474
475
    /**
476
     * Holds the class file transformers decorating this class loader. The
477
     * LinkedList implementation has faster inserts and faster interator
478
     * removals than an ArrayList.
479
     */
480
    private final LinkedList<ClassFileTransformer> transformers = new LinkedList<>();
465
481
482
466
    // ------------------------------------------------------------- Properties
483
    // ------------------------------------------------------------- Properties
467
484
468
    /**
485
    /**
Lines 736-743 Link Here
736
753
737
    // ------------------------------------------------------- Reloader Methods
754
    // ------------------------------------------------------- Reloader Methods
738
755
756
    /**
757
     * Adds the specified class file transformer to this class loader. The
758
     * transformer will then be able to modify the bytecode of any classes
759
     * loaded by this class loader after the invocation of this method.
760
     *
761
     * @param classFileTransformer The transformer to add to the class loader
762
     */
763
    @Override
764
    public void addTransformer(ClassFileTransformer transformer) {
739
765
766
        if (transformer == null) {
767
            throw new IllegalArgumentException(sm.getString(
768
                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
769
        }
770
771
        synchronized (this.transformers) {
772
            for (ClassFileTransformer t : this.transformers) {
773
                // if the same instance of this transformer was already added, bail out
774
                if (t == transformer) {
775
                    log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
776
                            transformer, getContextName()));
777
                    return;
778
                }
779
            }
780
            this.transformers.add(transformer);
781
        }
782
783
        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
784
785
    }
786
740
    /**
787
    /**
788
     * Removes the specified class file transformer from this class loader.
789
     * It will no longer be able to modify the byte code of any classes
790
     * loaded by the class loader after the invocation of this method.
791
     * However, any classes already modified by this transformer will
792
     * remain transformed.
793
     *
794
     * @param classFileTransformer The transformer to remove
795
     */
796
    @Override
797
    public void removeTransformer(ClassFileTransformer transformer) {
798
799
        if (transformer == null) {
800
            return;
801
        }
802
803
        synchronized (this.transformers) {
804
            for (Iterator<ClassFileTransformer> iterator = this.transformers.iterator();
805
                 iterator.hasNext();) {
806
                if (iterator.next() == transformer) {
807
                    iterator.remove();
808
                    log.info(sm.getString("webappClassLoader.removeTransformer",
809
                            transformer, getContextName()));
810
                    return;
811
                }
812
            }
813
        }
814
815
    }
816
817
    /**
818
     * Returns a copy of this class loader without any class file
819
     * transformers. This is a tool often used by Java Persistence API
820
     * providers to inspect entity classes in the absence of any
821
     * instrumentation, something that can't be guaranteed within the
822
     * context of a {@link ClassFileTransformer}'s
823
     * {@link ClassFileTransformer#transform() transform} method.
824
     * <p>
825
     * The returned class loader's resource cache will have been cleared
826
     * so that classes already instrumented will not be retained or
827
     * returned.
828
     *
829
     * @return the transformer-free copy of this class loader.
830
     */
831
    @Override
832
    public WebappClassLoader copyWithoutTransformers() {
833
834
        WebappClassLoader loader = new WebappClassLoader(this.parent);
835
836
        loader.antiJARLocking = this.antiJARLocking;
837
        loader.resources = this.resources;
838
        loader.delegate = this.delegate;
839
        loader.lastJarAccessed = this.lastJarAccessed;
840
        loader.repository = this.repository;
841
        loader.file = this.file;
842
        loader.jarPath = this.jarPath;
843
        loader.loaderDir = this.loaderDir;
844
        loader.canonicalLoaderDir = this.canonicalLoaderDir;
845
        loader.started = this.started;
846
        loader.needConvert = this.needConvert;
847
        loader.clearReferencesStatic = this.clearReferencesStatic;
848
        loader.clearReferencesStopThreads = this.clearReferencesStopThreads;
849
        loader.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
850
        loader.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
851
        loader.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
852
853
        loader.repositoryURLs = this.repositoryURLs.clone();
854
        loader.jarFiles = this.jarFiles.clone();
855
        loader.jarRealFiles = this.jarRealFiles.clone();
856
        loader.jarNames = this.jarNames.clone();
857
        loader.lastModifiedDates = this.lastModifiedDates.clone();
858
        loader.paths = this.paths.clone();
859
860
        loader.notFoundResources.putAll(this.notFoundResources);
861
        loader.permissionList.addAll(this.permissionList);
862
        loader.loaderPC.putAll(this.loaderPC);
863
864
        return loader;
865
866
    }
867
868
    /**
741
     * Set the place this ClassLoader can look for classes to be loaded.
869
     * Set the place this ClassLoader can look for classes to be loaded.
742
     *
870
     *
743
     * @param repository Name of a source of classes to be loaded, such as a
871
     * @param repository Name of a source of classes to be loaded, such as a
Lines 928-933 Link Here
928
            sb.append(this.parent.toString());
1056
            sb.append(this.parent.toString());
929
            sb.append("\r\n");
1057
            sb.append("\r\n");
930
        }
1058
        }
1059
        if (this.transformers.size() > 0) {
1060
            sb.append("----------> Class file transformers:\r\n");
1061
            for (ClassFileTransformer transformer : this.transformers) {
1062
                sb.append(transformer.getClass().getName()).append("\r\n");
1063
            }
1064
        }
931
        return (sb.toString());
1065
        return (sb.toString());
932
1066
933
    }
1067
    }
Lines 1183-1189 Link Here
1183
                try {
1317
                try {
1184
                    String repository = entry.codeBase.toString();
1318
                    String repository = entry.codeBase.toString();
1185
                    if ((repository.endsWith(".jar"))
1319
                    if ((repository.endsWith(".jar"))
1186
                            && (!(name.endsWith(".class")))) {
1320
                            && (!(name.endsWith(CLASS_FILE_SUFFIX)))) {
1187
                        // Copy binary content to the work directory if not present
1321
                        // Copy binary content to the work directory if not present
1188
                        File resourceFile = new File(loaderDir, name);
1322
                        File resourceFile = new File(loaderDir, name);
1189
                        url = getURI(resourceFile);
1323
                        url = getURI(resourceFile);
Lines 2552-2558 Link Here
2552
            throw new ClassNotFoundException(name);
2686
            throw new ClassNotFoundException(name);
2553
2687
2554
        String tempPath = name.replace('.', '/');
2688
        String tempPath = name.replace('.', '/');
2555
        String classPath = tempPath + ".class";
2689
        String classPath = tempPath + CLASS_FILE_SUFFIX;
2556
2690
2557
        ResourceEntry entry = null;
2691
        ResourceEntry entry = null;
2558
2692
Lines 2669-2675 Link Here
2669
     *
2803
     *
2670
     * @return the loaded resource, or null if the resource isn't found
2804
     * @return the loaded resource, or null if the resource isn't found
2671
     */
2805
     */
2672
    protected ResourceEntry findResourceInternal(String name, String path) {
2806
    protected ResourceEntry findResourceInternal(final String name, final String path) {
2673
2807
2674
        if (!started) {
2808
        if (!started) {
2675
            log.info(sm.getString("webappClassLoader.stopped", name));
2809
            log.info(sm.getString("webappClassLoader.stopped", name));
Lines 2685-2691 Link Here
2685
2819
2686
        int contentLength = -1;
2820
        int contentLength = -1;
2687
        InputStream binaryStream = null;
2821
        InputStream binaryStream = null;
2688
        boolean isClassResource = path.endsWith(".class");
2822
        boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
2689
2823
2690
        int jarFilesLength = jarFiles.length;
2824
        int jarFilesLength = jarFiles.length;
2691
2825
Lines 2782-2788 Link Here
2782
                        }
2916
                        }
2783
2917
2784
                        // Extract resources contained in JAR to the workdir
2918
                        // Extract resources contained in JAR to the workdir
2785
                        if (antiJARLocking && !(path.endsWith(".class"))) {
2919
                        if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
2786
                            byte[] buf = new byte[1024];
2920
                            byte[] buf = new byte[1024];
2787
                            File resourceFile = new File
2921
                            File resourceFile = new File
2788
                                (loaderDir, jarEntry.getName());
2922
                                (loaderDir, jarEntry.getName());
Lines 2793-2799 Link Here
2793
                                    JarEntry jarEntry2 =  entries.nextElement();
2927
                                    JarEntry jarEntry2 =  entries.nextElement();
2794
                                    if (!(jarEntry2.isDirectory())
2928
                                    if (!(jarEntry2.isDirectory())
2795
                                        && (!jarEntry2.getName().endsWith
2929
                                        && (!jarEntry2.getName().endsWith
2796
                                            (".class"))) {
2930
                                            (CLASS_FILE_SUFFIX))) {
2797
                                        resourceFile = new File
2931
                                        resourceFile = new File
2798
                                            (loaderDir, jarEntry2.getName());
2932
                                            (loaderDir, jarEntry2.getName());
2799
                                        try {
2933
                                        try {
Lines 2923-2928 Link Here
2923
            }
3057
            }
2924
        }
3058
        }
2925
3059
3060
        if (isClassResource && entry != null && entry.binaryContent != null &&
3061
            this.transformers.size() > 0) {
3062
            // If the resource is a class just being loaded, decorate it
3063
            // with any attached transformers
3064
            String className = name.endsWith(CLASS_FILE_SUFFIX) ?
3065
                    name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
3066
            String internalName = className.replace(".", "/");
3067
3068
            synchronized (this.transformers) {
3069
                for (ClassFileTransformer transformer : this.transformers) {
3070
                    try {
3071
                        byte[] transformed = transformer.transform(
3072
                                this, internalName, null, null, entry.binaryContent
3073
                        );
3074
                        if (transformed != null) {
3075
                            entry.binaryContent = transformed;
3076
                        }
3077
                    } catch (IllegalClassFormatException e) {
3078
                        log.error(sm.getString("webappClassLoader.transformError", name), e);
3079
                        return null;
3080
                    }
3081
                }
3082
            }
3083
        }
3084
2926
        // Add the entry in the local resource repository
3085
        // Add the entry in the local resource repository
2927
        synchronized (resourceEntries) {
3086
        synchronized (resourceEntries) {
2928
            // Ensures that all the threads which may be in a race to load
3087
            // Ensures that all the threads which may be in a race to load
Lines 3126-3132 Link Here
3126
                }
3285
                }
3127
                if (clazz == null)
3286
                if (clazz == null)
3128
                    continue;
3287
                    continue;
3129
                String name = triggers[i].replace('.', '/') + ".class";
3288
                String name = triggers[i].replace('.', '/') + CLASS_FILE_SUFFIX;
3130
                if (log.isDebugEnabled())
3289
                if (log.isDebugEnabled())
3131
                    log.debug(" Checking for " + name);
3290
                    log.debug(" Checking for " + name);
3132
                JarEntry jarEntry = jarFile.getJarEntry(name);
3291
                JarEntry jarEntry = jarFile.getJarEntry(name);
(-)java/org/apache/tomcat/InstrumentableClassLoader.java (+78 lines)
Line 0 Link Here
1
/*
2
 *  Licensed to the Apache Software Foundation (ASF) under one or more
3
 *  contributor license agreements.  See the NOTICE file distributed with
4
 *  this work for additional information regarding copyright ownership.
5
 *  The ASF licenses this file to You under the Apache License, Version 2.0
6
 *  (the "License"); you may not use this file except in compliance with
7
 *  the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *  Unless required by applicable law or agreed to in writing, software
12
 *  distributed under the License is distributed on an "AS IS" BASIS,
13
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *  See the License for the specific language governing permissions and
15
 *  limitations under the License.
16
 */
17
package org.apache.tomcat;
18
19
import java.lang.instrument.ClassFileTransformer;
20
21
/**
22
 * Specifies a class loader capable of being decorated with
23
 * {@link ClassFileTransformer}s. These transformers can instrument
24
 * (or weave) the byte code of classes loaded through this class loader
25
 * to alter their behavior. Currently only
26
 * {@link org.apache.catalina.loader.WebappClassLoader} implements this
27
 * interface. This allows web application frameworks or JPA providers
28
 * bundled with a web application to instrument web application classes
29
 * as necessary.
30
 * <p>
31
 * You should always program against the methods of this interface
32
 * (whether using reflection or otherwise). The methods in
33
 * {@code WebappClassLoader} are protected by the default security
34
 * manager if one is in use.
35
 *
36
 * @since 8.0, 7.0.43
37
 */
38
public interface InstrumentableClassLoader {
39
40
    /**
41
     * Adds the specified class file transformer to this class loader. The
42
     * transformer will then be able to instrument the bytecode of any
43
     * classes loaded by this class loader after the invocation of this
44
     * method.
45
     *
46
     * @param classFileTransformer The transformer to add to the class loader
47
     * @throws IllegalArgumentException if the {@literal transformer} is null.
48
     */
49
    void addTransformer(ClassFileTransformer transformer);
50
51
    /**
52
     * Removes the specified class file transformer from this class loader.
53
     * It will no longer be able to instrument the byte code of any classes
54
     * loaded by the class loader after the invocation of this method.
55
     * However, any classes already instrumented by this transformer before
56
     * this method call will remain in their instramented state.
57
     *
58
     * @param classFileTransformer The transformer to remove
59
     */
60
    void removeTransformer(ClassFileTransformer transformer);
61
62
    /**
63
     * Returns a copy of this class loader without any class file
64
     * transformers. This is a tool often used by Java Persistence API
65
     * providers to inspect entity classes in the absence of any
66
     * instrumentation, something that can't be guaranteed within the
67
     * context of a {@link ClassFileTransformer}'s
68
     * {@link ClassFileTransformer#transform() transform} method.
69
     * <p>
70
     * The returned class loader's resource cache will have been cleared
71
     * so that classes already instrumented will not be retained or
72
     * returned.
73
     *
74
     * @return the transformer-free copy of this class loader.
75
     */
76
    ClassLoader copyWithoutTransformers();
77
78
}
(-)test/org/apache/catalina/loader/TestWebappClassLoaderWeaving.java (+413 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.catalina.loader;
18
19
import java.io.File;
20
import java.io.FileOutputStream;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.lang.instrument.ClassFileTransformer;
24
import java.lang.reflect.Method;
25
import java.security.ProtectionDomain;
26
27
import static org.junit.Assert.assertEquals;
28
import static org.junit.Assert.assertNotNull;
29
import static org.junit.Assert.assertSame;
30
import static org.junit.Assert.fail;
31
32
import org.junit.After;
33
import org.junit.AfterClass;
34
import org.junit.Before;
35
import org.junit.BeforeClass;
36
import org.junit.Test;
37
38
import org.apache.catalina.Context;
39
import org.apache.catalina.startup.Tomcat;
40
import org.apache.catalina.startup.TomcatBaseTest;
41
import org.apache.tomcat.util.http.fileupload.FileUtils;
42
43
public class TestWebappClassLoaderWeaving extends TomcatBaseTest {
44
45
    private static final String PACKAGE_PREFIX = "org/apache/tomcat/unittest/weaving";
46
47
    private static String WEBAPP_DOC_BASE;
48
49
    @BeforeClass
50
    public static void setUpClass() throws Exception {
51
52
        WEBAPP_DOC_BASE = System.getProperty("java.io.tmpdir") + "/TestWebappClassLoaderWeaving";
53
        File classes = new File(WEBAPP_DOC_BASE + "/WEB-INF/classes/" + PACKAGE_PREFIX);
54
        classes.mkdirs();
55
56
        copyResource(PACKAGE_PREFIX + "/NeverWeavedClass.class",
57
                new File(classes, "NeverWeavedClass.class"));
58
        copyResource(PACKAGE_PREFIX + "/UnweavedClass.class",
59
                new File(classes, "UnweavedClass.class"));
60
61
    }
62
63
    @AfterClass
64
    public static void tearDownClass() throws Exception {
65
66
        FileUtils.deleteDirectory(new File(WEBAPP_DOC_BASE));
67
68
    }
69
70
    private Tomcat tomcat;
71
    private Context context;
72
    private WebappClassLoader loader;
73
74
    @Before
75
    @Override
76
    public void setUp() throws Exception {
77
78
        super.setUp();
79
80
        this.tomcat = getTomcatInstance();
81
        this.context = this.tomcat.addContext("/weaving", WEBAPP_DOC_BASE);
82
        this.tomcat.start();
83
84
        ClassLoader loader = this.context.getLoader().getClassLoader();
85
        assertNotNull("The class loader should not be null.", loader);
86
        assertSame("The class loader is not correct.", WebappClassLoader.class, loader.getClass());
87
88
        this.loader = (WebappClassLoader) loader;
89
90
    }
91
92
    @After
93
    @Override
94
    public void tearDown() throws Exception {
95
96
        try {
97
            this.loader = null;
98
99
            this.context.stop();
100
            this.tomcat.getHost().removeChild(this.context);
101
            this.context = null;
102
        } finally {
103
            super.tearDown();
104
        }
105
106
    }
107
108
    @Test
109
    public void testNoWeaving() throws Exception {
110
111
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
112
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
113
114
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
115
        assertEquals("The second result is not correct.", "Hello, Unweaved World!", result);
116
117
    }
118
119
    @Test
120
    public void testAddingNullTransformerThrowsException() throws Exception {
121
122
        try {
123
            this.loader.addTransformer(null);
124
            fail("Expected exception IllegalArgumentException, got no exception.");
125
        } catch (IllegalArgumentException ignore) {
126
            // good
127
        }
128
129
        // class loading should still work, no weaving
130
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
131
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
132
133
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
134
        assertEquals("The second result is not correct.", "Hello, Unweaved World!", result);
135
136
    }
137
138
    @Test
139
    public void testAddedTransformerInstrumentsClass1() throws Exception {
140
141
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
142
143
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
144
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
145
146
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
147
        assertEquals("The second result is not correct.", "Hello, Weaver #1!", result);
148
149
    }
150
151
    @Test
152
    public void testAddedTransformerInstrumentsClass2() throws Exception {
153
154
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
155
156
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
157
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
158
159
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
160
        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
161
162
    }
163
164
    @Test
165
    public void testTransformersExecuteInOrderAdded1() throws Exception {
166
167
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
168
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
169
170
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
171
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
172
173
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
174
        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
175
176
    }
177
178
    @Test
179
    public void testTransformersExecuteInOrderAdded2() throws Exception {
180
181
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
182
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
183
184
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
185
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
186
187
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
188
        assertEquals("The second result is not correct.", "Hello, Weaver #1!", result);
189
190
    }
191
192
    @Test
193
    public void testRemovedTransformerNoLongerInstruments1() throws Exception {
194
195
        ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_1);
196
        this.loader.addTransformer(removed);
197
        this.loader.removeTransformer(removed);
198
199
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
200
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
201
202
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
203
        assertEquals("The second result is not correct.", "Hello, Unweaved World!", result);
204
205
    }
206
207
    @Test
208
    public void testRemovedTransformerNoLongerInstruments2() throws Exception {
209
210
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
211
212
        ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_2);
213
        this.loader.addTransformer(removed);
214
        this.loader.removeTransformer(removed);
215
216
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
217
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
218
219
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
220
        assertEquals("The second result is not correct.", "Hello, Weaver #1!", result);
221
222
    }
223
224
    @Test
225
    public void testRemovedTransformerNoLongerInstruments3() throws Exception {
226
227
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
228
229
        ReplacementTransformer removed = new ReplacementTransformer(WEAVED_REPLACEMENT_1);
230
        this.loader.addTransformer(removed);
231
        this.loader.removeTransformer(removed);
232
233
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
234
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
235
236
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
237
        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
238
239
    }
240
241
    @Test
242
    public void testCopiedClassLoaderExcludesResourcesAndTransformers() throws Exception {
243
244
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_1));
245
        this.loader.addTransformer(new ReplacementTransformer(WEAVED_REPLACEMENT_2));
246
247
        String result = invokeDoMethodOnClass(this.loader, "NeverWeavedClass");
248
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
249
250
        result = invokeDoMethodOnClass(this.loader, "UnweavedClass");
251
        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
252
253
        WebappClassLoader copiedLoader = this.loader.copyWithoutTransformers();
254
255
        result = invokeDoMethodOnClass(copiedLoader, "NeverWeavedClass");
256
        assertEquals("The third result is not correct.", "This will never be weaved.", result);
257
258
        result = invokeDoMethodOnClass(copiedLoader, "UnweavedClass");
259
        assertEquals("The fourth result is not correct.", "Hello, Unweaved World!", result);
260
261
        assertEquals("getAntiJARLocking did not match.", this.loader.getAntiJARLocking(),
262
                copiedLoader.getAntiJARLocking());
263
        assertEquals("getClearReferencesHttpClientKeepAliveThread did not match.",
264
                this.loader.getClearReferencesHttpClientKeepAliveThread(),
265
                copiedLoader.getClearReferencesHttpClientKeepAliveThread());
266
        assertEquals("getClearReferencesLogFactoryRelease did not match.",
267
                this.loader.getClearReferencesLogFactoryRelease(),
268
                copiedLoader.getClearReferencesLogFactoryRelease());
269
        assertEquals("getClearReferencesStatic did not match.",
270
                this.loader.getClearReferencesStatic(),
271
                copiedLoader.getClearReferencesStatic());
272
        assertEquals("getClearReferencesStopThreads did not match.",
273
                this.loader.getClearReferencesStopThreads(),
274
                copiedLoader.getClearReferencesStopThreads());
275
        assertEquals("getClearReferencesStopTimerThreads did not match.",
276
                this.loader.getClearReferencesStopTimerThreads(),
277
                copiedLoader.getClearReferencesStopTimerThreads());
278
        assertEquals("getContextName did not match.", this.loader.getContextName(),
279
                copiedLoader.getContextName());
280
        assertEquals("getDelegate did not match.", this.loader.getDelegate(),
281
                copiedLoader.getDelegate());
282
        assertEquals("getJarPath did not match.", this.loader.getJarPath(),
283
                copiedLoader.getJarPath());
284
        assertEquals("getURLs did not match.", this.loader.getURLs().length,
285
                copiedLoader.getURLs().length);
286
        assertSame("getParent did not match.", this.loader.getParent(), copiedLoader.getParent());
287
288
    }
289
290
    private static void copyResource(String name, File file) throws Exception {
291
292
        InputStream is = TestWebappClassLoaderWeaving.class.getClassLoader()
293
                .getResourceAsStream(name);
294
        if (is == null) {
295
            throw new IOException("Resource " + name + " not found on classpath.");
296
        }
297
298
        FileOutputStream os = new FileOutputStream(file);
299
        try {
300
            for (int b = is.read(); b >= 0; b = is.read()) {
301
                os.write(b);
302
            }
303
        } finally {
304
            is.close();
305
            os.close();
306
        }
307
308
    }
309
310
    private static String invokeDoMethodOnClass(WebappClassLoader loader, String className)
311
            throws Exception {
312
313
        Class<?> c = loader.findClass("org.apache.tomcat.unittest.weaving." + className);
314
        assertNotNull("The loaded class should not be null.", c);
315
316
        Method m = c.getMethod("doMethod");
317
318
        Object o = c.newInstance();
319
        return (String) m.invoke(o);
320
321
    }
322
323
    private static class ReplacementTransformer implements ClassFileTransformer {
324
325
        private static final String CLASS_TO_WEAVE = PACKAGE_PREFIX + "/UnweavedClass";
326
327
        private final byte[] replacement;
328
329
        ReplacementTransformer(byte[] replacement) throws IOException {
330
331
            /*String resource = PACKAGE_PREFIX + "/" + replaceWithClass + ".class";
332
            InputStream is = Thread.currentThread().getContextClassLoader()
333
                    .getResourceAsStream(resource);
334
            if (is == null) {
335
                throw new IOException("Resource " + resource + " not found.");
336
            }
337
338
            ByteArrayOutputStream os = new ByteArrayOutputStream();
339
            try {
340
                for (int b = is.read(); b >= 0; b = is.read()) {
341
                    os.write(b);
342
                }
343
            } finally {
344
                is.close();
345
                os.flush();
346
            }
347
348
            this.replacement = os.toByteArray();*/
349
            this.replacement = replacement;
350
351
        }
352
353
        @Override
354
        public byte[] transform(ClassLoader loader, String className, Class<?> x,
355
                                ProtectionDomain y, byte[] b) {
356
357
            if (CLASS_TO_WEAVE.equals(className)) {
358
                return this.replacement;
359
            }
360
361
            return null;
362
363
        }
364
365
    }
366
367
    /**
368
     * Compiled version of org.apache.tomcat.unittest.weaving.UnweavedClass, except that
369
     * the doMethod method returns "Hello, Weaver #1!". Compiled with Oracle Java 1.6.0_51.
370
     */
371
    private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] {
372
            -54, -2, -70, -66, 0, 0, 0, 50, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16,
373
            1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101,
374
            1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8,
375
            100, 111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
376
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
377
            101, 70, 105, 108, 101, 1, 0, 18, 85, 110, 119, 101, 97, 118, 101, 100, 67, 108, 97,
378
            115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101, 108, 108, 111, 44,
379
            32, 87, 101, 97, 118, 101, 114, 32, 35, 49, 33, 1, 0, 48, 111, 114, 103, 47, 97, 112,
380
            97, 99, 104, 101, 47, 116, 111, 109, 99, 97, 116, 47, 117, 110, 105, 116, 116, 101,
381
            115, 116, 47, 119, 101, 97, 118, 105, 110, 103, 47, 85, 110, 119, 101, 97, 118, 101,
382
            100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79,
383
            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,
384
            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,
385
            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,
386
            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,
387
            0, 12
388
    };
389
390
    /**
391
     * Compiled version of org.apache.tomcat.unittest.weaving.UnweavedClass, except that
392
     * the doMethod method returns "Hello, Weaver #2!". Compiled with Oracle Java 1.6.0_51.
393
     */
394
    private static final byte[] WEAVED_REPLACEMENT_2 = new byte[] {
395
            -54, -2, -70, -66, 0, 0, 0, 50, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16,
396
            1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101,
397
            1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8,
398
            100, 111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
399
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
400
            101, 70, 105, 108, 101, 1, 0, 18, 85, 110, 119, 101, 97, 118, 101, 100, 67, 108, 97,
401
            115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101, 108, 108, 111, 44,
402
            32, 87, 101, 97, 118, 101, 114, 32, 35, 50, 33, 1, 0, 48, 111, 114, 103, 47, 97, 112,
403
            97, 99, 104, 101, 47, 116, 111, 109, 99, 97, 116, 47, 117, 110, 105, 116, 116, 101,
404
            115, 116, 47, 119, 101, 97, 118, 105, 110, 103, 47, 85, 110, 119, 101, 97, 118, 101,
405
            100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79,
406
            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,
407
            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,
408
            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,
409
            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,
410
            0, 12
411
    };
412
413
}
(-)test/org/apache/tomcat/unittest/weaving/NeverWeavedClass.java (+24 lines)
Line 0 Link Here
1
/*
2
 *  Licensed to the Apache Software Foundation (ASF) under one or more
3
 *  contributor license agreements.  See the NOTICE file distributed with
4
 *  this work for additional information regarding copyright ownership.
5
 *  The ASF licenses this file to You under the Apache License, Version 2.0
6
 *  (the "License"); you may not use this file except in compliance with
7
 *  the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *  Unless required by applicable law or agreed to in writing, software
12
 *  distributed under the License is distributed on an "AS IS" BASIS,
13
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *  See the License for the specific language governing permissions and
15
 *  limitations under the License.
16
 */
17
package org.apache.tomcat.unittest.weaving;
18
19
public class NeverWeavedClass {
20
21
    public String doMethod() {
22
        return "This will never be weaved.";
23
    }
24
}
(-)test/org/apache/tomcat/unittest/weaving/UnweavedClass.java (+24 lines)
Line 0 Link Here
1
/*
2
 *  Licensed to the Apache Software Foundation (ASF) under one or more
3
 *  contributor license agreements.  See the NOTICE file distributed with
4
 *  this work for additional information regarding copyright ownership.
5
 *  The ASF licenses this file to You under the Apache License, Version 2.0
6
 *  (the "License"); you may not use this file except in compliance with
7
 *  the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *  Unless required by applicable law or agreed to in writing, software
12
 *  distributed under the License is distributed on an "AS IS" BASIS,
13
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *  See the License for the specific language governing permissions and
15
 *  limitations under the License.
16
 */
17
package org.apache.tomcat.unittest.weaving;
18
19
public class UnweavedClass {
20
21
    public String doMethod() {
22
        return "Hello, Unweaved World!";
23
    }
24
}

Return to bug 55317