| /* |
| * 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.xbean.osgi.bundle.util; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.framework.wiring.BundleWiring; |
| |
| /** |
| * Bundle that delegates ClassLoader operations to a collection of {@link Bundle} objects. |
| * |
| * @version $Rev$ $Date$ |
| */ |
| public class DelegatingBundle implements Bundle { |
| |
| private static final String PACKAGE_CACHE = DelegatingBundle.class.getName() + ".packageCache"; |
| private static final String RESOURCE_CACHE_SIZE = DelegatingBundle.class.getName() + ".resourceCacheSize"; |
| |
| private static final URL NOT_FOUND_RESOURCE; |
| |
| static { |
| try { |
| NOT_FOUND_RESOURCE = new URL("file://foo"); |
| } catch (MalformedURLException e) { |
| throw new Error(e); |
| } |
| } |
| |
| private CopyOnWriteArrayList<Bundle> bundles; |
| private Bundle bundle; |
| private BundleContext bundleContext; |
| |
| private final boolean hasDynamicImports; |
| private final Map<String, URL> resourceCache; |
| private final boolean packageCacheEnabled; |
| private Map<String, Bundle> packageCache; |
| |
| public DelegatingBundle(Collection<Bundle> bundles) { |
| if (bundles.isEmpty()) { |
| throw new IllegalArgumentException("At least one bundle is required"); |
| } |
| this.bundles = new CopyOnWriteArrayList<Bundle>(bundles); |
| Iterator<Bundle> iterator = bundles.iterator(); |
| // assume first Bundle is the main bundle |
| this.bundle = iterator.next(); |
| this.bundleContext = new DelegatingBundleContext(this, bundle.getBundleContext()); |
| this.hasDynamicImports = hasDynamicImports(iterator); |
| this.resourceCache = initResourceCache(); |
| this.packageCacheEnabled = initPackageCacheEnabled(); |
| } |
| |
| public DelegatingBundle(Bundle bundle) { |
| this(Collections.singletonList(bundle)); |
| } |
| |
| private static Map<String, URL> initResourceCache() { |
| String value = System.getProperty(RESOURCE_CACHE_SIZE, "250"); |
| int size = Integer.parseInt(value); |
| if (size > 0) { |
| return Collections.synchronizedMap(new Cache<String, URL>(size)); |
| } else { |
| return null; |
| } |
| } |
| |
| private static boolean initPackageCacheEnabled() { |
| String value = System.getProperty(PACKAGE_CACHE, "true"); |
| boolean enabled = Boolean.parseBoolean(value); |
| return enabled; |
| } |
| |
| /* |
| * Returns true if a single bundle has Dynamic-ImportPackage: *. False, otherwise. |
| */ |
| private boolean hasDynamicImports(Iterator<Bundle> iterator) { |
| while (iterator.hasNext()) { |
| Bundle delegate = iterator.next(); |
| if (hasWildcardDynamicImport(delegate)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private synchronized Map<String, Bundle> getPackageBundleMap() { |
| if (packageCache == null) { |
| packageCache = buildPackageBundleMap(); |
| } |
| return packageCache; |
| } |
| |
| private synchronized void reset() { |
| resourceCache.clear(); |
| packageCache = null; |
| } |
| |
| private Map<String, Bundle> buildPackageBundleMap() { |
| Map<String, Bundle> map = new HashMap<String, Bundle>(); |
| Iterator<Bundle> iterator = bundles.iterator(); |
| // skip first bundle |
| iterator.next(); |
| // attempt to load the class from the remaining bundles |
| while (iterator.hasNext()) { |
| Bundle bundle = iterator.next(); |
| BundleWiring wiring = bundle.adapt(BundleWiring.class); |
| if (wiring != null) { |
| List<BundleCapability> capabilities = wiring.getCapabilities(BundleRevision.PACKAGE_NAMESPACE); |
| if (capabilities != null && !capabilities.isEmpty()) { |
| for (BundleCapability capability : capabilities) { |
| Map<String, Object> attributes = capability.getAttributes(); |
| if (attributes != null) { |
| String packageName = String.valueOf(attributes.get(BundleRevision.PACKAGE_NAMESPACE)); |
| if (!map.containsKey(packageName)) { |
| map.put(packageName, bundle); |
| } |
| } |
| } |
| } |
| } |
| } |
| return map; |
| } |
| |
| public Bundle getMainBundle() { |
| return bundle; |
| } |
| |
| public Class<?> loadClass(String name) throws ClassNotFoundException { |
| try { |
| Class<?> clazz = bundle.loadClass(name); |
| return clazz; |
| } catch (ClassNotFoundException cnfe) { |
| if (name.startsWith("java.")) { |
| throw cnfe; |
| } |
| |
| int index = name.lastIndexOf('.'); |
| if (index > 0 && bundles.size() > 1) { |
| String packageName = name.substring(0, index); |
| if (packageCacheEnabled) { |
| return findCachedClass(name, packageName, cnfe); |
| } else { |
| return findClass(name, packageName, cnfe); |
| } |
| } |
| |
| throw cnfe; |
| } |
| } |
| |
| private Class<?> findCachedClass(String className, String packageName, ClassNotFoundException cnfe) throws ClassNotFoundException { |
| Map<String, Bundle> map = getPackageBundleMap(); |
| Bundle bundle = map.get(packageName); |
| if (bundle == null) { |
| // Work-around for Introspector always looking for classes in sun.beans.infos |
| if (packageName.equals("sun.beans.infos") && className.endsWith("BeanInfo")) { |
| throw cnfe; |
| } |
| return findClass(className, packageName, cnfe); |
| } else { |
| return bundle.loadClass(className); |
| } |
| } |
| |
| private Class<?> findClass(String className, String packageName, ClassNotFoundException cnfe) throws ClassNotFoundException { |
| Iterator<Bundle> iterator = bundles.iterator(); |
| // skip first bundle |
| iterator.next(); |
| while (iterator.hasNext()) { |
| Bundle delegate = iterator.next(); |
| if (hasDynamicImports && hasWildcardDynamicImport(delegate)) { |
| // skip any bundles with Dynamic-ImportPackage: * to avoid unnecessary wires |
| continue; |
| } |
| try { |
| return delegate.loadClass(className); |
| } catch (ClassNotFoundException e) { |
| // ignore |
| } |
| } |
| throw cnfe; |
| } |
| |
| private static boolean hasWildcardDynamicImport(Bundle bundle) { |
| Dictionary<String, String> headers = bundle.getHeaders(); |
| if (headers != null) { |
| String value = headers.get(Constants.DYNAMICIMPORT_PACKAGE); |
| if (value == null) { |
| return false; |
| } else { |
| return "*".equals(value.trim()); |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| public void addBundle(Bundle b) { |
| bundles.add(b); |
| reset(); |
| } |
| |
| public void removeBundle(Bundle b) { |
| bundles.remove(b); |
| reset(); |
| } |
| |
| public URL getResource(String name) { |
| URL resource = null; |
| if (resourceCache == null) { |
| resource = findResource(name); |
| } else { |
| resource = findCachedResource(name); |
| } |
| return resource; |
| } |
| |
| private URL findCachedResource(String name) { |
| URL resource = bundle.getResource(name); |
| if (resource == null) { |
| resource = resourceCache.get(name); |
| if (resource == null) { |
| Iterator<Bundle> iterator = bundles.iterator(); |
| // skip first bundle |
| iterator.next(); |
| // look for resource in the remaining bundles |
| resource = findResource(name, iterator); |
| resourceCache.put(name, (resource == null) ? NOT_FOUND_RESOURCE : resource); |
| } else if (resource == NOT_FOUND_RESOURCE) { |
| resource = null; |
| } |
| } |
| return resource; |
| } |
| |
| private URL findResource(String name) { |
| Iterator<Bundle> iterator = bundles.iterator(); |
| return findResource(name, iterator); |
| } |
| |
| private URL findResource(String name, Iterator<Bundle> iterator) { |
| URL resource = null; |
| while (iterator.hasNext() && resource == null) { |
| Bundle delegate = iterator.next(); |
| resource = delegate.getResource(name); |
| } |
| return resource; |
| } |
| |
| public Enumeration<URL> getResources(String name) throws IOException { |
| ArrayList<URL> allResources = new ArrayList<URL>(); |
| for (Bundle bundle : bundles) { |
| Enumeration<URL> e = bundle.getResources(name); |
| addToList(allResources, e); |
| } |
| return Collections.enumeration(allResources); |
| } |
| |
| private static void addToList(List<URL> list, Enumeration<URL> enumeration) { |
| if (enumeration != null) { |
| while (enumeration.hasMoreElements()) { |
| list.add(enumeration.nextElement()); |
| } |
| } |
| } |
| |
| public BundleContext getBundleContext() { |
| return bundleContext; |
| } |
| |
| public Enumeration findEntries(String arg0, String arg1, boolean arg2) { |
| return bundle.findEntries(arg0, arg1, arg2); |
| } |
| |
| public long getBundleId() { |
| return bundle.getBundleId(); |
| } |
| |
| public URL getEntry(String arg0) { |
| return bundle.getEntry(arg0); |
| } |
| |
| public Enumeration getEntryPaths(String arg0) { |
| return bundle.getEntryPaths(arg0); |
| } |
| |
| public Dictionary getHeaders() { |
| return bundle.getHeaders(); |
| } |
| |
| public Dictionary getHeaders(String arg0) { |
| return bundle.getHeaders(arg0); |
| } |
| |
| public long getLastModified() { |
| return bundle.getLastModified(); |
| } |
| |
| public String getLocation() { |
| return bundle.getLocation(); |
| } |
| |
| public ServiceReference[] getRegisteredServices() { |
| return bundle.getRegisteredServices(); |
| } |
| |
| public ServiceReference[] getServicesInUse() { |
| return bundle.getServicesInUse(); |
| } |
| |
| public Map getSignerCertificates(int arg0) { |
| return bundle.getSignerCertificates(arg0); |
| } |
| |
| public int getState() { |
| return bundle.getState(); |
| } |
| |
| public String getSymbolicName() { |
| return bundle.getSymbolicName(); |
| } |
| |
| public Version getVersion() { |
| return bundle.getVersion(); |
| } |
| |
| public boolean hasPermission(Object arg0) { |
| return bundle.hasPermission(arg0); |
| } |
| |
| public void start() throws BundleException { |
| bundle.start(); |
| } |
| |
| public void start(int arg0) throws BundleException { |
| bundle.start(arg0); |
| } |
| |
| public void stop() throws BundleException { |
| bundle.stop(); |
| } |
| |
| public void stop(int arg0) throws BundleException { |
| bundle.stop(arg0); |
| } |
| |
| public void uninstall() throws BundleException { |
| bundle.uninstall(); |
| } |
| |
| public void update() throws BundleException { |
| bundle.update(); |
| } |
| |
| public void update(InputStream arg0) throws BundleException { |
| bundle.update(arg0); |
| } |
| |
| public int compareTo(Bundle other) { |
| return bundle.compareTo(other); |
| } |
| |
| public <A> A adapt(Class<A> type) { |
| return bundle.adapt(type); |
| } |
| |
| public File getDataFile(String filename) { |
| return bundle.getDataFile(filename); |
| } |
| |
| public String toString() { |
| return "[DelegatingBundle: " + bundles + "]"; |
| } |
| |
| private static class Cache<K, V> extends LinkedHashMap<K, V> { |
| |
| private final int maxSize; |
| |
| public Cache(int maxSize) { |
| this(16, maxSize, 0.75f); |
| } |
| |
| public Cache(int initialSize, int maxSize, float loadFactor) { |
| super(initialSize, loadFactor, true); |
| this.maxSize = maxSize; |
| } |
| |
| @Override |
| protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { |
| if (size() > maxSize) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| } |