/**
*
* Copyright 2005 The Apache Software Foundation
*
* 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.apache.geronimo.kernel.config;
import org.apache.commons.logging.LogFactory;
import java.beans.Introspector;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
/**
* A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class
* loader model to support a list of parent class loaders. Each operation that accesses a parent, has been replaced
* with a operation that checks each parent in order. This getParent method of this class will always return null,
* which may be interpreted by the calling code to mean that this class loader is a direct child of the system class
* loader.
* @version $Rev$ $Date$
*/
public class MultiParentClassLoader extends URLClassLoader {
private final URI id;
private final ClassLoader[] parents;
private final boolean inverseClassLoading;
private final String[] hiddenClasses;
private final String[] nonOverridableClasses;
/**
* Creates a named class loader with no parents.
* @param id the id of this class loader
* @param urls the urls from which this class loader will classes and resources
*/
public MultiParentClassLoader(URI id, URL[] urls) {
super(urls);
this.id = id;
parents = new ClassLoader[0];
inverseClassLoading = false;
hiddenClasses = new String[0];
nonOverridableClasses = new String[0];
}
/**
* Creates a named class loader as a child of the specified parent.
* @param id the id of this class loader
* @param urls the urls from which this class loader will classes and resources
* @param parent the parent of this class loader
*/
public MultiParentClassLoader(URI id, URL[] urls, ClassLoader parent) {
this(id, urls, new ClassLoader[] {parent});
}
public MultiParentClassLoader(URI id, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
this(id, urls, new ClassLoader[] {parent}, inverseClassLoading, hiddenClasses, nonOverridableClasses);
}
/**
* Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
* for accessing the urls..
* @param id the id of this class loader
* @param urls the urls from which this class loader will classes and resources
* @param parent the parent of this class loader
* @param factory the URLStreamHandlerFactory used to access the urls
*/
public MultiParentClassLoader(URI id, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
this(id, urls, new ClassLoader[] {parent}, factory);
}
/**
* Creates a named class loader as a child of the specified parents.
* @param id the id of this class loader
* @param urls the urls from which this class loader will classes and resources
* @param parents the parents of this class loader
*/
public MultiParentClassLoader(URI id, URL[] urls, ClassLoader[] parents) {
super(urls);
this.id = id;
this.parents = copyParents(parents);
inverseClassLoading = false;
hiddenClasses = new String[0];
nonOverridableClasses = new String[0];
}
public MultiParentClassLoader(URI id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
super(urls, new FilteringParentCL(hiddenClasses));
this.id = id;
this.parents = copyParents(parents);
this.inverseClassLoading = inverseClassLoading;
this.hiddenClasses = hiddenClasses;
this.nonOverridableClasses = nonOverridableClasses;
}
/**
* Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory
* for accessing the urls..
* @param id the id of this class loader
* @param urls the urls from which this class loader will classes and resources
* @param parents the parents of this class loader
* @param factory the URLStreamHandlerFactory used to access the urls
*/
public MultiParentClassLoader(URI id, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) {
super(urls, null, factory);
this.id = id;
this.parents = copyParents(parents);
inverseClassLoading = false;
hiddenClasses = new String[0];
nonOverridableClasses = new String[0];
}
private static ClassLoader[] copyParents(ClassLoader[] parents) {
ClassLoader[] newParentsArray = new ClassLoader[parents.length];
for (int i = 0; i < parents.length; i++) {
ClassLoader parent = parents[i];
if (parent == null) {
throw new NullPointerException("parent[" + i + "] is null");
}
newParentsArray[i] = parent;
}
return newParentsArray;
}
/**
* Gets the id of this class loader.
* @return the id of this class loader
*/
public URI getId() {
return id;
}
/**
* Gets the parents of this class loader.
* @return the parents of this class loader
*/
public ClassLoader[] getParents() {
return parents;
}
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = findLoadedClass(name);
if (null != clazz) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
if (inverseClassLoading) {
boolean checkMe = true;
for (int i = 0; i < nonOverridableClasses.length && checkMe; i++) {
if (name.startsWith(nonOverridableClasses[i])) {
checkMe = false;
}
}
if (checkMe) {
try {
clazz = findClass(name);
if (resolve) {
resolveClass(clazz);
}
return clazz;
} catch (ClassNotFoundException ignored) {
}
}
}
boolean checkParents = true;
for (int i = 0; i < hiddenClasses.length && checkParents; i++) {
if (name.startsWith(hiddenClasses[i])) {
checkParents = false;
}
}
if (checkParents) {
for (int i = 0; i < parents.length && clazz == null; i++) {
ClassLoader parent = parents[i];
try {
clazz = parent.loadClass(name);
} catch (ClassNotFoundException ignored) {
// this parent didn't have the class; try the next one
}
}
}
if (null == clazz) {
return super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
public URL getResource(String name) {
URL url = null;
if (inverseClassLoading) {
url = super.getResource(name);
if (null != url) {
return url;
}
}
for (int i = 0; i < parents.length && url == null; i++) {
ClassLoader parent = parents[i];
url = parent.getResource(name);
}
if (url == null && false == inverseClassLoading) {
// parents didn't have the resource; attempt to load it from my urls
return super.getResource(name);
} else {
return url;
}
}
public Enumeration findResources(String name) throws IOException {
List resources = new ArrayList();
// Add resources from all parents
for (int i = 0; i < parents.length; i++) {
ClassLoader parent = parents[i];
List parentResources = Collections.list(parent.getResources(name));
resources.addAll(parentResources);
}
// Add the resources from my urls
List myResources = Collections.list(super.findResources(name));
resources.addAll(myResources);
// return an enumeration over the list
return Collections.enumeration(resources);
}
public String toString() {
return "[" + getClass().getName() + " id=" + id + "]";
}
public void destroy() {
LogFactory.release(this);
clearSoftCache(ObjectInputStream.class, "subclassAudits");
clearSoftCache(ObjectOutputStream.class, "subclassAudits");
clearSoftCache(ObjectStreamClass.class, "localDescs");
clearSoftCache(ObjectStreamClass.class, "reflectors");
// The beanInfoCache in java.beans.Introspector will hold on to Classes which
// it has introspected. If we don't flush the cache, we may run out of
// Permanent Generation space.
Introspector.flushCaches();
}
private static class FilteringParentCL extends ClassLoader {
private final String[] hiddenClasses;
public FilteringParentCL(String[] hiddenClasses) {
this.hiddenClasses = hiddenClasses;
}
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
boolean checkParents = true;
for (int i = 0; i < hiddenClasses.length && checkParents; i++) {
if (name.startsWith(hiddenClasses[i])) {
checkParents = false;
}
}
if (checkParents) {
return super.loadClass(name, resolve);
}
throw new ClassNotFoundException(name);
}
}
private static Object lock = new Object();
private static boolean clearSoftCacheFailed = false;
private static void clearSoftCache(Class clazz, String fieldName) {
Map cache = null;
try {
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
cache = (Map) f.get(null);
} catch (Throwable e) {
synchronized (lock) {
if (!clearSoftCacheFailed) {
clearSoftCacheFailed = true;
LogFactory.getLog(ConfigurationClassLoader.class).error("Unable to clear SoftCache field " + fieldName + " in class " + clazz);
}
}
}
if (cache != null) {
synchronized (cache) {
cache.clear();
}
}
}
}