This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.commons.classloader-0.9.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-classloader.git
commit ce0fe406f4b477265a12ebb0cdf5f514e657802f Author: Carsten Ziegeler <[email protected]> AuthorDate: Tue Aug 4 14:03:59 2009 +0000 SLING-1070 : Change class loading of package admin class loader to parent first, cache classes and resources in the class loader facade and reregister manager factory, if a used bundle is changed. git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/classloader@800803 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/classloader/ClassLoaderWriter.java | 42 ++++++++-- .../classloader/DynamicClassLoaderManager.java | 5 ++ .../sling/commons/classloader/impl/Activator.java | 54 +++++++++++-- .../classloader/impl/BundleProxyClassLoader.java | 2 + .../classloader/impl/ClassLoaderFacade.java | 49 ++++++++---- .../impl/DynamicClassLoaderManagerFactory.java | 26 +++++- .../impl/DynamicClassLoaderManagerImpl.java | 21 ++++- .../classloader/impl/PackageAdminClassLoader.java | 92 +++++++++++++++++----- .../commons/classloader/impl/ClassLoadingTest.java | 8 +- 9 files changed, 243 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/apache/sling/commons/classloader/ClassLoaderWriter.java b/src/main/java/org/apache/sling/commons/classloader/ClassLoaderWriter.java index b39b746..82e6634 100644 --- a/src/main/java/org/apache/sling/commons/classloader/ClassLoaderWriter.java +++ b/src/main/java/org/apache/sling/commons/classloader/ClassLoaderWriter.java @@ -30,13 +30,45 @@ import java.io.OutputStream; */ public interface ClassLoaderWriter { - OutputStream getOutputStream(String name); + /** + * Get the output stream for a class or resource handled + * by the underlying class loader. + * If the resource/class does not exists it should be created. + * @param path The path of the class/resource. + * @return The output stream. + */ + OutputStream getOutputStream(String path); - InputStream getInputStream(String name) throws IOException; + /** + * Get the input stream for a class or resource handled + * by the underlying class loader. + * @param path The path of the class/resource. + * @return The input stream for the resource/class. + * @throws IOException If the resource/class does not exist. + */ + InputStream getInputStream(String path) throws IOException; - long getLastModified(String name); + /** + * Return the last modified for the class or resource. + * @param path The path of the class/resource. + * @return The last modified information or <code>-1</code> if + * the information can't be detected. + */ + long getLastModified(String path); - boolean delete(String name); + /** + * Delete the class/resource + * @param path The path of the class/resource. + * @return <code>true</code> if the resource exists and could be deleted, + * <code>false</code> otherwise. + */ + boolean delete(String path); - boolean rename(String oldName, String newName); + /** + * Rename a class/resource. + * @param oldPath The path of the class/resource. + * @param newPath The new path. + * @return <code>true</code> if the renaming has been successful. + */ + boolean rename(String oldPath, String newPath); } diff --git a/src/main/java/org/apache/sling/commons/classloader/DynamicClassLoaderManager.java b/src/main/java/org/apache/sling/commons/classloader/DynamicClassLoaderManager.java index ff23e78..25aae5b 100644 --- a/src/main/java/org/apache/sling/commons/classloader/DynamicClassLoaderManager.java +++ b/src/main/java/org/apache/sling/commons/classloader/DynamicClassLoaderManager.java @@ -24,6 +24,11 @@ package org.apache.sling.commons.classloader; * It provides a class loader that can be used by * bundles requiring access to all publically available * classes. + * + * The default implementation uses the package admin + * service to load classes and resources. The search + * path can be extended by providing + * {@link DynamicClassLoaderProvider}s. */ public interface DynamicClassLoaderManager { diff --git a/src/main/java/org/apache/sling/commons/classloader/impl/Activator.java b/src/main/java/org/apache/sling/commons/classloader/impl/Activator.java index 1ca885e..33ea4f5 100644 --- a/src/main/java/org/apache/sling/commons/classloader/impl/Activator.java +++ b/src/main/java/org/apache/sling/commons/classloader/impl/Activator.java @@ -21,17 +21,21 @@ package org.apache.sling.commons.classloader.impl; import java.util.Hashtable; import org.apache.sling.commons.classloader.DynamicClassLoaderManager; -import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.SynchronousBundleListener; import org.osgi.service.packageadmin.PackageAdmin; import org.osgi.util.tracker.ServiceTracker; /** * This activator registers the dynamic class loader manager. + * It listens for bundle events and reregisters the class loader manager + * if a bundle event for a used bundle occurs. */ -public class Activator implements BundleActivator { +public class Activator implements SynchronousBundleListener, BundleListener { /** Package admin service name */ private static String PACKAGE_ADMIN_NAME = PackageAdmin.class.getName(); @@ -45,26 +49,39 @@ public class Activator implements BundleActivator { /** The dynamic class loader service factory. */ private DynamicClassLoaderManagerFactory service; + /** The bundle context. */ + private BundleContext bundleContext; + /** * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ - public void start(BundleContext context) throws Exception { - this.packageAdminTracker = new ServiceTracker(context, PACKAGE_ADMIN_NAME, null); + public void start(BundleContext context) { + this.bundleContext = context; + + this.packageAdminTracker = new ServiceTracker(this.bundleContext, PACKAGE_ADMIN_NAME, null); this.packageAdminTracker.open(); // register service + this.registerManagerFactory(); + this.bundleContext.addBundleListener(this); + } + + /** + * Register the dynamic class loader manager factory. + */ + protected void registerManagerFactory() { final Hashtable<String, String> props = new Hashtable<String, String>(); props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Dynamic Class Loader Service"); props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation"); - this.service = new DynamicClassLoaderManagerFactory(context, + this.service = new DynamicClassLoaderManagerFactory(this.bundleContext, (PackageAdmin)this.packageAdminTracker.getService()); - this.serviceReg = context.registerService(new String[] {DynamicClassLoaderManager.class.getName()}, service, props); + this.serviceReg = this.bundleContext.registerService(new String[] {DynamicClassLoaderManager.class.getName()}, service, props); } /** - * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + * Unregister the dynamic class loader manager factory. */ - public void stop(BundleContext context) throws Exception { + protected void unregisterManagerFactory() { if ( this.serviceReg != null ) { this.serviceReg.unregister(); this.serviceReg = null; @@ -72,9 +89,30 @@ public class Activator implements BundleActivator { if ( this.service != null ) { this.service = null; } + } + + /** + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) { + context.removeBundleListener(this); + this.unregisterManagerFactory(); if ( this.packageAdminTracker != null ) { this.packageAdminTracker.close(); this.packageAdminTracker = null; } + this.bundleContext = null; + } + + /** + * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent) + */ + public void bundleChanged(BundleEvent event) { + final long bundleId = event.getBundle().getBundleId(); + boolean needsUpdate = this.service.isBundleUsed(bundleId); + if ( needsUpdate ) { + this.unregisterManagerFactory(); + this.registerManagerFactory(); + } } } diff --git a/src/main/java/org/apache/sling/commons/classloader/impl/BundleProxyClassLoader.java b/src/main/java/org/apache/sling/commons/classloader/impl/BundleProxyClassLoader.java index 75cb21d..efef5db 100644 --- a/src/main/java/org/apache/sling/commons/classloader/impl/BundleProxyClassLoader.java +++ b/src/main/java/org/apache/sling/commons/classloader/impl/BundleProxyClassLoader.java @@ -27,6 +27,8 @@ import org.osgi.framework.Bundle; /** * The <code>BundleProxyClassLoader</code> is a class loader * delegating to a bundle. + * We don't need to cache as the {@link ClassLoaderFacade} is + * already doing this. */ public class BundleProxyClassLoader extends ClassLoader { diff --git a/src/main/java/org/apache/sling/commons/classloader/impl/ClassLoaderFacade.java b/src/main/java/org/apache/sling/commons/classloader/impl/ClassLoaderFacade.java index e852bd8..d080d67 100644 --- a/src/main/java/org/apache/sling/commons/classloader/impl/ClassLoaderFacade.java +++ b/src/main/java/org/apache/sling/commons/classloader/impl/ClassLoaderFacade.java @@ -19,19 +19,30 @@ package org.apache.sling.commons.classloader.impl; import java.io.IOException; -import java.io.InputStream; import java.net.URL; import java.util.Enumeration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * The <code>ClassLoaderFacade</code> is a facade * for the dynamic class loading. + * This class loader is returned to the clients of the + * dynamic class loader manager. + * This class loader delegates to other class loaders + * but caches its result for performance. */ public class ClassLoaderFacade extends ClassLoader { private final DynamicClassLoaderManagerImpl manager; + /** A cache for resolved classes. */ + private Map<String, Class<?>> classCache = new ConcurrentHashMap<String, Class<?>>(); + + /** A cache for resolved urls. */ + private Map<String, URL> urlCache = new ConcurrentHashMap<String, URL>(); + public ClassLoaderFacade(final DynamicClassLoaderManagerImpl manager) { this.manager = manager; } @@ -40,11 +51,19 @@ public class ClassLoaderFacade extends ClassLoader { * @see java.lang.ClassLoader#getResource(java.lang.String) */ public URL getResource(String name) { + if ( !this.manager.isActive() ) { + throw new RuntimeException("Dynamic class loader has already been deactivated."); + } + final URL cachedURL = urlCache.get(name); + if ( cachedURL != null ) { + return cachedURL; + } final ClassLoader[] loaders = manager.getDynamicClassLoaders(); for(final ClassLoader cl : loaders) { if ( cl != null ) { final URL u = cl.getResource(name); if ( u != null ) { + urlCache.put(name, u); return u; } } @@ -53,25 +72,12 @@ public class ClassLoaderFacade extends ClassLoader { } /** - * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String) - */ - public InputStream getResourceAsStream(String name) { - final ClassLoader[] loaders = manager.getDynamicClassLoaders(); - for(final ClassLoader cl : loaders) { - if ( cl != null ) { - final InputStream i = cl.getResourceAsStream(name); - if ( i != null ) { - return i; - } - } - } - return null; - } - - /** * @see java.lang.ClassLoader#getResources(java.lang.String) */ public Enumeration<URL> getResources(String name) throws IOException { + if ( !this.manager.isActive() ) { + throw new RuntimeException("Dynamic class loader has already been deactivated."); + } final ClassLoader[] loaders = manager.getDynamicClassLoaders(); for(final ClassLoader cl : loaders) { if ( cl != null ) { @@ -88,13 +94,22 @@ public class ClassLoaderFacade extends ClassLoader { * @see java.lang.ClassLoader#loadClass(java.lang.String) */ public Class<?> loadClass(String name) throws ClassNotFoundException { + if ( !this.manager.isActive() ) { + throw new RuntimeException("Dynamic class loader has already been deactivated."); + } + final Class<?> cachedClass = this.classCache.get(name); + if ( cachedClass != null ) { + return cachedClass; + } final ClassLoader[] loaders = manager.getDynamicClassLoaders(); for(final ClassLoader cl : loaders) { if ( cl != null ) { try { final Class<?> c = cl.loadClass(name); + this.classCache.put(name, c); return c; } catch (Exception cnfe) { + cnfe.printStackTrace(); // we just ignore this and try the next class loader } } diff --git a/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerFactory.java b/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerFactory.java index cad3a05..3a257d2 100644 --- a/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerFactory.java +++ b/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerFactory.java @@ -16,6 +16,10 @@ */ package org.apache.sling.commons.classloader.impl; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceFactory; @@ -34,6 +38,8 @@ public class DynamicClassLoaderManagerFactory /** The bundle context. */ private final BundleContext context; + private final Set<Long> usedBundles = Collections.synchronizedSet(new HashSet<Long>()); + /** * Create a new service instance * @param ctx The bundle context. @@ -50,7 +56,7 @@ public class DynamicClassLoaderManagerFactory */ public Object getService(final Bundle bundle, final ServiceRegistration registration) { - return new DynamicClassLoaderManagerImpl(this.context, this.pckAdmin, new BundleProxyClassLoader(bundle)); + return new DynamicClassLoaderManagerImpl(this.context, this.pckAdmin, new BundleProxyClassLoader(bundle), this); } /** @@ -63,4 +69,22 @@ public class DynamicClassLoaderManagerFactory ((DynamicClassLoaderManagerImpl)service).deactivate(); } } + + /** + * Check if a bundle has been used for class loading. + * @param bundleId The bundle id. + * @return <code>true</code> if the bundle has been used. + */ + public boolean isBundleUsed(final long bundleId) { + return usedBundles.contains(bundleId); + } + + /** + * Notify that a bundle is used as a source for class loading. + * @param bundle The bundle. + */ + public void addUsedBundle(final Bundle bundle) { + final long id = bundle.getBundleId(); + this.usedBundles.add(id); + } } diff --git a/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerImpl.java b/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerImpl.java index 2e5c6e1..2388b5b 100644 --- a/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerImpl.java +++ b/src/main/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderManagerImpl.java @@ -44,10 +44,15 @@ public class DynamicClassLoaderManagerImpl /** The bundle context. */ private final BundleContext context; + /** The cached chain of class loaders. */ private ClassLoader[] cache; + /** Needs the cache an update? */ private boolean updateCache = false; + /** Is this service still active? */ + private boolean active = true; + /** * Create a new service instance * @param ctx The bundle context of the class loader bundle @@ -56,10 +61,11 @@ public class DynamicClassLoaderManagerImpl */ public DynamicClassLoaderManagerImpl(final BundleContext ctx, final PackageAdmin pckAdmin, - final ClassLoader parent) { + final ClassLoader parent, + final DynamicClassLoaderManagerFactory factory) { super(ctx, DynamicClassLoaderProvider.class.getName(), null); this.context = ctx; - this.pckAdminCL = new PackageAdminClassLoader(pckAdmin, parent); + this.pckAdminCL = new PackageAdminClassLoader(pckAdmin, parent, factory); this.cache = new ClassLoader[] {this.pckAdminCL}; this.open(); this.facade = new ClassLoaderFacade(this); @@ -85,8 +91,7 @@ public class DynamicClassLoaderManagerImpl final ServiceReference[] refs = this.getServiceReferences(); final ClassLoader[] loaders = new ClassLoader[1 + refs.length]; Arrays.sort(refs, ServiceReferenceComparator.INSTANCE); - loaders[0] = this.pckAdminCL; - int index = 1; + int index = 0; for(final ServiceReference ref : refs) { final DynamicClassLoaderProvider provider = (DynamicClassLoaderProvider)this.getService(ref); if ( provider != null ) { @@ -94,6 +99,7 @@ public class DynamicClassLoaderManagerImpl } index++; } + loaders[index] = this.pckAdminCL; // and now use new array this.cache = loaders; this.updateCache = false; @@ -104,9 +110,16 @@ public class DynamicClassLoaderManagerImpl * Deactivate this service. */ public void deactivate() { + this.active = false; this.close(); } + /** + * Check if this service is still active. + */ + public boolean isActive() { + return this.active; + } /** * @see org.apache.sling.commons.classloader.DynamicClassLoaderManager#getDynamicClassLoader() diff --git a/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java b/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java index dc98f88..f27bc8d 100644 --- a/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java +++ b/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java @@ -27,28 +27,52 @@ import org.osgi.service.packageadmin.ExportedPackage; import org.osgi.service.packageadmin.PackageAdmin; /** - * The <code>PackageAdminClassLoader</code> + * The <code>PackageAdminClassLoader</code> loads + * classes and resources through the package admin service. */ class PackageAdminClassLoader extends ClassLoader { + /** The package admin service. */ private final PackageAdmin packageAdmin; - public PackageAdminClassLoader(final PackageAdmin pckAdmin, final ClassLoader parent) { + /** The manager factory. */ + private final DynamicClassLoaderManagerFactory factory; + + public PackageAdminClassLoader(final PackageAdmin pckAdmin, + final ClassLoader parent, + final DynamicClassLoaderManagerFactory factory) { super(parent); this.packageAdmin = pckAdmin; + this.factory = factory; } + /** + * Find the bundle for a given package. + * @param pckName The package name. + * @return The bundle or <code>null</code> + */ private Bundle findBundleForPackage(final String pckName) { final ExportedPackage exportedPackage = this.packageAdmin.getExportedPackage(pckName); - return (exportedPackage == null ? null : exportedPackage.getExportingBundle()); + final Bundle bundle = (exportedPackage == null ? null : exportedPackage.getExportingBundle()); + return bundle; } + /** + * Return the package from a resource. + * @param resource The resource path. + * @return The package name. + */ private String getPackageFromResource(final String resource) { final int lastSlash = resource.lastIndexOf('/'); final String pckName = (lastSlash == -1 ? "" : resource.substring(0, lastSlash).replace('/', '.')); return pckName; } + /** + * Return the package from a class. + * @param resource The class name. + * @return The package name. + */ private String getPackageFromClassName(final String name) { final int lastDot = name.lastIndexOf('.'); final String pckName = (lastDot == -1 ? "" : name.substring(0, lastDot)); @@ -60,43 +84,73 @@ class PackageAdminClassLoader extends ClassLoader { */ @SuppressWarnings("unchecked") public Enumeration<URL> getResources(String name) throws IOException { - final Bundle bundle = this.findBundleForPackage(getPackageFromResource(name)); - if ( bundle == null ) { - return super.getResources(name); + Enumeration<URL> e = super.getResources(name); + if ( e == null || !e.hasMoreElements() ) { + final Bundle bundle = this.findBundleForPackage(getPackageFromResource(name)); + if ( bundle != null ) { + e = bundle.getResources(name); + if ( e != null && e.hasMoreElements() ) { + this.factory.addUsedBundle(bundle); + } + } } - return bundle.getResources(name); + return e; } /** * @see java.lang.ClassLoader#findResource(java.lang.String) */ public URL findResource(String name) { - final Bundle bundle = this.findBundleForPackage(getPackageFromResource(name)); - if ( bundle == null ) { - return super.findResource(name); + URL url = super.findResource(name); + if ( url == null ) { + final Bundle bundle = this.findBundleForPackage(getPackageFromResource(name)); + if ( bundle != null ) { + url = bundle.getResource(name); + if ( url != null ) { + this.factory.addUsedBundle(bundle); + } + } } - return bundle.getResource(name); + return url; } /** * @see java.lang.ClassLoader#findClass(java.lang.String) */ public Class<?> findClass(String name) throws ClassNotFoundException { - final Bundle bundle = this.findBundleForPackage(getPackageFromClassName(name)); - if ( bundle == null ) { - return super.findClass(name); + Class<?> clazz = null; + try { + clazz = super.findClass(name); + } catch (ClassNotFoundException cnfe) { + final Bundle bundle = this.findBundleForPackage(getPackageFromClassName(name)); + if ( bundle != null ) { + clazz = bundle.loadClass(name); + this.factory.addUsedBundle(bundle); + } } - return bundle.loadClass(name); + if ( clazz == null ) { + throw new ClassNotFoundException("Class not found " + name); + } + return clazz; } /** * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean) */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - final Bundle bundle = this.findBundleForPackage(getPackageFromClassName(name)); - if ( bundle == null ) { - return super.loadClass(name, resolve); + Class<?> clazz = null; + try { + clazz = super.loadClass(name, resolve); + } catch (ClassNotFoundException cnfe) { + final Bundle bundle = this.findBundleForPackage(getPackageFromClassName(name)); + if ( bundle != null ) { + clazz = bundle.loadClass(name); + this.factory.addUsedBundle(bundle); + } + } + if ( clazz == null ) { + throw new ClassNotFoundException("Class not found " + name); } - return bundle.loadClass(name); + return clazz; } } diff --git a/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java b/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java index e08be6e..e60256f 100644 --- a/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java +++ b/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java @@ -61,6 +61,8 @@ public class ClassLoadingTest { will(returnValue(ep)); allowing(ep).getExportingBundle(); will(returnValue(bundle)); + allowing(bundle).getBundleId(); + will(returnValue(2L)); one(bundle).loadClass("org.apache.sling.test.A"); inSequence(sequence); will(returnValue(java.util.Map.class)); one(bundle).loadClass("org.apache.sling.test.A"); inSequence(sequence); @@ -68,13 +70,15 @@ public class ClassLoadingTest { one(bundle).loadClass("org.apache.sling.test.A"); inSequence(sequence); will(returnValue(java.util.ArrayList.class)); }}); - DynamicClassLoaderManagerImpl manager = new DynamicClassLoaderManagerImpl(bundleContext, packageAdmin, null); + DynamicClassLoaderManagerImpl manager = new DynamicClassLoaderManagerImpl(bundleContext, packageAdmin, null, + new DynamicClassLoaderManagerFactory(bundleContext, packageAdmin)); final ClassLoader cl = manager.getDynamicClassLoader(); final Class c1 = cl.loadClass("org.apache.sling.test.A"); Assert.assertEquals("java.util.Map", c1.getName()); final Class c2 = cl.loadClass("org.apache.sling.test.A"); Assert.assertEquals("java.util.Map", c2.getName()); + // as we cache the result, we still get the map! final Class c3 = cl.loadClass("org.apache.sling.test.A"); - Assert.assertEquals("java.util.ArrayList", c3.getName()); + Assert.assertEquals("java.util.Map", c3.getName()); } } -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
