/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.axis2.classloader; import org.apache.commons.logging.LogFactory; import java.beans.Introspector; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; /** * 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 ClassLoader[] parents; private final boolean inverseClassLoading; private final String[] hiddenClasses; private final String[] nonOverridableClasses; private final String[] hiddenResources; private final String[] nonOverridableResources; private boolean destroyed = false; /** * Creates a named class loader with no parents. * * @param urls the urls from which this class loader will classes and resources */ public MultiParentClassLoader(URL[] urls) { super(urls); parents = new ClassLoader[]{ClassLoader.getSystemClassLoader()}; inverseClassLoading = false; hiddenClasses = new String[0]; nonOverridableClasses = new String[0]; hiddenResources = new String[0]; nonOverridableResources = new String[0]; } /** * Creates a named class loader as a child of the specified parent. * * @param urls the urls from which this class loader will classes and resources * @param parent the parent of this class loader */ public MultiParentClassLoader(URL[] urls, ClassLoader parent) { this(urls, new ClassLoader[]{parent}); } public MultiParentClassLoader(URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { this(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 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(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { this(urls, new ClassLoader[]{parent}, factory); } /** * Creates a named class loader as a child of the specified parents. * * @param urls the urls from which this class loader will classes and resources * @param parents the parents of this class loader */ public MultiParentClassLoader(URL[] urls, ClassLoader[] parents) { super(urls); this.parents = copyParents(parents); inverseClassLoading = false; hiddenClasses = new String[0]; nonOverridableClasses = new String[0]; hiddenResources = new String[0]; nonOverridableResources = new String[0]; } public MultiParentClassLoader(URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, Collection hiddenClasses, Collection nonOverridableClasses) { this(urls, parents, inverseClassLoading, (String[]) hiddenClasses.toArray(new String[hiddenClasses.size()]), (String[]) nonOverridableClasses.toArray(new String[nonOverridableClasses.size()])); } public MultiParentClassLoader(URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { super(urls); this.parents = copyParents(parents); this.inverseClassLoading = inverseClassLoading; this.hiddenClasses = hiddenClasses; this.nonOverridableClasses = nonOverridableClasses; hiddenResources = toResources(hiddenClasses); nonOverridableResources = toResources(nonOverridableClasses); } public MultiParentClassLoader(MultiParentClassLoader source) { this(source.getURLs(), deepCopyParents(source.parents), source.inverseClassLoading, source.hiddenClasses, source.nonOverridableClasses); } static ClassLoader copy(ClassLoader source) { if (source instanceof MultiParentClassLoader) { return new MultiParentClassLoader((MultiParentClassLoader) source); } else if (source instanceof URLClassLoader) { return new URLClassLoader(((URLClassLoader) source).getURLs(), source.getParent()); } else { return new URLClassLoader(new URL[0], source); } } ClassLoader copy() { return MultiParentClassLoader.copy(this); } private String[] toResources(String[] classes) { String[] resources = new String[classes.length]; for (int i = 0; i < classes.length; i++) { String className = classes[i]; resources[i] = className.replace('.', '/'); } return resources; } /** * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory * for accessing the urls.. * * @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(URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) { super(urls, null, factory); this.parents = copyParents(parents); inverseClassLoading = false; hiddenClasses = new String[0]; nonOverridableClasses = new String[0]; hiddenResources = new String[0]; nonOverridableResources = 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 RuntimeException("parent[" + i + "] is null"); } newParentsArray[i] = parent; } return newParentsArray; } private static ClassLoader[] deepCopyParents(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 RuntimeException("parent[" + i + "] is null"); } if (parent instanceof MultiParentClassLoader) { parent = ((MultiParentClassLoader) parent).copy(); } newParentsArray[i] = parent; } return newParentsArray; } /** * Gets the parents of this class loader. * * @return the parents of this class loader */ public ClassLoader[] getParents() { return parents; } public void addURL(URL url) { // todo this needs a security check super.addURL(url); } protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // // Check if class is in the loaded classes cache // Class cachedClass = findLoadedClass(name); if (cachedClass != null) { return resolveClass(cachedClass, resolve); } // // if we are using inverse class loading, check local urls first // if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) { try { Class clazz = findClass(name); return resolveClass(clazz, resolve); } catch (ClassNotFoundException ignored) { } } // // Check parent class loaders // if (!isHiddenClass(name)) { for (int i = 0; i < parents.length; i++) { ClassLoader parent = parents[i]; try { Class clazz = parent.loadClass(name); return resolveClass(clazz, resolve); } catch (ClassNotFoundException ignored) { // this parent didn't have the class; try the next one // TODO REVIEW FOR JAVA 6 // In Java 5, if you passed an array string such as "[Lcom.mypackage.MyClass;" to // loadClass, the class would indeed be loaded. // In JDK6, a ClassNotFoundException is thrown. // The work-around is to use code Class.forName instead. // Example: // try { // classLoader.loadClass(name); // } catch (ClassNotFoundException e) { // Class.forName(name, false, loader); // } } } } // // if we are not using inverse class loading, check local urls now // // don't worry about excluding non-overridable classes here... we // have alredy checked he parent and the parent didn't have the // class, so we can override now if (!isDestroyed()) { try { Class clazz = findClass(name); return resolveClass(clazz, resolve); } catch (ClassNotFoundException ignored) { } } throw new ClassNotFoundException(name); } private boolean isNonOverridableClass(String name) { for (int i = 0; i < nonOverridableClasses.length; i++) { if (name.startsWith(nonOverridableClasses[i])) { return true; } } return false; } private boolean isHiddenClass(String name) { for (int i = 0; i < hiddenClasses.length; i++) { if (name.startsWith(hiddenClasses[i])) { return true; } } return false; } private Class resolveClass(Class clazz, boolean resolve) { if (resolve) { resolveClass(clazz); } return clazz; } public URL getResource(String name) { if (isDestroyed()) { return null; } // // if we are using inverse class loading, check local urls first // if (inverseClassLoading && !isDestroyed() && !isNonOverridableResource(name)) { URL url = findResource(name); if (url != null) { return url; } } // // Check parent class loaders // if (!isHiddenResource(name)) { for (int i = 0; i < parents.length; i++) { ClassLoader parent = parents[i]; URL url = parent.getResource(name); if (url != null) { return url; } } } // // if we are not using inverse class loading, check local urls now // // don't worry about excluding non-overridable resources here... we // have alredy checked he parent and the parent didn't have the // resource, so we can override now if (!isDestroyed()) { // parents didn't have the resource; attempt to load it from my urls return findResource(name); } return null; } public Enumeration findResources(String name) throws IOException { if (isDestroyed()) { return Collections.enumeration(Collections.EMPTY_SET); } List resources = new ArrayList(); // // if we are using inverse class loading, add the resources from local urls first // if (inverseClassLoading && !isDestroyed()) { List myResources = Collections.list(super.findResources(name)); resources.addAll(myResources); } // // Add parent resources // for (int i = 0; i < parents.length; i++) { ClassLoader parent = parents[i]; List parentResources = Collections.list(parent.getResources(name)); resources.addAll(parentResources); } // // if we are not using inverse class loading, add the resources from local urls now // if (!inverseClassLoading && !isDestroyed()) { List myResources = Collections.list(super.findResources(name)); resources.addAll(myResources); } return Collections.enumeration(resources); } private boolean isNonOverridableResource(String name) { for (int i = 0; i < nonOverridableResources.length; i++) { if (name.startsWith(nonOverridableResources[i])) { return true; } } return false; } private boolean isHiddenResource(String name) { for (int i = 0; i < hiddenResources.length; i++) { if (name.startsWith(hiddenResources[i])) { return true; } } return false; } public String toString() { return "[" + getClass().getName() + "]"; } public synchronized boolean isDestroyed() { return destroyed; } public void destroy() { synchronized (this) { if (destroyed) { return; } destroyed = true; } 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 final 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(); // } // } // } }