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 <[email protected]> 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 "[email protected]" <[email protected]>.
