blob: f17cfbc9ad1999de012c2cd8575d5c07be0fbae3 [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 java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
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.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.felix.framework.cache.JarContent;
import org.apache.felix.framework.cache.Content;
import org.apache.felix.framework.resolver.ResolveException;
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.R4Library;
import org.apache.felix.framework.wiring.BundleRequirementImpl;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleReference;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.hooks.weaving.WeavingException;
import org.osgi.framework.hooks.weaving.WeavingHook;
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;
public class BundleWiringImpl implements BundleWiring
{
public final static int EAGER_ACTIVATION = 0;
public final static int LAZY_ACTIVATION = 1;
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;
// TODO: OSGi R4.3 - Perhaps we should make m_wires and m_importedPkgs volatile
// and copy-on-write instead of protecting them with object lock.
private final List<BundleWire> m_wires;
private final Map<String, BundleRevision> m_importedPkgs;
private final Map<String, List<BundleRevision>> m_requiredPkgs;
private final List<BundleCapability> m_resolvedCaps;
private final List<BundleRequirement> m_resolvedReqs;
private final List<R4Library> m_resolvedNativeLibs;
private final List<Content> m_fragmentContents;
private volatile List<BundleRequirement> m_wovenReqs = null;
private BundleClassLoader m_classLoader;
private boolean m_isActivationTriggered = false;
// 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
{
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 we are on an old JVM or not.
private volatile static boolean m_isPreJava5 = false;
// Flag indicating whether this wiring has been disposed.
private volatile boolean m_isDisposed = false;
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 = 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 = Collections.unmodifiableList(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).
// And nothing else at this time. Fragments currently have no capabilities.
boolean isFragment = Util.isFragment(revision);
List<BundleCapability> capList = (isFragment)
? Collections.EMPTY_LIST
: new ArrayList<BundleCapability>();
// TODO: OSGi R4.4 - Fragments currently have no capabilities, but they may
// have an identity capability in the future.
if (!isFragment)
{
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 (m_fragments != null)
{
for (BundleRevision fragment : m_fragments)
{
for (BundleCapability cap : fragment.getDeclaredCapabilities(null))
{
// TODO: OSGi R4.4 - OSGi R4.4 may introduce an identity capability, if so
// that will need to be excluded from here.
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);
}
}
}
}
}
}
m_resolvedCaps = Collections.unmodifiableList(capList);
List<R4Library> libList = (m_revision.getDeclaredNativeLibraries() == null)
? new ArrayList<R4Library>()
: new ArrayList<R4Library>(m_revision.getDeclaredNativeLibraries());
for (int fragIdx = 0;
(m_fragments != null) && (fragIdx < m_fragments.size());
fragIdx++)
{
List<R4Library> 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
: Collections.unmodifiableList(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)).booleanValue();
m_useLocalURLs =
(m_configMap.get(FelixConstants.USE_LOCALURLS_PROP) == null)
? false : true;
}
public synchronized void dispose()
{
if (m_fragmentContents != null)
{
for (Content content : m_fragmentContents)
{
content.close();
}
}
m_classLoader = null;
m_isDisposed = true;
}
// 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 synchronized 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 synchronized BundleRevision getImportedPackageSource(String pkgName)
{
return m_importedPkgs.get(pkgName);
}
List<BundleRevision> getFragments()
{
return m_fragments;
}
List<Content> getFragmentContents()
{
return m_fragmentContents;
}
public boolean isCurrent()
{
BundleRevision current = getBundle().adapt(BundleRevision.class);
return (current != null) && (current.getWiring() == this);
}
public synchronized boolean isInUse()
{
return !m_isDisposed;
}
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;
}
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<R4Library> getNativeLibraries()
{
return m_resolvedNativeLibs;
}
public List<BundleWire> getProvidedWires(String namespace)
{
if (isInUse())
{
return ((BundleImpl) m_revision.getBundle())
.getFramework().getDependencies().getProvidedWires(m_revision, namespace);
}
return null;
}
public synchronized 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)
{
m_wires.add(wire);
m_importedPkgs.put(
(String) wire.getCapability().getAttributes().get(BundleRevision.PACKAGE_NAMESPACE),
wire.getProviderWiring().getRevision());
}
public BundleRevision getRevision()
{
return m_revision;
}
public synchronized ClassLoader getClassLoader()
{
if (m_isDisposed)
{
return null;
}
if (m_classLoader == null)
{
// Determine which class loader to use based on which
// Java platform we are running on.
Class clazz;
if (m_isPreJava5)
{
clazz = BundleClassLoader.class;
}
else
{
try
{
clazz = BundleClassLoaderJava5.class;
}
catch (Throwable th)
{
// If we are on pre-Java5 then we will get a verify error
// here since we try to override a getResources() which is
// a final method in pre-Java5.
m_isPreJava5 = true;
clazz = BundleClassLoader.class;
}
}
// Use SecureAction to create the class loader if security is
// enabled; otherwise, create it directly.
try
{
Constructor ctor = (Constructor) BundleRevisionImpl.getSecureAction()
.getConstructor(clazz, new Class[] { BundleWiringImpl.class, ClassLoader.class });
m_classLoader = (BundleClassLoader)
BundleRevisionImpl.getSecureAction().invoke(ctor,
new Object[] { this, determineParentClassLoader() });
}
catch (Exception ex)
{
throw new RuntimeException("Unable to create module class loader: "
+ ex.getMessage() + " [" + ex.getClass().getName() + "]");
}
}
return m_classLoader;
}
public List<URL> findEntries(String path, String filePattern, int options)
{
if (isInUse())
{
if (!Util.isFragment(m_revision))
{
Enumeration<URL> e =
((BundleImpl) 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 Collections.unmodifiableList(entries);
}
return Collections.EMPTY_LIST;
}
return null;
}
public Collection<String> listResources(String path, String filePattern, int options)
{
if (isInUse())
{
throw new UnsupportedOperationException("Not supported yet.");
}
return null;
}
public Bundle 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 + "://" +
m_revision.getId() + ":" + port + path, m_revision.getURLStreamHandler());
}
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();
// First, try to resolve the originating module.
try
{
m_resolver.resolve(m_revision);
}
catch (Exception ex)
{
// The spec states that if the bundle cannot be resolved, then
// only the local bundle's resources should be searched. So we
// will ask the module's own class path.
return m_revision.getResourcesLocal(name);
}
// 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.
// TODO: OSGi R4.3 - Access should be guarded by object lock.
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 (ResolveException 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 < m_revision.getBootDelegationPackages().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 (m_revision.getBootDelegationPackageWildcards()[i]
&& pkgName.startsWith(m_revision.getBootDelegationPackages()[i]))
{
return true;
}
// If not wildcarded, then check for an exact match.
else if (m_revision.getBootDelegationPackages()[i].equals(pkgName))
{
return true;
}
}
}
return result;
}
synchronized ClassLoader getBootDelegationClassLoader()
{
// Get the appropriate class loader for delegation.
ClassLoader parent = (m_classLoader == null)
? determineParentClassLoader() : m_classLoader.getParent();
return (parent == null) ? m_bootClassLoader : parent;
}
private static final Constructor m_dexFileClassConstructor;
private static final Method m_dexFileClassLoadDex;
private static final Method m_dexFileClassLoadClass;
static
{
Constructor dexFileClassConstructor = null;
Method dexFileClassLoadDex = null;
Method dexFileClassLoadClass = null;
try
{
Class dexFileClass;
try
{
dexFileClass = Class.forName("dalvik.system.DexFile");
}
catch (Exception ex)
{
dexFileClass = Class.forName("android.dalvik.DexFile");
}
try
{
dexFileClassLoadDex = dexFileClass.getMethod("loadDex",
new Class[]{String.class, String.class, Integer.TYPE});
}
catch (Exception ex)
{
// Nothing we need to do
}
dexFileClassConstructor = dexFileClass.getConstructor(
new Class[] { java.io.File.class });
dexFileClassLoadClass = dexFileClass.getMethod("loadClass",
new Class[] { String.class, ClassLoader.class });
}
catch (Throwable ex)
{
dexFileClassConstructor = null;
dexFileClassLoadDex = null;
dexFileClassLoadClass = null;
}
m_dexFileClassConstructor = dexFileClassConstructor;
m_dexFileClassLoadDex= dexFileClassLoadDex;
m_dexFileClassLoadClass = dexFileClassLoadClass;
}
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());
}
return getClassLoader().loadClass(name);
}
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
{
// First, try to resolve the originating revision.
m_resolver.resolve(m_revision);
// Get the package of the target class/resource.
String pkgName = (isClass)
? Util.getClassPackage(name)
: 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();
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))
{
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;
}
}
}
// 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)
{
result = (isClass)
? (Object) ((BundleClassLoader) getClassLoader()).findClass(name)
: (Object) m_revision.getResourceLocal(name);
// If still not found, then try the revision's dynamic imports.
if (result == null)
{
result = searchDynamicImports(pkgName, name, isClass);
}
}
}
// TODO: OSGi R4.3 - If we eliminate resolving from this method, then we can
// simplify this catch, since resolve throws resolve and bundle exceptions.
catch (Exception ex)
{
if (!isClass && (ex instanceof ResolveException))
{
// The spec states that if the bundle cannot be resolved, then
// only the local bundle's resources should be searched. So we
// will ask the module's own class path.
URL url = m_revision.getResourceLocal(name);
if (url != null)
{
return url;
}
}
if (isClass)
{
if (!(ex instanceof ClassNotFoundException))
{
ClassNotFoundException cnfe = new ClassNotFoundException(
name
+ " not found in "
+ getBundle()
+ " : "
+ ex.getMessage());
ex.printStackTrace();
cnfe.initCause(ex);
throw cnfe;
}
throw (ClassNotFoundException) ex;
}
else
{
if (!(ex instanceof ResourceNotFoundException))
{
ResourceNotFoundException rnfe = new ResourceNotFoundException(
name
+ " not found in "
+ getBundle()
+ " : "
+ ex.getMessage());
rnfe.initCause(ex);
throw rnfe;
}
throw (ResourceNotFoundException) ex;
}
}
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.
// TODO: OSGi R4.3 - Access should be guarded by object lock.
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 (ResolveException 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);
}
// 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()
{
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;
}
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;
}
synchronized boolean isActivationTriggered()
{
return m_isActivationTriggered;
}
static class ToLocalUrlEnumeration implements Enumeration
{
final Enumeration m_enumeration;
ToLocalUrlEnumeration(Enumeration enumeration)
{
m_enumeration = enumeration;
}
public boolean hasMoreElements()
{
return m_enumeration.hasMoreElements();
}
public Object nextElement()
{
return convertToLocalUrl((URL) m_enumeration.nextElement());
}
}
public class BundleClassLoaderJava5 extends BundleClassLoader
{
public BundleClassLoaderJava5(ClassLoader parent)
{
super(parent);
}
@Override
public Enumeration getResources(String name)
{
Enumeration urls = BundleWiringImpl.this.getResourcesByDelegation(name);
if (m_useLocalURLs)
{
urls = new ToLocalUrlEnumeration(urls);
}
return urls;
}
@Override
protected Enumeration findResources(String name)
{
return m_revision.getResourcesLocal(name);
}
}
public class BundleClassLoader extends SecureClassLoader implements BundleReference
{
private final Map m_jarContentToDexFile;
private Object[][] m_cachedLibs = new Object[0][];
private static final int LIBNAME_IDX = 0;
private static final int LIBPATH_IDX = 1;
public BundleClassLoader(ClassLoader parent)
{
super(parent);
if (m_dexFileClassLoadClass != null)
{
m_jarContentToDexFile = new HashMap();
}
else
{
m_jarContentToDexFile = null;
}
}
public Bundle getBundle()
{
return BundleWiringImpl.this.getBundle();
}
@Override
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
Class clazz = null;
// Make sure the class was not already loaded.
synchronized (this)
{
clazz = findLoadedClass(name);
}
if (clazz == null)
{
try
{
clazz = (Class) 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;
String msg = name;
if (m_logger.getLogLevel() >= Logger.LOG_DEBUG)
{
msg = diagnoseClassLoadError(m_resolver, m_revision, name);
ex = (msg != null)
? new ClassNotFoundException(msg, cnfe)
: ex;
}
throw ex;
}
}
// Resolve the class and return it.
if (resolve)
{
resolveClass(clazz);
}
return clazz;
}
@Override
protected Class findClass(String name) throws ClassNotFoundException
{
Class clazz = null;
// Search for class in bundle revision.
if (clazz == null)
{
String actual = name.replace('.', '/') + ".class";
byte[] bytes = null;
// Check the bundle class path.
List<Content> contentPath = 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 = ((BundleImpl) m_revision.getBundle()).getFramework();
Set<ServiceReference<WeavingHook>> hooks =
felix.getHooks(WeavingHook.class);
WovenClassImpl wci = null;
if (!hooks.isEmpty())
{
// Create woven class to be used for hooks.
wci = new WovenClassImpl(name, BundleWiringImpl.this, bytes);
// Loop through hooks in service ranking order.
for (ServiceReference<WeavingHook> sr : hooks)
{
// Only use the hook if it is not black listed.
if (!felix.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);
if (wh != null)
{
try
{
// TODO: OSGi R4.3 - Need to call all hooks in privileged block.
wh.weave(wci);
}
catch (Throwable th)
{
if (!(th instanceof WeavingException))
{
felix.blackListHook(sr);
}
felix.fireFrameworkEvent(
FrameworkEvent.ERROR,
sr.getBundle(),
th);
// Mark the woven class as incomplete.
wci.complete(null, null, null);
// Throw class format exception per spec.
Error error = new ClassFormatError("Weaving hook failed.");
error.initCause(th);
throw error;
}
finally
{
felix.ungetService(felix, sr);
}
}
}
}
}
// Before we actually attempt to define the class, grab
// the lock for this class loader and make sure than no
// other thread has defined this class in the meantime.
synchronized (this)
{
byte[] wovenBytes = null;
Class wovenClass = null;
List<String> wovenImports = null;
try
{
clazz = findLoadedClass(name);
if (clazz == null)
{
// 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 = wovenBytes = wci.getBytes();
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_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_wovenReqs != null)
{
for (BundleRequirement req : 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_wovenReqs != null)
{
allWovenReqs.addAll(0, m_wovenReqs);
}
m_wovenReqs = allWovenReqs;
}
}
}
int activationPolicy =
((BundleImpl) getBundle()).isDeclaredActivationPolicyUsed()
? ((BundleRevisionImpl) getBundle()
.adapt(BundleRevision.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_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);
if (params != null)
{
definePackage(
pkgName,
(String) params[0],
(String) params[1],
(String) params[2],
(String) params[3],
(String) params[4],
(String) params[5],
null);
}
else
{
definePackage(pkgName, null, null,
null, null, null, null, null);
}
}
}
// If we can load the class from a dex file do so
if (content instanceof JarContent)
{
try
{
clazz = getDexFileClass((JarContent) content, name, this);
}
catch (Exception ex)
{
// Looks like we can't
}
}
if (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_revision.getProtectionDomain() != null)
{
clazz = defineClass(name, bytes, 0, bytes.length,
m_revision.getProtectionDomain());
}
else
{
clazz = defineClass(name, bytes, 0, bytes.length);
}
wovenClass = clazz;
}
// At this point if we have a trigger class, then the deferred
// activation trigger has tripped.
if (!m_isActivationTriggered && isTriggerClass && (clazz != null))
{
// TODO: OSGi R4.3 - This isn't protected by the correct lock.
m_isActivationTriggered = true;
}
}
}
finally
{
// If we have a woven class, mark it as complete.
// Not exactly clear how we should deal with the
// case where the weaving didn't happen because
// someone else beat us in defining the class.
if (wci != null)
{
wci.complete(wovenClass, wovenBytes, wovenImports);
}
}
}
// 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))
{
for (int i = deferredList.size() - 1; i >= 0; i--)
{
try
{
felix.getFramework().activateBundle(
(BundleImpl) ((Object[]) deferredList.get(i))[1], true);
}
catch (BundleException ex)
{
ex.printStackTrace();
}
}
deferredList.clear();
}
}
}
return clazz;
}
private Object[] definePackage(String pkgName)
{
String spectitle = (String) m_revision.getHeaders().get("Specification-Title");
String specversion = (String) m_revision.getHeaders().get("Specification-Version");
String specvendor = (String) m_revision.getHeaders().get("Specification-Vendor");
String impltitle = (String) m_revision.getHeaders().get("Implementation-Title");
String implversion = (String) m_revision.getHeaders().get("Implementation-Version");
String implvendor = (String) 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 null;
}
private Class getDexFileClass(JarContent content, String name, ClassLoader loader)
throws Exception
{
if (m_jarContentToDexFile == null)
{
return null;
}
Object dexFile = null;
if (!m_jarContentToDexFile.containsKey(content))
{
try
{
if (m_dexFileClassLoadDex != null)
{
dexFile = m_dexFileClassLoadDex.invoke(null,
new Object[]{content.getFile().getAbsolutePath(),
content.getFile().getAbsolutePath() + ".dex", new Integer(0)});
}
else
{
dexFile = m_dexFileClassConstructor.newInstance(
new Object[] { content.getFile() });
}
}
finally
{
m_jarContentToDexFile.put(content, dexFile);
}
}
else
{
dexFile = m_jarContentToDexFile.get(content);
}
if (dexFile != null)
{
return (Class) m_dexFileClassLoadClass.invoke(dexFile,
new Object[] { name.replace('.','/'), loader });
}
return null;
}
@Override
public URL getResource(String name)
{
URL url = BundleWiringImpl.this.getResourceByDelegation(name);
if (m_useLocalURLs)
{
url = convertToLocalUrl(url);
}
return url;
}
@Override
protected URL findResource(String name)
{
return m_revision.getResourceLocal(name);
}
// The findResources() method should only look at the revision itself, but
// instead it tries to delegate because in Java version prior to 1.5 the
// getResources() method was final and could not be overridden. We should
// override getResources() like getResource() to make it delegate, but we
// can't. As a workaround, we make findResources() delegate instead.
@Override
protected Enumeration findResources(String name)
{
Enumeration urls = BundleWiringImpl.this.getResourcesByDelegation(name);
if (m_useLocalURLs)
{
urls = new ToLocalUrlEnumeration(urls);
}
return urls;
}
@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<R4Library> libs = getNativeLibraries();
for (int libIdx = 0; (libs != null) && (libIdx < libs.size()); libIdx++)
{
if (libs.get(libIdx).match(m_configMap, name))
{
// Search bundle content first for native library.
result = m_revision.getContent().getEntryAsNativeLibrary(
libs.get(libIdx).getEntryName());
// If not found, then search fragments in order.
for (int i = 0;
(result == null) && (m_fragmentContents != null)
&& (i < m_fragmentContents.size());
i++)
{
result = 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;
}
@Override
public String toString()
{
return BundleWiringImpl.this.toString();
}
}
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 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();
StringBuffer sb = new StringBuffer("*** 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());
StringBuffer sb = new StringBuffer("*** 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 = new HashMap<String, Object>(1);
attrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
BundleRequirementImpl req = new BundleRequirementImpl(
revision, BundleRevision.PACKAGE_NAMESPACE, dirs, attrs);
Set<BundleCapability> exporters = resolver.getCandidates(req, false);
BundleRevision provider = null;
try
{
provider = resolver.resolve(revision, pkgName);
}
catch (Exception ex)
{
provider = null;
}
String exporter = (exporters.isEmpty())
? null : exporters.iterator().next().getRevision().getBundle().toString();
StringBuffer sb = new StringBuffer("*** 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 = new HashMap<String, Object>(1);
attrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
BundleRequirementImpl req = new BundleRequirementImpl(
revision, BundleRevision.PACKAGE_NAMESPACE, dirs, attrs);
Set<BundleCapability> exports = resolver.getCandidates(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().getRevision().getBundle().toString();
StringBuffer sb = new StringBuffer("*** 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);
StringBuffer sb = new StringBuffer("*** 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.
StringBuffer sb = new StringBuffer("*** 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();
}
}