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.core;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.Collections;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.Set;
025:
026: import org.springframework.util.Assert;
027: import org.springframework.util.FileCopyUtils;
028:
029: /**
030: * <code>ClassLoader</code> that does <i>not</i> always delegate to the
031: * parent loader, as normal class loaders do. This enables, for example,
032: * instrumentation to be forced in the overriding ClassLoader, or a
033: * "throwaway" class loading behavior, where selected classes are
034: * temporarily loaded in the overriding ClassLoader, in order to be load
035: * an instrumented version of the class in the parent ClassLoader later on.
036: *
037: * @author Rod Johnson
038: * @author Juergen Hoeller
039: * @since 2.0.1
040: */
041: public class OverridingClassLoader extends ClassLoader {
042:
043: private static final String CLASS_FILE_SUFFIX = ".class";
044:
045: private final Set excludedPackages = Collections
046: .synchronizedSet(new HashSet());
047:
048: private final Set excludedClasses = Collections
049: .synchronizedSet(new HashSet());
050:
051: /**
052: * Create a new OverridingClassLoader for the given class loader.
053: * @param parent the ClassLoader to build an overriding ClassLoader for
054: */
055: public OverridingClassLoader(ClassLoader parent) {
056: super (parent);
057: this .excludedPackages.add("java.");
058: this .excludedPackages.add("javax.");
059: this .excludedPackages.add("sun.");
060: }
061:
062: /**
063: * Add a package name to exclude from overriding.
064: * <p>Any class whose fully-qualified name starts with the name registered
065: * here will be handled by the parent ClassLoader in the usual fashion.
066: * @param packageName the package name to exclude
067: */
068: public void excludePackage(String packageName) {
069: Assert.notNull(packageName, "Package name must not be null");
070: this .excludedPackages.add(packageName);
071: }
072:
073: /**
074: * Add a class name to exclude from overriding.
075: * <p>Any class name registered here will be handled by
076: * the parent ClassLoader in the usual fashion.
077: * @param className the class name to exclude
078: */
079: public void excludeClass(String className) {
080: Assert.notNull(className, "Class name must not be null");
081: this .excludedClasses.add(className);
082: }
083:
084: protected Class loadClass(String name, boolean resolve)
085: throws ClassNotFoundException {
086: Class result = null;
087:
088: if (isEligibleForOverriding(name)) {
089: result = findLoadedClass(name);
090: if (result == null) {
091: String internalName = name.replace('.', '/')
092: + CLASS_FILE_SUFFIX;
093: InputStream is = getParent().getResourceAsStream(
094: internalName);
095: if (is != null) {
096: try {
097: // Load the raw bytes.
098: byte[] bytes = FileCopyUtils
099: .copyToByteArray(is);
100: // Transform if necessary and use the potentially transformed bytes.
101: byte[] transformed = transformIfNecessary(name,
102: bytes);
103: result = defineClass(name, transformed, 0,
104: transformed.length);
105: } catch (IOException ex) {
106: throw new ClassNotFoundException(
107: "Cannot load resource for class ["
108: + name + "]", ex);
109: }
110: }
111: }
112: }
113:
114: if (result != null) {
115: if (resolve) {
116: resolveClass(result);
117: }
118: return result;
119: } else {
120: return super .loadClass(name, resolve);
121: }
122: }
123:
124: /**
125: * Determine whether the specified class is eligible for overriding
126: * by this class loader.
127: * <p>The default implementation checks against excluded packages and classes.
128: * @param className the class name to check
129: * @return whether the specified class is eligible
130: * @see #excludePackage
131: * @see #excludeClass
132: */
133: protected boolean isEligibleForOverriding(String className) {
134: if (this .excludedClasses.contains(className)) {
135: return false;
136: }
137: for (Iterator it = this .excludedPackages.iterator(); it
138: .hasNext();) {
139: String packageName = (String) it.next();
140: if (className.startsWith(packageName)) {
141: return false;
142: }
143: }
144: return true;
145: }
146:
147: /**
148: * Transformation hook to be implemented by subclasses.
149: * <p>The default implementation simply returns the given bytes as-is.
150: * @param name the fully-qualified name of the class being transformed
151: * @param bytes the raw bytes of the class
152: * @return the transformed bytes (never <code>null</code>;
153: * same as the input bytes if the transformation produced no changes)
154: */
155: protected byte[] transformIfNecessary(String name, byte[] bytes) {
156: return bytes;
157: }
158:
159: }
|