| /******************************************************************************* |
| * Copyright (c) 2004, 2006 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.framework.internal.core; |
| |
| 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.util.KeyedHashSet; |
| 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.framework.internal.core.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 boolean USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK = "true".equals(FrameworkProperties.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; |
| /* 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 = exports.length > 10 ? (Collection) new HashSet(exports.length) : new ArrayList(exports.length); |
| for (int i = 0; i < exports.length; i++) { |
| if (proxy.forceSourceCreation(exports[i])) { |
| if (!exportedPackages.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); |
| } |
| } |
| exportedPackages.add(exports[i].getName()); |
| } |
| } else { |
| exportedPackages = null; |
| } |
| //This is the fastest way to access to the description for fragments since the hostdescription.getFragments() is slow |
| org.osgi.framework.Bundle[] fragmentObjects = bundle.getFragments(); |
| BundleDescription[] fragments = new BundleDescription[fragmentObjects == null ? 0 : fragmentObjects.length]; |
| for (int i = 0; i < fragments.length; i++) |
| fragments[i] = ((AbstractBundle) 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) : null; |
| } |
| |
| private synchronized KeyedHashSet getImportedSources() { |
| if ((loaderFlags & FLAG_IMPORTSINIT) != 0) |
| return importedSources; |
| ExportPackageDescription[] packages = proxy.getBundleDescription().getResolvedImports(); |
| if (packages != null && packages.length > 0) { |
| if (importedSources == null) |
| importedSources = new KeyedHashSet(packages.length, false); |
| for (int i = 0; i < packages.length; i++) { |
| PackageSource source = createExportPackageSource(packages[i]); |
| if (source != null) |
| importedSources.add(source); |
| } |
| } |
| loaderFlags |= FLAG_IMPORTSINIT; |
| return importedSources; |
| } |
| |
| final PackageSource createExportPackageSource(ExportPackageDescription export) { |
| BundleLoaderProxy exportProxy = getLoaderProxy(export.getExporter()); |
| if (exportProxy == null) |
| // TODO log error!! |
| return null; |
| PackageSource requiredSource = exportProxy.getBundleLoader().findRequiredSource(export.getName()); |
| 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 |
| */ |
| 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.framework.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(); |
| 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 Class loadClass(String name) throws ClassNotFoundException { |
| return createClassLoader().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); |
| } |
| |
| final synchronized ClassLoader getParentClassLoader() { |
| if (parent != null) |
| return parent; |
| createClassLoader(); |
| return parent; |
| } |
| |
| final synchronized BundleClassLoader createClassLoader() { |
| if (classloader != null) |
| return classloader; |
| try { |
| String[] classpath = bundle.getBundleData().getClassPath(); |
| if (classpath != null) { |
| BundleClassLoader bcl = createBCLPrevileged(bundle.getProtectionDomain(), classpath); |
| parent = getParentPrivileged(bcl); |
| classloader = bcl; |
| } else { |
| bundle.framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(Msg.BUNDLE_NO_CLASSPATH_MATCH)); |
| } |
| } catch (BundleException e) { |
| bundle.framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e); |
| } |
| 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 && 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; |
| // 3) search the imported packages |
| PackageSource source = findImportedSource(pkgName); |
| 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); |
| 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); |
| } |
| } |
| |
| // do buddy policy loading |
| if (result == null && policy != null) |
| result = policy.doBuddyClassLoading(name); |
| // hack to support backwards compatibiility for bootdelegation |
| if (parentCL != null && checkParent && !bootDelegation && bundle.framework.compatibiltyBootDelegation && result == null && source == null && !isExportedPackage(pkgName)) |
| // we don't need to continue if a CNFE is thrown here. |
| return parentCL.loadClass(name); |
| // last resort; do class context trick to work around VM bugs |
| if (parentCL != null && result == null && !bootDelegation && isRequestFromVM()) |
| result = parentCL.loadClass(name); |
| if (result == null) |
| throw new ClassNotFoundException(name); |
| return result; |
| } |
| |
| private boolean isRequestFromVM() { |
| if (bundle.framework.bootDelegateAll || !bundle.framework.contextBootDelegation) |
| 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 (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; |
| // 3) search the imported packages |
| PackageSource source = findImportedSource(pkgName); |
| if (source != null) |
| // 3) found import source terminate search at the source |
| return source.getResource(name); |
| // 4) search the required bundles |
| source = findRequiredSource(pkgName); |
| 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); |
| } |
| |
| // do buddy policy loading |
| if (result == null && policy != null) |
| result = policy.doBuddyResourceLoading(name); |
| // hack to support backwards compatibiility for bootdelegation |
| if (parentCL != null && checkParent && !bootDelegation && bundle.framework.compatibiltyBootDelegation && result == null && source == null && !isExportedPackage(pkgName)) |
| // we don't need to continue if the resource is not found here |
| return parentCL.getResource(name); |
| // last resort; do class context trick to work around VM bugs |
| if (parentCL != null && result == null && !bootDelegation && isRequestFromVM()) |
| result = parentCL.getResource(name); |
| return result; |
| } |
| |
| boolean isBootDelegationPackage(String name) { |
| if (bundle.framework.bootDelegateAll) |
| return true; |
| if (bundle.framework.bootDelegation != null) |
| for (int i = 0; i < bundle.framework.bootDelegation.length; i++) |
| if (name.equals(bundle.framework.bootDelegation[i])) |
| return true; |
| if (bundle.framework.bootDelegationStems != null) |
| for (int i = 0; i < bundle.framework.bootDelegationStems.length; i++) |
| if (name.startsWith(bundle.framework.bootDelegationStems[i])) |
| return true; |
| return false; |
| } |
| |
| /** |
| * 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; |
| // start at step 3 because of the comment above about ClassLoader#getResources |
| // 3) search the imported packages |
| PackageSource source = findImportedSource(pkgName); |
| if (source != null) |
| // 3) found import source terminate search at the source |
| return source.getResources(name); |
| // 4) search the required bundles |
| source = findRequiredSource(pkgName); |
| 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 (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. |
| */ |
| 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) || 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)); |
| } |
| |
| 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 = bundle.getBundleData().findLibrary(name); |
| if (result != null) |
| return result; |
| |
| org.osgi.framework.Bundle[] fragments = bundle.getFragments(); |
| if (fragments == null || fragments.length == 0) |
| return null; |
| |
| // look in fragments imports ... |
| for (int i = 0; i < fragments.length; i++) { |
| result = ((AbstractBundle) fragments[i]).getBundleData().findLibrary(name); |
| if (result != null) |
| return result; |
| } |
| return result; |
| } |
| |
| /* |
| * Return the bundle we are associated with. |
| */ |
| 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 |
| org.osgi.framework.Bundle[] fragments = bundle.getFragments(); |
| if (fragments != null) |
| for (int i = 0; i < fragments.length; i++) { |
| AbstractBundle fragment = (AbstractBundle) fragments[i]; |
| try { |
| bcl.attachFragment(fragment.getBundleData(), fragment.domain, fragment.getBundleData().getClassPath()); |
| } catch (BundleException be) { |
| bundle.framework.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); |
| // 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)) { |
| if (local instanceof BundleLoaderProxy.ReexportPackageSource) |
| local = new SingleSourcePackage(packageName, -1, proxy); |
| result.add(local); |
| } |
| } |
| |
| final boolean isExportedPackage(String name) { |
| return exportedPackages == null ? false : exportedPackages.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()])); |
| } |
| |
| final synchronized void attachFragment(BundleFragment fragment) throws BundleException { |
| if (classloader == null) |
| return; |
| String[] classpath = fragment.getBundleData().getClassPath(); |
| if (classpath != null) |
| classloader.attachFragment(fragment.getBundleData(), fragment.domain, classpath); |
| } |
| |
| /* |
| * 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); |
| if (result != null) |
| return result; |
| // Note that dynamic imports are not checked to avoid aggressive wiring (bug 105779) |
| return findRequiredSource(pkgName); |
| } |
| |
| private PackageSource findImportedSource(String pkgName) { |
| KeyedHashSet imports = getImportedSources(); |
| if (imports == null) |
| return null; |
| synchronized (imports) { |
| return (PackageSource) imports.getByKey(pkgName); |
| } |
| } |
| |
| private PackageSource findDynamicSource(String pkgName) { |
| if (isDynamicallyImported(pkgName)) { |
| ExportPackageDescription exportPackage = bundle.framework.adaptor.getState().linkDynamicImport(proxy.getBundleDescription(), pkgName); |
| if (exportPackage != null) { |
| PackageSource source = createExportPackageSource(exportPackage); |
| synchronized (this) { |
| if (importedSources == null) |
| importedSources = new KeyedHashSet(false); |
| } |
| synchronized (importedSources) { |
| importedSources.add(source); |
| } |
| return source; |
| } |
| } |
| return null; |
| } |
| |
| private PackageSource findRequiredSource(String pkgName) { |
| if (requiredBundles == null) |
| return null; |
| synchronized (requiredSources) { |
| PackageSource result = (PackageSource) requiredSources.getByKey(pkgName); |
| if (result != null) |
| return result.isNullSource() ? null : result; |
| } |
| KeyedHashSet 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. |
| */ |
| 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 (localSource instanceof BundleLoaderProxy.ReexportPackageSource) |
| localSource = new SingleSourcePackage(pkgName, -1, proxy); |
| 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(); |
| } |
| } |
| } |
| |
| } |