001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.instrument.classloading.tomcat;
018
019import java.lang.instrument.ClassFileTransformer;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022
023import org.springframework.core.OverridingClassLoader;
024import org.springframework.instrument.classloading.LoadTimeWeaver;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027import org.springframework.util.ClassUtils;
028
029/**
030 * {@link org.springframework.instrument.classloading.LoadTimeWeaver} implementation
031 * for Tomcat's new {@code org.apache.tomcat.InstrumentableClassLoader}.
032 * Also capable of handling Spring's TomcatInstrumentableClassLoader when encountered.
033 *
034 * @author Juergen Hoeller
035 * @since 4.0
036 */
037public class TomcatLoadTimeWeaver implements LoadTimeWeaver {
038
039        private static final String INSTRUMENTABLE_LOADER_CLASS_NAME = "org.apache.tomcat.InstrumentableClassLoader";
040
041
042        private final ClassLoader classLoader;
043
044        private final Method addTransformerMethod;
045
046        private final Method copyMethod;
047
048
049        /**
050         * Create a new instance of the {@link TomcatLoadTimeWeaver} class using
051         * the default {@link ClassLoader class loader}.
052         * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
053         */
054        public TomcatLoadTimeWeaver() {
055                this(ClassUtils.getDefaultClassLoader());
056        }
057
058        /**
059         * Create a new instance of the {@link TomcatLoadTimeWeaver} class using
060         * the supplied {@link ClassLoader}.
061         * @param classLoader the {@code ClassLoader} to delegate to for weaving
062         */
063        public TomcatLoadTimeWeaver(@Nullable ClassLoader classLoader) {
064                Assert.notNull(classLoader, "ClassLoader must not be null");
065                this.classLoader = classLoader;
066
067                Class<?> instrumentableLoaderClass;
068                try {
069                        instrumentableLoaderClass = classLoader.loadClass(INSTRUMENTABLE_LOADER_CLASS_NAME);
070                        if (!instrumentableLoaderClass.isInstance(classLoader)) {
071                                // Could still be a custom variant of a convention-compatible ClassLoader
072                                instrumentableLoaderClass = classLoader.getClass();
073                        }
074                }
075                catch (ClassNotFoundException ex) {
076                        // We're on an earlier version of Tomcat, probably with Spring's TomcatInstrumentableClassLoader
077                        instrumentableLoaderClass = classLoader.getClass();
078                }
079
080                try {
081                        this.addTransformerMethod = instrumentableLoaderClass.getMethod("addTransformer", ClassFileTransformer.class);
082                        // Check for Tomcat's new copyWithoutTransformers on InstrumentableClassLoader first
083                        Method copyMethod = ClassUtils.getMethodIfAvailable(instrumentableLoaderClass, "copyWithoutTransformers");
084                        if (copyMethod == null) {
085                                // Fallback: expecting TomcatInstrumentableClassLoader's getThrowawayClassLoader
086                                copyMethod = instrumentableLoaderClass.getMethod("getThrowawayClassLoader");
087                        }
088                        this.copyMethod = copyMethod;
089                }
090                catch (Throwable ex) {
091                        throw new IllegalStateException(
092                                        "Could not initialize TomcatLoadTimeWeaver because Tomcat API classes are not available", ex);
093                }
094        }
095
096
097        @Override
098        public void addTransformer(ClassFileTransformer transformer) {
099                try {
100                        this.addTransformerMethod.invoke(this.classLoader, transformer);
101                }
102                catch (InvocationTargetException ex) {
103                        throw new IllegalStateException("Tomcat addTransformer method threw exception", ex.getCause());
104                }
105                catch (Throwable ex) {
106                        throw new IllegalStateException("Could not invoke Tomcat addTransformer method", ex);
107                }
108        }
109
110        @Override
111        public ClassLoader getInstrumentableClassLoader() {
112                return this.classLoader;
113        }
114
115        @Override
116        public ClassLoader getThrowawayClassLoader() {
117                try {
118                        return new OverridingClassLoader(this.classLoader, (ClassLoader) this.copyMethod.invoke(this.classLoader));
119                }
120                catch (InvocationTargetException ex) {
121                        throw new IllegalStateException("Tomcat copy method threw exception", ex.getCause());
122                }
123                catch (Throwable ex) {
124                        throw new IllegalStateException("Could not invoke Tomcat copy method", ex);
125                }
126        }
127
128}