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 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 / +158 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 51-56 Link Here
51
import java.util.Map;
53
import java.util.Map;
52
import java.util.ResourceBundle;
54
import java.util.ResourceBundle;
53
import java.util.Set;
55
import java.util.Set;
56
import java.util.concurrent.CopyOnWriteArrayList;
54
import java.util.concurrent.ThreadPoolExecutor;
57
import java.util.concurrent.ThreadPoolExecutor;
55
import java.util.jar.Attributes;
58
import java.util.jar.Attributes;
56
import java.util.jar.Attributes.Name;
59
import java.util.jar.Attributes.Name;
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-118 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.44, 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.
107
 *
116
 *
108
 * @author Remy Maucherat
117
 * @author Remy Maucherat
109
 * @author Craig R. McClanahan
118
 * @author Craig R. McClanahan
110
 * @version $Id$
119
 * @version $Id$
111
 */
120
 */
112
public class WebappClassLoader
121
public class WebappClassLoader extends URLClassLoader
113
    extends URLClassLoader
122
        implements Lifecycle, InstrumentableClassLoader {
114
    implements Lifecycle
115
 {
116
123
117
    private static final org.apache.juli.logging.Log log=
124
    private static final org.apache.juli.logging.Log log=
118
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
125
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
Lines 125-130 Link Here
125
132
126
    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
133
    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
127
134
135
    private static final String CLASS_FILE_SUFFIX = ".class";
136
128
    static {
137
    static {
129
        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
138
        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
130
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
139
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
Lines 461-466 Link Here
461
     */
470
     */
462
    private boolean clearReferencesHttpClientKeepAliveThread = true;
471
    private boolean clearReferencesHttpClientKeepAliveThread = true;
463
472
473
    /**
474
     * Holds the class file transformers decorating this class loader. The
475
     * CopyOnWriteArrayList is thread safe. It is expensive on writes, but
476
     * those should be rare. It is very fast on reads, since synchronization
477
     * is not actually used. Importantly, the ClassLoader will never block
478
     * iterating over the transformers while loading a class.
479
     */
480
    private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<>();
464
481
465
    // ------------------------------------------------------------- Properties
482
    // ------------------------------------------------------------- Properties
466
483
Lines 735-742 Link Here
735
752
736
    // ------------------------------------------------------- Reloader Methods
753
    // ------------------------------------------------------- Reloader Methods
737
754
755
    /**
756
     * Adds the specified class file transformer to this class loader. The
757
     * transformer will then be able to modify the bytecode of any classes
758
     * loaded by this class loader after the invocation of this method.
759
     *
760
     * @param classFileTransformer The transformer to add to the class loader
761
     */
762
    @Override
763
    public void addTransformer(ClassFileTransformer transformer) {
738
764
765
        if (transformer == null) {
766
            throw new IllegalArgumentException(sm.getString(
767
                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
768
        }
769
770
        if (this.transformers.contains(transformer)) {
771
            // if the same instance of this transformer was already added, bail out
772
            log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
773
                    transformer, getContextName()));
774
            return;
775
        }
776
        this.transformers.add(transformer);
777
778
        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
779
780
    }
781
739
    /**
782
    /**
783
     * Removes the specified class file transformer from this class loader.
784
     * It will no longer be able to modify the byte code of any classes
785
     * loaded by the class loader after the invocation of this method.
786
     * However, any classes already modified by this transformer will
787
     * remain transformed.
788
     *
789
     * @param classFileTransformer The transformer to remove
790
     */
791
    @Override
792
    public void removeTransformer(ClassFileTransformer transformer) {
793
794
        if (transformer == null) {
795
            return;
796
        }
797
798
        if (this.transformers.remove(transformer)) {
799
            log.info(sm.getString("webappClassLoader.removeTransformer",
800
                    transformer, getContextName()));
801
            return;
802
        }
803
804
    }
805
806
    /**
807
     * Returns a copy of this class loader without any class file
808
     * transformers. This is a tool often used by Java Persistence API
809
     * providers to inspect entity classes in the absence of any
810
     * instrumentation, something that can't be guaranteed within the
811
     * context of a {@link ClassFileTransformer}'s
812
     * {@link ClassFileTransformer#transform() transform} method.
813
     * <p>
814
     * The returned class loader's resource cache will have been cleared
815
     * so that classes already instrumented will not be retained or
816
     * returned.
817
     *
818
     * @return the transformer-free copy of this class loader.
819
     */
820
    @Override
821
    public WebappClassLoader copyWithoutTransformers() {
822
823
        WebappClassLoader loader = new WebappClassLoader(this.parent);
824
825
        loader.antiJARLocking = this.antiJARLocking;
826
        loader.resources = this.resources;
827
        loader.delegate = this.delegate;
828
        loader.lastJarAccessed = this.lastJarAccessed;
829
        loader.repositoryPath = this.repositoryPath;
830
        loader.repository = this.repository;
831
        loader.jarPath = this.jarPath;
832
        loader.loaderDir = this.loaderDir;
833
        loader.canonicalLoaderDir = this.canonicalLoaderDir;
834
        loader.started = this.started;
835
        loader.needConvert = this.needConvert;
836
        loader.clearReferencesStatic = this.clearReferencesStatic;
837
        loader.clearReferencesStopThreads = this.clearReferencesStopThreads;
838
        loader.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
839
        loader.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
840
        loader.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
841
842
        loader.repositoryURLs = this.repositoryURLs.clone();
843
        loader.jarFiles = this.jarFiles.clone();
844
        loader.jarRealFiles = this.jarRealFiles.clone();
845
        loader.jarNames = this.jarNames.clone();
846
        loader.lastModifiedDates = this.lastModifiedDates.clone();
847
        loader.paths = this.paths.clone();
848
849
        loader.notFoundResources.putAll(this.notFoundResources);
850
        loader.permissionList.addAll(this.permissionList);
851
        loader.loaderPC.putAll(this.loaderPC);
852
853
        return loader;
854
855
    }
856
857
    /**
740
     * Set the place this ClassLoader can look for classes to be loaded.
858
     * Set the place this ClassLoader can look for classes to be loaded.
741
     *
859
     *
742
     * @param path  Path of a source of classes to be loaded, such as a
860
     * @param path  Path of a source of classes to be loaded, such as a
Lines 924-929 Link Here
924
            sb.append(this.parent.toString());
1042
            sb.append(this.parent.toString());
925
            sb.append("\r\n");
1043
            sb.append("\r\n");
926
        }
1044
        }
1045
        if (this.transformers.size() > 0) {
1046
            sb.append("----------> Class file transformers:\r\n");
1047
            for (ClassFileTransformer transformer : this.transformers) {
1048
                sb.append(transformer).append("\r\n");
1049
            }
1050
        }
927
        return (sb.toString());
1051
        return (sb.toString());
928
1052
929
    }
1053
    }
Lines 1179-1185 Link Here
1179
                try {
1303
                try {
1180
                    String repository = entry.codeBase.toString();
1304
                    String repository = entry.codeBase.toString();
1181
                    if ((repository.endsWith(".jar"))
1305
                    if ((repository.endsWith(".jar"))
1182
                            && (!(name.endsWith(".class")))) {
1306
                            && (!(name.endsWith(CLASS_FILE_SUFFIX)))) {
1183
                        // Copy binary content to the work directory if not present
1307
                        // Copy binary content to the work directory if not present
1184
                        File resourceFile = new File(loaderDir, name);
1308
                        File resourceFile = new File(loaderDir, name);
1185
                        url = getURI(resourceFile);
1309
                        url = getURI(resourceFile);
Lines 2546-2552 Link Here
2546
            throw new ClassNotFoundException(name);
2670
            throw new ClassNotFoundException(name);
2547
2671
2548
        String tempPath = name.replace('.', '/');
2672
        String tempPath = name.replace('.', '/');
2549
        String classPath = tempPath + ".class";
2673
        String classPath = tempPath + CLASS_FILE_SUFFIX;
2550
2674
2551
        ResourceEntry entry = null;
2675
        ResourceEntry entry = null;
2552
2676
Lines 2647-2653 Link Here
2647
     *
2771
     *
2648
     * @return the loaded resource, or null if the resource isn't found
2772
     * @return the loaded resource, or null if the resource isn't found
2649
     */
2773
     */
2650
    protected ResourceEntry findResourceInternal(String name, String path) {
2774
    protected ResourceEntry findResourceInternal(final String name, final String path) {
2651
2775
2652
        if (!started) {
2776
        if (!started) {
2653
            log.info(sm.getString("webappClassLoader.stopped", name));
2777
            log.info(sm.getString("webappClassLoader.stopped", name));
Lines 2663-2669 Link Here
2663
2787
2664
        int contentLength = -1;
2788
        int contentLength = -1;
2665
        InputStream binaryStream = null;
2789
        InputStream binaryStream = null;
2666
        boolean isClassResource = path.endsWith(".class");
2790
        boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
2667
2791
2668
        int jarFilesLength = jarFiles.length;
2792
        int jarFilesLength = jarFiles.length;
2669
2793
Lines 2751-2757 Link Here
2751
                        }
2875
                        }
2752
2876
2753
                        // Extract resources contained in JAR to the workdir
2877
                        // Extract resources contained in JAR to the workdir
2754
                        if (antiJARLocking && !(path.endsWith(".class"))) {
2878
                        if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
2755
                            byte[] buf = new byte[1024];
2879
                            byte[] buf = new byte[1024];
2756
                            File resourceFile = new File
2880
                            File resourceFile = new File
2757
                                (loaderDir, jarEntry.getName());
2881
                                (loaderDir, jarEntry.getName());
Lines 2762-2768 Link Here
2762
                                    JarEntry jarEntry2 =  entries.nextElement();
2886
                                    JarEntry jarEntry2 =  entries.nextElement();
2763
                                    if (!(jarEntry2.isDirectory())
2887
                                    if (!(jarEntry2.isDirectory())
2764
                                        && (!jarEntry2.getName().endsWith
2888
                                        && (!jarEntry2.getName().endsWith
2765
                                            (".class"))) {
2889
                                            (CLASS_FILE_SUFFIX))) {
2766
                                        resourceFile = new File
2890
                                        resourceFile = new File
2767
                                            (loaderDir, jarEntry2.getName());
2891
                                            (loaderDir, jarEntry2.getName());
2768
                                        try {
2892
                                        try {
Lines 2892-2897 Link Here
2892
            }
3016
            }
2893
        }
3017
        }
2894
3018
3019
        if (isClassResource && entry != null && entry.binaryContent != null &&
3020
            this.transformers.size() > 0) {
3021
            // If the resource is a class just being loaded, decorate it
3022
            // with any attached transformers
3023
            String className = name.endsWith(CLASS_FILE_SUFFIX) ?
3024
                    name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
3025
            String internalName = className.replace(".", "/");
3026
3027
            for (ClassFileTransformer transformer : this.transformers) {
3028
                try {
3029
                    byte[] transformed = transformer.transform(
3030
                            this, internalName, null, null, entry.binaryContent
3031
                    );
3032
                    if (transformed != null) {
3033
                        entry.binaryContent = transformed;
3034
                    }
3035
                } catch (IllegalClassFormatException e) {
3036
                    log.error(sm.getString("webappClassLoader.transformError", name), e);
3037
                    return null;
3038
                }
3039
            }
3040
        }
3041
2895
        // Add the entry in the local resource repository
3042
        // Add the entry in the local resource repository
2896
        synchronized (resourceEntries) {
3043
        synchronized (resourceEntries) {
2897
            // Ensures that all the threads which may be in a race to load
3044
            // Ensures that all the threads which may be in a race to load
Lines 3095-3101 Link Here
3095
                }
3242
                }
3096
                if (clazz == null)
3243
                if (clazz == null)
3097
                    continue;
3244
                    continue;
3098
                String name = triggers[i].replace('.', '/') + ".class";
3245
                String name = triggers[i].replace('.', '/') + CLASS_FILE_SUFFIX;
3099
                if (log.isDebugEnabled())
3246
                if (log.isDebugEnabled())
3100
                    log.debug(" Checking for " + name);
3247
                    log.debug(" Checking for " + name);
3101
                JarEntry jarEntry = jarFile.getJarEntry(name);
3248
                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.44
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 (+430 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/catalina/loader";
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 + "/TesterNeverWeavedClass.class",
57
                new File(classes, "TesterNeverWeavedClass.class"));
58
        copyResource(PACKAGE_PREFIX + "/TesterUnweavedClass.class",
59
                new File(classes, "TesterUnweavedClass.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, "TesterNeverWeavedClass");
112
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
113
114
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
131
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
132
133
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
144
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
145
146
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
157
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
158
159
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
171
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
172
173
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
185
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
186
187
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
200
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
201
202
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
217
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
218
219
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
234
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
235
236
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
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, "TesterNeverWeavedClass");
248
        assertEquals("The first result is not correct.", "This will never be weaved.", result);
249
250
        result = invokeDoMethodOnClass(this.loader, "TesterUnweavedClass");
251
        assertEquals("The second result is not correct.", "Hello, Weaver #2!", result);
252
253
        WebappClassLoader copiedLoader = this.loader.copyWithoutTransformers();
254
255
        result = invokeDoMethodOnClass(copiedLoader, "TesterNeverWeavedClass");
256
        assertEquals("The third result is not correct.", "This will never be weaved.", result);
257
258
        result = invokeDoMethodOnClass(copiedLoader, "TesterUnweavedClass");
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.catalina.loader." + 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 + "/TesterUnweavedClass";
326
327
        private final byte[] replacement;
328
329
        ReplacementTransformer(byte[] replacement) throws IOException {
330
331
            this.replacement = replacement;
332
333
        }
334
335
        @Override
336
        public byte[] transform(ClassLoader loader, String className, Class<?> x,
337
                                ProtectionDomain y, byte[] b) {
338
339
            if (CLASS_TO_WEAVE.equals(className)) {
340
                return this.replacement;
341
            }
342
343
            return null;
344
345
        }
346
347
    }
348
349
    /**
350
     * Compiled version of org.apache.catalina.loader.TesterUnweavedClass, except that
351
     * the doMethod method returns "Hello, Weaver #1!". Compiled with Oracle Java 1.6.0_51.
352
     */
353
    private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] {
354
            -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,
355
            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0,
356
            15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 100,
357
            111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97,
358
            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70,
359
            105, 108, 101, 1, 0, 24, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, 101,
360
            100, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101,
361
            108, 108, 111, 44, 32, 87, 101, 97, 118, 101, 114, 32, 35, 49, 33, 1, 0, 46, 111, 114,
362
            103, 47, 97, 112, 97, 99, 104, 101, 47, 99, 97, 116, 97, 108, 105, 110, 97, 47, 108,
363
            111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118,
364
            101, 100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
365
            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,
366
            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,
367
            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,
368
            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,
369
            2, 0, 12
370
    };
371
372
    /**
373
     * Compiled version of org.apache.catalina.loader.TesterUnweavedClass, except that
374
     * the doMethod method returns "Hello, Weaver #2!". Compiled with Oracle Java 1.6.0_51.
375
     */
376
    private static final byte[] WEAVED_REPLACEMENT_2 = new byte[] {
377
            -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,
378
            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0,
379
            15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 100,
380
            111, 77, 101, 116, 104, 111, 100, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97,
381
            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70,
382
            105, 108, 101, 1, 0, 24, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118, 101,
383
            100, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 17, 72, 101,
384
            108, 108, 111, 44, 32, 87, 101, 97, 118, 101, 114, 32, 35, 50, 33, 1, 0, 46, 111, 114,
385
            103, 47, 97, 112, 97, 99, 104, 101, 47, 99, 97, 116, 97, 108, 105, 110, 97, 47, 108,
386
            111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 101, 114, 85, 110, 119, 101, 97, 118,
387
            101, 100, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
388
            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,
389
            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,
390
            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,
391
            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,
392
            2, 0, 12
393
    };
394
395
    /*
396
     * The WEAVED_REPLACEMENT_1 and WEAVED_REPLACEMENT_2 field contents are generated using the
397
     * following code. To regenerate them, alter the TesterUnweavedClass code as desired, recompile,
398
     * and run this main method.
399
     */
400
    /*public static void main(String... arguments) throws Exception {
401
        InputStream input = Translator.class.getClassLoader()
402
                .getResourceAsStream("org/apache/catalina/loader/TesterUnweavedClass.class");
403
404
        StringBuilder builder = new StringBuilder();
405
        builder.append("            ");
406
407
        System.out.println("    private static final byte[] WEAVED_REPLACEMENT_1 = new byte[] {");
408
        try {
409
            for (int i = 0, b = input.read(); b >= 0; i++, b = input.read()) {
410
                String value = "" + ((byte)b);
411
                if (builder.length() + value.length() > 97) {
412
                    builder.append(",");
413
                    System.out.println(builder.toString());
414
                    builder = new StringBuilder();
415
                    builder.append("            ").append(value);
416
                } else {
417
                    if (i > 0) {
418
                        builder.append(", ");
419
                    }
420
                    builder.append(value);
421
                }
422
            }
423
            System.out.println(builder.toString());
424
        } finally {
425
            input.close();
426
        }
427
        System.out.println("    }");
428
    }*/
429
430
}
(-)test/org/apache/catalina/loader/TesterNeverWeavedClass.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.catalina.loader;
18
19
public class TesterNeverWeavedClass {
20
21
    public String doMethod() {
22
        return "This will never be weaved.";
23
    }
24
}
(-)test/org/apache/catalina/loader/TesterUnweavedClass.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.catalina.loader;
18
19
public class TesterUnweavedClass {
20
21
    public String doMethod() {
22
        return "Hello, Unweaved World!";
23
    }
24
}

Return to bug 55317