001: /*
002: * Copyright 2002-2006 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: * http://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:
017: package org.springframework.instrument.classloading.tomcat;
018:
019: import java.lang.instrument.ClassFileTransformer;
020: import java.lang.reflect.Field;
021: import java.lang.reflect.Modifier;
022:
023: import org.apache.catalina.loader.ResourceEntry;
024: import org.apache.catalina.loader.WebappClassLoader;
025:
026: import org.springframework.instrument.classloading.WeavingTransformer;
027:
028: /**
029: * Extension of Tomcat's default class loader which adds instrumentation
030: * to loaded classes without the need of using a VM-wide agent.
031: *
032: * <p>To be registered using a <code>Loader</code> tag in Tomcat's <code>Context</code>
033: * definition in the <code>server.xml</code> file, with the Spring-provided
034: * "spring-tomcat-weaver.jar" file deployed into Tomcat's "server/lib" directory.
035: * The required configuration tag looks as follows:
036: *
037: * <pre>
038: * <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/></pre>
039: *
040: * <p>Typically used in combination with a
041: * {@link org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver}
042: * defined in the Spring application context. The <code>addTransformer</code> and
043: * <code>getThrowawayClassLoader</code> methods mirror the corresponding methods
044: * in the LoadTimeWeaver interface, as expected by ReflectiveLoadTimeWeaver.
045: *
046: * <p>See the PetClinic sample application for a full example of this
047: * ClassLoader in action.
048: *
049: * <p><b>NOTE:</b> Requires Apache Tomcat version 5.0 or higher.
050: *
051: * @author Costin Leau
052: * @since 2.0
053: * @see #addTransformer
054: * @see #getThrowawayClassLoader
055: * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
056: */
057: public class TomcatInstrumentableClassLoader extends WebappClassLoader {
058:
059: /** Use an internal WeavingTransformer */
060: private final WeavingTransformer weavingTransformer;
061:
062: public TomcatInstrumentableClassLoader() {
063: super ();
064: this .weavingTransformer = new WeavingTransformer();
065: }
066:
067: public TomcatInstrumentableClassLoader(ClassLoader classLoader) {
068: super (classLoader);
069: this .weavingTransformer = new WeavingTransformer(classLoader);
070: }
071:
072: /**
073: * Delegate for LoadTimeWeaver's <code>addTransformer</code> method.
074: * Typically called through ReflectiveLoadTimeWeaver.
075: * @see org.springframework.instrument.classloading.LoadTimeWeaver#addTransformer
076: * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
077: */
078: public void addTransformer(ClassFileTransformer transformer) {
079: this .weavingTransformer.addTransformer(transformer);
080: }
081:
082: /**
083: * Delegate for LoadTimeWeaver's <code>getThrowawayClassLoader</code> method.
084: * Typically called through ReflectiveLoadTimeWeaver.
085: * @see org.springframework.instrument.classloading.LoadTimeWeaver#getThrowawayClassLoader
086: * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
087: */
088: public ClassLoader getThrowawayClassLoader() {
089: WebappClassLoader tempLoader = new WebappClassLoader();
090: // Use reflection to copy all the fields since most of them are private
091: // on pre-5.5 Tomcat.
092: shallowCopyFieldState(this , tempLoader);
093: return tempLoader;
094: }
095:
096: @Override
097: protected ResourceEntry findResourceInternal(String name,
098: String path) {
099: ResourceEntry entry = super .findResourceInternal(name, path);
100: // Postpone String parsing as much as possible (it is slow).
101: if (entry != null && entry.binaryContent != null
102: && path.endsWith(".class")) {
103: byte[] transformed = this .weavingTransformer
104: .transformIfNecessary(name, entry.binaryContent);
105: entry.binaryContent = transformed;
106: }
107: return entry;
108: }
109:
110: @Override
111: public String toString() {
112: StringBuilder sb = new StringBuilder(getClass().getName());
113: sb.append("\r\n");
114: sb.append(super .toString());
115: return sb.toString();
116: }
117:
118: // The code below is orginially taken from ReflectionUtils and optimized for
119: // local usage. There is no dependency on ReflectionUtils to keep this class
120: // self-contained (since it gets deployed into Tomcat's server class loader).
121:
122: /**
123: * Given the source object and the destination, which must be the same class
124: * or a subclass, copy all fields, including inherited fields. Designed to
125: * work on objects with public no-arg constructors.
126: * @throws IllegalArgumentException if arguments are incompatible or either
127: * is <code>null</code>
128: */
129: private static void shallowCopyFieldState(final Object src,
130: final Object dest) throws IllegalArgumentException {
131: if (src == null) {
132: throw new IllegalArgumentException(
133: "Source for field copy cannot be null");
134: }
135: if (dest == null) {
136: throw new IllegalArgumentException(
137: "Destination for field copy cannot be null");
138: }
139: Class targetClass = findCommonAncestor(src.getClass(), dest
140: .getClass());
141:
142: // Keep backing up the inheritance hierarchy.
143: do {
144: // Copy each field declared on this class unless it's static or
145: // file.
146: Field[] fields = targetClass.getDeclaredFields();
147: for (int i = 0; i < fields.length; i++) {
148: Field field = fields[i];
149: // Skip static and final fields (the old FieldFilter)
150: // do not copy resourceEntries - it's a cache that holds class entries.
151: if (!(Modifier.isStatic(field.getModifiers())
152: || Modifier.isFinal(field.getModifiers()) || field
153: .getName().equals("resourceEntries"))) {
154: try {
155: // copy the field (the old FieldCallback)
156: field.setAccessible(true);
157: Object srcValue = field.get(src);
158: field.set(dest, srcValue);
159: } catch (IllegalAccessException ex) {
160: throw new IllegalStateException(
161: "Shouldn't be illegal to access field '"
162: + fields[i].getName() + "': "
163: + ex);
164: }
165: }
166: }
167: targetClass = targetClass.getSuperclass();
168: } while (targetClass != null && targetClass != Object.class);
169: }
170:
171: private static Class findCommonAncestor(Class one, Class two)
172: throws IllegalArgumentException {
173: Class ancestor = one;
174: while (ancestor != Object.class || ancestor != null) {
175: if (ancestor.isAssignableFrom(two)) {
176: return ancestor;
177: }
178: ancestor = ancestor.getSuperclass();
179: }
180: // try the other class hierarchy
181: ancestor = two;
182: while (ancestor != Object.class || ancestor != null) {
183: if (ancestor.isAssignableFrom(one)) {
184: return ancestor;
185: }
186: ancestor = ancestor.getSuperclass();
187: }
188: return null;
189: }
190:
191: }
|