blob: 716f0789afaad5ab04a576b3b5b39b10a724d970 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2021 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<ClassContext>() {
@Override
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<String> exportedPackages;
private final BundleLoaderSources exportSources;
/* cache of required package sources. Key is packagename, value is PackageSource */
private final Map<String, PackageSource> requiredSources = new HashMap<>();
/* cache of imported packages. Key is packagename, Value is PackageSource */
private final Map<String, PackageSource> importedSources = new HashMap<>();
private final List<ModuleWire> 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<ModuleCapability> exports = wiring.getModuleCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
exports = exports == null ? Collections.emptyList() : exports;
exportedPackages = Collections.synchronizedCollection(exports.size() > 10 ? new HashSet<String>(exports.size()) : new ArrayList<String>(exports.size()));
initializeExports(exports, exportSources, exportedPackages);
// init the dynamic imports tables
addDynamicImportPackage(wiring.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE));
// initialize the required bundle wires
List<ModuleWire> currentRequireBundleWires = wiring.getRequiredModuleWires(BundleNamespace.BUNDLE_NAMESPACE);
requiredBundleWires = currentRequireBundleWires == null || currentRequireBundleWires.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(currentRequireBundleWires);
//Initialize the policy handler
List<ModuleCapability> moduleDatas = wiring.getRevision().getModuleCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE);
@SuppressWarnings("unchecked")
List<String> buddyList = (List<String>) (moduleDatas.isEmpty() ? null : moduleDatas.get(0).getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_BUDDY_POLICY));
policy = buddyList != null
? new PolicyHandler(this, buddyList, container.getStorage().getModuleContainer().getFrameworkWiring(),
container.getBootLoader())
: 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<ModuleCapability> exports) {
initializeExports(exports, exportSources, exportedPackages);
}
private static void initializeExports(List<ModuleCapability> exports, BundleLoaderSources sources, Collection<String> 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<BundleLoader> 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<SingleSourcePackage> sourceList = new ArrayList<>(sources.length);
for (PackageSource source : sources) {
SingleSourcePackage[] innerSources = source.getSuppliers();
for (SingleSourcePackage innerSource : innerSources) {
if (!sourceList.contains(innerSource)) {
sourceList.add(innerSource);
}
}
}
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<ClassLoaderHook> 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<ModuleClassLoader>() {
@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<Object>() {
@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<ModuleRevision> fragments) {
addFragmentExports(wiring.getModuleCapabilities(PackageNamespace.PACKAGE_NAMESPACE));
loadClassLoaderFragments(fragments);
clearManifestLocalizationCache();
}
protected void clearManifestLocalizationCache() {
Generation hostGen = (Generation) wiring.getRevision().getRevisionInfo();
hostGen.clearManifestCache();
List<ModuleWire> 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<ModuleRevision> fragments) {
ModuleClassLoader current = classloader;
if (current != null) {
current.loadFragments(fragments);
}
}
static ModuleClassLoader createClassLoaderPrivledged(ClassLoader parent, EquinoxConfiguration configuration, BundleLoader delegate, Generation generation, List<ClassLoaderHook> 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 {
if (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);
}
if (debug.DEBUG_LOADER)
Debug.println("BundleLoader[" + this + "].findClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String pkgName = getPackageName(name);
boolean bootDelegation = false;
// follow the OSGi delegation model
if (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) {
// last ditch find loaded check in case something is reflectively
// calling defineClass on our loader.
result = getModuleClassLoader().publicFindLoaded(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
&& ((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> E searchHooks(String name, int type) throws ClassNotFoundException, FileNotFoundException {
List<ClassLoaderHook> 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<ClassLoader>() {
@Override
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<URL> 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<URL> 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<URL> 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<URL> 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<URL> hookResources = searchHooks(name, POST_RESOURCES);
result = compoundEnumerations(result, hookResources);
} catch (ClassNotFoundException e) {
// will not happen
} catch (FileNotFoundException e) {
return null;
}
if (policy != null) {
Enumeration<URL> 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<String> 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<String> packages = new ArrayList<>();
// search imported package names
Map<String, PackageSource> importSources = getImportedSources(null);
Collection<PackageSource> 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<BundleLoader> 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<String> result = new LinkedHashSet<>();
Set<String> 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<String> 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<String> 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<URL> findEntries(String path, String filePattern, int options) {
return getModuleClassLoader().findEntries(path, filePattern, options);
}
public static <E> Enumeration<E> compoundEnumerations(Enumeration<E> list1, Enumeration<E> list2) {
if (list2 == null || !list2.hasMoreElements())
return list1 == null ? BundleLoader.emptyEnumeration() : list1;
if (list1 == null || !list1.hasMoreElements())
return list2 == null ? BundleLoader.emptyEnumeration() : list2;
List<E> 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 <E> Enumeration<E> 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<URL> findLocalResources(String name) {
return getModuleClassLoader().findLocalResources(name);
}
/**
* Return a string representation of this loader.
* @return String
*/
@Override
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 (String dynamicImportPackage : dynamicImportPackages) {
if (pkgname.equals(dynamicImportPackage)) {
return true;
}
}
/* match against names with trailing wildcards */
if (dynamicImportPackageStems != null)
for (String dynamicImportPackageStem : dynamicImportPackageStems) {
if (pkgname.startsWith(dynamicImportPackageStem)) {
return true;
}
}
}
return false;
}
final void addExportedProvidersFor(String packageName, List<PackageSource> result, Collection<BundleLoader> 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<String> result, boolean subPackages, Collection<BundleLoader> 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<ModuleRequirement> packageImports) {
if (packageImports == null || packageImports.isEmpty()) {
return;
}
List<String> 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<String> stems;
if (dynamicImportPackageStems == null) {
stems = new ArrayList<>(size);
} else {
stems = new ArrayList<>(size + dynamicImportPackageStems.length);
for (String dynamicImportPackageStem : dynamicImportPackageStems) {
stems.add(dynamicImportPackageStem);
}
}
List<String> names;
if (dynamicImportPackages == null) {
names = new ArrayList<>(size);
} else {
names = new ArrayList<>(size + dynamicImportPackages.length);
for (String dynamicImportPackage : dynamicImportPackages) {
names.add(dynamicImportPackage);
}
}
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<String> dynamicImports = new ArrayList<>(packages.length);
StringBuilder importSpec = new StringBuilder();
for (ManifestElement dynamicImportElement : packages) {
String[] names = dynamicImportElement.getValueComponents();
Collections.addAll(dynamicImports, names);
if (importSpec.length() > 0) {
importSpec.append(',');
}
importSpec.append(dynamicImportElement.toString());
}
if (dynamicImports.size() > 0) {
addDynamicImportPackage(dynamicImports.toArray(new String[dynamicImports.size()]));
Map<String, String> 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<BundleLoader> visited) {
Map<String, PackageSource> imports = getImportedSources(visited);
synchronized (imports) {
return imports.get(pkgName);
}
}
private Map<String, PackageSource> getImportedSources(Collection<BundleLoader> visited) {
synchronized (importedSources) {
if (importsInitialized) {
return importedSources;
}
List<ModuleWire> 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<BundleLoader> 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<PackageSource> 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
@Override
public Class<?>[] getClassContext() {
return super.getClassContext();
}
}
@Override
protected boolean getAndSetTrigger() {
return triggerClassLoaded.getAndSet(true);
}
@Override
public boolean isTriggerSet() {
return triggerClassLoaded.get();
}
}