/******************************************************************************* * Copyright (c) 2004, 2018 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.osgi.container.Module; import org.eclipse.osgi.container.ModuleCapability; import org.eclipse.osgi.container.ModuleLoader; import org.eclipse.osgi.container.ModuleRequirement; import org.eclipse.osgi.container.ModuleRevision; import org.eclipse.osgi.container.ModuleRevisionBuilder; import org.eclipse.osgi.container.ModuleWire; import org.eclipse.osgi.container.ModuleWiring; import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory; import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; import org.eclipse.osgi.internal.debug.Debug; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; import org.eclipse.osgi.internal.framework.EquinoxContainer; import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook; import org.eclipse.osgi.internal.loader.buddy.PolicyHandler; import org.eclipse.osgi.internal.loader.sources.MultiSourcePackage; import org.eclipse.osgi.internal.loader.sources.NullPackageSource; import org.eclipse.osgi.internal.loader.sources.PackageSource; import org.eclipse.osgi.internal.loader.sources.SingleSourcePackage; import org.eclipse.osgi.storage.BundleInfo.Generation; import org.eclipse.osgi.util.ManifestElement; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.namespace.BundleNamespace; import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.framework.wiring.BundleWiring; /** * 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.BundleLoaderSources */ public class BundleLoader extends ModuleLoader { public final static String DEFAULT_PACKAGE = "."; //$NON-NLS-1$ public final static String JAVA_PACKAGE = "java."; //$NON-NLS-1$ public final static ClassContext CLASS_CONTEXT = AccessController.doPrivileged(new PrivilegedAction() { public ClassContext run() { return new ClassContext(); } }); public final static ClassLoader FW_CLASSLOADER = getClassLoader(EquinoxContainer.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 Pattern PACKAGENAME_FILTER = Pattern.compile("\\(osgi.wiring.package\\s*=\\s*([^)]+)\\)"); //$NON-NLS-1$ // TODO needed instead of using Collections.emptyEnumertion until we no longer support Java 6 @SuppressWarnings("rawtypes") private final static Enumeration EMPTY_ENUMERATION = Collections.enumeration(Collections.emptyList()); private final ModuleWiring wiring; private final EquinoxContainer container; private final Debug debug; private final PolicyHandler policy; /* List of package names that are exported by this BundleLoader */ private final Collection exportedPackages; private final BundleLoaderSources exportSources; /* cache of required package sources. Key is packagename, value is PackageSource */ private final Map requiredSources = new HashMap<>(); /* cache of imported packages. Key is packagename, Value is PackageSource */ private final Map importedSources = new HashMap<>(); private final List requiredBundleWires; /* @GuardedBy("importedSources") */ private boolean importsInitialized = false; /* @GuardedBy("importedSources") */ private boolean dynamicAllPackages; /* If not null, list of package stems to import dynamically. */ /* @GuardedBy("importedSources") */ private String[] dynamicImportPackageStems; /* @GuardedBy("importedSources") */ /* If not null, list of package names to import dynamically. */ private String[] dynamicImportPackages; private final Object classLoaderCreatedMonitor = new Object(); /* @GuardedBy("classLoaderCreatedMonitor") */ private ModuleClassLoader classLoaderCreated; private volatile ModuleClassLoader classloader; private final ClassLoader parent; private final AtomicBoolean triggerClassLoaded = new AtomicBoolean(false); private final AtomicBoolean firstUseOfInvalidLoader = new AtomicBoolean(false); /** * 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; } public BundleLoader(ModuleWiring wiring, EquinoxContainer container, ClassLoader parent) { this.wiring = wiring; this.container = container; this.debug = container.getConfiguration().getDebug(); this.parent = parent; // init the provided packages set exportSources = new BundleLoaderSources(this); List exports = wiring.getModuleCapabilities(PackageNamespace.PACKAGE_NAMESPACE); exports = exports == null ? new ArrayList(0) : exports; exportedPackages = Collections.synchronizedCollection(exports.size() > 10 ? new HashSet(exports.size()) : new ArrayList(exports.size())); initializeExports(exports, exportSources, exportedPackages); // init the dynamic imports tables addDynamicImportPackage(wiring.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE)); // initialize the required bundle wires List currentRequireBundleWires = wiring.getRequiredModuleWires(BundleNamespace.BUNDLE_NAMESPACE); requiredBundleWires = currentRequireBundleWires == null || currentRequireBundleWires.isEmpty() ? Collections. emptyList() : Collections.unmodifiableList(currentRequireBundleWires); //Initialize the policy handler List moduleDatas = wiring.getRevision().getModuleCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE); @SuppressWarnings("unchecked") List buddyList = (List) (moduleDatas.isEmpty() ? null : moduleDatas.get(0).getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_BUDDY_POLICY)); policy = buddyList != null ? new PolicyHandler(this, buddyList, container.getPackageAdmin()) : null; if (policy != null) { Module systemModule = container.getStorage().getModuleContainer().getModule(0); Bundle systemBundle = systemModule.getBundle(); policy.open(systemBundle.getBundleContext()); } } public ModuleWiring getWiring() { return wiring; } public void addFragmentExports(List exports) { initializeExports(exports, exportSources, exportedPackages); } private static void initializeExports(List exports, BundleLoaderSources sources, Collection exportNames) { if (exports != null) { for (ModuleCapability export : exports) { String name = (String) export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); if (sources.forceSourceCreation(export)) { if (!exportNames.contains(name)) { // 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. sources.createPackageSource(export, true); } } exportNames.add(name); } } } final PackageSource createExportPackageSource(ModuleWire importWire, Collection visited) { String name = (String) importWire.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); BundleLoader providerLoader = getProviderLoader(importWire); if (providerLoader == null) { return createMultiSource(name, new PackageSource[0]); } PackageSource requiredSource = providerLoader.findRequiredSource(name, visited); PackageSource exportSource = providerLoader.exportSources.createPackageSource(importWire.getCapability(), false); if (requiredSource == null) return exportSource; return createMultiSource(name, new PackageSource[] {requiredSource, exportSource}); } private static PackageSource createMultiSource(String packageName, PackageSource[] sources) { if (sources.length == 1) return sources[0]; List 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, sourceList.toArray(new SingleSourcePackage[sourceList.size()])); } public ModuleClassLoader getModuleClassLoader() { ModuleClassLoader result = classloader; if (result != null) { return result; } // doing optimistic class loader creating here to avoid holding any locks, // this may result in multiple classloaders being constructed but only one will be used. final List hooks = container.getConfiguration().getHookRegistry().getClassLoaderHooks(); final Generation generation = (Generation) wiring.getRevision().getRevisionInfo(); if (System.getSecurityManager() == null) { result = createClassLoaderPrivledged(parent, generation.getBundleInfo().getStorage().getConfiguration(), this, generation, hooks); } else { final ClassLoader cl = parent; result = AccessController.doPrivileged(new PrivilegedAction() { @Override public ModuleClassLoader run() { return createClassLoaderPrivledged(cl, generation.getBundleInfo().getStorage().getConfiguration(), BundleLoader.this, generation, hooks); } }); } // Synchronize on classLoaderCreatedMonitor in order to ensure hooks are called before returning. // Note that we do hold a lock here while calling hooks. // Not ideal, but hooks really should do little work from classLoaderCreated method. synchronized (classLoaderCreatedMonitor) { if (classLoaderCreated == null) { // must set createdClassloader before calling hooks; otherwise we could enter // and endless loop if the hook causes re-entry (that would be a bad hook impl) classLoaderCreated = result; // only send to hooks if this thread wins in creating the class loader. final ModuleClassLoader cl = result; // protect with doPriv to avoid bubbling up permission checks that hooks may require AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { for (ClassLoaderHook hook : hooks) { hook.classLoaderCreated(cl); } return null; } }); // finally set the class loader for use after calling hooks classloader = classLoaderCreated; } else { // return the classLoaderCreated here; not the final classloader // this is necessary in case re-entry by a hook.classLoaderCreated method result = classLoaderCreated; if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "].getModuleClassLoader() - created duplicate classloader"); //$NON-NLS-1$ //$NON-NLS-2$ } } } return result; } @Override protected void loadFragments(Collection fragments) { addFragmentExports(wiring.getModuleCapabilities(PackageNamespace.PACKAGE_NAMESPACE)); loadClassLoaderFragments(fragments); clearManifestLocalizationCache(); } protected void clearManifestLocalizationCache() { Generation hostGen = (Generation) wiring.getRevision().getRevisionInfo(); hostGen.clearManifestCache(); List hostWires = wiring.getProvidedModuleWires(HostNamespace.HOST_NAMESPACE); // doing a null check because there is no lock on the module database here if (hostWires != null) { for (ModuleWire fragmentWire : hostWires) { Generation fragGen = (Generation) fragmentWire.getRequirer().getRevisionInfo(); fragGen.clearManifestCache(); } } } void loadClassLoaderFragments(Collection fragments) { ModuleClassLoader current = classloader; if (current != null) { current.loadFragments(fragments); } } static ModuleClassLoader createClassLoaderPrivledged(ClassLoader parent, EquinoxConfiguration configuration, BundleLoader delegate, Generation generation, List hooks) { // allow hooks to extend the ModuleClassLoader implementation for (ClassLoaderHook hook : hooks) { ModuleClassLoader hookClassLoader = hook.createClassLoader(parent, configuration, delegate, generation); if (hookClassLoader != null) { // first one to return non-null wins. return hookClassLoader; } } // just use the default one return new EquinoxClassLoader(parent, configuration, delegate, generation); } public void close() { if (policy != null) { Module systemModule = container.getStorage().getModuleContainer().getModule(0); BundleContext context = systemModule.getBundle().getBundleContext(); if (context != null) { policy.close(context); } } ModuleClassLoader current = classloader; if (current != null) { current.close(); } } @Override protected ClassLoader getClassLoader() { return getModuleClassLoader(); } public ClassLoader getParentClassLoader() { return this.parent; } /** * 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 getModuleClassLoader().getResource(name); } /** * 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 */ public Class findLocalClass(String name) throws ClassNotFoundException { long start = 0; if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "].findLocalClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ start = System.currentTimeMillis(); } try { Class clazz = getModuleClassLoader().findLocalClass(name); if (debug.DEBUG_LOADER && clazz != null) { long time = System.currentTimeMillis() - start; Debug.println("BundleLoader[" + this + "] found local class " + name + " " + time + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } return clazz; } catch (ClassNotFoundException e) { if (e.getCause() instanceof BundleException) { // Here we assume this is because of a lazy activation error 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 { if (checkParent && parent != 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 parent.loadClass(name); return findClassInternal(name, checkParent); } private Class findClassInternal(String name, boolean checkParent) throws ClassNotFoundException { if (debug.DEBUG_LOADER) Debug.println("BundleLoader[" + this + "].findClassInternal(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ String pkgName = getPackageName(name); boolean bootDelegation = false; // follow the OSGi delegation model if (checkParent && parent != null && container.isBootDelegationPackage(pkgName)) { // 2) if part of the bootdelegation list then delegate to parent and continue of failure try { return parent.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) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] loading from import package: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } // 3) found import source terminate search at the source result = source.loadClass(name); if (result != null) return result; throw new ClassNotFoundException(name + " cannot be found by " + this); //$NON-NLS-1$ } // 4) search the required bundles source = findRequiredSource(pkgName, null); if (source != null) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] loading from required bundle package: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } // 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 + " cannot be found by " + this); //$NON-NLS-1$ } } 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 compatibility for bootdelegation // or last resort; do class context trick to work around VM bugs if (parent != null && !bootDelegation && ((checkParent && container.getConfiguration().compatibilityBootDelegation) || isRequestFromVM())) { // we don't need to continue if a CNFE is thrown here. try { return parent.loadClass(name); } catch (ClassNotFoundException e) { // we want to generate our own exception below } } throw new ClassNotFoundException(name + " cannot be found by " + this); //$NON-NLS-1$ } @SuppressWarnings("unchecked") private E searchHooks(String name, int type) throws ClassNotFoundException, FileNotFoundException { List loaderHooks = container.getConfiguration().getHookRegistry().getClassLoaderHooks(); if (loaderHooks == null) return null; E result = null; for (ClassLoaderHook hook : loaderHooks) { switch (type) { case PRE_CLASS : result = (E) hook.preFindClass(name, getModuleClassLoader()); break; case POST_CLASS : result = (E) hook.postFindClass(name, getModuleClassLoader()); break; case PRE_RESOURCE : result = (E) hook.preFindResource(name, getModuleClassLoader()); break; case POST_RESOURCE : result = (E) hook.postFindResource(name, getModuleClassLoader()); break; case PRE_RESOURCES : result = (E) hook.preFindResources(name, getModuleClassLoader()); break; case POST_RESOURCES : result = (E) hook.postFindResources(name, getModuleClassLoader()); break; } if (result != null) { return result; } } return result; } private boolean isRequestFromVM() { if (!container.getConfiguration().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++) { Class clazz = context[i]; // Find the first class in the context which is not BundleLoader or the ModuleClassLoader; // We ignore ClassLoader because ModuleClassLoader extends it. // We ignore Class because of Class.forName (bug 471551) if (clazz != BundleLoader.class && !ModuleClassLoader.class.isAssignableFrom(clazz) && clazz != ClassLoader.class && clazz != Class.class && !clazz.getName().equals("java.lang.J9VMInternals")) { //$NON-NLS-1$ if (Bundle.class.isAssignableFrom(clazz)) { // We ignore any requests from Bundle (e.g. Bundle.loadClass case) return false; } // only find in parent if the class is not loaded with a ModuleClassLoader ClassLoader cl = getClassLoader(clazz); // extra check incase an adaptor adds another class into the stack besides an instance of ClassLoader if (cl != FW_CLASSLOADER) { // if the class is loaded from a class loader implemented by a bundle then we do not boot delegate ClassLoader last = null; while (cl != null && cl != last) { last = cl; if (cl instanceof ModuleClassLoader) { return false; } cl = getClassLoader(cl.getClass()); } // request is not from a bundle return true; } } } return false; } private static ClassLoader getClassLoader(final Class clazz) { if (System.getSecurityManager() == null) return clazz.getClassLoader(); return AccessController.doPrivileged(new PrivilegedAction() { public ClassLoader 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) { if (debug.DEBUG_LOADER) Debug.println("BundleLoader[" + this + "].findResource(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 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; // follow the OSGi delegation model // First check the parent classloader for system resources, if it is a java resource. if (parent != 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 parent.getResource(name); else if (container.isBootDelegationPackage(pkgName)) { // 2) if part of the bootdelegation list then delegate to parent and continue of failure URL result = parent.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) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] loading from import package: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } // 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) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] loading from required bundle package: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } // 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 compatibility for bootdelegation // or last resort; do class context trick to work around VM bugs if (parent != null && !bootDelegation && (container.getConfiguration().compatibilityBootDelegation || isRequestFromVM())) // we don't need to continue if the resource is not found here return parent.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 { if (debug.DEBUG_LOADER) Debug.println("BundleLoader[" + this + "].findResources(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ // 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 = emptyEnumeration(); boolean bootDelegation = false; // follow the OSGi delegation model // First check the parent classloader for system resources, if it is a java resource. if (parent != 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 parent.getResources(name); else if (container.isBootDelegationPackage(pkgName)) { // 2) if part of the bootdelegation list then delegate to parent and continue result = compoundEnumerations(result, parent.getResources(name)); bootDelegation = true; } } try { Enumeration hookResources = searchHooks(name, PRE_RESOURCES); if (hookResources != null) { return compoundEnumerations(result, hookResources); } } catch (ClassNotFoundException e) { // will not happen } catch (FileNotFoundException e) { return result; } // 3) search the imported packages PackageSource source = findImportedSource(pkgName, null); if (source != null) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] loading from import package: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } // 3) found import source terminate search at the source return compoundEnumerations(result, source.getResources(name)); } // 4) search the required bundles source = findRequiredSource(pkgName, null); if (source != null) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] loading from required bundle package: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } // 4) attempt to load from source but continue on failure result = compoundEnumerations(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 (source == null && !result.hasMoreElements()) { source = findDynamicSource(pkgName); if (source != null) return compoundEnumerations(result, source.getResources(name)); } if (!result.hasMoreElements()) try { Enumeration hookResources = searchHooks(name, POST_RESOURCES); result = compoundEnumerations(result, hookResources); } catch (ClassNotFoundException e) { // will not happen } catch (FileNotFoundException e) { return null; } if (policy != null) { Enumeration buddyResult = policy.doBuddyResourcesLoading(name); result = compoundEnumerations(result, buddyResult); } // hack to support backwards compatibility for bootdelegation // or last resort; do class context trick to work around VM bugs if (!result.hasMoreElements()) { if (parent != null && !bootDelegation && (container.getConfiguration().compatibilityBootDelegation || isRequestFromVM())) // we don't need to continue if the resource is not found here return parent.getResources(name); } return result; } private boolean isSubPackage(String parentPackage, String subPackage) { String prefix = (parentPackage.length() == 0 || parentPackage.equals(DEFAULT_PACKAGE)) ? "" : parentPackage + '.'; //$NON-NLS-1$ return subPackage.startsWith(prefix); } @Override protected Collection listResources(String path, String filePattern, int options) { String pkgName = getResourcePackageName(path.endsWith("/") ? path : path + '/'); //$NON-NLS-1$ if ((path.length() > 1) && (path.charAt(0) == '/')) /* if name has a leading slash */ path = path.substring(1); /* remove leading slash before search */ boolean subPackages = (options & BundleWiring.LISTRESOURCES_RECURSE) != 0; List packages = new ArrayList<>(); // search imported package names Map importSources = getImportedSources(null); Collection imports; synchronized (importSources) { imports = new ArrayList<>(importSources.values()); } for (PackageSource source : imports) { String id = source.getId(); if (id.equals(pkgName) || (subPackages && isSubPackage(pkgName, id))) packages.add(id); } // now add package names from required bundles Collection visited = new ArrayList<>(); visited.add(this); // always add ourselves so we do not recurse back to ourselves for (ModuleWire bundleWire : requiredBundleWires) { BundleLoader loader = getProviderLoader(bundleWire); if (loader != null) { loader.addProvidedPackageNames(pkgName, packages, subPackages, visited); } } boolean localSearch = (options & BundleWiring.LISTRESOURCES_LOCAL) != 0; // Use LinkedHashSet for optimized performance of contains() plus // ordering guarantees. LinkedHashSet result = new LinkedHashSet<>(); Set importedPackages = new HashSet<>(0); for (String name : packages) { // look for import source PackageSource externalSource = findImportedSource(name, null); if (externalSource != null) { // record this package is imported importedPackages.add(name); } else { // look for require bundle source externalSource = findRequiredSource(name, null); } // only add the content of the external source if this is not a localSearch if (externalSource != null && !localSearch) { String packagePath = name.replace('.', '/'); Collection externalResources = externalSource.listResources(packagePath, filePattern); for (String resource : externalResources) { if (!result.contains(resource)) // prevent duplicates; could happen if the package is split or exporter has fragments/multiple jars result.add(resource); } } } // now search locally Collection localResources = getModuleClassLoader().listLocalResources(path, filePattern, options); for (String resource : localResources) { String resourcePkg = getResourcePackageName(resource); if (!importedPackages.contains(resourcePkg) && !result.contains(resource)) result.add(resource); } return result; } @Override protected List findEntries(String path, String filePattern, int options) { return getModuleClassLoader().findEntries(path, filePattern, options); } public static Enumeration compoundEnumerations(Enumeration list1, Enumeration list2) { if (list2 == null || !list2.hasMoreElements()) return list1 == null ? BundleLoader. emptyEnumeration() : list1; if (list1 == null || !list1.hasMoreElements()) return list2 == null ? BundleLoader. emptyEnumeration() : list2; List compoundResults = new ArrayList<>(); while (list1.hasMoreElements()) compoundResults.add(list1.nextElement()); while (list2.hasMoreElements()) { E item = list2.nextElement(); if (!compoundResults.contains(item)) //don't add duplicates compoundResults.add(item); } return Collections.enumeration(compoundResults); } @SuppressWarnings("unchecked") private static Enumeration emptyEnumeration() { return EMPTY_ENUMERATION; } /** * 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. */ public URL findLocalResource(final String name) { return getModuleClassLoader().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 */ public Enumeration findLocalResources(String name) { return getModuleClassLoader().findLocalResources(name); } /** * Return a string representation of this loader. * @return String */ public final String toString() { ModuleRevision revision = wiring.getRevision(); String name = revision.getSymbolicName(); if (name == null) name = "unknown"; //$NON-NLS-1$ return name + '_' + revision.getVersion(); } /** * 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 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; synchronized (importedSources) { /* "*" shortcut */ if (dynamicAllPackages) 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 packageName, List result, Collection visited) { if (visited.contains(this)) return; visited.add(this); // See if we locally provide the package. PackageSource local = null; if (isExportedPackage(packageName)) local = exportSources.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. for (ModuleWire bundleWire : requiredBundleWires) { if (local != null || BundleNamespace.VISIBILITY_REEXPORT.equals(bundleWire.getRequirement().getDirectives().get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE))) { // 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. BundleLoader loader = getProviderLoader(bundleWire); if (loader != null) { loader.addExportedProvidersFor(packageName, result, visited); } } } // now add the locally provided package. if (local != null) result.add(local); } private BundleLoader getProviderLoader(ModuleWire wire) { ModuleWiring provider = wire.getProviderWiring(); if (provider == null) { if (firstUseOfInvalidLoader.getAndSet(true)) { // publish a framework event once per loader, include an exception to show the stack String message = "Invalid class loader from a refreshed bundle is being used: " + toString(); //$NON-NLS-1$ container.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, wiring.getBundle(), new IllegalStateException(message)); } return null; } return (BundleLoader) provider.getModuleLoader(); } final void addProvidedPackageNames(String packageName, List result, boolean subPackages, Collection visited) { if (visited.contains(this)) return; visited.add(this); synchronized (exportedPackages) { for (String exported : exportedPackages) { if (exported.equals(packageName) || (subPackages && isSubPackage(packageName, exported))) { if (!result.contains(exported)) result.add(exported); } } } for (String substituted : wiring.getSubstitutedNames()) { if (substituted.equals(packageName) || (subPackages && isSubPackage(packageName, substituted))) { if (!result.contains(substituted)) result.add(substituted); } } for (ModuleWire bundleWire : requiredBundleWires) { if (BundleNamespace.VISIBILITY_REEXPORT.equals(bundleWire.getRequirement().getDirectives().get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE))) { BundleLoader loader = getProviderLoader(bundleWire); if (loader != null) { loader.addProvidedPackageNames(packageName, result, subPackages, visited); } } } } public final boolean isExportedPackage(String name) { return exportedPackages.contains(name); } final boolean isSubstitutedExport(String name) { return wiring.isSubstitutedPackage(name); } private void addDynamicImportPackage(List packageImports) { if (packageImports == null || packageImports.isEmpty()) { return; } List dynamicImports = new ArrayList<>(packageImports.size()); for (ModuleRequirement packageImport : packageImports) { if (PackageNamespace.RESOLUTION_DYNAMIC.equals(packageImport.getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { Matcher matcher = PACKAGENAME_FILTER.matcher(packageImport.getDirectives().get(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE)); if (matcher.find()) { String dynamicName = matcher.group(1); if (dynamicName != null) { dynamicImports.add(dynamicName); } } } } if (dynamicImports.size() > 0) addDynamicImportPackage(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; synchronized (importedSources) { int size = packages.length; List 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]); } } List 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("*")) { //$NON-NLS-1$ // shortcut dynamicAllPackages = true; 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 = stems.toArray(new String[size]); size = names.size(); if (size > 0) dynamicImportPackages = 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 void addDynamicImportPackage(ManifestElement[] packages) { if (packages == null) return; List dynamicImports = new ArrayList<>(packages.length); StringBuilder importSpec = new StringBuilder(); for (ManifestElement dynamicImportElement : packages) { String[] names = dynamicImportElement.getValueComponents(); for (String name : names) dynamicImports.add(name); if (importSpec.length() > 0) { importSpec.append(','); } importSpec.append(dynamicImportElement.toString()); } if (dynamicImports.size() > 0) { addDynamicImportPackage(dynamicImports.toArray(new String[dynamicImports.size()])); Map dynamicImportMap = new HashMap<>(); dynamicImportMap.put(Constants.DYNAMICIMPORT_PACKAGE, importSpec.toString()); try { ModuleRevisionBuilder builder = OSGiManifestBuilderFactory.createBuilder(dynamicImportMap); wiring.addDynamicImports(builder); } catch (BundleException e) { throw new RuntimeException(e); } } } /* * 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, Collection visited) { Map imports = getImportedSources(visited); synchronized (imports) { return imports.get(pkgName); } } private Map getImportedSources(Collection visited) { synchronized (importedSources) { if (importsInitialized) { return importedSources; } List importWires = wiring.getRequiredModuleWires(PackageNamespace.PACKAGE_NAMESPACE); if (importWires != null) { for (ModuleWire importWire : importWires) { PackageSource source = createExportPackageSource(importWire, visited); if (source != null) { importedSources.put(source.getId(), source); } } } importsInitialized = true; return importedSources; } } private PackageSource findDynamicSource(String pkgName) { if (!isExportedPackage(pkgName) && isDynamicallyImported(pkgName)) { if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] attempting to resolve dynamic package: " + pkgName); //$NON-NLS-1$ //$NON-NLS-2$ } ModuleRevision revision = wiring.getRevision(); ModuleWire dynamicWire = revision.getRevisions().getModule().getContainer().resolveDynamic(pkgName, revision); if (dynamicWire != null) { PackageSource source = createExportPackageSource(dynamicWire, null); if (debug.DEBUG_LOADER) { Debug.println("BundleLoader[" + this + "] using dynamic import source: " + source); //$NON-NLS-1$ //$NON-NLS-2$ } synchronized (importedSources) { importedSources.put(source.getId(), source); } return source; } } return null; } private PackageSource findRequiredSource(String pkgName, Collection visited) { if (requiredBundleWires.isEmpty()) { return null; } synchronized (requiredSources) { PackageSource result = requiredSources.get(pkgName); if (result != null) return result.isNullSource() ? null : result; } if (visited == null) visited = new ArrayList<>(); if (!visited.contains(this)) visited.add(this); // always add ourselves so we do not recurse back to ourselves List result = new ArrayList<>(3); for (ModuleWire bundleWire : requiredBundleWires) { BundleLoader loader = getProviderLoader(bundleWire); if (loader != null) { loader.addExportedProvidersFor(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 = result.get(0); } else { // if there was more than one source, build a multisource and cache that. PackageSource[] srcs = result.toArray(new PackageSource[result.size()]); source = createMultiSource(pkgName, srcs); } synchronized (requiredSources) { requiredSources.put(source.getId(), 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 = exportSources.getPackageSource(pkgName); if (result == null) return localSource; if (localSource == null) return result; return createMultiSource(pkgName, new PackageSource[] {result, localSource}); } static final class ClassContext extends SecurityManager { // need to make this method public public Class[] getClassContext() { return super.getClassContext(); } } @Override protected boolean getAndSetTrigger() { return triggerClassLoaded.getAndSet(true); } @Override public boolean isTriggerSet() { return triggerClassLoaded.get(); } }