/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.context.support.internal.classloader;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.gemini.blueprint.util.internal.ClassUtils;
import org.springframework.util.Assert;
/**
* Chaining class loader implementation that delegates the resource and class loading to a number of class loaders
* passed in.
*
* <p/> This class loader parent (by default the AppClassLoader) can be specified and will be added automatically as the
* last entry in the list.
*
* <p/> Additionally, the class space of this class loader can be extended at runtime (by allowing more class loaders to
* be added).
*
* <strong>Note:</strong>non-OSGi class loaders are considered as special cases. As there are classes that are loaded by
* the Boot, Ext, App and Fwk ClassLoaders through boot delegation, this implementation tries to identify them and place
* them last in the chain. Otherwise, these loaders can pull in classes from outside OSGi causing
* {@link ClassCastException}s.
*
* @author Costin Leau
*/
public class ChainedClassLoader extends ClassLoader {
/** list of loaders */
private final List<ClassLoader> loaders = new ArrayList<ClassLoader>();
/** list of special, non-osgi loaders, added by the user */
private final List<ClassLoader> nonOsgiLoaders = new ArrayList<ClassLoader>();
/** parent class loader */
private final ClassLoader parent;
/**
* Constructs a new <code>ChainedClassLoader</code> instance.
*
* Equivalent to {@link #ChainedClassLoader(ClassLoader[], ClassLoader)} with the parent class loader initialized to
* the AppClassLoader (practically the system bundle class loader).
*
* Note that the AppClassLoader can be different then the {@link #getSystemClassLoader()}, used by
* {@link #ChainedClassLoader(ClassLoader[], ClassLoader)} if no parent is specified.
*
* @param loaders array of non-null class loaders
*/
public ChainedClassLoader(ClassLoader[] loaders) {
this(loaders, ClassUtils.getFwkClassLoader());
}
/**
* Constructs a new <code>ChainedClassLoader</code> instance.
*
* @param loaders array of non-null class loaders
* @param parent parent class loader (can be null)
*/
public ChainedClassLoader(ClassLoader[] loaders, ClassLoader parent) {
super(parent);
this.parent = parent;
Assert.notEmpty(loaders);
synchronized (this.loaders) {
for (int i = 0; i < loaders.length; i++) {
ClassLoader classLoader = loaders[i];
Assert.notNull(classLoader, "null classloaders not allowed");
addClassLoader(classLoader);
}
}
}
public URL getResource(final String name) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<URL>() {
public URL run() {
return doGetResource(name);
}
});
} else {
return doGetResource(name);
}
}
private URL doGetResource(String name) {
URL url = doGetResource(name, loaders);
if (url != null) {
return url;
} else {
url = doGetResource(name, nonOsgiLoaders);
}
if (url != null) {
return url;
}
return (parent != null ? parent.getResource(name) : url);
}
private URL doGetResource(String name, List<ClassLoader> classLoaders) {
URL url = null;
synchronized (classLoaders) {
for (int i = 0; i < classLoaders.size(); i++) {
ClassLoader loader = classLoaders.get(i);
url = loader.getResource(name);
if (url != null)
return url;
}
}
return url;
}
public Class<?> loadClass(final String name) throws ClassNotFoundException {
if (System.getSecurityManager() != null) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws Exception {
return doLoadClass(name);
}
});
} catch (PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
} else {
return doLoadClass(name);
}
}
private Class<?> doLoadClass(String name) throws ClassNotFoundException {
Class<?> clazz = doLoadClass(name, loaders);
if (clazz != null) {
return clazz;
} else {
clazz = doLoadClass(name, nonOsgiLoaders);
}
if (clazz != null) {
return clazz;
}
if (parent != null) {
return parent.loadClass(name);
}
else {
throw new ClassNotFoundException(name);
}
}
private Class<?> doLoadClass(String name, List<ClassLoader> classLoaders) throws ClassNotFoundException {
Class<?> clazz = null;
synchronized (classLoaders) {
for (int i = 0; i < classLoaders.size(); i++) {
ClassLoader loader = classLoaders.get(i);
try {
clazz = loader.loadClass(name);
return clazz;
} catch (ClassNotFoundException e) {
// keep moving through the class loaders
}
}
}
return clazz;
}
/**
* Adds a class loader defining the given class, to the chained class loader space.
*
* @param clazz
*/
public void addClassLoader(final Class<?> clazz) {
Assert.notNull(clazz, "a non-null class required");
if (System.getSecurityManager() != null) {
addClassLoader(AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return ClassUtils.getClassLoader(clazz);
}
}));
} else {
addClassLoader(ClassUtils.getClassLoader(clazz));
}
}
/**
* Adds the given class loader to the existing list.
*
* @param classLoader class loader to load classes from
*/
public void addClassLoader(ClassLoader classLoader) {
Assert.notNull(classLoader, "a non-null classLoader required");
if (!addNonOsgiLoader(classLoader)) {
addOsgiLoader(classLoader);
}
}
/**
* Checks if the given classloader is a known, non-OSGi loader. If it is, then it is added to a specific list based
* on the known ordering (ignoring the user defined one). This is done so that the discovered hierarchy is respected
* regardless of the user configuration.
*
* @param classLoader
* @return true if the class loader was added/is known, false otherwise
*/
private boolean addNonOsgiLoader(ClassLoader classLoader) {
// check if the classloader is known or not before doing any locking
if (ClassUtils.knownNonOsgiLoadersSet.contains(classLoader)) {
synchronized (nonOsgiLoaders) {
if (!nonOsgiLoaders.contains(classLoader)) {
int index = ClassUtils.knownNonOsgiLoaders.indexOf(classLoader);
// add the class loader to the list if there is a match
if (index >= 0) {
int insertIndex = 0;
// but consider the defined order
for (int i = 0; i < nonOsgiLoaders.size(); i++) {
int presentLoaderIndex = ClassUtils.knownNonOsgiLoaders.indexOf(nonOsgiLoaders.get(i));
if (presentLoaderIndex >= 0 && presentLoaderIndex < index) {
insertIndex = i + 1;
} else {
continue;
}
}
nonOsgiLoaders.add(insertIndex, classLoader);
return true;
}
}
}
return true;
}
return false;
}
private void addOsgiLoader(ClassLoader classLoader) {
synchronized (loaders) {
if (!loaders.contains(classLoader)) {
loaders.add(classLoader);
}
}
}
}