org.ops4j.pax.exam.container.def.internal.PaxRunnerTestContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.exam.container.def.internal.PaxRunnerTestContainer.java

Source

/*
 * 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);
    }

}