Package org.ops4j.pax.exam.nat.internal

Source Code of org.ops4j.pax.exam.nat.internal.NativeTestContainer

/*
* 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.nat.internal;

import static org.ops4j.pax.exam.Constants.EXAM_FAIL_ON_UNRESOLVED_KEY;
import static org.ops4j.pax.exam.Constants.START_LEVEL_TEST_BUNDLE;
import static org.ops4j.pax.exam.CoreOptions.systemPackage;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.osgi.framework.Constants.FRAMEWORK_BOOTDELEGATION;
import static org.osgi.framework.Constants.FRAMEWORK_STORAGE;
import static org.osgi.framework.Constants.FRAMEWORK_STORAGE_CLEAN;
import static org.osgi.framework.Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT;
import static org.osgi.framework.Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.ops4j.pax.exam.ConfigurationManager;
import org.ops4j.pax.exam.Constants;
import org.ops4j.pax.exam.ExamSystem;
import org.ops4j.pax.exam.Info;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.ProbeInvoker;
import org.ops4j.pax.exam.TestAddress;
import org.ops4j.pax.exam.TestContainer;
import org.ops4j.pax.exam.TestContainerException;
import org.ops4j.pax.exam.options.BootDelegationOption;
import org.ops4j.pax.exam.options.FrameworkPropertyOption;
import org.ops4j.pax.exam.options.FrameworkStartLevelOption;
import org.ops4j.pax.exam.options.ProvisionOption;
import org.ops4j.pax.exam.options.SystemPackageOption;
import org.ops4j.pax.exam.options.SystemPropertyOption;
import org.ops4j.pax.exam.options.ValueOption;
import org.ops4j.pax.exam.options.extra.CleanCachesOption;
import org.ops4j.pax.exam.options.extra.RepositoryOption;
import org.ops4j.pax.swissbox.tracker.ServiceLookup;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The Native Test Container starts an OSGi framework using {@link FrameworkFactory} and provisions
* the bundles configured in the Exam system.
* <p>
* When the framework has reached the configured start level, the container checks that all bundles
* are resolved and throws an exception otherwise.
*
* @author Toni Menzel
* @author Harald Wellmann
* @since Jan 7, 2010
*/
public class NativeTestContainer implements TestContainer {

    private static final Logger LOG = LoggerFactory.getLogger(NativeTestContainer.class);
    private static final String PROBE_SIGNATURE_KEY = "Probe-Signature";
    private final Stack<Long> installed = new Stack<Long>();
    private Long probeId;

    private final FrameworkFactory frameworkFactory;
    private ExamSystem system;

    private volatile Framework framework;

    public NativeTestContainer(ExamSystem system, FrameworkFactory frameworkFactory)
        throws IOException {
        this.frameworkFactory = frameworkFactory;
        this.system = system;
    }

    @Override
    public synchronized void call(TestAddress address) {
        Map<String, String> props = new HashMap<String, String>();
        props.put(PROBE_SIGNATURE_KEY, address.root().identifier());
        BundleContext bundleContext = framework.getBundleContext();
        ProbeInvoker probeInvokerService = ServiceLookup.getService(bundleContext,
            ProbeInvoker.class, props);
        probeInvokerService.call(address.arguments());
    }

    @Override
    public synchronized long install(String location, InputStream stream) {
        try {
            Bundle b = framework.getBundleContext().installBundle(location, stream);
            installed.push(b.getBundleId());
            LOG.debug("Installed bundle " + b.getSymbolicName() + " as Bundle ID "
                + b.getBundleId());
            setBundleStartLevel(b.getBundleId(), Constants.START_LEVEL_TEST_BUNDLE);
            b.start();
            return b.getBundleId();
        }
        catch (BundleException e) {
            e.printStackTrace();
        }
        return -1;
    }

    @Override
    public synchronized long install(InputStream stream) {
        return install("local", stream);
    }

    public synchronized void cleanup() {
        while ((!installed.isEmpty())) {
            try {
                Long id = installed.pop();
                Bundle bundle = framework.getBundleContext().getBundle(id);
                bundle.uninstall();
                LOG.debug("Uninstalled bundle " + id);
            }
            catch (BundleException e) {
                // Sometimes bundles go mad when install + uninstall happens too
                // fast.
            }
        }
    }

    public Bundle getSystemBundle() {
        return framework;
    }
   
    public void setBundleStartLevel(long bundleId, int startLevel) {
        Bundle bundle = framework.getBundleContext().getBundle(bundleId);
        BundleStartLevel sl = bundle.adapt(BundleStartLevel.class);
        sl.setStartLevel(startLevel);
    }

