You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:20:51 UTC

[sling-org-apache-sling-commons-classloader] 02/15: SLING-2438 : Class might never be loaded if the bundle is in state resolved on the first attempt

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.commons.classloader-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-classloader.git

commit 101324736b431b3ae5c79584ba33bfd94513d9b0
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Mar 16 08:14:59 2012 +0000

    SLING-2438 : Class might never be loaded if the bundle is in state resolved on the first attempt
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/classloader@1301365 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            | 114 +++++++++++++-
 .../sling/commons/classloader/impl/Activator.java  |  12 +-
 .../classloader/impl/PackageAdminClassLoader.java  |  60 ++++++--
 .../classloader/impl/DynamicClassLoaderIT.java     | 165 +++++++++++++++++++++
 4 files changed, 324 insertions(+), 27 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7a02fc5..d147154 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,15 @@
         <url>http://svn.apache.org/viewvc/sling/trunk/bundles/commons/classloader</url>
     </scm>
 
+    <properties>
+        <bundle.build.name>
+            ${basedir}/target
+        </bundle.build.name>
+        <bundle.file.name>
+            ${bundle.build.name}/${project.build.finalName}.jar
+        </bundle.file.name>
+    </properties>
+
     <build>
         <plugins>
             <plugin>
@@ -66,8 +75,44 @@
                     </instructions>
                 </configuration>
             </plugin>
-        </plugins>
+       </plugins>
     </build>
+    <profiles>
+        <profile>
+            <id>java6</id>
+            <activation>
+                <jdk>1.6</jdk>
+            </activation>
+            <build>
+                <plugins>
+                  <!-- integration tests run with pax-exam -->
+                    <plugin>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <version>2.12</version>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <configuration>
+                            <systemProperties>
+                                <property>
+                                    <name>project.bundle.file</name>
+                                    <value>${bundle.file.name}</value>
+                                </property>
+                            </systemProperties>
+                            <includes>
+                                <include>**/*IT.java</include>
+                            </includes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
     <reporting>
         <plugins>
             <plugin>
@@ -90,11 +135,7 @@
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-
+      <!-- Unit Testing -->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
@@ -107,5 +148,66 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
         </dependency>
+      <!-- Integration Testing with Pax Exam -->
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-forked</artifactId>
+            <version>2.4.0.RC1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>2.4.0.RC1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-link-mvn</artifactId>
+            <version>2.4.0.RC1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+            <version>1.4.0.RC1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-wrap</artifactId>
+            <version>1.4.0.RC1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-atinject_1.0_spec</artifactId>
+            <version>1.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.base</groupId>
+            <artifactId>ops4j-base-lang</artifactId>
+            <version>1.2.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.base</groupId>
+            <artifactId>ops4j-base-net</artifactId>
+            <version>1.2.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.tinybundles</groupId>
+            <artifactId>tinybundles</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>4.0.2</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
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 d02d899..3cf5c13 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
@@ -94,7 +94,7 @@ public class Activator implements SynchronousBundleListener, BundleActivator {
     /**
      * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
      */
