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.util.Assert;
026import org.springframework.util.ClassUtils;
027
028/**
029 * {@link org.springframework.instrument.classloading.LoadTimeWeaver} implementation
030 * for Tomcat's new {@code org.apache.tomcat.InstrumentableClassLoader}.
031 * Also capable of handling Spring's TomcatInstrumentableClassLoader when encountered.
032 *
033 * @author Juergen Hoeller
034 * @since 4.0
035 */
036public class TomcatLoadTimeWeaver implements LoadTimeWeaver {
037
038        private static final String INSTRUMENTABLE_LOADER_CLASS_NAME = "org.apache.tomcat.InstrumentableClassLoader";
039
040
041        private final ClassLoader classLoader;
042
043        private final Method addTransformerMethod;
044
045        private final Method copyMethod;
046
047
048        public TomcatLoadTimeWeaver() {
049                this(ClassUtils.getDefaultClassLoader());
050        }
051
052        public TomcatLoadTimeWeaver(ClassLoader classLoader) {
053                Assert.notNull(classLoader, "ClassLoader must not be null");
054                this.classLoader = classLoader;
055
056                Class<?> instrumentableLoaderClass;
057                try {
058                        instrumentableLoaderClass = classLoader.loadClass(INSTRUMENTABLE_LOADER_CLASS_NAME);
059                        if (!instrumentableLoaderClass.isInstance(classLoader)) {
060                                // Could still be a custom variant of a convention-compatible ClassLoader
061                                instrumentableLoaderClass = classLoader.getClass();
062                        }
063                }
064                catch (ClassNotFoundException ex) {
065                        // We're on an earlier version of Tomcat, probably with Spring's TomcatInstrumentableClassLoader
066                        instrumentableLoaderClass = classLoader.getClass();
067                }
068
069                try {
070                        this.addTransformerMethod = instrumentableLoaderClass.getMethod("addTransformer", ClassFileTransformer.class);
071                        // Check for Tomcat's new copyWithoutTransformers on InstrumentableClassLoader first
072                        Method copyMethod = ClassUtils.getMethodIfAvailable(instrumentableLoaderClass, "copyWithoutTransformers");
073                        if (copyMethod == null) {
074                                // Fallback: expecting TomcatInstrumentableClassLoader's getThrowawayClassLoader
075                                copyMethod = instrumentableLoaderClass.getMethod("getThrowawayClassLoader");
076                        }
077                        this.copyMethod = copyMethod;
078                }
079                catch (Throwable ex) {
080                        throw new IllegalStateException(
081                                        "Could not initialize TomcatLoadTimeWeaver because Tomcat API classes are not available", ex);
082                }
083        }
084
085
086        @Override
087        public void addTransformer(ClassFileTransformer transformer) {
088                try {
089                        this.addTransformerMethod.invoke(this.classLoader, transformer);
090                }
091                catch (InvocationTargetException ex) {
092                        throw new IllegalStateException("Tomcat addTransformer method threw exception", ex.getCause());
093                }
094                catch (Throwable ex) {
095                        throw new IllegalStateException("Could not invoke Tomcat addTransformer method", ex);
096                }
097        }
098
099        @Override
100        public ClassLoader getInstrumentableClassLoader() {
101                return this.classLoader;
102        }
103
104        @Override
105        public ClassLoader getThrowawayClassLoader() {
106                try {
107                        return new OverridingClassLoader(this.classLoader, (ClassLoader) this.copyMethod.invoke(this.classLoader));
108                }
109                catch (InvocationTargetException ex) {
110                        throw new IllegalStateException("Tomcat copy method threw exception", ex.getCause());
111                }
112                catch (Throwable ex) {
113                        throw new IllegalStateException("Could not invoke Tomcat copy method", ex);
114                }
115        }
116
117}