    @Override
    public TestContainer start() {
        try {
            system = system.fork(new Option[] {
                systemPackage("org.ops4j.pax.exam;version="
                    + skipSnapshotFlag(Info.getPaxExamVersion())),
                systemPackage("org.ops4j.pax.exam.options;version="
                    + skipSnapshotFlag(Info.getPaxExamVersion())),
                systemPackage("org.ops4j.pax.exam.util;version="
                    + skipSnapshotFlag(Info.getPaxExamVersion())),
                systemProperty("java.protocol.handler.pkgs").value("org.ops4j.pax.url") });
            Map<String, String> p = createFrameworkProperties();
            if (LOG.isDebugEnabled()) {
                logFrameworkProperties(p);
                logSystemProperties();
            }
            framework = frameworkFactory.newFramework(p);
            framework.init();
            installAndStartBundles(framework.getBundleContext());
        }
        catch (BundleException e) {
            throw new TestContainerException("Problem starting test container.", e);
        }
        catch (IOException e) {
            throw new TestContainerException("Problem starting test container.", e);
        }
        return this;
    }

    private void logFrameworkProperties(Map<String, String> p) {
        LOG.debug("==== Framework properties:");
        for (String key : p.keySet()) {
            LOG.debug("{} = {}", key, p.get(key));
        }
    }

