blob: ae867f25fda6a5cb18b1f868cf9c89ba49673b58 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2009 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.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.osgi.framework.adaptor.*;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.internal.core.*;
import org.eclipse.osgi.framework.util.KeyedHashSet;
import org.eclipse.osgi.internal.loader.buddy.PolicyHandler;
import org.eclipse.osgi.service.resolver.*;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
/**
* This object is responsible for all classloader delegation for a bundle.
* It represents the loaded state of the bundle. BundleLoader objects
* are created lazily; care should be taken not to force the creation
* of a BundleLoader unless it is necessary.
* @see org.eclipse.osgi.internal.loader.BundleLoaderProxy
*/
public class BundleLoader implements ClassLoaderDelegate {
public final static String DEFAULT_PACKAGE = "."; //$NON-NLS-1$
public final static String JAVA_PACKAGE = "java."; //$NON-NLS-1$
public final static byte FLAG_IMPORTSINIT = 0x01;
public final static byte FLAG_HASDYNAMICIMPORTS = 0x02;
public final static byte FLAG_HASDYNAMICEIMPORTALL = 0x04;
public final static byte FLAG_CLOSED = 0x08;
public final static ClassContext CLASS_CONTEXT = (ClassContext) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new ClassContext();
}
});
public final static ClassLoader FW_CLASSLOADER = getClassLoader(Framework.class);
private static final int PRE_CLASS = 1;
private static final int POST_CLASS = 2;
private static final int PRE_RESOURCE = 3;
private static final int POST_RESOURCE = 4;
private static final int PRE_RESOURCES = 5;
private static final int POST_RESOURCES = 6;
private static final int PRE_LIBRARY = 7;
private static final int POST_LIBRARY = 8;
private static final boolean USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK = "true".equals(BundleLoaderProxy.secureAction.getProperty("osgi.classloader.singleThreadLoads")); //$NON-NLS-1$//$NON-NLS-2$
private static final List waitingList = USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK ? new ArrayList(0) : null;
private static Object lockThread;
private static int lockCount = 0;
/* the proxy */
final private BundleLoaderProxy proxy;
/* Bundle object */
final BundleHost bundle;
final private PolicyHandler policy;
/* List of package names that are exported by this BundleLoader */
final private Collection exportedPackages;
final private Collection substitutedPackages;
/* List of required bundle BundleLoaderProxy objects */
final BundleLoaderProxy[] requiredBundles;
/* List of indexes into the requiredBundles list of reexported bundles */
final int[] reexportTable;
/* cache of required package sources. Key is packagename, value is PackageSource */
final private KeyedHashSet requiredSources;
// note that the following non-final must be access using synchronization
/* cache of imported packages. Key is packagename, Value is PackageSource */
private KeyedHashSet importedSources;
/* If not null, list of package stems to import dynamically. */
private String[] dynamicImportPackageStems;
/* If not null, list of package names to import dynamically. */
private String[] dynamicImportPackages;
/* loader flags */
private byte loaderFlags = 0;
/* The is the BundleClassLoader for the bundle */
private BundleClassLoader classloader;
private ClassLoader parent;
/**
* Returns the package name from the specified class name.
* The returned package is dot seperated.
*
* @param name Name of a class.
* @return Dot separated package name or null if the class
* has no package name.
*/
public final static String getPackageName(String name) {
if (name != null) {
int index = name.lastIndexOf('.'); /* find last period in class name */
if (index > 0)
return name.substring(0, index);
}
return DEFAULT_PACKAGE;
}
/**
* Returns the package name from the specified resource name.
* The returned package is dot seperated.
*
* @param name Name of a resource.
* @return Dot separated package name or null if the resource
* has no package name.
*/
public final static String getResourcePackageName(String name) {
if (name != null) {
/* check for leading slash*/
int begin = ((name.length() > 1) && (name.charAt(0) == '/')) ? 1 : 0;
int end = name.lastIndexOf('/'); /* index of last slash */
if (end > begin)
return name.substring(begin, end).replace('/', '.');
}
return DEFAULT_PACKAGE;
}
/**
* BundleLoader runtime constructor. This object is created lazily
* when the first request for a resource is made to this bundle.
*
* @param bundle Bundle object for this loader.
* @param proxy the BundleLoaderProxy for this loader.
* @exception org.osgi.framework.BundleException
*/
protected BundleLoader(BundleHost bundle, BundleLoaderProxy proxy) throws BundleException {
this.bundle = bundle;
this.proxy = proxy;
try {
bundle.getBundleData().open(); /* make sure the BundleData is open */
} catch (IOException e) {
throw new BundleException(Msg.BUNDLE_READ_EXCEPTION, e);
}
BundleDescription description = proxy.getBundleDescription();
// init the require bundles list.
BundleDescription[] required = description.getResolvedRequires();
if (required.length > 0) {
// get a list of re-exported symbolic names
HashSet reExportSet = new HashSet(required.length);
BundleSpecification[] requiredSpecs = description.getRequiredBundles();
if (requiredSpecs != null && requiredSpecs.length > 0)
for (int i = 0; i < requiredSpecs.length; i++)
if (requiredSpecs[i].isExported())
reExportSet.add(requiredSpecs[i].getName());
requiredBundles = new BundleLoaderProxy[required.length];
int[] reexported = new int[required.length];
int reexportIndex = 0;
for (int i = 0; i < required.length; i++) {
requiredBundles[i] = getLoaderProxy(required[i]);
if (reExportSet.contains(required[i].getSymbolicName()))
reexported[reexportIndex++] = i;
}
if (reexportIndex > 0) {
reexportTable = new int[reexportIndex];
System.arraycopy(reexported, 0, reexportTable, 0, reexportIndex);
} else {
reexportTable = null;
}
requiredSources = new KeyedHashSet(10, false);
} else {
requiredBundles = null;
reexportTable = null;
requiredSources = null;
}
// init the provided packages set
ExportPackageDescription[] exports = description.getSelectedExports();
if (exports != null && exports.length > 0) {
exportedPackages = Collections.synchronizedCollection(exports.length > 10 ? (Collection) new HashSet(exports.length) : new ArrayList(exports.length));
initializeExports(exports, exportedPackages);
} else {
exportedPackages = Collections.synchronizedCollection(new ArrayList(0));
}
ExportPackageDescription substituted[] = description.getSubstitutedExports();
if (substituted.length > 0) {
substitutedPackages = substituted.length > 10 ? (Collection) new HashSet(substituted.length) : new ArrayList(substituted.length);
for (int i = 0; i < substituted.length; i++)
substitutedPackages.add(substituted[i].getName());
} else {
substitutedPackages = null;
}
//This is the fastest way to access to the description for fragments since the hostdescription.getFragments() is slow
BundleFragment[] fragmentObjects = bundle.getFragments();
BundleDescription[] fragments = new BundleDescription[fragmentObjects == null ? 0 : fragmentObjects.length];
for (int i = 0; i < fragments.length; i++)
fragments[i] = fragmentObjects[i].getBundleDescription();
// init the dynamic imports tables
if (description.hasDynamicImports())
addDynamicImportPackage(description.getImportPackages());
// ...and its fragments
for (int i = 0; i < fragments.length; i++)
if (fragments[i].isResolved() && fragments[i].hasDynamicImports())
addDynamicImportPackage(fragments[i].getImportPackages());
//Initialize the policy handler
String buddyList = null;
try {
buddyList = (String) bundle.getBundleData().getManifest().get(Constants.BUDDY_LOADER);
} catch (BundleException e) {
// do nothing; buddyList == null
}
policy = buddyList != null ? new PolicyHandler(this, buddyList, bundle.getFramework().getPackageAdmin()) : null;
if (policy != null)
policy.open(bundle.getFramework().getSystemBundleContext());
}
private void initializeExports(ExportPackageDescription[] exports, Collection exportNames) {
for (int i = 0; i < exports.length; i++) {
if (proxy.forceSourceCreation(exports[i])) {
if (!exportNames.contains(exports[i].getName())) {
// must force filtered and reexport sources to be created early
// to prevent lazy normal package source creation.
// We only do this for the first export of a package name.
proxy.createPackageSource(exports[i], true);
}
}
exportNames.add(exports[i].getName());
}
}
public synchronized KeyedHashSet getImportedSources(KeyedHashSet visited) {
if ((loaderFlags & FLAG_IMPORTSINIT) != 0)
return importedSources;
BundleDescription bundleDesc = proxy.getBundleDescription();
ExportPackageDescription[] packages = bundleDesc.getResolvedImports();
if (packages != null && packages.length > 0) {
if (importedSources == null)
importedSources = new KeyedHashSet(packages.length, false);
for (int i = 0; i < packages.length; i++) {
if (packages[i].getExporter() == bundleDesc)
continue; // ignore imports resolved to this bundle
PackageSource source = createExportPackageSource(packages[i], visited);
if (source != null)
importedSources.add(source);
}
}
loaderFlags |= FLAG_IMPORTSINIT;
return importedSources;
}
final PackageSource createExportPackageSource(ExportPackageDescription export, KeyedHashSet visited) {
BundleLoaderProxy exportProxy = getLoaderProxy(export.getExporter());
if (exportProxy == null)
// TODO log error!!
return null;
PackageSource requiredSource = exportProxy.getBundleLoader().findRequiredSource(export.getName(), visited);
PackageSource exportSource = exportProxy.createPackageSource(export, false);
if (requiredSource == null)
return exportSource;
return createMultiSource(export.getName(), new PackageSource[] {requiredSource, exportSource});
}
private static PackageSource createMultiSource(String packageName, PackageSource[] sources) {
if (sources.length == 1)
return sources[0];
ArrayList sourceList = new ArrayList(sources.length);
for (int i = 0; i < sources.length; i++) {
SingleSourcePackage[] innerSources = sources[i].getSuppliers();
for (int j = 0; j < innerSources.length; j++)
if (!sourceList.contains(innerSources[j]))
sourceList.add(innerSources[j]);
}
return new MultiSourcePackage(packageName, (SingleSourcePackage[]) sourceList.toArray(new SingleSourcePackage[sourceList.size()]));
}
/*
* get the loader proxy for a bundle description
*/
public final BundleLoaderProxy getLoaderProxy(BundleDescription source) {
BundleLoaderProxy sourceProxy = (BundleLoaderProxy) source.getUserObject();
if (sourceProxy == null) {
// may need to force the proxy to be created
long exportingID = source.getBundleId();
BundleHost exportingBundle = (BundleHost) bundle.getFramework().getBundle(exportingID);
if (exportingBundle == null)
return null;
sourceProxy = exportingBundle.getLoaderProxy();
}
return sourceProxy;
}
/*
* Close the the BundleLoader.
*
*/
synchronized void close() {
if ((loaderFlags & FLAG_CLOSED) != 0)
return;
if (classloader != null)
classloader.close();
if (policy != null)
policy.close(bundle.getFramework().getSystemBundleContext());
loaderFlags |= FLAG_CLOSED; /* This indicates the BundleLoader is destroyed */
}
/**
* This method loads a class from the bundle. The class is searched for in the
* same manner as it would if it was being loaded from a bundle (i.e. all
* hosts, fragments, import, required bundles and local resources are searched.
*
* @param name the name of the desired Class.
* @return the resulting Class
* @exception java.lang.ClassNotFoundException if the class definition was not found.
*/
final public Class loadClass(String name) throws ClassNotFoundException {
BundleClassLoader bcl = createClassLoader();
// The instanceof check here is just to be safe. The javadoc contract stated in BundleClassLoader
// mandate that BundleClassLoaders be an instance of ClassLoader.
if (name.length() > 0 && name.charAt(0) == '[' && bcl instanceof ClassLoader)
return Class.forName(name, false, (ClassLoader) bcl);
return bcl.loadClass(name);
}
/**
* This method gets a resource from the bundle. The resource is searched
* for in the same manner as it would if it was being loaded from a bundle
* (i.e. all hosts, fragments, import, required bundles and
* local resources are searched).
*
* @param name the name of the desired resource.
* @return the resulting resource URL or null if it does not exist.
*/
final URL getResource(String name) {
return createClassLoader().getResource(name);
}
public final synchronized ClassLoader getParentClassLoader() {
if (parent != null)
return parent;
createClassLoader();
return parent;
}
final public synchronized BundleClassLoader createClassLoader() {
if (classloader != null)
return classloader;
String[] classpath;
try {
classpath = bundle.getBundleData().getClassPath();
} catch (BundleException e) {
// no classpath
classpath = new String[0];
bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
}
if (classpath == null) {
// no classpath
classpath = new String[0];
bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(Msg.BUNDLE_NO_CLASSPATH_MATCH, BundleException.MANIFEST_ERROR));
}
BundleClassLoader bcl = createBCLPrevileged(bundle.getProtectionDomain(), classpath);
parent = getParentPrivileged(bcl);
classloader = bcl;
return classloader;
}
/**
* Finds a class local to this bundle. Only the classloader for this bundle is searched.
* @param name The name of the class to find.
* @return The loaded Class or null if the class is not found.
* @throws ClassNotFoundException
*/
Class findLocalClass(String name) throws ClassNotFoundException {
if (Debug.DEBUG && Debug.DEBUG_LOADER)
Debug.println("BundleLoader[" + this + "].findLocalClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
try {
Class clazz = createClassLoader().findLocalClass(name);
if (Debug.DEBUG && Debug.DEBUG_LOADER && clazz != null)
Debug.println("BundleLoader[" + this + "] found local class " + name); //$NON-NLS-1$ //$NON-NLS-2$
return clazz;
} catch (ClassNotFoundException e) {
if (e instanceof StatusException) {
if ((((StatusException) e).getStatusCode() & StatusException.CODE_ERROR) != 0)
throw e;
}
return null;
}
}
/**
* Finds the class for a bundle. This method is used for delegation by the bundle's classloader.
*/
public Class findClass(String name) throws ClassNotFoundException {
return findClass(name, true);
}
Class findClass(String name, boolean checkParent) throws ClassNotFoundException {
ClassLoader parentCL = getParentClassLoader();
if (checkParent && parentCL != null && name.startsWith(JAVA_PACKAGE))
// 1) if startsWith "java." delegate to parent and terminate search
// we want to throw ClassNotFoundExceptions if a java.* class cannot be loaded from the parent.
return parentCL.loadClass(name);
try {
if (USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK)
lock(createClassLoader());
return findClassInternal(name, checkParent, parentCL);
} finally {
if (USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK)
unlock();
}
}
private Class findClassInternal(String name, boolean checkParent, ClassLoader parentCL) throws ClassNotFoundException {
if (Debug.DEBUG && Debug.DEBUG_LOADER)
Debug.println("BundleLoader[" + this + "].loadBundleClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String pkgName = getPackageName(name);
boolean bootDelegation = false;
// follow the OSGi delegation model
if (checkParent && parentCL != null && bundle.getFramework().isBootDelegationPackage(pkgName))
// 2) if part of the bootdelegation list then delegate to parent and continue of failure
try {
return parentCL.loadClass(name);
} catch (ClassNotFoundException cnfe) {
// we want to continue
bootDelegation = true;
}
Class result = null;
try {
result = (Class) searchHooks(name, PRE_CLASS);
} catch (ClassNotFoundException e) {
throw e;
} catch (FileNotFoundException e) {
// will not happen
}
if (result != null)
return result;
// 3) search the imported packages
PackageSource source = findImportedSource(pkgName, null);
if (source != null) {
// 3) found import source terminate search at the source
result = source.loadClass(name);
if (result != null)
return result;
throw new ClassNotFoundException(name);
}
// 4) search the required bundles
source = findRequiredSource(pkgName, null);
if (source != null)
// 4) attempt to load from source but continue on failure
result = source.loadClass(name);
// 5) search the local bundle
if (result == null)
result = findLocalClass(name);
if (result != null)
return result;
// 6) attempt to find a dynamic import source; only do this if a required source was not found
if (source == null) {
source = findDynamicSource(pkgName);
if (source != null) {
result = source.loadClass(name);
if (result != null)
return result;
// must throw CNFE if dynamic import source does not have the class
throw new ClassNotFoundException(name);
}
}
if (result == null)
try {
result = (Class) searchHooks(name, POST_CLASS);
} catch (ClassNotFoundException e) {
throw e;
} catch (FileNotFoundException e) {
// will not happen
}
// do buddy policy loading
if (result == null && policy != null)
result = policy.doBuddyClassLoading(name);
if (result != null)
return result;
// hack to support backwards compatibiility for bootdelegation
// or last resort; do class context trick to work around VM bugs
if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
// we don't need to continue if a CNFE is thrown here.
try {
return parentCL.loadClass(name);
} catch (ClassNotFoundException e) {
// we want to generate our own exception below
}
throw new ClassNotFoundException(name);
}
private Object searchHooks(String name, int type) throws ClassNotFoundException, FileNotFoundException {
ClassLoaderDelegateHook[] delegateHooks = bundle.getFramework().getDelegateHooks();
if (delegateHooks == null)
return null;
Object result = null;
for (int i = 0; i < delegateHooks.length && result == null; i++) {
switch (type) {
case PRE_CLASS :
result = delegateHooks[i].preFindClass(name, createClassLoader(), bundle.getBundleData());
break;
case POST_CLASS :
result = delegateHooks[i].postFindClass(name, createClassLoader(), bundle.getBundleData());
break;
case PRE_RESOURCE :
result = delegateHooks[i].preFindResource(name, createClassLoader(), bundle.getBundleData());
break;
case POST_RESOURCE :
result = delegateHooks[i].postFindResource(name, createClassLoader(), bundle.getBundleData());
break;
case PRE_RESOURCES :
result = delegateHooks[i].preFindResources(name, createClassLoader(), bundle.getBundleData());
break;
case POST_RESOURCES :
result = delegateHooks[i].postFindResources(name, createClassLoader(), bundle.getBundleData());
break;
case PRE_LIBRARY :
result = delegateHooks[i].preFindLibrary(name, createClassLoader(), bundle.getBundleData());
break;
case POST_LIBRARY :
result = delegateHooks[i].postFindLibrary(name, createClassLoader(), bundle.getBundleData());
break;
}
}
return result;
}
private boolean isRequestFromVM() {
if (bundle.getFramework().isBootDelegationPackage("*") || !bundle.getFramework().contextBootDelegation) //$NON-NLS-1$
return false;
// works around VM bugs that require all classloaders to have access to parent packages
Class[] context = CLASS_CONTEXT.getClassContext();
if (context == null || context.length < 2)
return false;
// skip the first class; it is the ClassContext class
for (int i = 1; i < context.length; i++)
// find the first class in the context which is not BundleLoader or instanceof ClassLoader
if (context[i] != BundleLoader.class && !ClassLoader.class.isAssignableFrom(context[i])) {
// only find in parent if the class is not "Class" (Class#forName case) or if the class is not loaded with a BundleClassLoader
ClassLoader cl = getClassLoader(context[i]);
if (cl != FW_CLASSLOADER) { // extra check incase an adaptor adds another class into the stack besides an instance of ClassLoader
if (Class.class != context[i] && !(cl instanceof BundleClassLoader))
return true;
break;
}
}
return false;
}
private static ClassLoader getClassLoader(final Class clazz) {
if (System.getSecurityManager() == null)
return clazz.getClassLoader();
return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return clazz.getClassLoader();
}
});
}
/**
* Finds the resource for a bundle. This method is used for delegation by the bundle's classloader.
*/
public URL findResource(String name) {
return findResource(name, true);
}
URL findResource(String name, boolean checkParent) {
if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
name = name.substring(1); /* remove leading slash before search */
String pkgName = getResourcePackageName(name);
boolean bootDelegation = false;
ClassLoader parentCL = getParentClassLoader();
// follow the OSGi delegation model
// First check the parent classloader for system resources, if it is a java resource.
if (checkParent && parentCL != null) {
if (pkgName.startsWith(JAVA_PACKAGE))
// 1) if startsWith "java." delegate to parent and terminate search
// we never delegate java resource requests past the parent
return parentCL.getResource(name);
else if (bundle.getFramework().isBootDelegationPackage(pkgName)) {
// 2) if part of the bootdelegation list then delegate to parent and continue of failure
URL result = parentCL.getResource(name);
if (result != null)
return result;
bootDelegation = true;
}
}
URL result = null;
try {
result = (URL) searchHooks(name, PRE_RESOURCE);
} catch (FileNotFoundException e) {
return null;
} catch (ClassNotFoundException e) {
// will not happen
}
if (result != null)
return result;
// 3) search the imported packages
PackageSource source = findImportedSource(pkgName, null);
if (source != null)
// 3) found import source terminate search at the source
return source.getResource(name);
// 4) search the required bundles
source = findRequiredSource(pkgName, null);
if (source != null)
// 4) attempt to load from source but continue on failure
result = source.getResource(name);
// 5) search the local bundle
if (result == null)
result = findLocalResource(name);
if (result != null)
return result;
// 6) attempt to find a dynamic import source; only do this if a required source was not found
if (source == null) {
source = findDynamicSource(pkgName);
if (source != null)
// must return the result of the dynamic import and do not continue
return source.getResource(name);
}
if (result == null)
try {
result = (URL) searchHooks(name, POST_RESOURCE);
} catch (FileNotFoundException e) {
return null;
} catch (ClassNotFoundException e) {
// will not happen
}
// do buddy policy loading
if (result == null && policy != null)
result = policy.doBuddyResourceLoading(name);
if (result != null)
return result;
// hack to support backwards compatibiility for bootdelegation
// or last resort; do class context trick to work around VM bugs
if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
// we don't need to continue if the resource is not found here
return parentCL.getResource(name);
return result;
}
/**
* Finds the resources for a bundle. This method is used for delegation by the bundle's classloader.
*/
public Enumeration findResources(String name) throws IOException {
// do not delegate to parent because ClassLoader#getResources already did and it is final!!
if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
name = name.substring(1); /* remove leading slash before search */
String pkgName = getResourcePackageName(name);
Enumeration result = null;
try {
result = (Enumeration) searchHooks(name, PRE_RESOURCES);
} catch (ClassNotFoundException e) {
// will not happen
} catch (FileNotFoundException e) {
return null;
}
if (result != null)
return result;
// start at step 3 because of the comment above about ClassLoader#getResources
// 3) search the imported packages
PackageSource source = findImportedSource(pkgName, null);
if (source != null)
// 3) found import source terminate search at the source
return source.getResources(name);
// 4) search the required bundles
source = findRequiredSource(pkgName, null);
if (source != null)
// 4) attempt to load from source but continue on failure
result = source.getResources(name);
// 5) search the local bundle
// compound the required source results with the local ones
Enumeration localResults = findLocalResources(name);
result = compoundEnumerations(result, localResults);
// 6) attempt to find a dynamic import source; only do this if a required source was not found
if (result == null && source == null) {
source = findDynamicSource(pkgName);
if (source != null)
return source.getResources(name);
}
if (result == null)
try {
result = (Enumeration) searchHooks(name, POST_RESOURCES);
} catch (ClassNotFoundException e) {
// will not happen
} catch (FileNotFoundException e) {
return null;
}
if (policy != null) {
Enumeration buddyResult = policy.doBuddyResourcesLoading(name);
result = compoundEnumerations(result, buddyResult);
}
return result;
}
/*
* This method is used by Bundle.getResources to do proper parent delegation.
*/
public Enumeration getResources(String name) throws IOException {
if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
name = name.substring(1); /* remove leading slash before search */
String pkgName = getResourcePackageName(name);
// follow the OSGi delegation model
// First check the parent classloader for system resources, if it is a java resource.
Enumeration result = null;
if (pkgName.startsWith(JAVA_PACKAGE) || bundle.getFramework().isBootDelegationPackage(pkgName)) {
// 1) if startsWith "java." delegate to parent and terminate search
// 2) if part of the bootdelegation list then delegate to parent and continue of failure
ClassLoader parentCL = getParentClassLoader();
result = parentCL == null ? null : parentCL.getResources(name);
if (pkgName.startsWith(JAVA_PACKAGE))
return result;
}
return compoundEnumerations(result, findResources(name));
}
public static Enumeration compoundEnumerations(Enumeration list1, Enumeration list2) {
if (list2 == null || !list2.hasMoreElements())
return list1;
if (list1 == null || !list1.hasMoreElements())
return list2;
Vector compoundResults = new Vector();
while (list1.hasMoreElements())
compoundResults.add(list1.nextElement());
while (list2.hasMoreElements()) {
Object item = list2.nextElement();
if (!compoundResults.contains(item)) //don't add duplicates
compoundResults.add(item);
}
return compoundResults.elements();
}
/**
* Finds a resource local to this bundle. Only the classloader for this bundle is searched.
* @param name The name of the resource to find.
* @return The URL to the resource or null if the resource is not found.
*/
URL findLocalResource(final String name) {
return createClassLoader().findLocalResource(name);
}
/**
* Returns an Enumeration of URLs representing all the resources with
* the given name. Only the classloader for this bundle is searched.
*
* @param name the resource name
* @return an Enumeration of URLs for the resources
*/
Enumeration findLocalResources(String name) {
return createClassLoader().findLocalResources(name);
}
/**
* Returns the absolute path name of a native library.
*
* @param name the library name
* @return the absolute path of the native library or null if not found
*/
public String findLibrary(final String name) {
if (System.getSecurityManager() == null)
return findLocalLibrary(name);
return (String) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return findLocalLibrary(name);
}
});
}
final String findLocalLibrary(final String name) {
String result = null;
try {
result = (String) searchHooks(name, PRE_LIBRARY);
} catch (FileNotFoundException e) {
return null;
} catch (ClassNotFoundException e) {
// will not happen
}
if (result != null)
return result;
result = bundle.getBundleData().findLibrary(name);
if (result != null)
return result;
// look in fragments imports ...
BundleFragment[] fragments = bundle.getFragments();
if (fragments != null)
for (int i = 0; i < fragments.length; i++) {
result = fragments[i].getBundleData().findLibrary(name);
if (result != null)
return result;
}
try {
return (String) searchHooks(name, POST_LIBRARY);
} catch (FileNotFoundException e) {
return null; // this is not necessary; but being consistent in case another step is added below
} catch (ClassNotFoundException e) {
// will not happen
}
return null;
}
/*
* Return the bundle we are associated with.
*/
public final AbstractBundle getBundle() {
return bundle;
}
private BundleClassLoader createBCLPrevileged(final BundleProtectionDomain pd, final String[] cp) {
// Create the classloader as previleged code if security manager is present.
if (System.getSecurityManager() == null)
return createBCL(pd, cp);
return (BundleClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return createBCL(pd, cp);
}
});
}
BundleClassLoader createBCL(final BundleProtectionDomain pd, final String[] cp) {
BundleClassLoader bcl = bundle.getBundleData().createClassLoader(BundleLoader.this, pd, cp);
// attach existing fragments to classloader
BundleFragment[] fragments = bundle.getFragments();
if (fragments != null)
for (int i = 0; i < fragments.length; i++) {
try {
bcl.attachFragment(fragments[i].getBundleData(), fragments[i].getProtectionDomain(), fragments[i].getBundleData().getClassPath());
} catch (BundleException be) {
bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be);
}
}
// finish the initialization of the classloader.
bcl.initialize();
return bcl;
}
/**
* Return a string representation of this loader.
* @return String
*/
public final String toString() {
BundleData result = bundle.getBundleData();
return result == null ? "BundleLoader.bundledata == null!" : result.toString(); //$NON-NLS-1$
}
/**
* Return true if the target package name matches
* a name in the DynamicImport-Package manifest header.
*
* @param pkgname The name of the requested class' package.
* @return true if the package should be imported.
*/
private final synchronized boolean isDynamicallyImported(String pkgname) {
if (this instanceof SystemBundleLoader)
return false; // system bundle cannot dynamically import
// must check for startsWith("java.") to satisfy R3 section 4.7.2
if (pkgname.startsWith("java.")) //$NON-NLS-1$
return true;
/* quick shortcut check */
if ((loaderFlags & FLAG_HASDYNAMICIMPORTS) == 0)
return false;
/* "*" shortcut */
if ((loaderFlags & FLAG_HASDYNAMICEIMPORTALL) != 0)
return true;
/* match against specific names */
if (dynamicImportPackages != null)
for (int i = 0; i < dynamicImportPackages.length; i++)
if (pkgname.equals(dynamicImportPackages[i]))
return true;
/* match against names with trailing wildcards */
if (dynamicImportPackageStems != null)
for (int i = 0; i < dynamicImportPackageStems.length; i++)
if (pkgname.startsWith(dynamicImportPackageStems[i]))
return true;
return false;
}
final void addExportedProvidersFor(String symbolicName, String packageName, ArrayList result, KeyedHashSet visited) {
if (!visited.add(bundle))
return;
// See if we locally provide the package.
PackageSource local = null;
if (isExportedPackage(packageName))
local = proxy.getPackageSource(packageName);
else if (isSubstitutedExport(packageName)) {
result.add(findImportedSource(packageName, visited));
return; // should not continue to required bundles in this case
}
// Must search required bundles that are exported first.
if (requiredBundles != null) {
int size = reexportTable == null ? 0 : reexportTable.length;
int reexportIndex = 0;
for (int i = 0; i < requiredBundles.length; i++) {
if (local != null) {
// always add required bundles first if we locally provide the package
// This allows a bundle to provide a package from a required bundle without
// re-exporting the whole required bundle.
requiredBundles[i].getBundleLoader().addExportedProvidersFor(symbolicName, packageName, result, visited);
} else if (reexportIndex < size && reexportTable[reexportIndex] == i) {
reexportIndex++;
requiredBundles[i].getBundleLoader().addExportedProvidersFor(symbolicName, packageName, result, visited);
}
}
}
// now add the locally provided package.
if (local != null && local.isFriend(symbolicName))
result.add(local);
}
final boolean isExportedPackage(String name) {
return exportedPackages.contains(name);
}
final boolean isSubstitutedExport(String name) {
return substitutedPackages == null ? false : substitutedPackages.contains(name);
}
private void addDynamicImportPackage(ImportPackageSpecification[] packages) {
if (packages == null)
return;
ArrayList dynamicImports = new ArrayList(packages.length);
for (int i = 0; i < packages.length; i++)
if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(packages[i].getDirective(Constants.RESOLUTION_DIRECTIVE)))
dynamicImports.add(packages[i].getName());
if (dynamicImports.size() > 0)
addDynamicImportPackage((String[]) dynamicImports.toArray(new String[dynamicImports.size()]));
}
/**
* Adds a list of DynamicImport-Package manifest elements to the dynamic
* import tables of this BundleLoader. Duplicate packages are checked and
* not added again. This method is not thread safe. Callers should ensure
* synchronization when calling this method.
* @param packages the DynamicImport-Package elements to add.
*/
private void addDynamicImportPackage(String[] packages) {
if (packages == null)
return;
loaderFlags |= FLAG_HASDYNAMICIMPORTS;
int size = packages.length;
ArrayList stems;
if (dynamicImportPackageStems == null) {
stems = new ArrayList(size);
} else {
stems = new ArrayList(size + dynamicImportPackageStems.length);
for (int i = 0; i < dynamicImportPackageStems.length; i++) {
stems.add(dynamicImportPackageStems[i]);
}
}
ArrayList names;
if (dynamicImportPackages == null) {
names = new ArrayList(size);
} else {
names = new ArrayList(size + dynamicImportPackages.length);
for (int i = 0; i < dynamicImportPackages.length; i++) {
names.add(dynamicImportPackages[i]);
}
}
for (int i = 0; i < size; i++) {
String name = packages[i];
if (isDynamicallyImported(name))
continue;
if (name.equals("*")) { /* shortcut *///$NON-NLS-1$
loaderFlags |= FLAG_HASDYNAMICEIMPORTALL;
return;
}
if (name.endsWith(".*")) //$NON-NLS-1$
stems.add(name.substring(0, name.length() - 1));
else
names.add(name);
}
size = stems.size();
if (size > 0)
dynamicImportPackageStems = (String[]) stems.toArray(new String[size]);
size = names.size();
if (size > 0)
dynamicImportPackages = (String[]) names.toArray(new String[size]);
}
/**
* Adds a list of DynamicImport-Package manifest elements to the dynamic
* import tables of this BundleLoader. Duplicate packages are checked and
* not added again.
* @param packages the DynamicImport-Package elements to add.
*/
public final synchronized void addDynamicImportPackage(ManifestElement[] packages) {
if (packages == null)
return;
ArrayList dynamicImports = new ArrayList(packages.length);
for (int i = 0; i < packages.length; i++)
dynamicImports.add(packages[i].getValue());
if (dynamicImports.size() > 0)
addDynamicImportPackage((String[]) dynamicImports.toArray(new String[dynamicImports.size()]));
}
synchronized public void attachFragment(BundleFragment fragment) throws BundleException {
ExportPackageDescription[] exports = proxy.getBundleDescription().getSelectedExports();
if (classloader == null) {
initializeExports(exports, exportedPackages);
return;
}
String[] classpath = fragment.getBundleData().getClassPath();
if (classpath != null)
classloader.attachFragment(fragment.getBundleData(), fragment.getProtectionDomain(), classpath);
initializeExports(exports, exportedPackages);
}
/*
* Finds a packagesource that is either imported or required from another bundle.
* This will not include an local package source
*/
private PackageSource findSource(String pkgName) {
if (pkgName == null)
return null;
PackageSource result = findImportedSource(pkgName, null);
if (result != null)
return result;
// Note that dynamic imports are not checked to avoid aggressive wiring (bug 105779)
return findRequiredSource(pkgName, null);
}
private PackageSource findImportedSource(String pkgName, KeyedHashSet visited) {
KeyedHashSet imports = getImportedSources(visited);
if (imports == null)
return null;
synchronized (imports) {
return (PackageSource) imports.getByKey(pkgName);
}
}
private PackageSource findDynamicSource(String pkgName) {
if (isDynamicallyImported(pkgName)) {
ExportPackageDescription exportPackage = bundle.getFramework().getAdaptor().getState().linkDynamicImport(proxy.getBundleDescription(), pkgName);
if (exportPackage != null) {
PackageSource source = createExportPackageSource(exportPackage, null);
synchronized (this) {
if (importedSources == null)
importedSources = new KeyedHashSet(false);
}
synchronized (importedSources) {
importedSources.add(source);
}
return source;
}
}
return null;
}
private PackageSource findRequiredSource(String pkgName, KeyedHashSet visited) {
if (requiredBundles == null)
return null;
synchronized (requiredSources) {
PackageSource result = (PackageSource) requiredSources.getByKey(pkgName);
if (result != null)
return result.isNullSource() ? null : result;
}
if (visited == null)
visited = new KeyedHashSet(false);
visited.add(bundle); // always add ourselves so we do not recurse back to ourselves
ArrayList result = new ArrayList(3);
for (int i = 0; i < requiredBundles.length; i++) {
BundleLoader requiredLoader = requiredBundles[i].getBundleLoader();
requiredLoader.addExportedProvidersFor(proxy.getSymbolicName(), pkgName, result, visited);
}
// found some so cache the result for next time and return
PackageSource source;
if (result.size() == 0) {
// did not find it in our required bundles lets record the failure
// so we do not have to do the search again for this package.
source = NullPackageSource.getNullPackageSource(pkgName);
} else if (result.size() == 1) {
// if there is just one source, remember just the single source
source = (PackageSource) result.get(0);
} else {
// if there was more than one source, build a multisource and cache that.
PackageSource[] srcs = (PackageSource[]) result.toArray(new PackageSource[result.size()]);
source = createMultiSource(pkgName, srcs);
}
synchronized (requiredSources) {
requiredSources.add(source);
}
return source.isNullSource() ? null : source;
}
/*
* Gets the package source for the pkgName. This will include the local package source
* if the bundle exports the package. This is used to compare the PackageSource of a
* package from two different bundles.
*/
public final PackageSource getPackageSource(String pkgName) {
PackageSource result = findSource(pkgName);
if (!isExportedPackage(pkgName))
return result;
// if the package is exported then we need to get the local source
PackageSource localSource = proxy.getPackageSource(pkgName);
if (result == null)
return localSource;
if (localSource == null)
return result;
return createMultiSource(pkgName, new PackageSource[] {result, localSource});
}
private ClassLoader getParentPrivileged(final BundleClassLoader bcl) {
if (System.getSecurityManager() == null)
return bcl.getParent();
return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return bcl.getParent();
}
});
}
static final class ClassContext extends SecurityManager {
// need to make this method public
public Class[] getClassContext() {
return super.getClassContext();
}
}
/*
* see bug 121737
* To ensure that we do not enter a deadly embrace between classloader cycles
* we attempt to obtain a global lock before do normal osgi delegation.
* This approach ensures that only one thread has a classloader locked at a time
*/
private static void lock(Object loader) {
Thread currentThread = Thread.currentThread();
boolean interrupted = false;
synchronized (loader) {
if (tryLock(currentThread, loader))
return; // this thread has the lock
do {
try {
// we wait on the loader object here to release its lock incase we have it.
// we do not way to wait while holding this lock because that will cause deadlock
loader.wait();
} catch (InterruptedException e) {
interrupted = true;
// we still want to try again
}
} while (!tryLock(currentThread));
}
if (interrupted)
currentThread.interrupt();
}
/*
* returns true if this thread can obtain the global lock or already has the lock;
* otherwise this loader and thread are added to the waitingList
*/
private synchronized static boolean tryLock(Thread currentThread, Object loader) {
if (lockThread == currentThread) {
lockCount++;
return true;
}
if (lockThread == null) {
lockCount++;
lockThread = currentThread;
return true;
}
waitingList.add(new Object[] {currentThread, loader});
return false;
}
/*
* returns true if this thread already has the global lock
*/
private synchronized static boolean tryLock(Thread currentThread) {
if (lockThread == currentThread) {
lockCount++;
return true;
}
return false;
}
/*
* unlocks the global lock and notifies the first waiting thread that they
* now have the lock
*/
private static void unlock() {
Thread waitingThread = null;
Object loader = null;
synchronized (BundleLoader.class) {
lockCount--;
if (lockCount != 0)
return;
if (waitingList.isEmpty()) {
lockThread = null;
return;
}
Object[] waiting = (Object[]) waitingList.get(0);
waitingThread = (Thread) waiting[0];
loader = waiting[1];
}
synchronized (loader) {
synchronized (BundleLoader.class) {
lockThread = waitingThread;
waitingList.remove(0);
loader.notifyAll();
}
}
}
static public void closeBundleLoader(BundleLoaderProxy proxy) {
if (proxy == null)
return;
// First close the BundleLoader
BundleLoader loader = proxy.getBasicBundleLoader();
if (loader != null)
loader.close();
proxy.setStale();
// if proxy is not null then make sure to unset user object
// associated with the proxy in the state
BundleDescription description = proxy.getBundleDescription();
description.setUserObject(null);
}
}