blob: fa1f7299ec7f875611c3cad8afb68e2c5c7fb7ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.framework;
import org.apache.felix.framework.cache.Content;
import org.apache.felix.framework.capabilityset.SimpleFilter;
import org.apache.felix.framework.resolver.ResourceNotFoundException;
import org.apache.felix.framework.util.CompoundEnumeration;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.SecurityManagerEx;
import org.apache.felix.framework.util.Util;
import org.apache.felix.framework.util.manifestparser.ManifestParser;
import org.apache.felix.framework.util.manifestparser.NativeLibrary;
import org.apache.felix.framework.wiring.BundleRequirementImpl;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleReference;
import org.osgi.framework.CapabilityPermission;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.PackagePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.hooks.weaving.WeavingException;
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.framework.hooks.weaving.WovenClass;
import org.osgi.framework.hooks.weaving.WovenClassListener;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Wire;
import org.osgi.service.resolver.ResolutionException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureClassLoader;
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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
public class BundleWiringImpl implements BundleWiring
{
public final static int LISTRESOURCES_DEBUG = 1048576;
public final static int EAGER_ACTIVATION = 0;
public final static int LAZY_ACTIVATION = 1;
public static final ClassLoader CNFE_CLASS_LOADER = new ClassLoader()
{
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
throw new ClassNotFoundException("Unable to load class '" + name + "'");
}
};
private final Logger m_logger;
private final Map m_configMap;
private final StatefulResolver m_resolver;
private final BundleRevisionImpl m_revision;
private final List<BundleRevision> m_fragments;
// Wire list is copy-on-write since it may change due to
// dynamic imports.
private volatile List<BundleWire> m_wires;
// Imported package map is copy-on-write since it may change
// due to dynamic imports.
private volatile Map<String, BundleRevision> m_importedPkgs;
private final Map<String, List<BundleRevision>> m_requiredPkgs;
private final List<BundleCapability> m_resolvedCaps;
private final Map<String, List<List<String>>> m_includedPkgFilters;
private final Map<String, List<List<String>>> m_excludedPkgFilters;
private final List<BundleRequirement> m_resolvedReqs;
private final List<NativeLibrary> m_resolvedNativeLibs;
private final List<Content> m_fragmentContents;
private volatile List<BundleRequirement> m_wovenReqs = null;
private volatile BundleClassLoader m_classLoader;
// Bundle-specific class loader for boot delegation.
private final ClassLoader m_bootClassLoader;
// Default class loader for boot delegation.
private final static ClassLoader m_defBootClassLoader;
// Statically define the default class loader for boot delegation.
static
{
ClassLoader cl = null;
try
{
cl = (ClassLoader) BundleRevisionImpl.getSecureAction().invokeDirect(
BundleRevisionImpl.getSecureAction().getMethod(ClassLoader.class, "getPlatformClassLoader", null)
,null, null);
}
catch (Throwable t)
{
// Not on Java9
try
{
Constructor ctor = BundleRevisionImpl.getSecureAction().getDeclaredConstructor(
SecureClassLoader.class, new Class[]{ClassLoader.class});
BundleRevisionImpl.getSecureAction().setAccesssible(ctor);
cl = (ClassLoader) BundleRevisionImpl.getSecureAction().invoke(
ctor, new Object[]{null});
}
catch (Throwable ex)
{
// On Android we get an exception if we set the parent class loader
// to null, so we will work around that case by setting the parent
// class loader to the system class loader in getClassLoader() below.
cl = null;
System.err.println("Problem creating boot delegation class loader: " + ex);
}
}
m_defBootClassLoader = cl;
}
// Boolean flag to enable/disable implicit boot delegation.
private final boolean m_implicitBootDelegation;
// Boolean flag to enable/disable local URLs.
private final boolean m_useLocalURLs;
// Re-usable security manager for accessing class context.
private static SecurityManagerEx m_sm = new SecurityManagerEx();
// Thread local to detect class loading cycles.
private final ThreadLocal m_cycleCheck = new ThreadLocal();
// Thread local to keep track of deferred activation.
private static final ThreadLocal m_deferredActivation = new ThreadLocal();
// Flag indicating whether this wiring has been disposed.
private volatile boolean m_isDisposed = false;
private volatile ConcurrentHashMap<String, ClassLoader> m_accessorLookupCache;
BundleWiringImpl(
Logger logger, Map configMap, StatefulResolver resolver,
BundleRevisionImpl revision, List<BundleRevision> fragments,
List<BundleWire> wires,
Map<String, BundleRevision> importedPkgs,
Map<String, List<BundleRevision>> requiredPkgs) throws Exception
{
m_logger = logger;
m_configMap = configMap;
m_resolver = resolver;
m_revision = revision;
m_importedPkgs = importedPkgs;
m_requiredPkgs = requiredPkgs;
m_wires = Util.newImmutableList(wires);
// We need to sort the fragments and add ourself as a dependent of each one.
// We also need to create an array of fragment contents to attach to our
// content path.
List<Content> fragmentContents = null;
if (fragments != null)
{
// Sort fragments according to ID order, if necessary.
// Note that this sort order isn't 100% correct since
// it uses a string, but it is likely close enough and
// avoids having to create more objects.
if (fragments.size() > 1)
{
SortedMap<String, BundleRevision> sorted = new TreeMap<String, BundleRevision>();
for (BundleRevision f : fragments)
{
sorted.put(((BundleRevisionImpl) f).getId(), f);
}
fragments = new ArrayList(sorted.values());
}
fragmentContents = new ArrayList<Content>(fragments.size());
for (int i = 0; (fragments != null) && (i < fragments.size()); i++)
{
fragmentContents.add(
((BundleRevisionImpl) fragments.get(i)).getContent()
.getEntryAsContent(FelixConstants.CLASS_PATH_DOT));
}
}
m_fragments = fragments;
m_fragmentContents = fragmentContents;
// Calculate resolved list of requirements, which includes:
// 1. All requirements for which we have a wire.
// 2. All dynamic imports from the host and any fragments.
// Also create set of imported packages so we can eliminate any
// substituted exports from our resolved capabilities.
Set<String> imports = new HashSet<String>();
List<BundleRequirement> reqList = new ArrayList<BundleRequirement>();
// First add resolved requirements from wires.
for (BundleWire bw : wires)
{
// Fragments may have multiple wires for the same requirement, so we
// need to check for and avoid duplicates in that case.
if (!bw.getRequirement().getNamespace().equals(BundleRevision.HOST_NAMESPACE)
|| !reqList.contains(bw.getRequirement()))
{
reqList.add(bw.getRequirement());
if (bw.getRequirement().getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
imports.add((String)
bw.getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
}
}
}
// Next add dynamic requirements from host.
for (BundleRequirement req : m_revision.getDeclaredRequirements(null))
{
if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
String resolution = req.getDirectives().get(Constants.RESOLUTION_DIRECTIVE);
if ((resolution != null) && (resolution.equals("dynamic")))
{
reqList.add(req);
}
}
}
// Finally, add dynamic requirements from fragments.
if (m_fragments != null)
{
for (BundleRevision fragment : m_fragments)
{
for (BundleRequirement req : fragment.getDeclaredRequirements(null))
{
if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
String resolution = req.getDirectives().get(Constants.RESOLUTION_DIRECTIVE);
if ((resolution != null) && (resolution.equals("dynamic")))
{
reqList.add(req);
}
}
}
}
}
m_resolvedReqs = Util.newImmutableList(reqList);
// Calculate resolved list of capabilities, which includes:
// 1. All capabilities from host and any fragments except for exported
// packages that we have an import (i.e., the export was substituted).
// 2. For fragments the identity capability only.
// And nothing else at this time.
boolean isFragment = Util.isFragment(revision);
List<BundleCapability> capList = new ArrayList<BundleCapability>();
// Also keep track of whether any resolved package capabilities are filtered.
Map<String, List<List<String>>> includedPkgFilters =
new HashMap<String, List<List<String>>>();
Map<String, List<List<String>>> excludedPkgFilters =
new HashMap<String, List<List<String>>>();
if (isFragment)
{
// This is a fragment, add its identity capability
for (BundleCapability cap : m_revision.getDeclaredCapabilities(null))
{
if (IdentityNamespace.IDENTITY_NAMESPACE.equals(cap.getNamespace()))
{
String effective = cap.getDirectives().get(Constants.EFFECTIVE_DIRECTIVE);
if ((effective == null) || (effective.equals(Constants.EFFECTIVE_RESOLVE)))
{
capList.add(cap);
}
}
}
}
else
{
for (BundleCapability cap : m_revision.getDeclaredCapabilities(null))
{
if (!cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
|| (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
&& !imports.contains(cap.getAttributes()
.get(BundleRevision.PACKAGE_NAMESPACE).toString())))
{
// TODO: OSGi R4.4 - We may need to make this more flexible since in the future it may
// be possible to consider other effective values via OBR's Environment.isEffective().
String effective = cap.getDirectives().get(Constants.EFFECTIVE_DIRECTIVE);
if ((effective == null) || (effective.equals(Constants.EFFECTIVE_RESOLVE)))
{
capList.add(cap);
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
List<List<String>> filters =
parsePkgFilters(cap, Constants.INCLUDE_DIRECTIVE);
if (filters != null)
{
includedPkgFilters.put((String)
cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE),
filters);
}
filters = parsePkgFilters(cap, Constants.EXCLUDE_DIRECTIVE);
if (filters != null)
{
excludedPkgFilters.put((String)
cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE),
filters);
}
}
}
}
}
if (m_fragments != null)
{
for (BundleRevision fragment : m_fragments)
{
for (BundleCapability cap : fragment.getDeclaredCapabilities(null))
{
if (IdentityNamespace.IDENTITY_NAMESPACE.equals(cap.getNamespace())) {
// The identity capability is not transferred from the fragment to the bundle
continue;
}
if (!cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
|| (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
&& !imports.contains(cap.getAttributes()
.get(BundleRevision.PACKAGE_NAMESPACE).toString())))
{
// TODO: OSGi R4.4 - We may need to make this more flexible since in the future it may
// be possible to consider other effective values via OBR's Environment.isEffective().
String effective = cap.getDirectives().get(Constants.EFFECTIVE_DIRECTIVE);
if ((effective == null) || (effective.equals(Constants.EFFECTIVE_RESOLVE)))
{
capList.add(cap);
if (cap.getNamespace().equals(
BundleRevision.PACKAGE_NAMESPACE))
{
List<List<String>> filters =
parsePkgFilters(
cap, Constants.INCLUDE_DIRECTIVE);
if (filters != null)
{
includedPkgFilters.put((String)
cap.getAttributes()
.get(BundleRevision.PACKAGE_NAMESPACE),
filters);
}
filters = parsePkgFilters(cap, Constants.EXCLUDE_DIRECTIVE);
if (filters != null)
{
excludedPkgFilters.put((String)
cap.getAttributes()
.get(BundleRevision.PACKAGE_NAMESPACE),
filters);
}
}
}
}
}
}
}
}
if (System.getSecurityManager() != null)
{
for (Iterator<BundleCapability> iter = capList.iterator();iter.hasNext();)
{
BundleCapability cap = iter.next();
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
if (!((BundleProtectionDomain) ((BundleRevisionImpl) cap.getRevision()).getProtectionDomain()).impliesDirect(
new PackagePermission((String) cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE), PackagePermission.EXPORTONLY)))
{
iter.remove();
}
}
else if (!cap.getNamespace().equals(BundleRevision.HOST_NAMESPACE) && !cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE) &&
!cap.getNamespace().equals("osgi.ee"))
{
if (!((BundleProtectionDomain) ((BundleRevisionImpl) cap.getRevision()).getProtectionDomain()).impliesDirect(
new CapabilityPermission(cap.getNamespace(), CapabilityPermission.PROVIDE)))
{
iter.remove();
}
}
}
}
m_resolvedCaps = Util.newImmutableList(capList);
m_includedPkgFilters = (includedPkgFilters.isEmpty())
? Collections.EMPTY_MAP : includedPkgFilters;
m_excludedPkgFilters = (excludedPkgFilters.isEmpty())
? Collections.EMPTY_MAP : excludedPkgFilters;
List<NativeLibrary> libList = (m_revision.getDeclaredNativeLibraries() == null)
? new ArrayList<NativeLibrary>()
: new ArrayList<NativeLibrary>(m_revision.getDeclaredNativeLibraries());
for (int fragIdx = 0;
(m_fragments != null) && (fragIdx < m_fragments.size());
fragIdx++)
{
List<NativeLibrary> libs =
((BundleRevisionImpl) m_fragments.get(fragIdx))
.getDeclaredNativeLibraries();
for (int reqIdx = 0;
(libs != null) && (reqIdx < libs.size());
reqIdx++)
{
libList.add(libs.get(reqIdx));
}
}
// We need to return null here if we don't have any libraries, since a
// zero-length array is used to indicate that matching native libraries
// could not be found when resolving the bundle.
m_resolvedNativeLibs = (libList.isEmpty()) ? null : Util.newImmutableList(libList);
ClassLoader bootLoader = m_defBootClassLoader;
if (revision.getBundle().getBundleId() != 0)
{
Object map = m_configMap.get(FelixConstants.BOOT_CLASSLOADERS_PROP);
if (map instanceof Map)
{
Object l = ((Map) map).get(m_revision.getBundle());
if (l instanceof ClassLoader)
{
bootLoader = (ClassLoader) l;
}
}
}
m_bootClassLoader = bootLoader;
m_implicitBootDelegation =
(m_configMap.get(FelixConstants.IMPLICIT_BOOT_DELEGATION_PROP) == null)
|| Boolean.valueOf(
(String) m_configMap.get(
FelixConstants.IMPLICIT_BOOT_DELEGATION_PROP));
m_useLocalURLs =
m_configMap.get(FelixConstants.USE_LOCALURLS_PROP) != null;
}
private static List<List<String>> parsePkgFilters(BundleCapability cap, String filtername)
{
List<List<String>> filters = null;
String include = cap.getDirectives().get(filtername);
if (include != null)
{
List<String> filterStrings = ManifestParser.parseDelimitedString(include, ",");
filters = new ArrayList<List<String>>(filterStrings.size());
for (int filterIdx = 0; filterIdx < filterStrings.size(); filterIdx++)
{
List<String> substrings =
SimpleFilter.parseSubstring(filterStrings.get(filterIdx));
filters.add(substrings);
}
}
return filters;
}
@Override
public String toString()
{
return m_revision.getBundle().toString();
}
public synchronized void dispose()
{
if (m_fragmentContents != null)
{
for (Content content : m_fragmentContents)
{
content.close();
}
}
m_classLoader = null;
m_isDisposed = true;
m_accessorLookupCache = null;
}
// TODO: OSGi R4.3 - This really shouldn't be public, but it is needed by the
// resolver to determine if a bundle can dynamically import.
public boolean hasPackageSource(String pkgName)
{
return (m_importedPkgs.containsKey(pkgName) || m_requiredPkgs.containsKey(pkgName));
}
// TODO: OSGi R4.3 - This really shouldn't be public, but it is needed by the
// to implement dynamic imports.
public BundleRevision getImportedPackageSource(String pkgName)
{
return m_importedPkgs.get(pkgName);
}
List<BundleRevision> getFragments()
{
return m_fragments;
}
List<Content> getFragmentContents()
{
return m_fragmentContents;
}
@Override
public boolean isCurrent()
{
BundleRevision current = getBundle().adapt(BundleRevision.class);
return (current != null) && (current.getWiring() == this);
}
@Override
public boolean isInUse()
{
return !m_isDisposed;
}
@Override
public List<Capability> getResourceCapabilities(String namespace)
{
return BundleRevisionImpl.asCapabilityList(getCapabilities(namespace));
}
@Override
public List<BundleCapability> getCapabilities(String namespace)
{
if (isInUse())
{
List<BundleCapability> result = m_resolvedCaps;
if (namespace != null)
{
result = new ArrayList<BundleCapability>();
for (BundleCapability cap : m_resolvedCaps)
{
if (cap.getNamespace().equals(namespace))
{
result.add(cap);
}
}
}
return result;
}
return null;
}
@Override
public List<Requirement> getResourceRequirements(String namespace)
{
return BundleRevisionImpl.asRequirementList(getRequirements(namespace));
}
@Override
public List<BundleRequirement> getRequirements(String namespace)
{
if (isInUse())
{
List<BundleRequirement> searchReqs = m_resolvedReqs;
List<BundleRequirement> wovenReqs = m_wovenReqs;
List<BundleRequirement> result = m_resolvedReqs;
if (wovenReqs != null)
{
searchReqs = new ArrayList<BundleRequirement>(m_resolvedReqs);
searchReqs.addAll(wovenReqs);
result = searchReqs;
}
if (namespace != null)
{
result = new ArrayList<BundleRequirement>();
for (BundleRequirement req : searchReqs)
{
if (req.getNamespace().equals(namespace))
{
result.add(req);
}
}
}
return result;
}
return null;
}
public List<NativeLibrary> getNativeLibraries()
{
return m_resolvedNativeLibs;
}
private static List<Wire> asWireList(List wires)
{
return wires;
}
@Override
public List<Wire> getProvidedResourceWires(String namespace)
{
return asWireList(getProvidedWires(namespace));
}
@Override
public List<BundleWire> getProvidedWires(String namespace)
{
if (isInUse())
{
return m_revision.getBundle()
.getFramework().getDependencies().getProvidedWires(m_revision, namespace);
}
return null;
}
@Override
public List<Wire> getRequiredResourceWires(String namespace)
{
return asWireList(getRequiredWires(namespace));
}
@Override
public List<BundleWire> getRequiredWires(String namespace)
{
if (isInUse())
{
List<BundleWire> result = m_wires;
if (namespace != null)
{
result = new ArrayList<BundleWire>();
for (BundleWire bw : m_wires)
{
if (bw.getRequirement().getNamespace().equals(namespace))
{
result.add(bw);
}
}
}
return result;
}
return null;
}
public synchronized void addDynamicWire(BundleWire wire)
{
// Make new wires list.
List<BundleWire> wires = new ArrayList<BundleWire>(m_wires);
wires.add(wire);
if (wire.getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE) != null)
{
// Make new imported package map.
Map<String, BundleRevision> importedPkgs =
new HashMap<String, BundleRevision>(m_importedPkgs);
importedPkgs.put(
(String) wire.getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE),
wire.getProviderWiring().getRevision());
m_importedPkgs = importedPkgs;
}
// Update associated member values.
// Technically, there is a window here where readers won't see
// both values updates at the same time, but it seems unlikely
// to cause any issues.
m_wires = Util.newImmutableList(wires);
}
@Override
public BundleRevision getResource()
{
return m_revision;
}
@Override
public BundleRevision getRevision()
{
return m_revision;
}
@Override
public ClassLoader getClassLoader()
{
if (m_isDisposed || Util.isFragment(m_revision))
{
return null;
}
return getClassLoaderInternal();
}
private ClassLoader getClassLoaderInternal()
{
ClassLoader classLoader = m_classLoader;
if (m_classLoader != null)
{
return classLoader;
}
else
{
return _getClassLoaderInternal();
}
}
private synchronized ClassLoader _getClassLoaderInternal()
{
// Only try to create the class loader if the bundle
// is not disposed.
if (!m_isDisposed && (m_classLoader == null))
{
m_classLoader = BundleRevisionImpl.getSecureAction().run(
new PrivilegedAction<BundleClassLoader>()
{
@Override
public BundleClassLoader run()
{
return new BundleClassLoader(BundleWiringImpl.this, determineParentClassLoader(), m_logger);
}
}
);
}
return m_classLoader;
}
@Override
public List<URL> findEntries(String path, String filePattern, int options)
{
if (isInUse())
{
if (!Util.isFragment(m_revision))
{
Enumeration<URL> e =
m_revision.getBundle().getFramework()
.findBundleEntries(m_revision, path, filePattern,
(options & BundleWiring.FINDENTRIES_RECURSE) > 0);
List<URL> entries = new ArrayList<URL>();
while ((e != null) && e.hasMoreElements())
{
entries.add(e.nextElement());
}
return Util.newImmutableList(entries);
}
return Collections.EMPTY_LIST;
}
return null;
}
// Thread local to detect class loading cycles.
private final ThreadLocal m_listResourcesCycleCheck = new ThreadLocal();
@Override
public Collection<String> listResources(
String path, String filePattern, int options)
{
// Implementation note: If you enable the DEBUG option for
// listResources() to print from where each resource comes,
// it will not give 100% accurate answers in the face of
// Require-Bundle cycles with overlapping content since
// the actual source will depend on who does the class load
// first. Further, normal class loaders cache class load
// results so it is always the same subsequently, but we
// don't do that here so it will always return a different
// result depending upon who is asking. Moral to the story:
// don't do cycles and certainly don't do them with
// overlapping content.
Collection<String> resources = null;
// Normalize path.
if ((path.length() > 0) && (path.charAt(0) == '/'))
{
path = path.substring(1);
}
if ((path.length() > 0) && (path.charAt(path.length() - 1) != '/'))
{
path = path + '/';
}
// Parse the file filter.
filePattern = (filePattern == null) ? "*" : filePattern;
List<String> pattern = SimpleFilter.parseSubstring(filePattern);
// We build an internal collection of ResourceSources, since this
// allows us to print out additional debug information.
Collection<ResourceSource> sources = listResourcesInternal(path, pattern, options);
if (sources != null)
{
boolean debug = (options & LISTRESOURCES_DEBUG) > 0;
resources = new TreeSet<String>();
for (ResourceSource source : sources)
{
if (debug)
{
resources.add(source.toString());
}
else
{
resources.add(source.m_resource);
}
}
}
return resources;
}
private synchronized Collection<ResourceSource> listResourcesInternal(
String path, List<String> pattern, int options)
{
if (isInUse())
{
boolean recurse = (options & BundleWiring.LISTRESOURCES_RECURSE) > 0;
boolean localOnly = (options & BundleWiring.LISTRESOURCES_LOCAL) > 0;
// Check for cycles, which can happen with Require-Bundle.
Set<String> cycles = (Set<String>) m_listResourcesCycleCheck.get();
if (cycles == null)
{
cycles = new HashSet<String>();
m_listResourcesCycleCheck.set(cycles);
}
if (cycles.contains(path))
{
return Collections.EMPTY_LIST;
}
cycles.add(path);
try
{
// Calculate set of remote resources (i.e., those either
// imported or required).
Collection<ResourceSource> remoteResources = new TreeSet<ResourceSource>();
// Imported packages cannot have merged content, so we need to
// keep track of these packages.
Set<String> noMerging = new HashSet<String>();
// Loop through wires to compute remote resources.
for (BundleWire bw : m_wires)
{
if (bw.getCapability().getNamespace()
.equals(BundleRevision.PACKAGE_NAMESPACE))
{
// For imported packages, we only need to calculate
// the remote resources of the specific imported package.
remoteResources.addAll(
calculateRemotePackageResources(
bw, bw.getCapability(), recurse,
path, pattern, noMerging));
}
else if (bw.getCapability().getNamespace()
.equals(BundleRevision.BUNDLE_NAMESPACE))
{
// For required bundles, all declared package capabilities
// from the required bundle will be available to requirers,
// so get the target required bundle's declared packages
// and handle them in a similar fashion to a normal import
// except that their content can be merged with local
// packages.
List<BundleCapability> exports =
bw.getProviderWiring().getRevision()
.getDeclaredCapabilities(BundleRevision.PACKAGE_NAMESPACE);
for (BundleCapability export : exports)
{
remoteResources.addAll(
calculateRemotePackageResources(
bw, export, recurse, path, pattern, null));
}
// Since required bundle may reexport bundles it requires,
// check its wires for this case.
List<BundleWire> requiredBundles =
bw.getProviderWiring().getRequiredWires(
BundleRevision.BUNDLE_NAMESPACE);
for (BundleWire rbWire : requiredBundles)
{
String visibility =
rbWire.getRequirement().getDirectives()
.get(Constants.VISIBILITY_DIRECTIVE);
if ((visibility != null)
&& (visibility.equals(Constants.VISIBILITY_REEXPORT)))
{
// For each reexported required bundle, treat them
// in a similar fashion as a normal required bundle
// by including all of their declared package
// capabilities in the requiring bundle's class
// space.
List<BundleCapability> reexports =
rbWire.getProviderWiring().getRevision()
.getDeclaredCapabilities(BundleRevision.PACKAGE_NAMESPACE);
for (BundleCapability reexport : reexports)
{
remoteResources.addAll(
calculateRemotePackageResources(
bw, reexport, recurse, path, pattern, null));
}
}
}
}
}
// Calculate set of local resources (i.e., those contained
// in the revision or its fragments).
Collection<ResourceSource> localResources = new TreeSet<ResourceSource>();
// Get the revision's content path, which includes contents
// from fragments.
List<Content> contentPath = m_revision.getContentPath();
for (Content content : contentPath)
{
Enumeration<String> e = content.getEntries();
if (e != null)
{
while (e.hasMoreElements())
{
String resource = e.nextElement();
String resourcePath = getTrailingPath(resource);
if (!noMerging.contains(resourcePath))
{
if ((!recurse && resourcePath.equals(path))
|| (recurse && resourcePath.startsWith(path)))
{
if (matchesPattern(pattern, getPathHead(resource)))
{
localResources.add(
new ResourceSource(resource, m_revision));
}
}
}
}
}
}
if (localOnly)
{
return localResources;
}
else
{
remoteResources.addAll(localResources);
return remoteResources;
}
}
finally
{
cycles.remove(path);
if (cycles.isEmpty())
{
m_listResourcesCycleCheck.set(null);
}
}
}
return null;
}
private Collection<ResourceSource> calculateRemotePackageResources(
BundleWire bw, BundleCapability cap, boolean recurse,
String path, List<String> pattern, Set<String> noMerging)
{
Collection<ResourceSource> resources = Collections.EMPTY_SET;
// Convert package name to a path.
String subpath = (String) cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE);
subpath = subpath.replace('.', '/') + '/';
// If necessary, record that this package should not be merged
// with local content.
if (noMerging != null)
{
noMerging.add(subpath);
}
// If we are not recuring, check for path equality or if
// we are recursing, check that the subpath starts with
// the target path.
if ((!recurse && subpath.equals(path))
|| (recurse && subpath.startsWith(path)))
{
// Delegate to the original provider wiring to have it calculate
// the list of resources in the package. In this case, we don't
// want to recurse since we want the precise package.
resources =
((BundleWiringImpl) bw.getProviderWiring()).listResourcesInternal(
subpath, pattern, 0);
// The delegatedResources result will include subpackages
// which need to be filtered out, since imported packages
// do not give access to subpackages. If a subpackage is
// imported, it will be added by its own wire.
for (Iterator<ResourceSource> it = resources.iterator();
it.hasNext(); )
{
ResourceSource reqResource = it.next();
if (reqResource.m_resource.charAt(
reqResource.m_resource.length() - 1) == '/')
{
it.remove();
}
}
}
// If we are not recursing, but the required package
// is a child of the desired path, then include its
// immediate child package. We do this so that it is
// possible to use listResources() to walk the resource
// tree similar to doing a directory walk one level
// at a time.
else if (!recurse && subpath.startsWith(path))
{
int idx = subpath.indexOf('/', path.length());
if (idx >= 0)
{
subpath = subpath.substring(0, idx + 1);
}
if (matchesPattern(pattern, getPathHead(subpath)))
{
resources = Collections.singleton(
new ResourceSource(subpath, bw.getProviderWiring().getRevision()));
}
}
return resources;
}
private static String getPathHead(String resource)
{
if (resource.length() == 0)
{
return resource;
}
int idx = (resource.charAt(resource.length() - 1) == '/')
? resource.lastIndexOf('/', resource.length() - 2)
: resource.lastIndexOf('/');
if (idx < 0)
{
return resource;
}
return resource.substring(idx + 1);
}
private static String getTrailingPath(String resource)
{
if (resource.length() == 0)
{
return null;
}
int idx = (resource.charAt(resource.length() - 1) == '/')
? resource.lastIndexOf('/', resource.length() - 2)
: resource.lastIndexOf('/');
if (idx < 0)
{
return "";
}
return resource.substring(0, idx + 1);
}
private static boolean matchesPattern(List<String> pattern, String resource)
{
if (resource.charAt(resource.length() - 1) == '/')
{
resource = resource.substring(0, resource.length() - 1);
}
return SimpleFilter.compareSubstring(pattern, resource);
}
@Override
public BundleImpl getBundle()
{
return m_revision.getBundle();
}
//
// Class loader implementation methods.
//
private URL createURL(int port, String path)
{
// Add a slash if there is one already, otherwise
// the is no slash separating the host from the file
// in the resulting URL.
if (!path.startsWith("/"))
{
path = "/" + path;
}
try
{
return BundleRevisionImpl.getSecureAction().createURL(null,
FelixConstants.BUNDLE_URL_PROTOCOL + "://" +
getBundle().getFramework()._getProperty(Constants.FRAMEWORK_UUID) + "_" + m_revision.getId() + ":" + port + path,
getBundle().getFramework().getBundleStreamHandler());
}
catch (MalformedURLException ex)
{
m_logger.log(m_revision.getBundle(),
Logger.LOG_ERROR,
"Unable to create resource URL.",
ex);
}
return null;
}
public Enumeration getResourcesByDelegation(String name)
{
Set requestSet = (Set) m_cycleCheck.get();
if (requestSet == null)
{
requestSet = new HashSet();
m_cycleCheck.set(requestSet);
}
if (!requestSet.contains(name))
{
requestSet.add(name);
try
{
return findResourcesByDelegation(name);
}
finally
{
requestSet.remove(name);
}
}
return null;
}
private Enumeration findResourcesByDelegation(String name)
{
Enumeration urls = null;
List completeUrlList = new ArrayList();
// Get the package of the target class/resource.
String pkgName = Util.getResourcePackage(name);
// Delegate any packages listed in the boot delegation
// property to the parent class loader.
if (shouldBootDelegate(pkgName))
{
try
{
// Get the appropriate class loader for delegation.
ClassLoader bdcl = getBootDelegationClassLoader();
urls = bdcl.getResources(name);
}
catch (IOException ex)
{
// This shouldn't happen and even if it does, there
// is nothing we can do, so just ignore it.
}
// If this is a java.* package, then always terminate the
// search; otherwise, continue to look locally.
if (pkgName.startsWith("java."))
{
return urls;
}
completeUrlList.add(urls);
}
// Look in the revisions's imported packages. If the package is
// imported, then we stop searching no matter the result since
// imported packages cannot be split.
BundleRevision provider = m_importedPkgs.get(pkgName);
if (provider != null)
{
// Delegate to the provider revision.
urls = ((BundleWiringImpl) provider.getWiring()).getResourcesByDelegation(name);
// If we find any resources, then add them.
if ((urls != null) && (urls.hasMoreElements()))
{
completeUrlList.add(urls);
}
// Always return here since imported packages cannot be split
// across required bundles or the revision's content.
return new CompoundEnumeration((Enumeration[])
completeUrlList.toArray(new Enumeration[completeUrlList.size()]));
}
// See whether we can get the resource from the required bundles and
// regardless of whether or not this is the case continue to the next
// step potentially passing on the result of this search (if any).
List<BundleRevision> providers = m_requiredPkgs.get(pkgName);
if (providers != null)
{
for (BundleRevision p : providers)
{
// Delegate to the provider revision.
urls = ((BundleWiringImpl) p.getWiring()).getResourcesByDelegation(name);
// If we find any resources, then add them.
if ((urls != null) && (urls.hasMoreElements()))
{
completeUrlList.add(urls);
}
// Do not return here, since required packages can be split
// across the revision's content.
}
}
// Try the module's own class path. If we can find the resource then
// return it together with the results from the other searches else
// try to look into the dynamic imports.
urls = m_revision.getResourcesLocal(name);
if ((urls != null) && (urls.hasMoreElements()))
{
completeUrlList.add(urls);
}
else
{
// If not found, then try the module's dynamic imports.
// At this point, the module's imports were searched and so was the
// the module's content. Now we make an attempt to load the
// class/resource via a dynamic import, if possible.
try
{
provider = m_resolver.resolve(m_revision, pkgName);
}
catch (ResolutionException ex)
{
// Ignore this since it is likely normal.
}
catch (BundleException ex)
{
// Ignore this since it is likely the result of a resolver hook.
}
if (provider != null)
{
// Delegate to the provider revision.
urls = ((BundleWiringImpl) provider.getWiring()).getResourcesByDelegation(name);
// If we find any resources, then add them.
if ((urls != null) && (urls.hasMoreElements()))
{
completeUrlList.add(urls);
}
}
}
return new CompoundEnumeration((Enumeration[])
completeUrlList.toArray(new Enumeration[completeUrlList.size()]));
}
private ClassLoader determineParentClassLoader()
{
// Determine the class loader's parent based on the
// configuration property; use boot class loader by
// default.
String cfg = (String) m_configMap.get(Constants.FRAMEWORK_BUNDLE_PARENT);
cfg = (cfg == null) ? Constants.FRAMEWORK_BUNDLE_PARENT_BOOT : cfg;
final ClassLoader parent;
if (cfg.equalsIgnoreCase(Constants.FRAMEWORK_BUNDLE_PARENT_APP))
{
parent = BundleRevisionImpl.getSecureAction().getSystemClassLoader();
}
else if (cfg.equalsIgnoreCase(Constants.FRAMEWORK_BUNDLE_PARENT_EXT))
{
parent = BundleRevisionImpl.getSecureAction().getParentClassLoader(
BundleRevisionImpl.getSecureAction().getSystemClassLoader());
}
else if (cfg.equalsIgnoreCase(Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK))
{
parent = BundleRevisionImpl.getSecureAction()
.getClassLoader(BundleRevisionImpl.class);
}
// On Android we cannot set the parent class loader to be null, so
// we special case that situation here and set it to the system
// class loader by default instead, which is not really spec.
else if (m_bootClassLoader == null)
{
parent = BundleRevisionImpl.getSecureAction().getSystemClassLoader();
}
else
{
parent = null;
}
return parent;
}
boolean shouldBootDelegate(String pkgName)
{
// Always boot delegate if the bundle has a configured
// boot class loader.
if (m_bootClassLoader != m_defBootClassLoader)
{
return true;
}
boolean result = false;
// Only consider delegation if we have a package name, since
// we don't want to promote the default package. The spec does
// not take a stand on this issue.
if (pkgName.length() > 0)
{
for (int i = 0;
!result
&& (i < getBundle()
.getFramework().getBootPackages().length);
i++)
{
// Check if the boot package is wildcarded.
// A wildcarded boot package will be in the form "foo.",
// so a matching subpackage will start with "foo.", e.g.,
// "foo.bar".
if (getBundle().getFramework().getBootPackageWildcards()[i]
&& pkgName.startsWith(
getBundle().getFramework().getBootPackages()[i]))
{
return true;
}
// If not wildcarded, then check for an exact match.
else if (getBundle()
.getFramework().getBootPackages()[i].equals(pkgName))
{
return true;
}
}
}
return result;
}
ClassLoader getBootDelegationClassLoader()
{
ClassLoader loader = m_classLoader;
// Get the appropriate class loader for delegation.
ClassLoader parent = (loader == null) ?
determineParentClassLoader() :
BundleRevisionImpl.getSecureAction().getParentClassLoader(loader);
return (parent == null) ? m_bootClassLoader : parent;
}
public Class getClassByDelegation(String name) throws ClassNotFoundException
{
// We do not call getClassLoader().loadClass() for arrays because
// it does not correctly handle array types, which is necessary in
// cases like deserialization using a wrapper class loader.
if ((name != null) && (name.length() > 0) && (name.charAt(0) == '['))
{
return Class.forName(name, false, getClassLoader());
}
// Check to see if the requested class is filtered.
if (isFiltered(name))
{
throw new ClassNotFoundException(name);
}
ClassLoader cl = getClassLoaderInternal();
if (cl == null)
{
throw new ClassNotFoundException(
"Unable to load class '"
+ name
+ "' because the bundle wiring for "
+ m_revision.getSymbolicName()
+ " is no longer valid.");
}
return cl.loadClass(name);
}
private boolean isFiltered(String name)
{
String pkgName = Util.getClassPackage(name);
List<List<String>> includeFilters = m_includedPkgFilters.get(pkgName);
List<List<String>> excludeFilters = m_excludedPkgFilters.get(pkgName);
if ((includeFilters == null) && (excludeFilters == null))
{
return false;
}
// Get the class name portion of the target class.
String className = Util.getClassName(name);
// If there are no include filters then all classes are included
// by default, otherwise try to find one match.
boolean included = (includeFilters == null);
for (int i = 0;
(!included) && (includeFilters != null) && (i < includeFilters.size());
i++)
{
included = SimpleFilter.compareSubstring(includeFilters.get(i), className);
}
// If there are no exclude filters then no classes are excluded
// by default, otherwise try to find one match.
boolean excluded = false;
for (int i = 0;
(!excluded) && (excludeFilters != null) && (i < excludeFilters.size());
i++)
{
excluded = SimpleFilter.compareSubstring(excludeFilters.get(i), className);
}
return !included || excluded;
}
public URL getResourceByDelegation(String name)
{
try
{
return (URL) findClassOrResourceByDelegation(name, false);
}
catch (ClassNotFoundException ex)
{
// This should never be thrown because we are loading resources.
}
catch (ResourceNotFoundException ex)
{
m_logger.log(m_revision.getBundle(),
Logger.LOG_DEBUG,
ex.getMessage());
}
return null;
}
private Object findClassOrResourceByDelegation(String name, boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
Object result = null;
Set requestSet = (Set) m_cycleCheck.get();
if (requestSet == null)
{
requestSet = new HashSet();
m_cycleCheck.set(requestSet);
}
if (requestSet.add(name))
{
try
{
// Get the package of the target class/resource.
String pkgName = (isClass) ? Util.getClassPackage(name) : Util.getResourcePackage(name);
boolean accessor = name.startsWith("sun.reflect.Generated") || name.startsWith("jdk.internal.reflect.");
if (accessor)
{
if (m_accessorLookupCache == null)
{
m_accessorLookupCache = new ConcurrentHashMap<String, ClassLoader>();
}
ClassLoader loader = m_accessorLookupCache.get(name);
if (loader != null)
{
return loader.loadClass(name);
}
}
// Delegate any packages listed in the boot delegation
// property to the parent class loader.
if (shouldBootDelegate(pkgName))
{
try
{
// Get the appropriate class loader for delegation.
ClassLoader bdcl = getBootDelegationClassLoader();
result = (isClass) ? (Object) bdcl.loadClass(name) : (Object) bdcl.getResource(name);
// If this is a java.* package, then always terminate the
// search; otherwise, continue to look locally if not found.
if (pkgName.startsWith("java.") || (result != null))
{
if (accessor)
{
m_accessorLookupCache.put(name, bdcl);
}
return result;
}
}
catch (ClassNotFoundException ex)
{
// If this is a java.* package, then always terminate the
// search; otherwise, continue to look locally if not found.
if (pkgName.startsWith("java."))
{
throw ex;
}
}
}
if (accessor)
{
List<Collection<BundleRevision>> allRevisions = new ArrayList<Collection<BundleRevision>>( 1 + m_requiredPkgs.size());
allRevisions.add(m_importedPkgs.values());
allRevisions.addAll(m_requiredPkgs.values());
for (Collection<BundleRevision> revisions : allRevisions)
{
for (BundleRevision revision : revisions)
{
ClassLoader loader = revision.getWiring().getClassLoader();
if (loader != null && loader instanceof BundleClassLoader)
{
BundleClassLoader bundleClassLoader = (BundleClassLoader) loader;
result = bundleClassLoader.findLoadedClassInternal(name);
if (result != null)
{
m_accessorLookupCache.put(name, bundleClassLoader);
return result;
}
}
}
}
try
{
result = tryImplicitBootDelegation(name, isClass);
}
catch (Exception ex)
{
// Ignore, will throw using CNFE_CLASS_LOADER
}
if (result != null)
{
m_accessorLookupCache.put(name, BundleRevisionImpl.getSecureAction()
.getClassLoader(this.getClass()));
return result;
}
else
{
m_accessorLookupCache.put(name, CNFE_CLASS_LOADER);
CNFE_CLASS_LOADER.loadClass(name);
}
}
// Look in the revision's imports. Note that the search may
// be aborted if this method throws an exception, otherwise
// it continues if a null is returned.
result = searchImports(pkgName, name, isClass);
// If not found, try the revision's own class path.
if (result == null)
{
if (isClass)
{
ClassLoader cl = getClassLoaderInternal();
if (cl == null)
{
throw new ClassNotFoundException(
"Unable to load class '"
+ name
+ "' because the bundle wiring for "
+ m_revision.getSymbolicName()
+ " is no longer valid.");
}
result = ((BundleClassLoader) cl).findClass(name);
}
else
{
result = m_revision.getResourceLocal(name);
}
// If still not found, then try the revision's dynamic imports.
if (result == null)
{
result = searchDynamicImports(pkgName, name, isClass);
}
}
}
finally
{
requestSet.remove(name);
}
}
else
{
// If a cycle is detected, we should return null to break the
// cycle. This should only ever be return to internal class
// loading code and not to the actual instigator of the class load.
return null;
}
if (result == null)
{
if (isClass)
{
throw new ClassNotFoundException(
name + " not found by " + this.getBundle());
}
else
{
throw new ResourceNotFoundException(
name + " not found by " + this.getBundle());
}
}
return result;
}
private Object searchImports(String pkgName, String name, boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
// Check if the package is imported.
BundleRevision provider = m_importedPkgs.get(pkgName);
if (provider != null)
{
// If we find the class or resource, then return it.
Object result = (isClass)
? (Object) ((BundleWiringImpl) provider.getWiring()).getClassByDelegation(name)
: (Object) ((BundleWiringImpl) provider.getWiring()).getResourceByDelegation(name);
if (result != null)
{
return result;
}
// If no class or resource was found, then we must throw an exception
// since the provider of this package did not contain the
// requested class and imported packages are atomic.
if (isClass)
{
throw new ClassNotFoundException(name);
}
throw new ResourceNotFoundException(name);
}
// Check if the package is required.
List<BundleRevision> providers = m_requiredPkgs.get(pkgName);
if (providers != null)
{
for (BundleRevision p : providers)
{
// If we find the class or resource, then return it.
try
{
Object result = (isClass)
? (Object) ((BundleWiringImpl) p.getWiring()).getClassByDelegation(name)
: (Object) ((BundleWiringImpl) p.getWiring()).getResourceByDelegation(name);
if (result != null)
{
return result;
}
}
catch (ClassNotFoundException ex)
{
// Since required packages can be split, don't throw an
// exception here if it is not found. Instead, we'll just
// continue searching other required bundles and the
// revision's local content.
}
}
}
return null;
}
private Object searchDynamicImports(
final String pkgName, final String name, final boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
// At this point, the module's imports were searched and so was the
// the module's content. Now we make an attempt to load the
// class/resource via a dynamic import, if possible.
BundleRevision provider = null;
try
{
provider = m_resolver.resolve(m_revision, pkgName);
}
catch (ResolutionException ex)
{
// Ignore this since it is likely normal.
}
catch (BundleException ex)
{
// Ignore this since it is likely the result of a resolver hook.
}
// If the dynamic import was successful, then this initial
// time we must directly return the result from dynamically
// created package sources, but subsequent requests for
// classes/resources in the associated package will be
// processed as part of normal static imports.
if (provider != null)
{
// Return the class or resource.
return (isClass)
? (Object) ((BundleWiringImpl) provider.getWiring()).getClassByDelegation(name)
: (Object) ((BundleWiringImpl) provider.getWiring()).getResourceByDelegation(name);
}
return tryImplicitBootDelegation(name, isClass);
}
private Object tryImplicitBootDelegation(final String name, final boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
// If implicit boot delegation is enabled, then try to guess whether
// we should boot delegate.
if (m_implicitBootDelegation)
{
// At this point, the class/resource could not be found by the bundle's
// static or dynamic imports, nor its own content. Before we throw
// an exception, we will try to determine if the instigator of the
// class/resource load was a class from a bundle or not. This is necessary
// because the specification mandates that classes on the class path
// should be hidden (except for java.*), but it does allow for these
// classes/resources to be exposed by the system bundle as an export.
// However, in some situations classes on the class path make the faulty
// assumption that they can access everything on the class path from
// every other class loader that they come in contact with. This is
// not true if the class loader in question is from a bundle. Thus,
// this code tries to detect that situation. If the class instigating
// the load request was NOT from a bundle, then we will make the
// assumption that the caller actually wanted to use the parent class
// loader and we will delegate to it. If the class was
// from a bundle, then we will enforce strict class loading rules
// for the bundle and throw an exception.
// Get the class context to see the classes on the stack.
final Class[] classes = m_sm.getClassContext();
try
{
if (System.getSecurityManager() != null)
{
return AccessController
.doPrivileged(new PrivilegedExceptionAction()
{
@Override
public Object run() throws Exception
{
return doImplicitBootDelegation(classes, name,
isClass);
}
});
}
else
{
return doImplicitBootDelegation(classes, name, isClass);
}
}
catch (PrivilegedActionException ex)
{
Exception cause = ex.getException();
if (cause instanceof ClassNotFoundException)
{
throw (ClassNotFoundException) cause;
}
else
{
throw (ResourceNotFoundException) cause;
}
}
}
return null;
}
private Object doImplicitBootDelegation(Class[] classes, String name, boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
// Start from 1 to skip security manager class.
for (int i = 1; i < classes.length; i++)
{
// Find the first class on the call stack that is not from
// the class loader that loaded the Felix classes or is not
// a class loader or class itself, because we want to ignore
// calls to ClassLoader.loadClass() and Class.forName() since
// we are trying to find out who instigated the class load.
// Also ignore inner classes of class loaders, since we can
// assume they are a class loader too.
// TODO: FRAMEWORK - This check is a hack and we should see if we can think
// of another way to do it, since it won't necessarily work in all situations.
// Since Felix uses threads for changing the start level
// and refreshing packages, it is possible that there are no
// bundle classes on the call stack; therefore, as soon as we
// see Thread on the call stack we exit this loop. Other cases
// where bundles actually use threads are not an issue because
// the bundle classes will be on the call stack before the
// Thread class.
if (Thread.class.equals(classes[i]))
{
break;
}
// Break if the current class came from a bundle, since we should
// not implicitly boot delegate in that case.
else if (isClassLoadedFromBundleRevision(classes[i]))
{
break;
}
// Break if this goes through BundleImpl because it must be a call
// to Bundle.loadClass() which should not implicitly boot delegate.
else if (BundleImpl.class.equals(classes[i]))
{
break;
}
// Break if this goes through ServiceRegistrationImpl.ServiceReferenceImpl
// because it must be a assignability check which should not implicitly boot delegate
else if (ServiceRegistrationImpl.ServiceReferenceImpl.class.equals(classes[i]))
{
break;
}
else if (isClassExternal(classes[i]))
{
try
{
// Return the class or resource from the parent class loader.
return (isClass)
? (Object) BundleRevisionImpl.getSecureAction()
.getClassLoader(this.getClass()).loadClass(name)
: (Object) BundleRevisionImpl.getSecureAction()
.getClassLoader(this.getClass()).getResource(name);
}
catch (NoClassDefFoundError ex)
{
// Ignore, will return null
}
break;
}
}
return null;
}
private boolean isClassLoadedFromBundleRevision(Class clazz)
{
// The target class is loaded by a bundle class loader,
// then return true.
if (BundleClassLoader.class.isInstance(
BundleRevisionImpl.getSecureAction().getClassLoader(clazz)))
{
return true;
}
// If the target class was loaded from a class loader that
// came from a bundle, then return true.
ClassLoader last = null;
for (ClassLoader cl = BundleRevisionImpl.getSecureAction().getClassLoader(clazz);
(cl != null) && (last != cl);
cl = BundleRevisionImpl.getSecureAction().getClassLoader(cl.getClass()))
{
last = cl;
if (BundleClassLoader.class.isInstance(cl))
{
return true;
}
}
return false;
}
/**
* Tries to determine whether the given class is part of the framework or not.
* Framework classes include everything in org.apache.felix.framework.* and
* org.osgi.framework.*. We also consider ClassLoader and Class to be internal
* classes, because they are inserted into the stack trace as a result of
* method overloading. Typically, ClassLoader or Class will be mixed in
* between framework classes or will be at the point where the class loading
* request enters the framework class loading mechanism, which will then be
* followed by either bundle or external code, which will then exit our
* attempt to determine if we should boot delegate or not. Other standard
* class loaders, like URLClassLoader, are considered external classes and
* should trigger boot delegation. This means that bundles can create standard
* class loaders to get access to boot packages, but this is the standard
* behavior of class loaders.
* @param clazz the class to determine if it is external or not.
* @return <tt>true</tt> if the class is external, otherwise <tt>false</tt>.
*/
private boolean isClassExternal(Class clazz)
{
if (clazz.getName().startsWith("org.apache.felix.framework."))
{
return false;
}
else if (clazz.getName().startsWith("org.osgi.framework."))
{
return false;
}
else if (ClassLoader.class.equals(clazz))
{
return false;
}
else if (Class.class.equals(clazz))
{
return false;
}
return true;
}
static class ToLocalUrlEnumeration implements Enumeration
{
final Enumeration m_enumeration;
ToLocalUrlEnumeration(Enumeration enumeration)
{
m_enumeration = enumeration;
}
@Override
public boolean hasMoreElements()
{
return m_enumeration.hasMoreElements();
}
@Override
public Object nextElement()
{
return convertToLocalUrl((URL) m_enumeration.nextElement());
}
}
public static class BundleClassLoader extends SecureClassLoader implements BundleReference
{
static final boolean m_isParallel;
static
{
m_isParallel = registerAsParallel();
}
@IgnoreJRERequirement
private static boolean registerAsParallel()
{
boolean registered = false;
try
{
registered = ClassLoader.registerAsParallelCapable();
}
catch (Throwable th)
{
// This is OK on older java versions
}
return registered;
}
// Flag used to determine if a class has been loaded from this class
// loader or not.
private volatile boolean m_isActivationTriggered = false;
private Object[][] m_cachedLibs = new Object[0][];
private static final int LIBNAME_IDX = 0;
private static final int LIBPATH_IDX = 1;
private final ConcurrentHashMap<String, Thread> m_classLocks = new ConcurrentHashMap<String, Thread>();
private final BundleWiringImpl m_wiring;
private final Logger m_logger;
public BundleClassLoader(BundleWiringImpl wiring, ClassLoader parent, Logger logger)
{
super(parent);
m_wiring = wiring;
m_logger = logger;
}
public boolean isActivationTriggered()
{
return m_isActivationTriggered;
}
@Override
public BundleImpl getBundle()
{
return m_wiring.getBundle();
}
@Override
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
Class clazz = findLoadedClass(name);
if (clazz == null)
{
try
{
clazz = (Class) m_wiring.findClassOrResourceByDelegation(name, true);
}
catch (ResourceNotFoundException ex)
{
// This should never happen since we are asking for a class,
// so just ignore it.
}
catch (ClassNotFoundException cnfe)
{
ClassNotFoundException ex = cnfe;
if (m_logger.getLogLevel() >= Logger.LOG_DEBUG)
{
String msg = diagnoseClassLoadError(m_wiring.m_resolver, m_wiring.m_revision, name);
ex = (msg != null)
? new ClassNotFoundException(msg, cnfe)
: ex;
}
throw ex;
}
if (clazz == null)
{
// We detected a cycle
throw new ClassNotFoundException("Cycle detected while trying to load class: " + name);
}
}
// Resolve the class and return it.
if (resolve)
{
resolveClass(clazz);
}
return clazz;
}
@Override
protected Class findClass(String name) throws ClassNotFoundException
{
Class clazz = findLoadedClass(name);
// Search for class in bundle revision.
if (clazz == null)
{
// Do a quick check to try to avoid searching for classes on a
// disposed class loader, which will avoid some odd exception.
// This won't prevent all weird exception, since the wiring could
// still get disposed of after this check, but it will prevent
// some, perhaps.
if (m_wiring.m_isDisposed)
{
throw new ClassNotFoundException(
"Unable to load class '"
+ name
+ "' because the bundle wiring for "
+ m_wiring.m_revision.getSymbolicName()
+ " is no longer valid.");
}
String actual = name.replace('.', '/') + ".class";
byte[] bytes = null;
// Check the bundle class path.
List<Content> contentPath = m_wiring.m_revision.getContentPath();
Content content = null;
for (int i = 0;
(bytes == null) &&
(i < contentPath.size()); i++)
{
bytes = contentPath.get(i).getEntryAsBytes(actual);
content = contentPath.get(i);
}
if (bytes != null)
{
// Get package name.
String pkgName = Util.getClassPackage(name);
// Get weaving hooks and invoke them to give them a
// chance to weave the class' byte code before we
// define it.
// NOTE: We don't try to dynamically track hook addition
// or removal, we just get a snapshot and leave any changes
// as a race condition, doing any necessary clean up in
// the error handling.
Felix felix = m_wiring.m_revision.getBundle().getFramework();
Set<ServiceReference<WeavingHook>> hooks =
felix.getHookRegistry().getHooks(WeavingHook.class);
Set<ServiceReference<WovenClassListener>> wovenClassListeners =
felix.getHookRegistry().getHooks(WovenClassListener.class);
WovenClassImpl wci = null;
if (!hooks.isEmpty())
{
// Create woven class to be used for hooks.
wci = new WovenClassImpl(name, m_wiring, bytes);
try
{
transformClass(felix, wci, hooks, wovenClassListeners,
name, bytes);
}
catch (Error e)
{
// Mark the woven class as incomplete.
wci.complete(null, null, null);
wci.setState(WovenClass.TRANSFORMING_FAILED);
callWovenClassListeners(felix, wovenClassListeners, wci);
throw e;
}
}
try
{
clazz = isParallel() ? defineClassParallel(name, felix, wovenClassListeners, wci, bytes, content, pkgName) :
defineClassNotParallel(name, felix, wovenClassListeners, wci, bytes, content, pkgName);
}
catch (ClassFormatError e)
{
if (wci != null)
{
wci.setState(WovenClass.DEFINE_FAILED);
callWovenClassListeners(felix, wovenClassListeners, wci);
}
throw e;
}
// Perform deferred activation without holding the class loader lock,
// if the class we are returning is the instigating class.
List deferredList = (List) m_deferredActivation.get();
if ((deferredList != null)
&& (deferredList.size() > 0)
&& ((Object[]) deferredList.get(0))[0].equals(name))
{
// Null the deferred list.
m_deferredActivation.set(null);
while (!deferredList.isEmpty())
{
// Lazy bundles should be activated in the reverse order
// of when they were added to the deferred list, so grab
// them from the end of the deferred list.
Object[] lazy = (Object[]) deferredList.remove(deferredList.size() - 1);
try
{
felix.getFramework().activateBundle((BundleImpl) (lazy)[1], true);
}
catch (Throwable ex)
{
m_logger.log((BundleImpl) (lazy)[1],
Logger.LOG_WARNING,
"Unable to lazily start bundle.",
ex);
}
}
}
}
}
return clazz;
}
Class defineClassParallel(String name, Felix felix, Set<ServiceReference<WovenClassListener>> wovenClassListeners, WovenClassImpl wci, byte[] bytes,
Content content, String pkgName) throws ClassFormatError
{
Class clazz = null;
Thread me = Thread.currentThread();
while (clazz == null && m_classLocks.putIfAbsent(name, me) != me)
{
clazz = findLoadedClass(name);
}
if (clazz == null)
{
try
{
clazz = findLoadedClass(name);
if (clazz == null)
{
clazz = defineClass(felix, wovenClassListeners, wci, name,
bytes, content, pkgName);
}
}
finally
{
m_classLocks.remove(name);
}
}
return clazz;
}
Class defineClassNotParallel(String name, Felix felix, Set<ServiceReference<WovenClassListener>> wovenClassListeners, WovenClassImpl wci, byte[] bytes,
Content content, String pkgName) throws ClassFormatError
{
Class clazz = findLoadedClass(name);
if (clazz == null)
{
synchronized (m_classLocks)
{
clazz = findLoadedClass(name);
if (clazz == null)
{
clazz = defineClass(felix, wovenClassListeners, wci, name,
bytes, content, pkgName);
}
}
}
return clazz;
}
Class defineClass(Felix felix,
Set<ServiceReference<WovenClassListener>> wovenClassListeners,
WovenClassImpl wci, String name, byte[] bytes, Content content, String pkgName)
throws ClassFormatError
{
// If we have a woven class then get the class bytes from
// it since they may have changed.
// NOTE: We are taking a snapshot of these values and
// are not preventing a malbehaving weaving hook from
// modifying them after the fact. The price of preventing
// this isn't worth it, since they can already wreck
// havoc via weaving anyway. However, we do pass the
// snapshot values into the woven class when we mark it
// as complete so that it will refect the actual values
// we used to define the class.
if (wci != null)
{
bytes = wci._getBytes();
List<String> wovenImports = wci.getDynamicImportsInternal();
// Try to add any woven dynamic imports, since they
// could potentially be needed when defining the class.
List<BundleRequirement> allWovenReqs =
new ArrayList<BundleRequirement>();
for (String s : wovenImports)
{
try
{
List<BundleRequirement> wovenReqs =
ManifestParser.parseDynamicImportHeader(
m_logger, m_wiring.m_revision, s);
allWovenReqs.addAll(wovenReqs);
}
catch (BundleException ex)
{
// There should be no exception here
// since we checked syntax before adding
// dynamic import strings to list.
}
}
// Add the dynamic requirements.
if (!allWovenReqs.isEmpty())
{
// Check for duplicate woven imports.
// First grab existing woven imports, if any.
Set<String> filters = new HashSet<String>();
if (m_wiring.m_wovenReqs != null)
{
for (BundleRequirement req : m_wiring.m_wovenReqs)
{
filters.add(
((BundleRequirementImpl) req)
.getFilter().toString());
}
}
// Then check new woven imports for duplicates
// against existing and self.
int idx = allWovenReqs.size();
while (idx < allWovenReqs.size())
{
BundleRequirement wovenReq = allWovenReqs.get(idx);
String filter = ((BundleRequirementImpl)
wovenReq).getFilter().toString();
if (!filters.contains(filter))
{
filters.add(filter);
idx++;
}
else
{
allWovenReqs.remove(idx);
}
}
// Merge existing with new imports, if any.
if (!allWovenReqs.isEmpty())
{
if (m_wiring.m_wovenReqs != null)
{
allWovenReqs.addAll(0, m_wiring.m_wovenReqs);
}
m_wiring.m_wovenReqs = allWovenReqs;
}
}
}
int activationPolicy =
getBundle().isDeclaredActivationPolicyUsed()
? getBundle()
.adapt(BundleRevisionImpl.class).getDeclaredActivationPolicy()
: EAGER_ACTIVATION;
// If the revision is using deferred activation, then if
// we load this class from this revision we need to activate
// the bundle before returning the class. We will short
// circuit the trigger matching if the trigger is already
// tripped.
boolean isTriggerClass = m_isActivationTriggered
? false : m_wiring.m_revision.isActivationTrigger(pkgName);
if (!m_isActivationTriggered
&& isTriggerClass
&& (activationPolicy == BundleRevisionImpl.LAZY_ACTIVATION)
&& (getBundle().getState() == Bundle.STARTING))
{
List deferredList = (List) m_deferredActivation.get();
if (deferredList == null)
{
deferredList = new ArrayList();
m_deferredActivation.set(deferredList);
}
deferredList.add(new Object[]{name, getBundle()});
}
// We need to try to define a Package object for the class
// before we call defineClass() if we haven't already
// created it.
if (pkgName.length() > 0)
{
if (getPackage(pkgName) == null)
{
Object[] params = definePackage(pkgName);
// This is a harmless check-then-act situation,
// where threads might be racing to create different
// classes in the same package, so catch and ignore
// any IAEs that may occur.
try
{
definePackage(
pkgName,
(String) params[0],
(String) params[1],
(String) params[2],
(String) params[3],
(String) params[4],
(String) params[5],
null);
}
catch (IllegalArgumentException ex)
{
// Ignore.
}
}
}
Class clazz = null;
// If we have a security context, then use it to
// define the class with it for security purposes,
// otherwise define the class without a protection domain.
if (m_wiring.m_revision.getProtectionDomain() != null)
{
clazz = defineClass(name, bytes, 0, bytes.length,
m_wiring.m_revision.getProtectionDomain());
}
else
{
clazz = defineClass(name, bytes, 0, bytes.length);
}
if (wci != null)
{
wci.completeDefine(clazz);
wci.setState(WovenClass.DEFINED);
callWovenClassListeners(felix, wovenClassListeners, wci);
}
// At this point if we have a trigger class, then the deferred
// activation trigger has tripped.
if (!m_isActivationTriggered && isTriggerClass && (clazz != null))
{
m_isActivationTriggered = true;
}
return clazz;
}
void transformClass(Felix felix, WovenClassImpl wci,
Set<ServiceReference<WeavingHook>> hooks,
Set<ServiceReference<WovenClassListener>> wovenClassListeners,
String name, byte[] bytes) throws Error {
// Loop through hooks in service ranking order.
for (ServiceReference<WeavingHook> sr : hooks)
{
// Only use the hook if it is not black listed.
if (!felix.getHookRegistry().isHookBlackListed(sr))
{
// Get the hook service object.
// Note that we don't use the bundle context
// to get the service object since that would
// perform sercurity checks.
WeavingHook wh = felix.getService(felix, sr, false);
if (wh != null)
{
try
{
BundleRevisionImpl.getSecureAction()
.invokeWeavingHook(wh, wci);
}
catch (Throwable th)
{
if (!(th instanceof WeavingException))
{
felix.getHookRegistry().blackListHook(sr);
}
felix.fireFrameworkEvent(
FrameworkEvent.ERROR,
sr.getBundle(),
th);
// Throw class format exception per spec.
Error error = new ClassFormatError("Weaving hook failed.");
error.initCause(th);
throw error;
}
finally
{
felix.ungetService(felix, sr, null);
}
}
}
}
wci.setState(WovenClass.TRANSFORMED);
callWovenClassListeners(felix, wovenClassListeners, wci);
}
protected void callWovenClassListeners(Felix felix, Set<ServiceReference<WovenClassListener>> wovenClassListeners, WovenClass wovenClass)
{
if(wovenClassListeners != null)
{
for(ServiceReference<WovenClassListener> currentWovenClassListenerRef : wovenClassListeners)
{
WovenClassListener currentWovenClassListner = felix.getService(felix, currentWovenClassListenerRef, false);
try
{
BundleRevisionImpl.getSecureAction().invokeWovenClassListener(currentWovenClassListner, wovenClass);
}
catch (Exception e)
{
m_logger.log(Logger.LOG_ERROR, "Woven Class Listner failed.", e);
}
finally
{
felix.ungetService(felix, currentWovenClassListenerRef, null);
}
}
}
}
private Object[] definePackage(String pkgName)
{
String spectitle = (String) m_wiring.m_revision.getHeaders().get("Specification-Title");
String specversion = (String) m_wiring.m_revision.getHeaders().get("Specification-Version");
String specvendor = (String) m_wiring.m_revision.getHeaders().get("Specification-Vendor");
String impltitle = (String) m_wiring.m_revision.getHeaders().get("Implementation-Title");
String implversion = (String) m_wiring.m_revision.getHeaders().get("Implementation-Version");
String implvendor = (String) m_wiring.m_revision.getHeaders().get("Implementation-Vendor");
if ((spectitle != null)
|| (specversion != null)
|| (specvendor != null)
|| (impltitle != null)
|| (implversion != null)
|| (implvendor != null))
{
return new Object[] {
spectitle, specversion, specvendor, impltitle, implversion, implvendor
};
}
return new Object[] {null, null, null, null, null, null};
}
@Override
public URL getResource(String name)
{
URL url = m_wiring.getResourceByDelegation(name);
if (m_wiring.m_useLocalURLs)
{
url = convertToLocalUrl(url);
}
return url;
}
@Override
protected URL findResource(String name)
{
return m_wiring.m_revision.getResourceLocal(name);
}
@Override
protected Enumeration findResources(String name)
{
return m_wiring.m_revision.getResourcesLocal(name);
}
@Override
protected String findLibrary(String name)
{
// Remove leading slash, if present.
if (name.startsWith("/"))
{
name = name.substring(1);
}
String result = null;
// CONCURRENCY: In the long run, we might want to break this
// sync block in two to avoid manipulating the cache while
// holding the lock, but for now we will do it the simple way.
synchronized (this)
{
// Check to make sure we haven't already found this library.
for (int i = 0; (result == null) && (i < m_cachedLibs.length); i++)
{
if (m_cachedLibs[i][LIBNAME_IDX].equals(name))
{
result = (String) m_cachedLibs[i][LIBPATH_IDX];
}
}
// If we don't have a cached result, see if we have a matching
// native library.
if (result == null)
{
List<NativeLibrary> libs = m_wiring.getNativeLibraries();
for (int libIdx = 0; (libs != null) && (libIdx < libs.size()); libIdx++)
{
if (libs.get(libIdx).match(m_wiring.m_configMap, name))
{
// Search bundle content first for native library.
result = m_wiring.m_revision.getContent().getEntryAsNativeLibrary(
libs.get(libIdx).getEntryName());
// If not found, then search fragments in order.
for (int i = 0;
(result == null) && (m_wiring.m_fragmentContents != null)
&& (i < m_wiring.m_fragmentContents.size());
i++)
{
result = m_wiring.m_fragmentContents.get(i).getEntryAsNativeLibrary(
libs.get(libIdx).getEntryName());
}
}
}
// Remember the result for future requests.
if (result != null)
{
Object[][] tmp = new Object[m_cachedLibs.length + 1][];
System.arraycopy(m_cachedLibs, 0, tmp, 0, m_cachedLibs.length);
tmp[m_cachedLibs.length] = new Object[] { name, result };
m_cachedLibs = tmp;
}
}
}
return result;
}
protected boolean isParallel()
{
return m_isParallel;
}
@Override
public Enumeration getResources(String name)
{
Enumeration urls = m_wiring.getResourcesByDelegation(name);
if (m_wiring.m_useLocalURLs)
{
urls = new ToLocalUrlEnumeration(urls);
}
return urls;
}
@Override
public String toString()
{
return m_wiring.toString();
}
Class<?> findLoadedClassInternal(String name)
{
return super.findLoadedClass(name);
}
}
static URL convertToLocalUrl(URL url)
{
if (url.getProtocol().equals("bundle"))
{
try
{
url = ((URLHandlersBundleURLConnection)
url.openConnection()).getLocalURL();
}
catch (IOException ex)
{
// Ignore and add original url.
}
}
return url;
}
private static class ResourceSource implements Comparable<ResourceSource>
{
public final String m_resource;
public final BundleRevision m_revision;
public ResourceSource(String resource, BundleRevision revision)
{
m_resource = resource;
m_revision = revision;
}
@Override
public boolean equals(Object o)
{
if (o instanceof ResourceSource)
{
return m_resource.equals(((ResourceSource) o).m_resource);
}
return false;
}
@Override
public int hashCode()
{
return m_resource.hashCode();
}
@Override
public int compareTo(ResourceSource t)
{
return m_resource.compareTo(t.m_resource);
}
@Override
public String toString()
{
return m_resource
+ " -> "
+ m_revision.getSymbolicName()
+ " [" + m_revision + "]";
}
}
private static String diagnoseClassLoadError(
StatefulResolver resolver, BundleRevision revision, String name)
{
// We will try to do some diagnostics here to help the developer
// deal with this exception.
// Get package name.
String pkgName = Util.getClassPackage(name);
if (pkgName.length() == 0)
{
return null;
}
// First, get the bundle string of the revision doing the class loader.
String importer = revision.getBundle().toString();
// Next, check to see if the revision imports the package.
List<BundleWire> wires = (revision.getWiring() == null)
? null : revision.getWiring().getProvidedWires(null);
for (int i = 0; (wires != null) && (i < wires.size()); i++)
{
if (wires.get(i).getCapability().getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE) &&
wires.get(i).getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).equals(pkgName))
{
String exporter = wires.get(i).getProviderWiring().getBundle().toString();
StringBuilder sb = new StringBuilder("*** Package '");
sb.append(pkgName);
sb.append("' is imported by bundle ");
sb.append(importer);
sb.append(" from bundle ");
sb.append(exporter);
sb.append(", but the exported package from bundle ");
sb.append(exporter);
sb.append(" does not contain the requested class '");
sb.append(name);
sb.append("'. Please verify that the class name is correct in the importing bundle ");
sb.append(importer);
sb.append(" and/or that the exported package is correctly bundled in ");
sb.append(exporter);
sb.append(". ***");
return sb.toString();
}
}
// Next, check to see if the package was optionally imported and
// whether or not there is an exporter available.
List<BundleRequirement> reqs = revision.getWiring().getRequirements(null);
/*
* TODO: RB - Fix diagnostic message for optional imports.
for (int i = 0; (reqs != null) && (i < reqs.length); i++)
{
if (reqs[i].getName().equals(pkgName) && reqs[i].isOptional())
{
// Try to see if there is an exporter available.
IModule[] exporters = getResolvedExporters(reqs[i], true);
exporters = (exporters.length == 0)
? getUnresolvedExporters(reqs[i], true) : exporters;
// An exporter might be available, but it may have attributes
// that do not match the importer's required attributes, so
// check that case by simply looking for an exporter of the
// desired package without any attributes.
if (exporters.length == 0)
{
IRequirement pkgReq = new Requirement(
ICapability.PACKAGE_NAMESPACE, "(package=" + pkgName + ")");
exporters = getResolvedExporters(pkgReq, true);
exporters = (exporters.length == 0)
? getUnresolvedExporters(pkgReq, true) : exporters;
}
long expId = (exporters.length == 0)
? -1 : Util.getBundleIdFromModuleId(exporters[0].getId());
StringBuilder sb = new StringBuilder("*** Class '");
sb.append(name);
sb.append("' was not found, but this is likely normal since package '");
sb.append(pkgName);
sb.append("' is optionally imported by bundle ");
sb.append(impId);
sb.append(".");
if (exporters.length > 0)
{
sb.append(" However, bundle ");
sb.append(expId);
if (reqs[i].isSatisfied(
Util.getExportPackage(exporters[0], reqs[i].getName())))
{
sb.append(" does export this package. Bundle ");
sb.append(expId);
sb.append(" must be installed before bundle ");
sb.append(impId);
sb.append(" is resolved or else the optional import will be ignored.");
}
else
{
sb.append(" does export this package with attributes that do not match.");
}
}
sb.append(" ***");
return sb.toString();
}
}
*/
// Next, check to see if the package is dynamically imported by the revision.
if (resolver.isAllowedDynamicImport(revision, pkgName))
{
// Try to see if there is an exporter available.
Map<String, String> dirs = Collections.EMPTY_MAP;
Map<String, Object> attrs = Collections.singletonMap(
BundleRevision.PACKAGE_NAMESPACE, (Object) pkgName);
BundleRequirementImpl req = new BundleRequirementImpl(
revision, BundleRevision.PACKAGE_NAMESPACE, dirs, attrs);
List<BundleCapability> exporters = resolver.findProviders(req, false);
BundleRevision provider = null;
try
{
provider = resolver.resolve(revision, pkgName);
}
catch (Exception ex)
{
provider = null;
}
String exporter = (exporters.isEmpty())
? null : exporters.iterator().next().toString();
StringBuilder sb = new StringBuilder("*** Class '");
sb.append(name);
sb.append("' was not found, but this is likely normal since package '");
sb.append(pkgName);
sb.append("' is dynamically imported by bundle ");
sb.append(importer);
sb.append(".");
if ((exporters.size() > 0) && (provider == null))
{
sb.append(" However, bundle ");
sb.append(exporter);
sb.append(" does export this package with attributes that do not match.");
}
sb.append(" ***");
return sb.toString();
}
// Next, check to see if there are any exporters for the package at all.
Map<String, String> dirs = Collections.EMPTY_MAP;
Map<String, Object> attrs = Collections.singletonMap(
BundleRevision.PACKAGE_NAMESPACE, (Object) pkgName);
BundleRequirementImpl req = new BundleRequirementImpl(
revision, BundleRevision.PACKAGE_NAMESPACE, dirs, attrs);
List<BundleCapability> exports = resolver.findProviders(req, false);
if (exports.size() > 0)
{
boolean classpath = false;
try
{
BundleRevisionImpl.getSecureAction()
.getClassLoader(BundleClassLoader.class).loadClass(name);
classpath = true;
}
catch (NoClassDefFoundError err)
{
// Ignore
}
catch (Exception ex)
{
// Ignore
}
String exporter = exports.iterator().next().toString();
StringBuilder sb = new StringBuilder("*** Class '");
sb.append(name);
sb.append("' was not found because bundle ");
sb.append(importer);
sb.append(" does not import '");
sb.append(pkgName);
sb.append("' even though bundle ");
sb.append(exporter);
sb.append(" does export it.");
if (classpath)
{
sb.append(" Additionally, the class is also available from the system class loader. There are two fixes: 1) Add an import for '");
sb.append(pkgName);
sb.append("' to bundle ");
sb.append(importer);
sb.append("; imports are necessary for each class directly touched by bundle code or indirectly touched, such as super classes if their methods are used. ");
sb.append("2) Add package '");
sb.append(pkgName);
sb.append("' to the '");
sb.append(Constants.FRAMEWORK_BOOTDELEGATION);
sb.append("' property; a library or VM bug can cause classes to be loaded by the wrong class loader. The first approach is preferable for preserving modularity.");
}
else
{
sb.append(" To resolve this issue, add an import for '");
sb.append(pkgName);
sb.append("' to bundle ");
sb.append(importer);
sb.append(".");
}
sb.append(" ***");
return sb.toString();
}
// Next, try to see if the class is available from the system
// class loader.
try
{
BundleRevisionImpl.getSecureAction()
.getClassLoader(BundleClassLoader.class).loadClass(name);
StringBuilder sb = new StringBuilder("*** Package '");
sb.append(pkgName);
sb.append("' is not imported by bundle ");
sb.append(importer);
sb.append(", nor is there any bundle that exports package '");
sb.append(pkgName);
sb.append("'. However, the class '");
sb.append(name);
sb.append("' is available from the system class loader. There are two fixes: 1) Add package '");
sb.append(pkgName);
sb.append("' to the '");
sb.append(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
sb.append("' property and modify bundle ");
sb.append(importer);
sb.append(" to import this package; this causes the system bundle to export class path packages. 2) Add package '");
sb.append(pkgName);
sb.append("' to the '");
sb.append(Constants.FRAMEWORK_BOOTDELEGATION);
sb.append("' property; a library or VM bug can cause classes to be loaded by the wrong class loader. The first approach is preferable for preserving modularity.");
sb.append(" ***");
return sb.toString();
}
catch (Exception ex2)
{
}
// Finally, if there are no imports or exports for the package
// and it is not available on the system class path, simply
// log a message saying so.
StringBuilder sb = new StringBuilder("*** Class '");
sb.append(name);
sb.append("' was not found. Bundle ");
sb.append(importer);
sb.append(" does not import package '");
sb.append(pkgName);
sb.append("', nor is the package exported by any other bundle or available from the system class loader.");
sb.append(" ***");
return sb.toString();
}
}