-    public void stop(BundleContext context) {
+    public void stop(final BundleContext context) {
         context.removeBundleListener(this);
         this.unregisterManagerFactory();
         if ( this.packageAdminTracker != null ) {
@@ -107,12 +107,14 @@ public class Activator implements SynchronousBundleListener, BundleActivator {
     /**
      * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
      */
-    public void bundleChanged(BundleEvent event) {
+    public void bundleChanged(final BundleEvent event) {
         synchronized ( this ) {
+            final boolean lazyBundle = event.getBundle().getHeaders().get( Constants.BUNDLE_ACTIVATIONPOLICY ) != null;
+
             final boolean reload;
-            if ( event.getType() == BundleEvent.RESOLVED ) {
-                reload = this.service.isBundleUsed(event.getBundle().getBundleId())
-                    || this.service.hasUnresolvedPackages(event.getBundle());
+            if ( ( event.getType() == BundleEvent.STARTED && !lazyBundle)
+                 || (event.getType() == BundleEvent.STARTING && lazyBundle) ) {
+                reload = this.service.hasUnresolvedPackages(event.getBundle());
             } else if ( event.getType() == BundleEvent.UNRESOLVED ) {
                 reload = this.service.isBundleUsed(event.getBundle().getBundleId());
             } else {
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 fb3bed9..3258c8f 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
@@ -28,6 +28,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
 
@@ -61,6 +62,38 @@ class PackageAdminClassLoader extends ClassLoader {
     }
 
     /**
+     * Returns <code>true</code> if the <code>bundle</code> is to be considered
+     * active from the perspective of declarative services.
+     * <p>
+     * As of R4.1 a bundle may have lazy activation policy which means a bundle
+     * remains in the STARTING state until a class is loaded from that bundle
+     * (unless that class is declared to not cause the bundle to start).
+     *
+     * @param bundle The bundle check
+     * @return <code>true</code> if <code>bundle</code> is not <code>null</code>
+     *          and the bundle is either active or has lazy activation policy
+     *          and is in the starting state.
+     */
+    private boolean isBundleActive( final Bundle bundle ) {
+        if ( bundle != null ) {
+            if ( bundle.getState() == Bundle.ACTIVE ) {
+                return true;
+            }
+
+            if ( bundle.getState() == Bundle.STARTING ) {
+                // according to the spec the activationPolicy header is only
+                // set to request a bundle to be lazily activated. So in this
+                // simple check we just verify the header is set to assume
+                // the bundle is considered a lazily activated bundle
+                return bundle.getHeaders().get( Constants.BUNDLE_ACTIVATIONPOLICY ) != null;
+            }
+        }
+
+        // fall back: bundle is not considered active
+        return false;
+    }
+
+    /**
      * Find the bundle for a given package.
      * @param pckName The package name.
      * @return The bundle or <code>null</code>
@@ -70,11 +103,6 @@ class PackageAdminClassLoader extends ClassLoader {
         Bundle bundle = null;
         if (exportedPackage != null && !exportedPackage.isRemovalPending() ) {
             bundle = exportedPackage.getExportingBundle();
-            if ( bundle != null ) {
-                if ( bundle.getState() != Bundle.ACTIVE ) {
-                    bundle = null;
-                }
-            }
         }
         return bundle;
     }
@@ -105,11 +133,11 @@ class PackageAdminClassLoader extends ClassLoader {
      * @see java.lang.ClassLoader#getResources(java.lang.String)
      */
     @SuppressWarnings("unchecked")
-    public Enumeration<URL> getResources(String name) throws IOException {
+    public Enumeration<URL> getResources(final String name) throws IOException {
         Enumeration<URL> e = super.getResources(name);
         if ( e == null || !e.hasMoreElements() ) {
             final Bundle bundle = this.findBundleForPackage(getPackageFromResource(name));
-            if ( bundle != null ) {
+            if ( this.isBundleActive(bundle) ) {
                 e = bundle.getResources(name);
                 if ( e != null && e.hasMoreElements() ) {
                     this.factory.addUsedBundle(bundle);
@@ -122,7 +150,7 @@ class PackageAdminClassLoader extends ClassLoader {
     /**
      * @see java.lang.ClassLoader#findResource(java.lang.String)
      */
-    public URL findResource(String name) {
+    public URL findResource(final String name) {
         final URL cachedURL = urlCache.get(name);
         if ( cachedURL != null ) {
             return cachedURL;
@@ -130,7 +158,7 @@ class PackageAdminClassLoader extends ClassLoader {
         URL url = super.findResource(name);
         if ( url == null ) {
             final Bundle bundle = this.findBundleForPackage(getPackageFromResource(name));
-            if ( bundle != null ) {
+            if ( this.isBundleActive(bundle) ) {
                 url = bundle.getResource(name);
                 if ( url != null ) {
                     this.factory.addUsedBundle(bundle);
@@ -144,7 +172,7 @@ class PackageAdminClassLoader extends ClassLoader {
     /**
      * @see java.lang.ClassLoader#findClass(java.lang.String)
      */
-    public Class<?> findClass(String name) throws ClassNotFoundException {
+    public Class<?> findClass(final String name) throws ClassNotFoundException {
         final Class<?> cachedClass = this.classCache.get(name);
         if ( cachedClass != null ) {
             return cachedClass;
@@ -154,7 +182,7 @@ class PackageAdminClassLoader extends ClassLoader {
             clazz = super.findClass(name);
         } catch (ClassNotFoundException cnfe) {
             final Bundle bundle = this.findBundleForPackage(getPackageFromClassName(name));
-            if ( bundle != null ) {
+            if ( this.isBundleActive(bundle) ) {
                 clazz = bundle.loadClass(name);
                 this.factory.addUsedBundle(bundle);
             }
@@ -169,7 +197,7 @@ class PackageAdminClassLoader extends ClassLoader {
     /**
      * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
      */
-    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+    protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
         final Class<?> cachedClass = this.classCache.get(name);
         if ( cachedClass != null ) {
             return cachedClass;
@@ -180,18 +208,18 @@ class PackageAdminClassLoader extends ClassLoader {
         Class<?> clazz = null;
         try {
             clazz = super.loadClass(name, resolve);
-        } catch (ClassNotFoundException cnfe) {
+        } catch (final ClassNotFoundException cnfe) {
             final String pckName = getPackageFromClassName(name);
             final Bundle bundle = this.findBundleForPackage(pckName);
-            if ( bundle != null ) {
+            if ( this.isBundleActive(bundle) ) {
                 try {
                     clazz = bundle.loadClass(name);
-                } catch (ClassNotFoundException inner) {
+                    this.factory.addUsedBundle(bundle);
+                } catch (final ClassNotFoundException inner) {
                     negativeClassCache.add(name);
                     this.factory.addUnresolvedPackage(pckName);
                     throw inner;
                 }
-                this.factory.addUsedBundle(bundle);
             }
         }
         if ( clazz == null ) {
diff --git a/src/test/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderIT.java b/src/test/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderIT.java
new file mode 100644
index 0000000..512b2de
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/classloader/impl/DynamicClassLoaderIT.java
@@ -0,0 +1,165 @@
+/*
+ * 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.sling.commons.classloader.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.inject.Inject;
+
+import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.TestProbeBuilder;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.ops4j.pax.exam.junit.ProbeBuilder;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+
+@RunWith(JUnit4TestRunner.class)
+public class DynamicClassLoaderIT {
+
+    // the name of the system property providing the bundle file to be installed and tested
+    private static final String BUNDLE_JAR_SYS_PROP = "project.bundle.file";
+
+    @Inject
+    protected BundleContext bundleContext;
+
+    protected ClassLoader dynamicClassLoader;
+
+    protected ServiceReference classLoaderManagerReference;
+
+    /**
+     * Helper method to get a service of the given type
+     */
+    @SuppressWarnings("unchecked")
+	protected <T> T getService(Class<T> clazz) {
+    	final ServiceReference ref = bundleContext.getServiceReference(clazz.getName());
+    	assertNotNull("getService(" + clazz.getName() + ") must find ServiceReference", ref);
+    	final T result = (T)(bundleContext.getService(ref));
+    	assertNotNull("getService(" + clazz.getName() + ") must find service", result);
+    	return result;
+    }
+
+    protected ClassLoader getDynamicClassLoader() {
+        if ( classLoaderManagerReference == null || classLoaderManagerReference.getBundle() == null ) {
+            dynamicClassLoader = null;
+            classLoaderManagerReference = bundleContext.getServiceReference(DynamicClassLoaderManager.class.getName());
+        }
+        if ( dynamicClassLoader == null && classLoaderManagerReference != null ) {
+            final DynamicClassLoaderManager dclm = (DynamicClassLoaderManager) bundleContext.getService(classLoaderManagerReference);
+            if ( dclm != null ) {
+                dynamicClassLoader = dclm.getDynamicClassLoader();
+            }
+        }
+        return dynamicClassLoader;
+    }
+
+    @ProbeBuilder
+    public TestProbeBuilder extendProbe(TestProbeBuilder builder) {
+        builder.setHeader(Constants.IMPORT_PACKAGE, "org.osgi.framework,org.apache.sling.commons.classloader");
+        builder.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "org.ops4j.pax.exam,org.junit,javax.inject,org.ops4j.pax.exam.options");
+        builder.setHeader("Bundle-ManifestVersion", "2");
+        return builder;
+    }
+
+    @Configuration
+    public static Option[] configuration() {
+        final String bundleFileName = System.getProperty( BUNDLE_JAR_SYS_PROP );
+        final File bundleFile = new File( bundleFileName );
+        if ( !bundleFile.canRead() ) {
+            throw new IllegalArgumentException( "Cannot read from bundle file " + bundleFileName + " specified in the "
+                + BUNDLE_JAR_SYS_PROP + " system property" );
+        }
+
+        return options(
+            provision(
+                CoreOptions.bundle( bundleFile.toURI().toString() ),
+                mavenBundle( "org.ops4j.pax.tinybundles", "tinybundles", "1.0.0" ),
+                mavenBundle("org.apache.sling", "org.apache.sling.commons.log", "2.1.2"),
+                mavenBundle("org.apache.felix", "org.apache.felix.eventadmin", "1.2.14"),
+                mavenBundle("org.ops4j.pax.url", "pax-url-mvn", "1.3.5")
+             ),
+             junitBundles()
+
+        );
+    }
+
+    @Test
+    public void testPackageAdminClassLoader() throws Exception {
+        // check class loader
+        assertNotNull(getDynamicClassLoader());
+
+        final URL url = new URL(mavenBundle("org.apache.sling", "org.apache.sling.commons.osgi", "2.1.0").getURL());
+        final InputStream is = url.openStream();
+        Bundle osgiBundle = null;
+        try {
+            osgiBundle = this.bundleContext.installBundle(url.toExternalForm(), is);
+        } finally {
+            try { is.close(); } catch ( final IOException ignore) {}
+        }
+        assertNotNull(osgiBundle);
+        assertEquals(Bundle.INSTALLED, osgiBundle.getState());
+
+        final String className = "org.apache.sling.commons.osgi.PropertiesUtil";
+
+        // try to load class when bundle is in state install: should fail
+        try {
+            getDynamicClassLoader().loadClass(className);
+            fail("Class should not be available");
+        } catch (final ClassNotFoundException expected) {
+            // expected
+        }
+
+        // force resolving of the bundle
+        osgiBundle.getResource("/something");
+        assertEquals(Bundle.RESOLVED, osgiBundle.getState());
+        // try to load class when bundle is in state resolve: should fail
+        try {
+            getDynamicClassLoader().loadClass(className);
+            fail("Class should not be available");
+        } catch (final ClassNotFoundException expected) {
+            // expected
+        }
+
+        // start bundle
+        osgiBundle.start();
+        assertEquals(Bundle.ACTIVE, osgiBundle.getState());
+        // try to load class when bundle is in state activate: should work
+        try {
+            getDynamicClassLoader().loadClass(className);
+        } catch (final ClassNotFoundException expected) {
+            fail("Class should be available");
+        }
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.