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;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.lang.instrument.ClassFileTransformer;
022: import java.lang.instrument.IllegalClassFormatException;
023: import java.net.URL;
024: import java.util.Collections;
025: import java.util.Enumeration;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.LinkedList;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.Set;
033:
034: import org.springframework.util.Assert;
035: import org.springframework.util.FileCopyUtils;
036: import org.springframework.util.StringUtils;
037:
038: /**
039: * ClassLoader decorator that shadows an enclosing ClassLoader, applying
040: * registered transformers to all affected classes.
041: *
042: * @author Rob Harrop
043: * @author Rod Johnson
044: * @author Costin Leau
045: * @author Juergen Hoeller
046: * @since 2.0
047: * @see #addTransformer
048: * @see org.springframework.core.OverridingClassLoader
049: */
050: public class ShadowingClassLoader extends ClassLoader {
051:
052: /** Packages that are excluded by default */
053: public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[] {
054: "java.", "javax.", "sun.", "com.sun.", "org.w3c.",
055: "org.xml.", "org.dom4j.", "org.aspectj.",
056: "org.apache.xerces.", "org.apache.commons.logging." };
057:
058: private final ClassLoader enclosingClassLoader;
059:
060: private final Set excludedPackages = Collections
061: .synchronizedSet(new HashSet());
062:
063: private final Set excludedClasses = Collections
064: .synchronizedSet(new HashSet());
065:
066: private final List<ClassFileTransformer> classFileTransformers = new LinkedList<ClassFileTransformer>();
067:
068: private final Map<String, Class> classCache = new HashMap<String, Class>();
069:
070: /**
071: * Create a new ShadowingClassLoader, decorating the given ClassLoader.
072: * @param enclosingClassLoader the ClassLoader to decorate
073: */
074: public ShadowingClassLoader(ClassLoader enclosingClassLoader) {
075: Assert.notNull(enclosingClassLoader,
076: "Enclosing ClassLoader must not be null");
077: this .enclosingClassLoader = enclosingClassLoader;
078: for (int i = 0; i < DEFAULT_EXCLUDED_PACKAGES.length; i++) {
079: this .excludedPackages.add(DEFAULT_EXCLUDED_PACKAGES[i]);
080: }
081: }
082:
083: /**
084: * Add a package name to exclude from shadowing.
085: * <p>Any class whose fully-qualified name starts with the name registered
086: * here will be handled by the enclosing ClassLoader in the usual fashion.
087: * @param packageName the package name to exclude
088: */
089: public void excludePackage(String packageName) {
090: Assert.notNull(packageName, "Package name must not be null");
091: this .excludedPackages.add(packageName);
092: }
093:
094: /**
095: * Add a class name to exclude from shadowing.
096: * <p>Any class name registered here will be handled by
097: * the enclosing ClassLoader in the usual fashion.
098: * @param className the class name to exclude
099: */
100: public void excludeClass(String className) {
101: Assert.notNull(className, "Class name must not be null");
102: this .excludedClasses.add(className);
103: }
104:
105: /**
106: * Add the given ClassFileTransformer to the list of transformers that this
107: * ClassLoader will apply.
108: * @param transformer the ClassFileTransformer
109: */
110: public void addTransformer(ClassFileTransformer transformer) {
111: Assert.notNull(transformer, "Transformer must not be null");
112: this .classFileTransformers.add(transformer);
113: }
114:
115: /**
116: * Copy all ClassFileTransformers from the given ClassLoader to the list of
117: * transformers that this ClassLoader will apply.
118: * @param other the ClassLoader to copy from
119: */
120: public void copyTransformers(ShadowingClassLoader other) {
121: Assert.notNull(other, "Other ClassLoader must not be null");
122: this .classFileTransformers.addAll(other.classFileTransformers);
123: }
124:
125: public Class<?> loadClass(String name)
126: throws ClassNotFoundException {
127: if (shouldShadow(name)) {
128: Class cls = this .classCache.get(name);
129: if (cls != null) {
130: return cls;
131: }
132: return doLoadClass(name);
133: } else {
134: return this .enclosingClassLoader.loadClass(name);
135: }
136: }
137:
138: /**
139: * Determine whether the given class should be excluded from shadowing.
140: * @param className the name of the class
141: * @return whether the specified class should be shadowed
142: */
143: private boolean shouldShadow(String className) {
144: return (!className.equals(getClass().getName())
145: && !className.endsWith("ShadowingClassLoader")
146: && isEligibleForShadowing(className) && !isClassNameExcludedFromShadowing(className));
147: }
148:
149: /**
150: * Determine whether the specified class is eligible for shadowing
151: * by this class loader.
152: * <p>The default implementation checks against excluded packages and classes.
153: * @param className the class name to check
154: * @return whether the specified class is eligible
155: * @see #excludePackage
156: * @see #excludeClass
157: */
158: protected boolean isEligibleForShadowing(String className) {
159: if (this .excludedClasses.contains(className)) {
160: return false;
161: }
162: for (Iterator it = this .excludedPackages.iterator(); it
163: .hasNext();) {
164: String packageName = (String) it.next();
165: if (className.startsWith(packageName)) {
166: return false;
167: }
168: }
169: return true;
170: }
171:
172: /**
173: * Subclasses can override this method to specify whether or not a
174: * particular class should be excluded from shadowing.
175: * @param className the class name to test
176: * @return whether the specified class is excluded
177: * @deprecated in favor of {@link #isEligibleForShadowing}
178: */
179: protected boolean isClassNameExcludedFromShadowing(String className) {
180: return false;
181: }
182:
183: private Class doLoadClass(String name)
184: throws ClassNotFoundException {
185: String internalName = StringUtils.replace(name, ".", "/")
186: + ".class";
187: InputStream is = this .enclosingClassLoader
188: .getResourceAsStream(internalName);
189: if (is == null) {
190: throw new ClassNotFoundException(name);
191: }
192: try {
193: byte[] bytes = FileCopyUtils.copyToByteArray(is);
194: bytes = applyTransformers(name, bytes);
195: Class cls = defineClass(name, bytes, 0, bytes.length);
196: // Additional check for defining the package, if not defined yet.
197: if (cls.getPackage() == null) {
198: String packageName = name.substring(0, name
199: .lastIndexOf('.'));
200: definePackage(packageName, null, null, null, null,
201: null, null, null);
202: }
203: this .classCache.put(name, cls);
204: return cls;
205: } catch (IOException ex) {
206: throw new ClassNotFoundException(
207: "Cannot load resource for class [" + name + "]", ex);
208: }
209: }
210:
211: private byte[] applyTransformers(String name, byte[] bytes) {
212: String internalName = StringUtils.replace(name, ".", "/");
213: try {
214: for (ClassFileTransformer transformer : this .classFileTransformers) {
215: byte[] transformed = transformer.transform(this ,
216: internalName, null, null, bytes);
217: bytes = (transformed != null ? transformed : bytes);
218: }
219: return bytes;
220: } catch (IllegalClassFormatException ex) {
221: throw new IllegalStateException(ex);
222: }
223: }
224:
225: public URL getResource(String name) {
226: return this .enclosingClassLoader.getResource(name);
227: }
228:
229: public InputStream getResourceAsStream(String name) {
230: return this .enclosingClassLoader.getResourceAsStream(name);
231: }
232:
233: public Enumeration<URL> getResources(String name)
234: throws IOException {
235: return this.enclosingClassLoader.getResources(name);
236: }
237:
238: }
|