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>.