/* * 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.*; import static org.ops4j.pax.exam.Constants.*; import static org.ops4j.pax.exam.CoreOptions.*; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; 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.Configuration; import org.ops4j.pax.exam.junit.PaxExam; import org.ops4j.pax.exam.ProbeBuilder; import org.ops4j.pax.exam.options.AbstractDelegateProvisionOption; import org.ops4j.pax.exam.options.MavenArtifactProvisionOption; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; @RunWith(PaxExam.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"; private static MavenArtifactCoordinates commonsOsgi = new MavenArtifactCoordinates("org.apache.sling", "org.apache.sling.commons.osgi", "2.1.0"); @Inject protected BundleContext bundleContext; protected ClassLoader dynamicClassLoader; protected ServiceReference<DynamicClassLoaderManager> classLoaderManagerReference; /** * Helper method to get a service of the given type */ protected <T> T getService(Class<T> clazz) { final ServiceReference<T> ref = bundleContext.getServiceReference(clazz); assertNotNull("getService(" + clazz.getName() + ") must find ServiceReference", ref); final T result = 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); } if ( dynamicClassLoader == null && classLoaderManagerReference != null ) { final DynamicClassLoaderManager dclm = 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") ), // below is instead of normal Pax Exam junitBundles() to deal // with build server issue new DirectURLJUnitBundlesOption(), systemProperty("pax.exam.invoker").value("junit"), bundle("link:classpath:META-INF/links/org.ops4j.pax.exam.invoker.junit.link") ); } @Test public void testPackageAdminClassLoader() throws Exception { // check class loader assertNotNull(getDynamicClassLoader()); Bundle osgiBundle; try ( InputStream input = commonsOsgi.openInputStream() ) { osgiBundle = this.bundleContext.installBundle(commonsOsgi.getMavenBundle().getURL(), input); } 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"); } } /** * Clone of Pax Exam's JunitBundlesOption which uses a direct * URL to the SpringSource JUnit bundle to avoid some weird * repository issues on the Apache build server. */ private static class DirectURLJUnitBundlesOption extends AbstractDelegateProvisionOption<DirectURLJUnitBundlesOption> { /** * Constructor. */ public DirectURLJUnitBundlesOption(){ super( bundle("http://repository.springsource.com/ivy/bundles/external/org.junit/com.springsource.org.junit/4.9.0/com.springsource.org.junit-4.9.0.jar") ); noUpdate(); startLevel(START_LEVEL_SYSTEM_BUNDLES); } /** * {@inheritDoc} */ @Override public String toString() { return String.format("DirectURLJUnitBundlesOption{url=%s}", getURL()); } /** * {@inheritDoc} */ protected DirectURLJUnitBundlesOption itself() { return this; } } /** * Helper class which simplifies accesing a Maven artifact based on its coordinates */ static class MavenArtifactCoordinates { private String groupId; private String artifactId; private String version; private MavenArtifactCoordinates(String groupId, String artifactId, String version) { this.groupId = groupId; this.artifactId = artifactId; this.version = version; } public InputStream openInputStream() throws FileNotFoundException { // note that this contains a lot of Maven-related logic, but I did not find the // right set of dependencies to make this work inside the OSGi container // // The tough part is making sure that this also works on Jenkins where a // private local repository is specified using -Dmaven.repo.local Path localRepo = Paths.get(System.getProperty("user.home"), ".m2", "repository"); String overridenRepo = System.getProperty("maven.repo.local"); if ( overridenRepo != null ) { localRepo = Paths.get(overridenRepo); } Path artifact = Paths.get(localRepo.toString(), groupId.replace('.', File.separatorChar), artifactId, version, artifactId+"-" + version+".jar"); if ( !artifact.toFile().exists() ) { throw new RuntimeException("Artifact at " + artifact + " does not exist."); } return new FileInputStream(artifact.toFile()); } public MavenArtifactProvisionOption getMavenBundle() { return mavenBundle(groupId, artifactId, version); } } }