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 / +190 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-57 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;
53
import java.util.Set;
56
import java.util.Set;
54
import java.util.concurrent.ThreadPoolExecutor;
57
import java.util.concurrent.ThreadPoolExecutor;
58
import java.util.concurrent.locks.Lock;
59
import java.util.concurrent.locks.ReadWriteLock;
60
import java.util.concurrent.locks.ReentrantReadWriteLock;
55
import java.util.jar.Attributes;
61
import java.util.jar.Attributes;
56
import java.util.jar.Attributes.Name;
62
import java.util.jar.Attributes.Name;
57
import java.util.jar.JarEntry;
63
import java.util.jar.JarEntry;
Lines 65-70 Link Here
65
import org.apache.catalina.LifecycleState;
71
import org.apache.catalina.LifecycleState;
66
import org.apache.catalina.WebResource;
72
import org.apache.catalina.WebResource;
67
import org.apache.catalina.WebResourceRoot;
73
import org.apache.catalina.WebResourceRoot;
74
import org.apache.tomcat.InstrumentableClassLoader;
68
import org.apache.tomcat.util.ExceptionUtils;
75
import org.apache.tomcat.util.ExceptionUtils;
69
import org.apache.tomcat.util.IntrospectionUtils;
76
import org.apache.tomcat.util.IntrospectionUtils;
70
import org.apache.tomcat.util.res.StringManager;
77
import org.apache.tomcat.util.res.StringManager;
Lines 104-109 Link Here
104
 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
111
 * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
105
 * security is made unless a security manager is present.
112
 * security is made unless a security manager is present.
106
 * <p>
113
 * <p>
114
 * <strong>IMPLEMENTATION NOTE</strong> - As of 8.0 and 7.0.43, this class
115
 * loader implements {@link InstrumentableClassLoader}, permitting web
116
 * application classes to instrument other classes in the same web
117
 * application. It does not permit instrumentation of system or container
118
 * classes or classes in other web apps.
119
 * <p>
107
 * TODO: Is there any requirement to provide a proper Lifecycle implementation
120
 * TODO: Is there any requirement to provide a proper Lifecycle implementation
108
 *       rather than the current stubbed implementation?
121
 *       rather than the current stubbed implementation?
109
 *
122
 *
Lines 111-120 Link Here
111
 * @author Craig R. McClanahan
124
 * @author Craig R. McClanahan
112
 * @version $Id$
125
 * @version $Id$
113
 */
126
 */
114
public class WebappClassLoader
127
public class WebappClassLoader extends URLClassLoader
115
    extends URLClassLoader
