blob: c51758544ba23b31a04e80cd7a5c9e60796acf9c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.loader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.*;
import java.security.cert.Certificate;
import java.util.*;
import org.eclipse.osgi.container.ModuleRevision;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
import org.eclipse.osgi.internal.loader.classpath.ClasspathEntry;
import org.eclipse.osgi.internal.loader.classpath.ClasspathManager;
import org.eclipse.osgi.signedcontent.SignedContent;
import org.eclipse.osgi.signedcontent.SignerInfo;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.bundlefile.BundleFile;
import org.eclipse.osgi.storage.bundlefile.BundleFileWrapperChain;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleReference;
public abstract class ModuleClassLoader extends ClassLoader implements BundleReference {
public static class GenerationProtectionDomain extends ProtectionDomain implements BundleReference {
private final Generation generation;
public GenerationProtectionDomain(CodeSource codesource, PermissionCollection permissions, Generation generation) {
super(codesource, permissions);
this.generation = generation;
}
public Bundle getBundle() {
return generation.getRevision().getBundle();
}
}
/**
* A PermissionCollection for AllPermissions; shared across all ProtectionDomains when security is disabled
*/
protected static final PermissionCollection ALLPERMISSIONS;
protected static final boolean REGISTERED_AS_PARALLEL;
static {
AllPermission allPerm = new AllPermission();
ALLPERMISSIONS = allPerm.newPermissionCollection();
if (ALLPERMISSIONS != null)
ALLPERMISSIONS.add(allPerm);
boolean registeredAsParallel;
try {
Method parallelCapableMetod = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable", (Class[]) null); //$NON-NLS-1$
parallelCapableMetod.setAccessible(true);
registeredAsParallel = ((Boolean) parallelCapableMetod.invoke(null, (Object[]) null)).booleanValue();
} catch (Throwable e) {
// must do everything to avoid failing in clinit
registeredAsParallel = false;
}
REGISTERED_AS_PARALLEL = registeredAsParallel;
}
/**
* Holds the result of a defining a class.
*
*/
public static class DefineClassResult {
/**
* The class object that either got defined or was found as previously loaded.
*/
public final Class<?> clazz;
/**
* Set to true if the class object got defined; set to false if the class
* did not get defined correctly or the class was found as a previously loaded
* class.
*/
public final boolean defined;
public DefineClassResult(Class<?> clazz, boolean defined) {
this.clazz = clazz;
this.defined = defined;
}
}
private final Map<String, Thread> classNameLocks = new HashMap<String, Thread>(5);
private final Object pkgLock = new Object();
/**
* Constructs a new ModuleClassLoader.
* @param parent the parent classloader
*/
public ModuleClassLoader(ClassLoader parent) {
super(parent);
}
/**
* Returns the generation of the host revision associated with this class loader
* @return the generation for this class loader
*/
protected abstract Generation getGeneration();
/**
* Returns the Debug object for the Framework instance
* @return the Debug object for the Framework instance
*/
protected abstract Debug getDebug();
/**
* Returns the classpath manager for this class loader
* @return the classpath manager for this class loader
*/
public abstract ClasspathManager getClasspathManager();
/**
* Returns the configuration for the Framework instance
* @return the configuration for the Framework instance
*/
protected abstract EquinoxConfiguration getConfiguration();
/**
* Returns the bundle loader for this class loader
* @return the bundle loader for this class loader
*/
public abstract BundleLoader getBundleLoader();
/**
* Returns true if this class loader implementation has been
* registered with the JVM as a parallel class loader.
* This requires Java 7 or later.
* @return true if this class loader implementation has been
* registered with the JVM as a parallel class loader; otherwise
* false is returned.
*/
public abstract boolean isRegisteredAsParallel();
/**
* Loads a class for the bundle. First delegate.findClass(name) is called.
* The delegate will query the system class loader, bundle imports, bundle
* local classes, bundle hosts and fragments. The delegate will call
* BundleClassLoader.findLocalClass(name) to find a class local to this
* bundle.
* @param name the name of the class to load.
* @param resolve indicates whether to resolve the loaded class or not.
* @return The Class object.
* @throws ClassNotFoundException if the class is not found.
*/
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (getDebug().DEBUG_LOADER)
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].loadClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
try {
// Just ask the delegate. This could result in findLocalClass(name) being called.
Class<?> clazz = getBundleLoader().findClass(name);
// resolve the class if asked to.
if (resolve)
resolveClass(clazz);
return (clazz);
} catch (Error e) {
if (getDebug().DEBUG_LOADER) {
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].loadClass(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Debug.printStackTrace(e);
}
throw e;
} catch (ClassNotFoundException e) {
// If the class is not found do not try to look for it locally.
// The delegate would have already done that for us.
if (getDebug().DEBUG_LOADER) {
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].loadClass(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Debug.printStackTrace(e);
}
throw e;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return findLocalClass(name);
}
/**
* Gets a resource for the bundle. First delegate.findResource(name) is
* called. The delegate will query the system class loader, bundle imports,
* bundle local resources, bundle hosts and fragments. The delegate will
* call BundleClassLoader.findLocalResource(name) to find a resource local
* to this bundle.
* @param name The resource path to get.
* @return The URL of the resource or null if it does not exist.
*/
public URL getResource(String name) {
if (getDebug().DEBUG_LOADER) {
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResource(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
URL url = getBundleLoader().findResource(name);
if (url != null)
return (url);
if (getDebug().DEBUG_LOADER) {
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResource(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return (null);
}
@Override
protected URL findResource(String name) {
return findLocalResource(name);
}
/**
* Gets resources for the bundle. First delegate.findResources(name) is
* called. The delegate will query the system class loader, bundle imports,
* bundle local resources, bundle hosts and fragments. The delegate will
* call BundleClassLoader.findLocalResources(name) to find a resource local
* to this bundle.
* @param name The resource path to get.
* @return The Enumeration of the resource URLs.
*/
public Enumeration<URL> getResources(String name) throws IOException {
if (getDebug().DEBUG_LOADER) {
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResources(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Enumeration<URL> result = getBundleLoader().findResources(name);
if (getDebug().DEBUG_LOADER) {
if (result == null || !result.hasMoreElements()) {
Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResources(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
return result;
}
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
return findLocalResources(name);
}
/**
* Finds a library for this bundle. Simply calls
* manager.findLibrary(libname) to find the library.
* @param libname The library to find.
* @return The absolution path to the library or null if not found
*/
protected String findLibrary(String libname) {
// let the manager find the library for us
return getClasspathManager().findLibrary(libname);
}
public ClasspathEntry createClassPathEntry(BundleFile bundlefile, Generation entryGeneration) {
return new ClasspathEntry(bundlefile, createProtectionDomain(bundlefile, entryGeneration), entryGeneration);
}
public DefineClassResult defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry) {
// Note that we must check findLoadedClass again here since no locks are held between
// calling findLoadedClass the first time and defineClass.
// This is to allow weavers to get called while holding no locks.
// See ClasspathManager.findLocalClass(String)
boolean defined = false;
Class<?> result = null;
if (isRegisteredAsParallel()) {
// lock by class name in this case
boolean initialLock = lockClassName(name);
try {
result = findLoadedClass(name);
if (result == null) {
result = defineClass(name, classbytes, 0, classbytes.length, classpathEntry.getDomain());
defined = true;
}
} finally {
if (initialLock) {
unlockClassName(name);
}
}
} else {
// lock by class loader instance in this case
synchronized (this) {
result = findLoadedClass(name);
if (result == null) {
result = defineClass(name, classbytes, 0, classbytes.length, classpathEntry.getDomain());
defined = true;
}
}
}
return new DefineClassResult(result, defined);
}
public Class<?> publicFindLoaded(String classname) {
if (isRegisteredAsParallel()) {
boolean initialLock = lockClassName(classname);
try {
return findLoadedClass(classname);
} finally {
if (initialLock)
unlockClassName(classname);
}
}
synchronized (this) {
return findLoadedClass(classname);
}
}
public Package publicGetPackage(String pkgname) {
synchronized (pkgLock) {
return getPackage(pkgname);
}
}
public Package publicDefinePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) {
synchronized (pkgLock) {
Package pkg = getPackage(name);
return pkg != null ? pkg : definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
}
}
public URL findLocalResource(String resource) {
return getClasspathManager().findLocalResource(resource);
}
public Enumeration<URL> findLocalResources(String resource) {
return getClasspathManager().findLocalResources(resource);
}
public Class<?> findLocalClass(String classname) throws ClassNotFoundException {
return getClasspathManager().findLocalClass(classname);
}
/**
* Creates a ProtectionDomain which uses specified BundleFile and the permissions of the baseDomain
* @param bundlefile The source bundlefile the domain is for.
* @param domainGeneration the source generation for the domain
* @return a ProtectionDomain which uses specified BundleFile and the permissions of the baseDomain
*/
@SuppressWarnings("deprecation")
protected ProtectionDomain createProtectionDomain(BundleFile bundlefile, Generation domainGeneration) {
// create a protection domain which knows about the codesource for this classpath entry (bug 89904)
ProtectionDomain baseDomain = domainGeneration.getDomain();
try {
// use the permissions supplied by the domain passed in from the framework
PermissionCollection permissions;
if (baseDomain != null) {
permissions = baseDomain.getPermissions();
} else {
// no domain specified. Better use a collection that has all permissions
// this is done just incase someone sets the security manager later
permissions = ALLPERMISSIONS;
}
Certificate[] certs = null;
SignedContent signedContent = null;
if (bundlefile instanceof BundleFileWrapperChain) {
BundleFileWrapperChain wrapper = (BundleFileWrapperChain) bundlefile;
while (wrapper != null && (!(wrapper.getWrapped() instanceof SignedContent)))
wrapper = wrapper.getNext();
signedContent = wrapper == null ? null : (SignedContent) wrapper.getWrapped();
}
if (getConfiguration().CLASS_CERTIFICATE && signedContent != null && signedContent.isSigned()) {
SignerInfo[] signers = signedContent.getSignerInfos();
if (signers.length > 0)
certs = signers[0].getCertificateChain();
}
return new GenerationProtectionDomain(new CodeSource(bundlefile.getBaseFile().toURL(), certs), permissions, getGeneration());
//return new ProtectionDomain(new CodeSource(bundlefile.getBaseFile().toURL(), certs), permissions);
} catch (MalformedURLException e) {
// Failed to create our own domain; just return the baseDomain
return baseDomain;
}
}
public Bundle getBundle() {
return getGeneration().getRevision().getBundle();
}
public List<URL> findEntries(String path, String filePattern, int options) {
return getClasspathManager().findEntries(path, filePattern, options);
}
public Collection<String> listResources(String path, String filePattern, int options) {
return getBundleLoader().listResources(path, filePattern, options);
}
public Collection<String> listLocalResources(String path, String filePattern, int options) {
return getClasspathManager().listLocalResources(path, filePattern, options);
}
public String toString() {
Bundle b = getBundle();
StringBuffer result = new StringBuffer(super.toString());
if (b == null)
return result.toString();
return result.append('[').append(b.getSymbolicName()).append(':').append(b.getVersion()).append("(id=").append(b.getBundleId()).append(")]").toString(); //$NON-NLS-1$//$NON-NLS-2$
}
public void loadFragments(Collection<ModuleRevision> fragments) {
getClasspathManager().loadFragments(fragments);
}
private boolean lockClassName(String classname) {
synchronized (classNameLocks) {
Object lockingThread = classNameLocks.get(classname);
Thread current = Thread.currentThread();
if (lockingThread == current)
return false;
boolean previousInterruption = Thread.interrupted();
try {
while (true) {
if (lockingThread == null) {
classNameLocks.put(classname, current);
return true;
}
classNameLocks.wait();
lockingThread = classNameLocks.get(classname);
}
} catch (InterruptedException e) {
current.interrupt();
throw (LinkageError) new LinkageError(classname).initCause(e);
} finally {
if (previousInterruption) {
current.interrupt();
}
}
}
}
private void unlockClassName(String classname) {
synchronized (classNameLocks) {
classNameLocks.remove(classname);
classNameLocks.notifyAll();
}
}
public void close() {
getClasspathManager().close();
}
}