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