Return-Path: X-Original-To: apmail-geronimo-xbean-scm-archive@minotaur.apache.org Delivered-To: apmail-geronimo-xbean-scm-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B76A7D3D4 for ; Thu, 9 Aug 2012 20:59:17 +0000 (UTC) Received: (qmail 31163 invoked by uid 500); 9 Aug 2012 20:59:17 -0000 Delivered-To: apmail-geronimo-xbean-scm-archive@geronimo.apache.org Received: (qmail 31134 invoked by uid 500); 9 Aug 2012 20:59:17 -0000 Mailing-List: contact xbean-scm-help@geronimo.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: xbean-scm@geronimo.apache.org Delivered-To: mailing list xbean-scm@geronimo.apache.org Received: (qmail 31127 invoked by uid 99); 9 Aug 2012 20:59:17 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 09 Aug 2012 20:59:17 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 09 Aug 2012 20:59:13 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id E2A362388962; Thu, 9 Aug 2012 20:58:28 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1371482 - /geronimo/xbean/trunk/xbean-bundleutils/src/main/java/org/apache/xbean/osgi/bundle/util/DelegatingBundle.java Date: Thu, 09 Aug 2012 20:58:28 -0000 To: xbean-scm@geronimo.apache.org From: gawor@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120809205828.E2A362388962@eris.apache.org> Author: gawor Date: Thu Aug 9 20:58:28 2012 New Revision: 1371482 URL: http://svn.apache.org/viewvc?rev=1371482&view=rev Log: XBEAN-208: Optimize class and resource lookups Modified: geronimo/xbean/trunk/xbean-bundleutils/src/main/java/org/apache/xbean/osgi/bundle/util/DelegatingBundle.java Modified: geronimo/xbean/trunk/xbean-bundleutils/src/main/java/org/apache/xbean/osgi/bundle/util/DelegatingBundle.java URL: http://svn.apache.org/viewvc/geronimo/xbean/trunk/xbean-bundleutils/src/main/java/org/apache/xbean/osgi/bundle/util/DelegatingBundle.java?rev=1371482&r1=1371481&r2=1371482&view=diff ============================================================================== --- geronimo/xbean/trunk/xbean-bundleutils/src/main/java/org/apache/xbean/osgi/bundle/util/DelegatingBundle.java (original) +++ geronimo/xbean/trunk/xbean-bundleutils/src/main/java/org/apache/xbean/osgi/bundle/util/DelegatingBundle.java Thu Aug 9 20:58:28 2012 @@ -22,26 +22,29 @@ package org.apache.xbean.osgi.bundle.uti 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.HashSet; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; 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.service.packageadmin.ExportedPackage; -import org.osgi.service.packageadmin.PackageAdmin; +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. @@ -50,134 +53,238 @@ import org.osgi.service.packageadmin.Pac */ 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 bundles; private Bundle bundle; private BundleContext bundleContext; + private final boolean hasDynamicImports; + private final Map resourceCache; + private final boolean packageCacheEnabled; + private Map packageCache; + public DelegatingBundle(Collection bundles) { if (bundles.isEmpty()) { throw new IllegalArgumentException("At least one bundle is required"); } this.bundles = new CopyOnWriteArrayList(bundles); + Iterator iterator = bundles.iterator(); // assume first Bundle is the main bundle - this.bundle = bundles.iterator().next(); + 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 initResourceCache() { + String value = System.getProperty(RESOURCE_CACHE_SIZE, "250"); + int size = Integer.parseInt(value); + if (size > 0) { + return Collections.synchronizedMap(new Cache(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 iterator) { + while (iterator.hasNext()) { + Bundle delegate = iterator.next(); + if (hasWildcardDynamicImport(delegate)) { + return true; + } + } + return false; + } + + private synchronized Map getPackageBundleMap() { + if (packageCache == null) { + packageCache = buildPackageBundleMap(); + } + return packageCache; + } + + private synchronized void reset() { + resourceCache.clear(); + packageCache = null; + } + + private Map buildPackageBundleMap() { + Map map = new HashMap(); + Iterator 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 capabilities = wiring.getCapabilities(BundleRevision.PACKAGE_NAMESPACE); + if (capabilities != null && !capabilities.isEmpty()) { + for (BundleCapability capability : capabilities) { + Map 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 { - return bundle.loadClass(name); - } catch (ClassNotFoundException ex) { + 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) { - // see if there are any bundles exporting the package String packageName = name.substring(0, index); - Set packageBundles = getPackageBundles(packageName); - if (packageBundles == null) { - // package is NOT exported - - Iterator iterator = bundles.iterator(); - // skip first bundle - iterator.next(); - // attempt to load the class from the remaining bundles - while (iterator.hasNext()) { - Bundle delegate = iterator.next(); - try { - return delegate.loadClass(name); - } catch (ClassNotFoundException e) { - // ignore - } - } - - throw ex; + if (packageCacheEnabled) { + return findCachedClass(name, packageName, cnfe); } else { - // package is exported - - // see if any of our bundles is wired to the exporter - Bundle delegate = findFirstBundle(packageBundles); - if (delegate == null || delegate == bundle) { - // nope. no static wires but might need to check for dynamic wires in the future. - throw ex; - } else { - // yes. attempt to load the class from it - return delegate.loadClass(name); - } + return findClass(name, packageName, cnfe); } - } else { - // no package name - throw ex; } + + throw cnfe; } } - - private Set getPackageBundles(String packageName) { - BundleContext context = bundle.getBundleContext(); - ServiceReference reference = context.getServiceReference(PackageAdmin.class.getName()); - PackageAdmin packageAdmin = (PackageAdmin) context.getService(reference); - Set bundles = null; - try { - ExportedPackage[] exportedPackages = packageAdmin.getExportedPackages(packageName); - if (exportedPackages != null && exportedPackages.length > 0) { - bundles = new HashSet(); - for (ExportedPackage exportedPackage : exportedPackages) { - bundles.add(exportedPackage.getExportingBundle()); - Bundle[] importingBundles = exportedPackage.getImportingBundles(); - if (importingBundles != null) { - for (Bundle importingBundle : importingBundles) { - bundles.add(importingBundle); - } - } - } + + private Class findCachedClass(String className, String packageName, ClassNotFoundException cnfe) throws ClassNotFoundException { + Map 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 bundles; - } finally { - context.ungetService(reference); + return findClass(className, packageName, cnfe); + } else { + return bundle.loadClass(className); } } - - private Bundle findFirstBundle(Set packageBundles) { - Collection c1 = bundles; - Collection c2 = packageBundles; - - if (bundles instanceof Set && bundles.size() > packageBundles.size()) { - c1 = packageBundles; - c2 = bundles; + + private Class findClass(String className, String packageName, ClassNotFoundException cnfe) throws ClassNotFoundException { + Iterator 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 + } } - - for (Bundle bundle : c1) { - if (c2.contains(bundle)) { - return bundle; + throw cnfe; + } + + private static boolean hasWildcardDynamicImport(Bundle bundle) { + Dictionary 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; } - - return null; } - + 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; - for (Bundle bundle : bundles) { - resource = bundle.getResource(name); - if (resource != null) { - return resource; + 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 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 null; + return resource; + } + + private URL findResource(String name) { + Iterator iterator = bundles.iterator(); + return findResource(name, iterator); + } + + private URL findResource(String name, Iterator iterator) { + URL resource = null; + while (iterator.hasNext() && resource == null) { + Bundle delegate = iterator.next(); + resource = delegate.getResource(name); + } + return resource; } public Enumeration getResources(String name) throws IOException { @@ -304,5 +411,28 @@ public class DelegatingBundle implements public String toString() { return "[DelegatingBundle: " + bundles + "]"; } + + private static class Cache extends LinkedHashMap { + + 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 eldest) { + if (size() > maxSize) { + return true; + } else { + return false; + } + } + } }