diff --git a/build-spring-framework/build.xml b/build-spring-framework/build.xml index 1f595b74fb1..fa932eb5c86 100644 --- a/build-spring-framework/build.xml +++ b/build-spring-framework/build.xml @@ -3,6 +3,7 @@ + diff --git a/org.springframework.instrument.classloading/build.xml b/org.springframework.instrument.classloading/build.xml new file mode 100644 index 00000000000..1b09555968b --- /dev/null +++ b/org.springframework.instrument.classloading/build.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/org.springframework.instrument.classloading/ivy.xml b/org.springframework.instrument.classloading/ivy.xml new file mode 100644 index 00000000000..e12614ee219 --- /dev/null +++ b/org.springframework.instrument.classloading/ivy.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.instrument.classloading/pom.xml b/org.springframework.instrument.classloading/pom.xml new file mode 100644 index 00000000000..838a192ade2 --- /dev/null +++ b/org.springframework.instrument.classloading/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + org.springframework + org.springframework.parent + 3.0-M1-SNAPSHOT + + org.springframework.agent + jar + Spring Framework: Agent + \ No newline at end of file diff --git a/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/WeavingTransformer.java b/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/WeavingTransformer.java new file mode 100644 index 00000000000..bcae2c2f5cc --- /dev/null +++ b/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/WeavingTransformer.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.instrument.classloading; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; + +/** + * ClassFileTransformer-based weaver, allowing for a list of transformers to be + * applied on a class byte array. Normally used inside class loaders. + * + *

Note: This class is deliberately implemented for minimal external dependencies, + * since it is included in weaver jars (to be deployed into application servers). + * + * @author Rod Johnson + * @author Costin Leau + * @author Juergen Hoeller + * @since 2.0 + */ +public class WeavingTransformer { + + private final ClassLoader classLoader; + + private final List transformers = new ArrayList(); + + + /** + * Create a new WeavingTransformer for the given class loader. + * @param classLoader the ClassLoader to build a transformer for + */ + public WeavingTransformer(ClassLoader classLoader) { + if (classLoader == null) { + throw new IllegalArgumentException("ClassLoader must not be null"); + } + this.classLoader = classLoader; + } + + + /** + * Add a class file transformer to be applied by this weaver. + * @param transformer the class file transformer to register + */ + public void addTransformer(ClassFileTransformer transformer) { + if (transformer == null) { + throw new IllegalArgumentException("Transformer must not be null"); + } + this.transformers.add(transformer); + } + + + /** + * Apply transformation on a given class byte definition. + * The method will always return a non-null byte array (if no transformation has taken place + * the array content will be identical to the original one). + * @param className the full qualified name of the class in dot format (i.e. some.package.SomeClass) + * @param bytes class byte definition + * @return (possibly transformed) class byte definition + */ + public byte[] transformIfNecessary(String className, byte[] bytes) { + String internalName = className.replace(".", "/"); + return transformIfNecessary(className, internalName, bytes, null); + } + + /** + * Apply transformation on a given class byte definition. + * The method will always return a non-null byte array (if no transformation has taken place + * the array content will be identical to the original one). + * @param className the full qualified name of the class in dot format (i.e. some.package.SomeClass) + * @param internalName class name internal name in / format (i.e. some/package/SomeClass) + * @param bytes class byte definition + * @param pd protection domain to be used (can be null) + * @return (possibly transformed) class byte definition + */ + public byte[] transformIfNecessary(String className, String internalName, byte[] bytes, ProtectionDomain pd) { + byte[] result = bytes; + for (ClassFileTransformer cft : this.transformers) { + try { + byte[] transformed = cft.transform(this.classLoader, internalName, null, pd, result); + if (transformed != null) { + result = transformed; + } + } + catch (IllegalClassFormatException ex) { + throw new IllegalStateException("Class file transformation failed", ex); + } + } + return result; + } + +} diff --git a/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java b/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java new file mode 100644 index 00000000000..cb66e5d7cfd --- /dev/null +++ b/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/tomcat/TomcatInstrumentableClassLoader.java @@ -0,0 +1,196 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.instrument.classloading.tomcat; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import org.apache.catalina.loader.ResourceEntry; +import org.apache.catalina.loader.WebappClassLoader; + +import org.springframework.instrument.classloading.WeavingTransformer; + +/** + * Extension of Tomcat's default class loader which adds instrumentation + * to loaded classes without the need to use a VM-wide agent. + * + *

To be registered using a Loader tag in Tomcat's Context + * definition in the server.xml file, with the Spring-provided + * "spring-tomcat-weaver.jar" file deployed into Tomcat's "server/lib" directory. + * The required configuration tag looks as follows: + * + *

<Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
+ * + *

Typically used in combination with a + * {@link org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver} + * defined in the Spring application context. The addTransformer and + * getThrowawayClassLoader methods mirror the corresponding methods + * in the LoadTimeWeaver interface, as expected by ReflectiveLoadTimeWeaver. + * + *

See the PetClinic sample application for a full example of this + * ClassLoader in action. + * + *

NOTE: Requires Apache Tomcat version 5.0 or higher. + * + * @author Costin Leau + * @author Juergen Hoeller + * @since 2.0 + * @see #addTransformer + * @see #getThrowawayClassLoader + * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver + */ +public class TomcatInstrumentableClassLoader extends WebappClassLoader { + + /** Use an internal WeavingTransformer */ + private final WeavingTransformer weavingTransformer; + + + /** + * Create a new TomcatInstrumentableClassLoader using the + * current context class loader. + * @see #TomcatInstrumentableClassLoader(ClassLoader) + */ + public TomcatInstrumentableClassLoader() { + super(); + this.weavingTransformer = new WeavingTransformer(this); + } + + /** + * Create a new TomcatInstrumentableClassLoader with the + * supplied class loader as parent. + * @param parent the parent {@link ClassLoader} to be used + */ + public TomcatInstrumentableClassLoader(ClassLoader parent) { + super(parent); + this.weavingTransformer = new WeavingTransformer(this); + } + + + /** + * Delegate for LoadTimeWeaver's addTransformer method. + * Typically called through ReflectiveLoadTimeWeaver. + * @see org.springframework.instrument.classloading.LoadTimeWeaver#addTransformer + * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver + */ + public void addTransformer(ClassFileTransformer transformer) { + this.weavingTransformer.addTransformer(transformer); + } + + /** + * Delegate for LoadTimeWeaver's getThrowawayClassLoader method. + * Typically called through ReflectiveLoadTimeWeaver. + * @see org.springframework.instrument.classloading.LoadTimeWeaver#getThrowawayClassLoader + * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver + */ + public ClassLoader getThrowawayClassLoader() { + WebappClassLoader tempLoader = new WebappClassLoader(); + // Use reflection to copy all the fields since most of them are private + // on pre-5.5 Tomcat. + shallowCopyFieldState(this, tempLoader); + return tempLoader; + } + + + @Override + protected ResourceEntry findResourceInternal(String name, String path) { + ResourceEntry entry = super.findResourceInternal(name, path); + // Postpone String parsing as much as possible (it is slow). + if (entry != null && entry.binaryContent != null && path.endsWith(".class")) { + byte[] transformed = this.weavingTransformer.transformIfNecessary(name, entry.binaryContent); + entry.binaryContent = transformed; + } + return entry; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(getClass().getName()); + sb.append("\r\n"); + sb.append(super.toString()); + return sb.toString(); + } + + + // The code below is orginially taken from ReflectionUtils and optimized for + // local usage. There is no dependency on ReflectionUtils to keep this class + // self-contained (since it gets deployed into Tomcat's server class loader). + + /** + * Given the source object and the destination, which must be the same class + * or a subclass, copy all fields, including inherited fields. Designed to + * work on objects with public no-arg constructors. + * @throws IllegalArgumentException if arguments are incompatible or either + * is null + */ + private static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException { + if (src == null) { + throw new IllegalArgumentException("Source for field copy cannot be null"); + } + if (dest == null) { + throw new IllegalArgumentException("Destination for field copy cannot be null"); + } + Class targetClass = findCommonAncestor(src.getClass(), dest.getClass()); + + // Keep backing up the inheritance hierarchy. + do { + // Copy each field declared on this class unless it's static or + // file. + Field[] fields = targetClass.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + // Skip static and final fields (the old FieldFilter) + // do not copy resourceEntries - it's a cache that holds class entries. + if (!(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()) || + field.getName().equals("resourceEntries"))) { + try { + // copy the field (the old FieldCallback) + field.setAccessible(true); + Object srcValue = field.get(src); + field.set(dest, srcValue); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException( + "Shouldn't be illegal to access field '" + fields[i].getName() + "': " + ex); + } + } + } + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + } + + private static Class findCommonAncestor(Class one, Class two) throws IllegalArgumentException { + Class ancestor = one; + while (ancestor != Object.class || ancestor != null) { + if (ancestor.isAssignableFrom(two)) { + return ancestor; + } + ancestor = ancestor.getSuperclass(); + } + // try the other class hierarchy + ancestor = two; + while (ancestor != Object.class || ancestor != null) { + if (ancestor.isAssignableFrom(one)) { + return ancestor; + } + ancestor = ancestor.getSuperclass(); + } + return null; + } + +} diff --git a/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/tomcat/package.html b/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/tomcat/package.html new file mode 100644 index 00000000000..e2c41af28c0 --- /dev/null +++ b/org.springframework.instrument.classloading/src/main/java/org/springframework/instrument/classloading/tomcat/package.html @@ -0,0 +1,7 @@ + + + +Support for class instrumentation on Apache Tomcat. + + + diff --git a/org.springframework.instrument.classloading/src/main/java/overview.html b/org.springframework.instrument.classloading/src/main/java/overview.html new file mode 100644 index 00000000000..1eb7a2e8c19 --- /dev/null +++ b/org.springframework.instrument.classloading/src/main/java/overview.html @@ -0,0 +1,7 @@ + + +

+The Spring Data Binding framework, an internal library used by Spring Web Flow. +

+ + \ No newline at end of file diff --git a/org.springframework.instrument.classloading/src/test/resources/log4j.xml b/org.springframework.instrument.classloading/src/test/resources/log4j.xml new file mode 100644 index 00000000000..767b96d6206 --- /dev/null +++ b/org.springframework.instrument.classloading/src/test/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.instrument.classloading/template.mf b/org.springframework.instrument.classloading/template.mf new file mode 100644 index 00000000000..cfb085d25cf --- /dev/null +++ b/org.springframework.instrument.classloading/template.mf @@ -0,0 +1,6 @@ +Bundle-SymbolicName: org.springframework.instrument.classloading +Bundle-Name: Spring Instrument Classloading +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + org.apache.catalina.*;version="[6.0.16, 7.0.0)"