blob: 1bed94702d2f7650ef5e56007d6847358557166c [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.aries.spifly;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleReference;
import org.osgi.framework.Constants;
import org.osgi.framework.ServicePermission;
/**
* Methods used from ASM-generated code. They store, change and reset the thread context classloader.
* The methods are static to make it easy to access them from generated code.
*/
public class Util {
static ThreadLocal<ClassLoader> storedClassLoaders = new ThreadLocal<ClassLoader>();
// Provided as static method to make it easier to call from ASM-modified code
public static void storeContextClassloader() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
storedClassLoaders.set(Thread.currentThread().getContextClassLoader());
return null;
}
});
}
// Provided as static method to make it easier to call from ASM-modified code
public static void restoreContextClassloader() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
Thread.currentThread().setContextClassLoader(storedClassLoaders.get());
storedClassLoaders.set(null);
return null;
}
});
}
public static <C,S> ServiceLoader<S> serviceLoaderLoad(Class<S> service, Class<C> caller) {
if (BaseActivator.activator == null) {
// The system is not yet initialized. We can't do anything.
return null;
}
ClassLoader bundleLoader = AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return caller.getClassLoader();
}
}
);
if (!(bundleLoader instanceof BundleReference)) {
BaseActivator.activator.log(Level.FINE, "Classloader of consuming bundle doesn't implement BundleReference: " + bundleLoader);
return ServiceLoader.load(service);
}
BundleReference bundleReference = (BundleReference)bundleLoader;
final ClassLoader bundleClassloader = findContextClassloader(
bundleReference.getBundle(), ServiceLoader.class.getName(), "load", service);
if (bundleClassloader == null) {
return ServiceLoader.load(service);
}
Thread thread = Thread.currentThread();
return AccessController.doPrivileged(
new PrivilegedAction<ServiceLoader<S>>() {
@Override
public ServiceLoader<S> run() {
ClassLoader contextClassLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(bundleClassloader);
return ServiceLoader.load(service);
}
finally {
thread.setContextClassLoader(contextClassLoader);
}
}
}
);
}
public static <C,S> ServiceLoader<S> serviceLoaderLoad(
Class<S> service, ClassLoader specifiedClassLoader, Class<C> caller) {
if (BaseActivator.activator == null) {
// The system is not yet initialized. We can't do anything.
return null;
}
ClassLoader bundleLoader = AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return caller.getClassLoader();
}
}
);
if (!(bundleLoader instanceof BundleReference)) {
BaseActivator.activator.log(Level.FINE, "Classloader of consuming bundle doesn't implement BundleReference: " + bundleLoader);
return ServiceLoader.load(service, specifiedClassLoader);
}
BundleReference bundleReference = (BundleReference)bundleLoader;
final ClassLoader bundleClassloader = findContextClassloader(
bundleReference.getBundle(), ServiceLoader.class.getName(), "load", service);
if (bundleClassloader == null) {
return ServiceLoader.load(service, specifiedClassLoader);
}
return ServiceLoader.load(service, new WrapperCL(specifiedClassLoader, bundleClassloader));
}
public static void fixContextClassloader(String cls, String method, Class<?> clsArg, ClassLoader bundleLoader) {
BundleReference br = getBundleReference(bundleLoader);
if (br == null) {
return;
}
final ClassLoader cl = findContextClassloader(br.getBundle(), cls, method, clsArg);
if (cl != null) {
BaseActivator.activator.log(Level.FINE, "Temporarily setting Thread Context Classloader to: " + cl);
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
Thread.currentThread().setContextClassLoader(cl);
return null;
}
});
} else {
BaseActivator.activator.log(Level.FINE, "No classloader found for " + cls + ":" + method + "(" + clsArg + ")");
}
}
private static ClassLoader findContextClassloader(Bundle consumerBundle, String className, String methodName, Class<?> clsArg) {
BaseActivator activator = BaseActivator.activator;
String requestedClass;
Map<Pair<Integer, String>, String> args;
if (ServiceLoader.class.getName().equals(className) && "load".equals(methodName)) {
requestedClass = clsArg.getName();
args = new HashMap<Pair<Integer,String>, String>();
args.put(new Pair<Integer, String>(0, Class.class.getName()), requestedClass);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkPermission(new ServicePermission(requestedClass, ServicePermission.GET));
} catch (AccessControlException ace) {
// access denied
activator.log(Level.FINE, "No permission to obtain service of type: " + requestedClass);
return null;
}
}
} else {
requestedClass = className;
args = null; // only supported on ServiceLoader.load() at the moment
}
Collection<Bundle> bundles = new ArrayList<Bundle>(activator.findProviderBundles(requestedClass));
activator.log(Level.FINE, "Found bundles providing " + requestedClass + ": " + bundles);
Collection<Bundle> allowedBundles = activator.findConsumerRestrictions(consumerBundle, className, methodName, args);
if (allowedBundles != null) {
for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) {
if (!allowedBundles.contains(it.next())) {
it.remove();
}
}
}
switch (bundles.size()) {
case 0:
return null;
case 1:
Bundle bundle = bundles.iterator().next();
return getBundleClassLoader(bundle);
default:
List<ClassLoader> loaders = new ArrayList<ClassLoader>();
for (Bundle b : bundles) {
loaders.add(getBundleClassLoader(b));
}
return new MultiDelegationClassloader(loaders.toArray(new ClassLoader[loaders.size()]));
}
}
private static ClassLoader getBundleClassLoader(final Bundle b) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return getBundleClassLoaderPrivileged(b);
}
});
}
private static ClassLoader getBundleClassLoaderPrivileged(Bundle b) {
// In 4.3 this can be done much easier by using the BundleWiring, but we want this code to
// be 4.2 compliant.
// Here we're just finding any class in the bundle, load that and then use its classloader.
try {
Method adaptMethod = Bundle.class.getMethod("adapt", Class.class);
if (adaptMethod != null) {
return getBundleClassLoaderViaAdapt(b, adaptMethod);
}
} catch (Exception e) {
// No Bundle.adapt(), use the fallback approach to find the bundle classloader
}
List<String> rootPaths = new ArrayList<String>();
rootPaths.add("/");
while(rootPaths.size() > 0) {
String rootPath = rootPaths.remove(0);
Enumeration<String> paths = b.getEntryPaths(rootPath);
while(paths != null && paths.hasMoreElements()) {
String path = paths.nextElement();
if (path.endsWith(".class")) {
ClassLoader cl = getClassLoaderFromClassResource(b, path);
if (cl != null)
return cl;
} else if (path.endsWith("/")) {
rootPaths.add(path);
}
}
}
// if we can't find any classes in the bundle directly, try the Bundle-ClassPath
Object bcp = b.getHeaders().get(Constants.BUNDLE_CLASSPATH);
if (bcp instanceof String) {
for (String entry : ((String) bcp).split(",")) {
entry = entry.trim();
if (entry.equals("."))
continue;
URL url = b.getResource(entry);
if (url != null) {
ClassLoader cl = getClassLoaderViaBundleClassPath(b, url);
if (cl != null)
return cl;
}
}
}
throw new RuntimeException("Could not obtain classloader for bundle " + b);
}
private static ClassLoader getBundleClassLoaderViaAdapt(Bundle b, Method adaptMethod) {
// This method uses reflection to avoid a hard dependency on OSGi 4.3 APIs
try {
// Load the BundleRevision and BundleWiring classes from the System Bundle.
Bundle systemBundle = b.getBundleContext().getBundle(0);
Class<?> bundleRevisionClass = systemBundle.loadClass("org.osgi.framework.wiring.BundleRevision");
Object bundleRevision = adaptMethod.invoke(b, bundleRevisionClass);
Method getWiringMethod = bundleRevisionClass.getDeclaredMethod("getWiring");
Object bundleWiring = getWiringMethod.invoke(bundleRevision);
Class<?> bundleWiringClass = systemBundle.loadClass("org.osgi.framework.wiring.BundleWiring");
Method getClassLoaderMethod = bundleWiringClass.getDeclaredMethod("getClassLoader");
return (ClassLoader) getClassLoaderMethod.invoke(bundleWiring);
} catch (Exception e) {
throw new RuntimeException("Can't obtain Bundle Class Loader for bundle: " + b, e);
}
}
private static BundleReference getBundleReference(ClassLoader bundleLoader) {
if (BaseActivator.activator == null) {
// The system is not yet initialized. We can't do anything.
return null;
}
if (!(bundleLoader instanceof BundleReference)) {
BaseActivator.activator.log(Level.FINE, "Classloader of consuming bundle doesn't implement BundleReference: " + bundleLoader);
return null;
}
return (BundleReference) bundleLoader;
}
private static ClassLoader getClassLoaderViaBundleClassPath(Bundle b, URL url) {
try {
JarInputStream jis = null;
try {
jis = new JarInputStream(url.openStream());
JarEntry je = null;
while ((je = jis.getNextJarEntry()) != null) {
String path = je.getName();
if (path.endsWith(".class")) {
ClassLoader cl = getClassLoaderFromClassResource(b, path);
if (cl != null)
return cl;
}
}
} finally {
if (jis != null)
jis.close();
}
} catch (IOException e) {
BaseActivator.activator.log(Level.FINE, "Problem loading class from embedded jar file: " + url +
" in bundle " + b.getSymbolicName(), e);
}
return null;
}
private static ClassLoader getClassLoaderFromClassResource(Bundle b, String path) {
String className = path.substring(0, path.length() - ".class".length());
if (className.startsWith("/"))
className = className.substring(1);
className = className.replace('/', '.');
try {
Class<?> cls = b.loadClass(className);
return cls.getClassLoader();
} catch (ClassNotFoundException e) {
// try the next class
}
return null;
}
private static class WrapperCL extends ClassLoader {
private final ClassLoader bundleClassloader;
public WrapperCL(ClassLoader specifiedClassLoader, ClassLoader bundleClassloader) {
super(specifiedClassLoader);
this.bundleClassloader = bundleClassloader;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return bundleClassloader.loadClass(name);
}
@Override
protected URL findResource(String name) {
return bundleClassloader.getResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
return bundleClassloader.getResources(name);
}
}
}