    private void logSystemProperties() {
        LOG.debug("==== System properties:");
        SortedMap<Object, Object> map = new TreeMap<Object, Object>(System.getProperties());
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            LOG.debug("{} = {}", entry.getKey(), entry.getValue());
        }
    }

    @Override
    public TestContainer stop() {
        if (framework != null) {
            try {
                cleanup();
                stopOrAbort();
                framework = null;
                system.clear();
            }
            catch (BundleException e) {
                LOG.warn("Problem during stopping fw.", e);
            }
            catch (InterruptedException e) {
                LOG.warn("InterruptedException during stopping fw.", e);
            }
        }
        else {
            LOG.warn("Framework does not exist. Called start() before ? ");
        }
        return this;
    }

    private void stopOrAbort() throws BundleException, InterruptedException {
        framework.stop();
        long timeout = system.getTimeout().getValue();
        Thread stopper = new Stopper(timeout);
        stopper.start();
        stopper.join(timeout + 500);

        // If the framework is not stopped, then we're in trouble anyway, so we do not worry
        // about stopping the worker thread.

        if (framework.getState() != Framework.RESOLVED) {
            String message = "Framework has not yet stopped after " + timeout
                + " ms. waitForStop did not return";
            throw new TestContainerException(message);
        }
    }

    private Map<String, String> createFrameworkProperties() throws IOException {
        final Map<String, String> p = new HashMap<String, String>();
        CleanCachesOption cleanCaches = system.getSingleOption(CleanCachesOption.class);
        if (cleanCaches != null && cleanCaches.getValue() != null && cleanCaches.getValue()) {
            p.put(FRAMEWORK_STORAGE_CLEAN, FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
        }

        p.put(FRAMEWORK_STORAGE, system.getTempFolder().getAbsolutePath());
        p.put(FRAMEWORK_SYSTEMPACKAGES_EXTRA,
            buildString(system.getOptions(SystemPackageOption.class)));
        p.put(FRAMEWORK_BOOTDELEGATION, buildString(system.getOptions(BootDelegationOption.class)));

        for (FrameworkPropertyOption option : system.getOptions(FrameworkPropertyOption.class)) {
            p.put(option.getKey(), (String) option.getValue());
        }

        for (SystemPropertyOption option : system.getOptions(SystemPropertyOption.class)) {
            System.setProperty(option.getKey(), option.getValue());
        }

        String repositories = buildString(system.getOptions(RepositoryOption.class));
        if (!repositories.isEmpty()) {
            System.setProperty("org.ops4j.pax.url.mvn.repositories", repositories);
        }
        return p;
    }

    private String buildString(ValueOption<?>[] options) {
        return buildString(new String[0], options, new String[0]);
    }

    @SuppressWarnings("unused")
    private String buildString(String[] prepend, ValueOption<?>[] options) {
        return buildString(prepend, options, new String[0]);
    }

    @SuppressWarnings("unused")
    private String buildString(ValueOption<?>[] options, String[] append) {
        return buildString(new String[0], options, append);
    }

    private String buildString(String[] prepend, ValueOption<?>[] options, String[] append) {
        StringBuilder builder = new StringBuilder();
        for (String a : prepend) {
            builder.append(a);
            builder.append(",");
        }
        for (ValueOption<?> option : options) {
            builder.append(option.getValue());
            builder.append(",");
        }
        for (String a : append) {
            builder.append(a);
            builder.append(",");
        }
        if (builder.length() > 0) {
            return builder.substring(0, builder.length() - 1);
        }
        else {
            return "";
        }
    }

    private void installAndStartBundles(BundleContext context) throws BundleException {
        List<Bundle> bundles = new ArrayList<Bundle>();
        for (ProvisionOption<?> bundle : system.getOptions(ProvisionOption.class)) {
            Bundle b = context.installBundle(bundle.getURL());
            bundles.add(b);
            int startLevel = getStartLevel(bundle);
            BundleStartLevel sl = b.adapt(BundleStartLevel.class);
            sl.setStartLevel(startLevel);
            if (bundle.shouldStart()) {
                try {
                    b.start();
                }
                catch (BundleException e) {
                    throw new BundleException("Error starting bundle " + b.getSymbolicName() + ". " + e.getMessage(), e);
                }
                LOG.debug("+ Install (start@{}) {}", startLevel, bundle);
            }
            else {
                LOG.debug("+ Install (no start) {}", bundle);
            }
        }
        // All bundles are installed, we can now start the framework...
        framework.start();
        FrameworkStartLevel fsl = framework.adapt(FrameworkStartLevel.class);
        setFrameworkStartLevel(context, fsl);
        verifyThatBundlesAreResolved(bundles);
    }

    private void setFrameworkStartLevel(BundleContext context, final FrameworkStartLevel sl) {
        FrameworkStartLevelOption startLevelOption = system
            .getSingleOption(FrameworkStartLevelOption.class);
        final int startLevel = startLevelOption == null ? START_LEVEL_TEST_BUNDLE
            : startLevelOption.getStartLevel();
        LOG.debug("Jump to startlevel: " + startLevel);
        final CountDownLatch latch = new CountDownLatch(1);
        context.addFrameworkListener(new FrameworkListener() {

            @Override
            public void frameworkEvent(FrameworkEvent frameworkEvent) {
                if (frameworkEvent.getType() == FrameworkEvent.STARTLEVEL_CHANGED) {
                    if (sl.getStartLevel() == startLevel) {
                        latch.countDown();
                    }
                }
            }
        });
        sl.setStartLevel(startLevel);

        // Check the current start level before starting to wait.
        if (sl.getStartLevel() == startLevel) {
            LOG.debug("requested start level reached");
            return;
        }
        else {
            LOG.debug("start level {} requested, current start level is {}", startLevel,
                sl.getStartLevel());
        }

        try {
            long timeout = system.getTimeout().getValue();
            if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
                // Before throwing an exception, do a last check
                if (startLevel != sl.getStartLevel()) {
                    String msg = String.format("start level %d has not been reached within %d ms",
                        startLevel, timeout);
                    throw new TestContainerException(msg);
                }
                else {
                    // We reached the requested start level.
                    LOG.debug("requested start level reached");
                }

            }
        }
        catch (InterruptedException e) {
            throw new TestContainerException(e);
        }
    }

    private void verifyThatBundlesAreResolved(List<Bundle> bundles) {
        boolean hasUnresolvedBundles = false;
        for (Bundle bundle : bundles) {
            if (bundle.getState() == Bundle.INSTALLED) {
                LOG.error("Bundle [{}] is not resolved", bundle);
                hasUnresolvedBundles = true;
            }
        }
        ConfigurationManager cm = new ConfigurationManager();
        boolean failOnUnresolved = Boolean.parseBoolean(cm.getProperty(EXAM_FAIL_ON_UNRESOLVED_KEY,
            "false"));
        if (hasUnresolvedBundles && failOnUnresolved) {
            throw new TestContainerException(
                "There are unresolved bundles. See previous ERROR log messages for details.");
        }
    }

    private int getStartLevel(ProvisionOption<?> bundle) {
        Integer start = bundle.getStartLevel();
        if (start == null) {
            start = Constants.START_LEVEL_DEFAULT_PROVISION;
        }
        return start;
    }

    private String skipSnapshotFlag(String version) {
        int idx = version.indexOf("-");
        if (idx >= 0) {
            return version.substring(0, idx);
        }
        else {
            return version;
        }
    }

    @Override
    public String toString() {
        return "Native:" + frameworkFactory.getClass().getSimpleName();
    }

    /**
     * Worker thread for shutting down the framework. We'd expect Framework.waitForStop(timeout) to
     * return after the given timeout, but this is not the case with Equinox (tested on 3.6.2 and
     * 3.7.0), so we use this worker thread to avoid blocking the main thread.
     *
     * @author Harald Wellmann
     */
    private class Stopper extends Thread {

        private final long timeout;

        private Stopper(long timeout) {
            this.timeout = timeout;
        }

        @Override
        public void run() {
            try {
                FrameworkEvent frameworkEvent = framework.waitForStop(timeout);
                if (frameworkEvent.getType() != FrameworkEvent.STOPPED) {
                    LOG.error("Framework has not yet stopped after {} ms. "
                        + "waitForStop returned: {}", timeout, frameworkEvent);
                }
            }
            catch (InterruptedException exc) {
                LOG.error("Stopper thread was interrupted");
            }
        }
    }

    @Override
    public synchronized long installProbe(InputStream stream) {
        probeId = install(stream);
        installed.pop();
        return probeId;
    }

    @Override
    public synchronized void uninstallProbe() {
        Bundle bundle = framework.getBundleContext().getBundle(probeId);
        try {
            bundle.uninstall();
            probeId = null;
        }
        catch (BundleException exc) {
            throw new TestContainerException(exc);
        }
    }
}
TOP

Related Classes of org.ops4j.pax.exam.nat.internal.NativeTestContainer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.