128
        implements Lifecycle, InstrumentableClassLoader {
116
    implements Lifecycle
117
 {
118
    private static final org.apache.juli.logging.Log log=
129
    private static final org.apache.juli.logging.Log log=
119
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
130
        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
Lines 127-132 Link Here
127
    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
138
    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
139
    private static final String CLASS_FILE_SUFFIX = ".class";
140
128
    static {
141
    static {
129
        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
142
        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
130
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
143
        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
Lines 462-468 Link Here
462
     */
475
     */
463
    private boolean clearReferencesHttpClientKeepAliveThread = true;
476
    private boolean clearReferencesHttpClientKeepAliveThread = true;
477
    /**
478
     * Holds the class file transformers decorating this class loader. The
479
     * LinkedList implementation has faster inserts than an ArrayList.
480
     */
481
    private final LinkedList<ClassFileTransformer> transformers = new LinkedList<>();
482
    /**
483
     * These locks control access to the transformers list in a way that
484
     * performs better than simple synchronization. Multiple threads will be
485
     * able to read the list at the same time, but only one may update the list
486
     * at a time, and no other thread may read the list while it's being updated.
487
     */
488
    private final ReadWriteLock transformersMutex = new ReentrantReadWriteLock();
489
    private final Lock transformersReadLock = transformersMutex.readLock();
490
    private final Lock transformersWriteLock = transformersMutex.writeLock();
491
492
464
    // ------------------------------------------------------------- Properties
493
    // ------------------------------------------------------------- Properties
465
    /**
494
    /**
Lines 736-743 Link Here
736
    // ------------------------------------------------------- Reloader Methods
765
    // ------------------------------------------------------- Reloader Methods
766
    /**
767
     * Adds the specified class file transformer to this class loader. The
768
     * transformer will then be able to modify the bytecode of any classes
769
     * loaded by this class loader after the invocation of this method.
770
     *
771
     * @param classFileTransformer The transformer to add to the class loader
772
     */
773
    @Override
774
    public void addTransformer(ClassFileTransformer transformer) {
775
        if (transformer == null) {
776
            throw new IllegalArgumentException(sm.getString(
777
                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
778
        }
779
780
        this.transformersWriteLock.lock();
781
        try {
782
            if (this.transformers.contains(transformer)) {
783
                // if the same instance of this transformer was already added, bail out
784
                log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
785
                        transformer, getContextName()));
786
                return;
787
            }
788
            this.transformers.add(transformer);
789
        } finally {
790
            this.transformersWriteLock.unlock();
791
        }
792
793
        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
794
795
    }
796
737
    /**
797
    /**
798
     * Removes the specified class file transformer from this class loader.
799
     * It will no longer be able to modify the byte code of any classes
800
     * loaded by the class loader after the invocation of this method.
801
     * However, any classes already modified by this transformer will
802
     * remain transformed.
803
     *
804
     * @param classFileTransformer The transformer to remove
805
     */
806
    @Override
807
    public void removeTransformer(ClassFileTransformer transformer) {
808
809
        if (transformer == null) {
810
            return;
811
        }
812
813
        this.transformersWriteLock.lock();
814
        try {
815
            if (this.transformers.remove(transformer)) {
816
                log.info(sm.getString("webappClassLoader.removeTransformer",
817
                        transformer, getContextName()));
818
                return;
819
            }
820
        } finally {
821
            this.transformersWriteLock.unlock();
822
        }
823
824
    }
825
826
    /**
827
     * Returns a copy of this class loader without any class file
828
     * transformers. This is a tool often used by Java Persistence API
829
     * providers to inspect entity classes in the absence of any
830
     * instrumentation, something that can't be guaranteed within the
831
     * context of a {@link ClassFileTransformer}'s
832
     * {@link ClassFileTransformer#transform() transform} method.
833
     * <p>
834
     * The returned class loader's resource cache will have been cleared
835
     * so that classes already instrumented will not be retained or
836
     * returned.
837
     *
838
     * @return the transformer-free copy of this class loader.
839
     */
840
    @Override
841
    public WebappClassLoader copyWithoutTransformers() {
842
843
        WebappClassLoader loader = new WebappClassLoader(this.parent);
844
845
        loader.antiJARLocking = this.antiJARLocking;
846
        loader.resources = this.resources;
847
        loader.delegate = this.delegate;
848
        loader.lastJarAccessed = this.lastJarAccessed;
849
        loader.repository = this.repository;
850
        loader.file = this.file;
851
        loader.jarPath = this.jarPath;
852
        loader.loaderDir = this.loaderDir;
853
        loader.canonicalLoaderDir = this.canonicalLoaderDir;
854
        loader.started = this.started;
855
        loader.needConvert = this.needConvert;
856
        loader.clearReferencesStatic = this.clearReferencesStatic;
857
        loader.clearReferencesStopThreads = this.clearReferencesStopThreads;
858
        loader.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
859
        loader.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
860
        loader.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
861
862
        loader.repositoryURLs = this.repositoryURLs.clone();
863
        loader.jarFiles = this.jarFiles.clone();
864
        loader.jarRealFiles = this.jarRealFiles.clone();
865
        loader.jarNames = this.jarNames.clone();
866
        loader.lastModifiedDates = this.lastModifiedDates.clone();
867
        loader.paths = this.paths.clone();
868
869
        loader.notFoundResources.putAll(this.notFoundResources);
870
        loader.permissionList.addAll(this.permissionList);
871
        loader.loaderPC.putAll(this.loaderPC);
872
873
        return loader;
874
875
    }
876
877
    /**
738
     * Set the place this ClassLoader can look for classes to be loaded.
878
     * Set the place this ClassLoader can look for classes to be loaded.
739
     *
879
     *
740
     * @param repository Name of a source of classes to be loaded, such as a
880
     * @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());
1068
            sb.append(this.parent.toString());
929
            sb.append("\r\n");
1069
            sb.append("\r\n");
930
        }
1070
        }
1071
        if (this.transformers.size() > 0) {
1072
            sb.append("----------> Class file transformers:\r\n");
1073
            this.transformersReadLock.lock(); // prevent a concurrency exception in toString
1074
            try {
1075
                for (ClassFileTransformer transformer : this.transformers) {
1076
                    sb.append(transformer.getClass().getName()).append("\r\n");
1077
                }
1078
            } finally {
1079
                this.transformersReadLock.unlock();
1080
            }
1081
        }
931
        return (sb.toString());
1082
        return (sb.toString());
932
    }
1083
    }
Lines 1183-1189 Link Here
1183
                try {
1334
                try {
1184
                    String repository = entry.codeBase.toString();
1335
                    String repository = entry.codeBase.toString();
1185
                    if ((repository.endsWith(".jar"))
1336
                    if ((repository.endsWith(".jar"))
1186
                            && (!(name.endsWith(".class")))) {
1337
                            && (!(name.endsWith(CLASS_FILE_SUFFIX)))) {
1187
                        // Copy binary content to the work directory if not present
1338
                        // Copy binary content to the work directory if not present
1188
                        File resourceFile = new File(loaderDir, name);
1339
                        File resourceFile = new File(loaderDir, name);
1189
                        url = getURI(resourceFile);
1340
                        url = getURI(resourceFile);
Lines 2552-2558 Link Here
2552
            throw new ClassNotFoundException(name);
2703
            throw new ClassNotFoundException(name);
2553
        String tempPath = name.replace('.', '/');
2704
        String tempPath = name.replace('.', '/');
2554
        String classPath = tempPath + ".class";
2705
        String classPath = tempPath + CLASS_FILE_SUFFIX;
2555
        ResourceEntry entry = null;
2706
        ResourceEntry entry = null;
Lines 2669-2675 Link Here
2669
     *
2820
     *
2670
     * @return the loaded resource, or null if the resource isn't found
2821
     * @return the loaded resource, or null if the resource isn't found
2671
     */
2822
     */
2672
    protected ResourceEntry findResourceInternal(String name, String path) {
2823
    protected ResourceEntry findResourceInternal(final String name, final String path) {
2673
        if (!started) {
2824
        if (!started) {
2674
            log.info(sm.getString("webappClassLoader.stopped", name));
2825
            log.info(sm.getString("webappClassLoader.stopped", name));
Lines 2685-2691 Link Here
2685
        int contentLength = -1;
2836
        int contentLength = -1;
2686
        InputStream binaryStream = null;
2837
        InputStream binaryStream = null;
2687
        boolean isClassResource = path.endsWith(".class");
2838
        boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX);
2688
        int jarFilesLength = jarFiles.length;
2839
        int jarFilesLength = jarFiles.length;
Lines 2782-2788 Link Here
2782
                        }
2933
                        }
2783
                        // Extract resources contained in JAR to the workdir
2934
                        // Extract resources contained in JAR to the workdir
2784
                        if (antiJARLocking && !(path.endsWith(".class"))) {
2935
                        if (antiJARLocking && !(path.endsWith(CLASS_FILE_SUFFIX))) {
2785
                            byte[] buf = new byte[1024];
2936
                            byte[] buf = new byte[1024];
2786
                            File resourceFile = new File
2937
                            File resourceFile = new File
2787
                                (loaderDir, jarEntry.getName());
2938
                                (loaderDir, jarEntry.getName());
Lines 2793-2799 Link Here
2793
                                    JarEntry jarEntry2 =  entries.nextElement();
2944
                                    JarEntry jarEntry2 =  entries.nextElement();
2794
                                    if (!(jarEntry2.isDirectory())
2945
                                    if (!(jarEntry2.isDirectory())
2795
                                        && (!jarEntry2.getName().endsWith
2946
                                        && (!jarEntry2.getName().endsWith
2796
                                            (".class"))) {
2947
                                            (CLASS_FILE_SUFFIX))) {
2797
                                        resourceFile = new File
2948
                                        resourceFile = new File
2798
                                            (loaderDir, jarEntry2.getName());
2949
                                            (loaderDir, jarEntry2.getName());
2799
                                        try {
2950
                                        try {
Lines 2923-2928 Link Here
2923
            }
3074
            }
2924
        }
3075
        }
3076
        if (isClassResource && entry != null && entry.binaryContent != null &&
3077
            this.transformers.size() > 0) {
3078
            // If the resource is a class just being loaded, decorate it
3079
            // with any attached transformers
3080
            String className = name.endsWith(CLASS_FILE_SUFFIX) ?
3081
                    name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name;
3082
            String internalName = className.replace(".", "/");
3083
3084
            this.transformersReadLock.lock();
3085
            try {
3086
                for (ClassFileTransformer transformer : this.transformers) {
3087
                    try {
3088
                        byte[] transformed = transformer.transform(
3089
                                this, internalName, null, null, entry.binaryContent
3090
                        );
3091
                        if (transformed != null) {
3092
                            entry.binaryContent = transformed;
3093
                        }
3094
                    } catch (IllegalClassFormatException e) {
3095
                        log.error(sm.getString("webappClassLoader.transformError", name), e);
3096
                        return null;
3097
                    }
3098
                }
3099
            } finally {
3100
                this.transformersReadLock.unlock();
3101
            }
3102
        }
3103
2925
        // Add the entry in the local resource repository
3104
        // Add the entry in the local resource repository
2926
        synchronized (resourceEntries) {
3105
        synchronized (resourceEntries) {
2927
            // Ensures that all the threads which may be in a race to load
3106
            // Ensures that all the threads which may be in a race to load
Lines 3126-3132 Link Here
3126
                }
3305
                }
3127
                if (clazz == null)
3306
                if (clazz == null)
3128
                    continue;
3307
                    continue;
3129
                String name = triggers[i].replace('.', '/') + ".class";
3308
                String name = triggers[i].replace('.', '/') + CLASS_FILE_SUFFIX;
3130
                if (log.isDebugEnabled())
3309
                if (log.isDebugEnabled())
3131
                    log.debug(" Checking for " + name);
3310
                    log.debug(" Checking for " + name);
3132
                JarEntry jarEntry = jarFile.getJarEntry(name);
3311
                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 (+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