Java tutorial
/* * Copyright 2008 Alin Dreghiciu. * Copyright 2009 Toni Menzel. * * Licensed 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.ops4j.pax.exam.container.def.internal; import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES; import static org.ops4j.pax.exam.Constants.WAIT_FOREVER; import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.CoreOptions.systemProperty; import static org.ops4j.pax.exam.OptionUtils.combine; import static org.ops4j.pax.exam.OptionUtils.expand; import static org.ops4j.pax.exam.OptionUtils.filter; import static org.ops4j.pax.exam.OptionUtils.remove; import static org.ops4j.pax.exam.container.def.PaxRunnerOptions.scanBundle; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.rmi.NoSuchObjectException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ops4j.io.FileUtils; import org.ops4j.pax.exam.CompositeCustomizer; import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.Info; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.container.def.options.BundleScannerProvisionOption; import org.ops4j.pax.exam.container.def.options.RBCLookupTimeoutOption; import org.ops4j.pax.exam.container.def.options.Scanner; import org.ops4j.pax.exam.options.ProvisionOption; import org.ops4j.pax.exam.options.TestContainerStartTimeoutOption; import org.ops4j.pax.exam.rbc.Constants; import org.ops4j.pax.exam.rbc.client.RemoteBundleContextClient; import org.ops4j.pax.exam.spi.container.TestContainer; import org.ops4j.pax.exam.spi.container.TestContainerException; import org.ops4j.pax.exam.spi.container.TimeoutException; import org.ops4j.pax.runner.Run; import org.ops4j.pax.runner.handler.internal.URLUtils; import org.ops4j.pax.runner.platform.DefaultJavaRunner; import org.ops4j.store.Handle; import org.ops4j.store.Store; import org.ops4j.store.StoreFactory; import org.osgi.framework.Bundle; /** * {@link TestContainer} implementation using Pax Runner. * * @author Alin Dreghiciu (adreghiciu@gmail.com) * @since 0.3.0, December 09, 2008 */ class PaxRunnerTestContainer implements TestContainer { /** * JCL logger. */ // private static final Log LOG = GrowlFactory.getLogger( LogFactory.getLog( PaxRunnerTestContainer.class ), // "Pax Exam", // GrowlLogger.GROWL_INFO | GrowlLogger.GROWL_ERROR // ); private static final Log LOG = LogFactory.getLog(PaxRunnerTestContainer.class); /** * Number of ports to check for a free rmi communication port. */ private static final int AMOUNT_OF_PORTS_TO_CHECK = 100; /** * System bundle id. */ private static final int SYSTEM_BUNDLE = 0; /** * Remote bundle context client. */ private final RemoteBundleContextClient m_remoteBundleContextClient; /** * Java runner to be used to start up Pax Runner. */ private final DefaultJavaRunner m_javaRunner; /** * Pax Runner arguments, out of options. */ private final ArgumentsBuilder m_arguments; /** * test container start timeout. */ private final long m_startTimeout; private final Store<InputStream> m_store; private final Map<String, Handle> m_cache; private final CompositeCustomizer m_customizers; /** * */ private TestContainerSemaphore m_semaphore; private boolean m_started = false; /** * RMI registry. */ private Registry m_registry; /** * Constructor. * * @param javaRunner java runner to be used to start up Pax Runner * @param options user startup options */ PaxRunnerTestContainer(final DefaultJavaRunner javaRunner, final Option... options) { m_javaRunner = javaRunner; m_startTimeout = getTestContainerStartTimeout(options); int registryPort = createRegistry(); m_remoteBundleContextClient = new RemoteBundleContextClient(registryPort, getRMITimeout(options)); m_arguments = new ArgumentsBuilder(wrap(expand(combine(options, localOptions())))); m_customizers = new CompositeCustomizer(m_arguments.getCustomizers()); m_store = StoreFactory.sharedLocalStore(); m_cache = new HashMap<String, Handle>(); } /** * {@inheritDoc} Delegates to {@link RemoteBundleContextClient}. */ public <T> T getService(final Class<T> serviceType) { LOG.debug("Lookup a [" + serviceType.getName() + "]"); return m_remoteBundleContextClient.getService(serviceType); } /** * {@inheritDoc} Delegates to {@link RemoteBundleContextClient}. */ public <T> T getService(final Class<T> serviceType, final long timeoutInMillis) { LOG.debug("Lookup a [" + serviceType.getName() + "]"); return m_remoteBundleContextClient.getService(serviceType, timeoutInMillis); } /** * {@inheritDoc} Delegates to {@link RemoteBundleContextClient}. */ public long installBundle(final String bundleUrl) { LOG.debug("Preparing and Installing bundle [" + bundleUrl + "] .."); long id; try { id = m_remoteBundleContextClient .installBundle(m_store.getLocation(storeAndGetData(bundleUrl)).toASCIIString()); } catch (IOException e) { throw new RuntimeException(e); } LOG.debug("Installed bundle " + bundleUrl + " as ID: " + id); return id; } private Handle storeAndGetData(String bundleUrl) { try { Handle handle = m_cache.get(bundleUrl); if (handle == null) { // new, so build, customize and store URL url = new URL(bundleUrl); InputStream in = url.openStream(); in = m_customizers.customizeTestProbe(in); // store in and overwrite handle handle = m_store.store(in); m_cache.put(bundleUrl, handle); } return handle; } catch (Exception e) { LOG.error("problem in preparing probe. ", e); } return null; } /** * {@inheritDoc} Delegates to {@link RemoteBundleContextClient}. */ public long installBundle(final String bundleLocation, final byte[] bundle) { LOG.debug("Installing bundle [" + bundleLocation + "] .."); final long id = m_remoteBundleContextClient.installBundle(bundleLocation, bundle); LOG.debug("Installed bundle " + bundleLocation + " as ID: " + id); return id; } /** * {@inheritDoc} Delegates to {@link RemoteBundleContextClient}. */ public void startBundle(long bundleId) throws TestContainerException { LOG.debug("Starting test bundle with ID " + bundleId); m_remoteBundleContextClient.startBundle(bundleId); LOG.debug("Started test bundle with ID " + bundleId); } /** * {@inheritDoc} Delegates to {@link RemoteBundleContextClient}. */ public void setBundleStartLevel(final long bundleId, final int startLevel) throws TestContainerException { m_remoteBundleContextClient.setBundleStartLevel(bundleId, startLevel); } /** * {@inheritDoc} */ public void start() { LOG.info("Starting up the test container (Pax Runner " + Info.getPaxRunnerVersion() + " )"); /** */ m_semaphore = new TestContainerSemaphore(m_arguments.getWorkingFolder()); // this makes sure the system is ready to launch a new instance. // this could fail, based on what acquire actually checks. // this also creates some persistent mark that will be removed by m_semaphore.release() if (!m_semaphore.acquire()) { // here we can react. // Prompt user with the fact that there might be another instance running. if (!FileUtils.delete(m_arguments.getWorkingFolder())) { throw new RuntimeException("There might be another instance of Pax Exam running. Have a look at " + m_semaphore.getLockFile().getAbsolutePath()); } } // customize environment m_customizers.customizeEnvironment(m_arguments.getWorkingFolder()); long startedAt = System.currentTimeMillis(); URLUtils.resetURLStreamHandlerFactory(); Run.start(m_javaRunner, m_arguments.getArguments()); LOG.info("Test container (Pax Runner " + Info.getPaxRunnerVersion() + ") started in " + (System.currentTimeMillis() - startedAt) + " millis"); LOG.info("Wait for test container to finish its initialization " + (m_startTimeout == WAIT_FOREVER ? "without timing out" : "for " + m_startTimeout + " millis")); try { waitForState(SYSTEM_BUNDLE, Bundle.ACTIVE, m_startTimeout); } catch (TimeoutException e) { throw new TimeoutException( "Test container did not initialize in the expected time of " + m_startTimeout + " millis"); } m_started = true; } /** * {@inheritDoc} */ public void stop() { LOG.info("Shutting down the test container (Pax Runner)"); try { if (m_started) { if (m_remoteBundleContextClient != null) { m_remoteBundleContextClient.stop(); } if (m_javaRunner != null) { m_javaRunner.waitForExit(); } if (m_registry != null) { try { UnicastRemoteObject.unexportObject(m_registry, true); } catch (NoSuchObjectException e) { LOG.error("Problem in shutting down RMI registry. ", e); } // this is necessary, unfortunately.. RMI wouldn' stop otherwise System.gc(); LOG.info("RMI registry stopped"); m_registry = null; } } } finally { m_semaphore.release(); m_started = false; } } /** * {@inheritDoc} */ public void waitForState(final long bundleId, final int state, final long timeoutInMillis) throws TimeoutException { m_remoteBundleContextClient.waitForState(bundleId, state, timeoutInMillis); } /** * Return the options required by this container implementation. * * @return local options */ private Option[] localOptions() { return new Option[] { // remote bundle context bundle mavenBundle().groupId("org.ops4j.pax.exam").artifactId("pax-exam-container-rbc") .version(Info.getPaxExamVersion()).update(Info.isPaxExamSnapshotVersion()) .startLevel(START_LEVEL_SYSTEM_BUNDLES), // rmi communication port systemProperty(Constants.RMI_PORT_PROPERTY) .value(m_remoteBundleContextClient.getRmiPort().toString()), // boot delegation for sun.*. This seems only necessary in Knopflerfish version > 2.0.0 bootDelegationPackage("sun.*") }; } /** * Wrap provision options that are not already scanner provision bundles with a {@link BundleScannerProvisionOption} * in order to force update. * * @param options options to be wrapped (can be null or an empty array) * @return eventual wrapped bundles */ private Option[] wrap(final Option... options) { if (options != null && options.length > 0) { // get provison options out of options final ProvisionOption[] provisionOptions = filter(ProvisionOption.class, options); if (provisionOptions != null && provisionOptions.length > 0) { final List<Option> processed = new ArrayList<Option>(); for (final ProvisionOption provisionOption : provisionOptions) { if (!(provisionOption instanceof Scanner)) { processed.add(scanBundle(provisionOption).start(provisionOption.shouldStart()) .startLevel(provisionOption.getStartLevel()) .update(provisionOption.shouldUpdate())); } else { processed.add(provisionOption); } } // finally combine the processed provision options with the original options // (where provison options are removed) return combine(remove(ProvisionOption.class, options), processed.toArray(new Option[processed.size()])); } } // if there is nothing to process of there are no provision options just return the original options return options; } /** * Determine the rmi lookup timeout.<br/> * Timeout is dermined by first looking for a {@link RBCLookupTimeoutOption} in the user options. If not specified a * default is used. * * @param options user options * @return rmi lookup timeout */ private static long getRMITimeout(final Option... options) { final RBCLookupTimeoutOption[] timeoutOptions = filter(RBCLookupTimeoutOption.class, options); if (timeoutOptions.length > 0) { return timeoutOptions[0].getTimeout(); } return getTestContainerStartTimeout(options); } /** * Determine the timeout while starting the osgi framework.<br/> * Timeout is dermined by first looking for a {@link TestContainerStartTimeoutOption} in the user options. If not * specified a default is used. * * @param options user options * @return rmi lookup timeout */ private static long getTestContainerStartTimeout(final Option... options) { final TestContainerStartTimeoutOption[] timeoutOptions = filter(TestContainerStartTimeoutOption.class, options); if (timeoutOptions.length > 0) { return timeoutOptions[0].getTimeout(); } return CoreOptions.waitForFrameworkStartup().getTimeout(); } @Override public String toString() { return "PaxRunnerTestContainer{}"; } /** * Creates an RMI registry on the first free port found. * * @return RMI registry */ protected int createRegistry() { for (int port = Registry.REGISTRY_PORT; port <= Registry.REGISTRY_PORT + AMOUNT_OF_PORTS_TO_CHECK; port++) { try { m_registry = LocateRegistry.createRegistry(port); LOG.info("RMI registry started on port [" + port + "]"); return port; } catch (Exception e) { // ignore and try next port number } } throw new RuntimeException("No free port in range " + Registry.REGISTRY_PORT + ":" + Registry.REGISTRY_PORT + AMOUNT_OF_PORTS_TO_CHECK); } }