Author: radu
Date: Thu Nov 10 14:19:08 2016
New Revision: 1769127

URL: http://svn.apache.org/viewvc?rev=1769127&view=rev
Log:
SLING-6258 - The PackageAdminClassLoader cannot load classes from bundles 
providing older API versions

* made sure to lock-down the bundle which provides first a resource / class 
from a requested package so
that subsequent calls for resolving classes / resources from the same package 
will always use the same
bundle

Modified:
    
sling/trunk/bundles/commons/classloader/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java
    
sling/trunk/bundles/commons/classloader/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java

Modified: 
sling/trunk/bundles/commons/classloader/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/classloader/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java?rev=1769127&r1=1769126&r2=1769127&view=diff
==============================================================================
--- 
sling/trunk/bundles/commons/classloader/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java
 (original)
+++ 
sling/trunk/bundles/commons/classloader/src/main/java/org/apache/sling/commons/classloader/impl/PackageAdminClassLoader.java
 Thu Nov 10 14:19:08 2016
@@ -23,6 +23,7 @@ import java.net.URL;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -31,6 +32,8 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * The <code>PackageAdminClassLoader</code> loads
@@ -38,6 +41,8 @@ import org.osgi.service.packageadmin.Pac
  */
 class PackageAdminClassLoader extends ClassLoader {
 
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PackageAdminClassLoader.class);
+
     /** The package admin service. */
     private final PackageAdmin packageAdmin;
 
@@ -50,6 +55,8 @@ class PackageAdminClassLoader extends Cl
     /** Negative class cache. */
     private Set<String> negativeClassCache = Collections.synchronizedSet(new 
HashSet<String>());
 
+    private Map<String, Bundle> packageProviders = new ConcurrentHashMap<>();
+
     /** A cache for resolved urls. */
     private Map<String, URL> urlCache = new ConcurrentHashMap<String, URL>();
 
@@ -100,7 +107,7 @@ class PackageAdminClassLoader extends Cl
      */
     private Set<Bundle> findBundlesForPackage(final String pckName) {
         final ExportedPackage[] exportedPackages = 
this.packageAdmin.getExportedPackages(pckName);
-        Set<Bundle> bundles = new HashSet<>();
+        Set<Bundle> bundles = new LinkedHashSet<>();
         if (exportedPackages != null) {
             for (ExportedPackage exportedPackage : exportedPackages) {
                 if (!exportedPackage.isRemovalPending()) {
@@ -143,10 +150,23 @@ class PackageAdminClassLoader extends Cl
     public Enumeration<URL> getResources(final String name) throws IOException 
{
         Enumeration<URL> e = super.getResources(name);
         if ( e == null || !e.hasMoreElements() ) {
-            for (Bundle bundle : 
findBundlesForPackage(getPackageFromResource(name))) {
-                e = bundle.getResources(name);
-                if (e != null) {
-                    return e;
+            String packageName = getPackageFromResource(name);
+            Bundle providingBundle = packageProviders.get(packageName);
+            if (providingBundle == null) {
+                for (Bundle bundle : 
findBundlesForPackage(getPackageFromResource(name))) {
+                    e = bundle.getResources(name);
+                    if (e != null) {
+                        packageProviders.put(packageName, bundle);
+                        LOGGER.debug("Marking bundle {}:{} as the provider for 
API package {}.", bundle.getSymbolicName(), bundle
+                                .getVersion().toString(), packageName);
+                        return e;
+                    }
+                }
+            } else {
+                e = providingBundle.getResources(name);
+                if (e == null) {
+                    LOGGER.debug("Cannot find resources {} in bundle {}:{} 
which was marked as the provider for package {}.", name,
+                            providingBundle.getSymbolicName(), 
providingBundle.getVersion().toString(), packageName);
                 }
             }
         }
@@ -163,12 +183,25 @@ class PackageAdminClassLoader extends Cl
         }
         URL url = super.findResource(name);
         if ( url == null ) {
-            Set<Bundle> bundles = 
findBundlesForPackage(getPackageFromResource(name));
-            for (Bundle bundle : bundles) {
-                url = bundle.getResource(name);
-                if ( url != null ) {
-                    urlCache.put(name, url);
-                    return url;
+            String packageName = getPackageFromResource(name);
+            Bundle providingBundle = packageProviders.get(packageName);
+            if (providingBundle == null) {
+                Set<Bundle> bundles = 
findBundlesForPackage(getPackageFromResource(name));
+                for (Bundle bundle : bundles) {
+                    url = bundle.getResource(name);
+                    if (url != null) {
+                        urlCache.put(name, url);
+                        packageProviders.put(packageName, bundle);
+                        LOGGER.debug("Marking bundle {}:{} as the provider for 
API package {}.", bundle.getSymbolicName(), bundle
+                                .getVersion().toString(), packageName);
+                        return url;
+                    }
+                }
+            } else {
+                url = providingBundle.getResource(name);
+                if (url == null) {
+                    LOGGER.debug("Cannot find resource {} in bundle {}:{} 
which was marked as the provider for package {}.", name,
+                            providingBundle.getSymbolicName(), 
providingBundle.getVersion().toString(), packageName);
                 }
             }
         }
@@ -183,19 +216,14 @@ class PackageAdminClassLoader extends Cl
         if ( cachedClass != null ) {
             return cachedClass;
         }
-        Class<?> clazz = null;
+        Class<?> clazz;
         try {
             clazz = super.findClass(name);
         } catch (ClassNotFoundException cnfe) {
-            Set<Bundle> bundles = 
findBundlesForPackage(getPackageFromClassName(name));
-            for (Bundle bundle : bundles) {
-                try {
-                    clazz = bundle.loadClass(name);
-                    this.factory.addUsedBundle(bundle);
-                    break;
-                } catch (ClassNotFoundException icnfe) {
-                    // do nothing; we need to loop over the bundles providing 
the class' package
-                }
+            try {
+                clazz = getClassFromBundles(name);
+            } catch (ClassNotFoundException innerCNFE) {
+                throw innerCNFE;
             }
         }
         if ( clazz == null ) {
@@ -216,29 +244,54 @@ class PackageAdminClassLoader extends Cl
         if ( negativeClassCache.contains(name) ) {
             throw new ClassNotFoundException("Class not found " + name);
         }
-        Class<?> clazz = null;
+        String packageName = getPackageFromClassName(name);
+        Class<?> clazz;
         try {
             clazz = super.loadClass(name, resolve);
         } catch (final ClassNotFoundException cnfe) {
-            final String pckName = getPackageFromClassName(name);
-            Set<Bundle> bundles = findBundlesForPackage(pckName);
+            try {
+                clazz = getClassFromBundles(name);
+            } catch (ClassNotFoundException innerCNFE) {
+                negativeClassCache.add(name);
+                this.factory.addUnresolvedPackage(packageName);
+                throw innerCNFE;
+            }
+        }
+        if ( clazz == null ) {
+            negativeClassCache.add(name);
+            this.factory.addUnresolvedPackage(packageName);
+            throw new ClassNotFoundException("Class not found " + name);
+        }
+        this.classCache.put(name, clazz);
+        return clazz;
+    }
+
+    private Class<?> getClassFromBundles(String name) throws 
ClassNotFoundException {
+        Class<?> clazz = null;
+        String packageName = getPackageFromClassName(name);
+        Bundle providingBundle = packageProviders.get(packageName);
+        if (providingBundle == null) {
+            Set<Bundle> bundles = findBundlesForPackage(packageName);
             for (Bundle bundle : bundles) {
                 try {
                     clazz = bundle.loadClass(name);
                     this.factory.addUsedBundle(bundle);
+                    packageProviders.put(packageName, bundle);
+                    LOGGER.debug("Marking bundle {}:{} as the provider for API 
package {}.", bundle.getSymbolicName(), bundle
+                            .getVersion().toString(), packageName);
                     break;
-                } catch (final ClassNotFoundException inner) {
+                } catch (ClassNotFoundException innerCNFE) {
                     // do nothing; we need to loop over the bundles providing 
the class' package
                 }
             }
+        } else {
+            try {
+                clazz = providingBundle.loadClass(name);
+            } catch (ClassNotFoundException icnfe) {
+                throw new ClassNotFoundException(String.format("Cannot find 
class %s in bundle %s:%s which was marked as the provider for" +
+                        " package %s.", name, 
providingBundle.getSymbolicName(), providingBundle.getVersion().toString(), 
packageName), icnfe);
+            }
         }
-        if ( clazz == null ) {
-            negativeClassCache.add(name);
-            final String pckName = getPackageFromClassName(name);
-            this.factory.addUnresolvedPackage(pckName);
-            throw new ClassNotFoundException("Class not found " + name);
-        }
-        this.classCache.put(name, clazz);
         return clazz;
     }
 }

Modified: 
sling/trunk/bundles/commons/classloader/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/classloader/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java?rev=1769127&r1=1769126&r2=1769127&view=diff
==============================================================================
--- 
sling/trunk/bundles/commons/classloader/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java
 (original)
+++ 
sling/trunk/bundles/commons/classloader/src/test/java/org/apache/sling/commons/classloader/impl/ClassLoadingTest.java
 Thu Nov 10 14:19:08 2016
@@ -16,7 +16,11 @@
  */
 package org.apache.sling.commons.classloader.impl;
 
+import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -27,6 +31,7 @@ import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceListener;
+import org.osgi.framework.Version;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
 
@@ -68,6 +73,10 @@ public class ClassLoadingTest {
             will(returnValue(2L));
             allowing(bundle).getState();
             will(returnValue(Bundle.ACTIVE));
+            allowing(bundle).getSymbolicName();
+            will(returnValue("org.apache.sling.test"));
+            allowing(bundle).getVersion();
+            will(returnValue(new Version("1.0.0")));
             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);
@@ -92,8 +101,10 @@ public class ClassLoadingTest {
         final PackageAdmin packageAdmin = 
this.context.mock(PackageAdmin.class);
         final ExportedPackage ep1 = this.context.mock(ExportedPackage.class, 
"ep1");
         final ExportedPackage ep2 = this.context.mock(ExportedPackage.class, 
"ep2");
+        final ExportedPackage ep3 = this.context.mock(ExportedPackage.class, 
"ep3");
         final Bundle bundle1 = this.context.mock(Bundle.class, "bundle1");
         final Bundle bundle2 = this.context.mock(Bundle.class, "bundle2");
+        final Bundle bundle3 = this.context.mock(Bundle.class, "bundle3");
         final ClassNotFoundException cnfe = new ClassNotFoundException();
         this.context.checking(new Expectations() {{
             allowing(bundleContext).createFilter(with(any(String.class)));
@@ -102,8 +113,10 @@ public class ClassLoadingTest {
             will(returnValue(null));
             
allowing(bundleContext).addServiceListener(with(any(ServiceListener.class)), 
with(any(String.class)));
             
allowing(bundleContext).removeServiceListener(with(any(ServiceListener.class)));
-            
allowing(packageAdmin).getExportedPackages("org.apache.sling.test");
+            
allowing(packageAdmin).getExportedPackages(with("org.apache.sling.test"));
             will(returnValue(new ExportedPackage[] {ep1, ep2}));
+            
allowing(packageAdmin).getExportedPackages(with("org.apache.sling.test3"));
+            will(returnValue(new ExportedPackage[] {ep3}));
 
             allowing(ep1).getExportingBundle();
             will(returnValue(bundle1));
@@ -115,27 +128,81 @@ public class ClassLoadingTest {
             allowing(ep2).isRemovalPending();
             will(returnValue(false));
 
+            allowing(ep3).getExportingBundle();
+            will(returnValue(bundle3));
+            allowing(ep3).isRemovalPending();
+            will(returnValue(false));
+
             allowing(bundle1).getBundleId();
-            will(returnValue(2L));
+            will(returnValue(1L));
             allowing(bundle1).getState();
             will(returnValue(Bundle.ACTIVE));
+            allowing(bundle1).getVersion();
+            will(returnValue(new Version("1.0.0")));
+            allowing(bundle1).getSymbolicName();
+            will(returnValue("org.apache.sling.test1"));
 
 
             allowing(bundle2).getBundleId();
-            will(returnValue(3L));
+            will(returnValue(2L));
             allowing(bundle2).getState();
             will(returnValue(Bundle.ACTIVE));
+            allowing(bundle2).getVersion();
+            will(returnValue(new Version("2.0.0")));
+            allowing(bundle2).getSymbolicName();
+            will(returnValue("org.apache.sling.test2"));
+
+            allowing(bundle3).getBundleId();
+            will(returnValue(3L));
+            allowing(bundle3).getState();
+            will(returnValue(Bundle.ACTIVE));
+            allowing(bundle3).getVersion();
+            will(returnValue(new Version("1.2.3")));
+            allowing(bundle3).getSymbolicName();
+            will(returnValue("org.apache.sling.test3"));
 
             allowing(bundle1).loadClass("org.apache.sling.test.T1");
             will(throwException(cnfe));
+            allowing(bundle1).getResource("org/apache/sling/test/T3.class");
+            will(returnValue(new 
URL("jar:file:/ws/org.apache.sling.test1.jar!/org/apache/sling/test/T3.class")));
 
             allowing(bundle2).loadClass("org.apache.sling.test.T1");
             will(returnValue(ArrayList.class));
+            allowing(bundle2).loadClass("org.apache.sling.test.T2");
+            will(throwException(new ClassNotFoundException()));
+            allowing(bundle2).getResource("org/apache/sling/test/T3.class");
+            will(returnValue(null));
+            allowing(bundle2).getResources("org/apache/sling/test/T3.class");
+            will(returnValue(null));
 
+            allowing(bundle3).getResource("org/apache/sling/test3/T4.class");
+            will(returnValue(new 
URL("jar:file:/ws/org.apache.sling.test3.jar!/org/apache/sling/test3/T4.class")));
+            allowing(bundle3).getResources("org/apache/sling/test3/T4.class");
+            will(returnValue(Collections.enumeration(new ArrayList<URL>() {{
+                add(new 
URL("jar:file:/ws/org.apache.sling.test3.jar!/org/apache/sling/test3/T4.class"));
+            }})));
         }});
         DynamicClassLoaderManagerImpl manager = new 
DynamicClassLoaderManagerImpl(bundleContext, packageAdmin, null,
                 new DynamicClassLoaderManagerFactory(bundleContext, 
packageAdmin));
         final ClassLoader cl = manager.getDynamicClassLoader();
         Assert.assertEquals(ArrayList.class, 
cl.loadClass("org.apache.sling.test.T1"));
+        try {
+            cl.loadClass("org.apache.sling.test.T2");
+        } catch (Exception e) {
+            Assert.assertEquals(ClassNotFoundException.class, e.getClass());
+        }
+        Assert.assertNull(cl.getResource("org/apache/sling/test/T3.class"));
+        
Assert.assertFalse(cl.getResources("org/apache/sling/test/T3.class").hasMoreElements());
+        
Assert.assertEquals("jar:file:/ws/org.apache.sling.test3.jar!/org/apache/sling/test3/T4.class",
 cl.getResource
+                ("org/apache/sling/test3/T4.class").toString());
+        Enumeration<URL> resourceURLs = 
cl.getResources("org/apache/sling/test3/T4.class");
+        int count = 0;
+        URL lastURL = new URL("https://sling.apache.org";);
+        while (resourceURLs.hasMoreElements()) {
+            count++;
+            lastURL = resourceURLs.nextElement();
+        }
+        Assert.assertEquals(1, count);
+        
Assert.assertEquals("jar:file:/ws/org.apache.sling.test3.jar!/org/apache/sling/test3/T4.class",
 lastURL.toString());
     }
 }


Reply via email to