org.ops4j.pax.exam.karaf.container.internal.KarafTestContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.exam.karaf.container.internal.KarafTestContainer.java

Source

/*
 * 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.ops4j.pax.exam.karaf.container.internal;

import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFileExtend;
import static org.ops4j.pax.exam.rbc.Constants.RMI_HOST_PROPERTY;
import static org.ops4j.pax.exam.rbc.Constants.RMI_NAME_PROPERTY;
import static org.ops4j.pax.exam.rbc.Constants.RMI_PORT_PROPERTY;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
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.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.ops4j.net.FreePort;
import org.ops4j.pax.exam.ExamSystem;
import org.ops4j.pax.exam.Info;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.RelativeTimeout;
import org.ops4j.pax.exam.TestAddress;
import org.ops4j.pax.exam.TestContainer;
import org.ops4j.pax.exam.TestContainerException;
import org.ops4j.pax.exam.container.remote.RBCRemoteTarget;
import org.ops4j.pax.exam.karaf.container.internal.adaptions.KarafManipulator;
import org.ops4j.pax.exam.karaf.container.internal.adaptions.KarafManipulatorFactory;
import org.ops4j.pax.exam.karaf.container.internal.runner.Runner;
import org.ops4j.pax.exam.karaf.options.DoNotModifyLogOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionBaseConfigurationOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionConfigurationConsoleOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionConfigurationFileExtendOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionConfigurationFileOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionConfigurationFilePutOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionConfigurationFileReplacementOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionConfigurationSecurityOption;
import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
import org.ops4j.pax.exam.karaf.options.KarafExamSystemConfigurationOption;
import org.ops4j.pax.exam.karaf.options.KarafFeaturesOption;
import org.ops4j.pax.exam.karaf.options.KeepRuntimeFolderOption;
import org.ops4j.pax.exam.karaf.options.LogLevelOption;
import org.ops4j.pax.exam.karaf.options.configs.CustomProperties;
import org.ops4j.pax.exam.karaf.options.configs.FeaturesCfg;
import org.ops4j.pax.exam.options.BootDelegationOption;
import org.ops4j.pax.exam.options.MavenArtifactUrlReference;
import org.ops4j.pax.exam.options.PropagateSystemPropertyOption;
import org.ops4j.pax.exam.options.ServerModeOption;
import org.ops4j.pax.exam.options.SystemPackageOption;
import org.ops4j.pax.exam.options.SystemPropertyOption;
import org.ops4j.pax.exam.options.extra.VMOption;
import org.ops4j.pax.exam.rbc.client.RemoteBundleContextClient;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class KarafTestContainer implements TestContainer {

    private static final Logger LOGGER = LoggerFactory.getLogger(KarafTestContainer.class);

    private static final String KARAF_TEST_CONTAINER = "KarafTestContainer.start";
    private static final String EXAM_INJECT_PROPERTY = "pax.exam.inject";
    private static final MavenArtifactUrlReference EXAM_REPO_URL = maven().groupId("org.ops4j.pax.exam")
            .artifactId("pax-exam-features").version(Info.getPaxExamVersion()).type("xml");

    private final Runner runner;
    private final ExamSystem system;
    private KarafDistributionBaseConfigurationOption framework;
    @SuppressWarnings("unused")
    private KarafManipulator versionAdaptions;
    private boolean started;
    private RBCRemoteTarget target;

    private File targetFolder;

    private Registry rgstry;

    public KarafTestContainer(ExamSystem system, KarafDistributionBaseConfigurationOption framework,
            Runner runner) {
        this.framework = framework;
        this.system = system;
        this.runner = runner;
    }

    @Override
    public synchronized TestContainer start() {
        try {
            String name = system.createID(KARAF_TEST_CONTAINER);

            Option invokerConfiguration = getInvokerConfiguration();

            //registry.selectGracefully();
            FreePort freePort = new FreePort(21000, 21099);
            int port = freePort.getPort();
            LOGGER.debug("using RMI registry at port {}", port);
            rgstry = LocateRegistry.createRegistry(port);

            String host = InetAddress.getLocalHost().getHostName();

            ExamSystem subsystem = system.fork(options(systemProperty(RMI_HOST_PROPERTY).value(host),
                    systemProperty(RMI_PORT_PROPERTY).value(Integer.toString(port)),
                    systemProperty(RMI_NAME_PROPERTY).value(name), invokerConfiguration,
                    systemProperty(EXAM_INJECT_PROPERTY).value("true"),
                    editConfigurationFileExtend("etc/system.properties", "jline.shutdownhook", "true")));
            target = new RBCRemoteTarget(name, port, subsystem.getTimeout());

            System.setProperty("java.protocol.handler.pkgs", "org.ops4j.pax.url");

            URL sourceDistribution = new URL(framework.getFrameworkURL());
            targetFolder = retrieveFinalTargetFolder(subsystem);
            ArchiveExtractor.extract(sourceDistribution, targetFolder);

            File karafBase = searchKarafBase(targetFolder);
            File karafHome = karafBase;

            versionAdaptions = createVersionAdapter(karafBase);
            DependenciesDeployer deployer = new DependenciesDeployer(subsystem, karafBase, karafHome);
            deployer.copyBootClasspathLibraries();

            updateLogProperties(karafHome, subsystem);
            setupSystemProperties(karafHome, subsystem);

            List<KarafDistributionConfigurationFileOption> options = new ArrayList<KarafDistributionConfigurationFileOption>(
                    Arrays.asList(subsystem.getOptions(KarafDistributionConfigurationFileOption.class)));
            options.addAll(fromFeatureOptions(subsystem.getOptions(KarafFeaturesOption.class)));
            options.addAll(fromFeatureOptions(KarafDistributionOption.features(EXAM_REPO_URL, "exam")));

            if (framework.isUseDeployFolder()) {
                deployer.copyReferencedArtifactsToDeployFolder();
            } else {
                options.addAll(fromFeatureOptions(deployer.getDependenciesFeature()));
            }

            options.addAll(configureBootDelegation(subsystem));
            options.addAll(configureSystemPackages(subsystem));
            updateUserSetProperties(karafHome, options);

            startKaraf(subsystem, karafBase, karafHome);
            started = true;
        } catch (IOException e) {
            throw new RuntimeException("Problem starting container", e);
        }
        return this;
    }

    private KarafManipulator createVersionAdapter(File karafBase) {
        File karafEtc = new File(karafBase, "etc");
        File distributionInfo = new File(karafEtc, "distribution.info");

        framework = new InternalKarafDistributionConfigurationOption(framework, distributionInfo);
        return KarafManipulatorFactory.createManipulator(framework.getKarafVersion());
    }

    private void startKaraf(ExamSystem subsystem, File karafBase, File karafHome) {
        long startedAt = System.currentTimeMillis();
        File karafBin = new File(karafBase, "bin");
        File karafEtc = new File(karafBase, "etc");
        String karafData = karafHome + "/data";
        String[] classPath = buildKarafClasspath(karafHome);
        makeScriptsInBinExec(karafBin);
        File javaHome = new File(System.getProperty("java.home"));
        String main = "org.apache.karaf.main.Main";
        String options = "";
        String[] environment = new String[] {};
        ArrayList<String> javaOpts = new ArrayList<String>();
        appendVmSettingsFromSystem(javaOpts, subsystem);
        String[] javaEndorsedDirs = new String[] { javaHome + "/jre/lib/endorsed", javaHome + "/lib/endorsed",
                karafHome + "/lib/endorsed" };
        String[] javaExtDirs = new String[] { javaHome + "/jre/lib/ext", javaHome + "/lib/ext",
                javaHome + "/lib/ext" };
        List<String> opts = Arrays.asList("-Dkaraf.startLocalConsole=" + shouldLocalConsoleBeStarted(subsystem),
                "-Dkaraf.startRemoteShell=" + shouldRemoteShellBeStarted(subsystem));
        boolean enableMBeanServerBuilder = shouldMBeanServerBuilderBeEnabled(subsystem);
        String[] karafOpts = new String[] {};
        runner.exec(environment, karafBase, javaHome.toString(), javaOpts.toArray(new String[] {}),
                javaEndorsedDirs, javaExtDirs, karafHome.toString(), karafData, karafEtc.toString(), karafOpts,
                opts.toArray(new String[] {}), classPath, main, options, enableMBeanServerBuilder);

        LOGGER.debug("Test Container started in " + (System.currentTimeMillis() - startedAt) + " millis");
        LOGGER.info("Wait for test container to finish its initialization " + subsystem.getTimeout());

        if (subsystem.getOptions(ServerModeOption.class).length == 0) {
            waitForState(org.ops4j.pax.exam.karaf.container.internal.Constants.SYSTEM_BUNDLE, Bundle.ACTIVE,
                    subsystem.getTimeout());
        } else {
            LOGGER.info(
                    "System runs in Server Mode. Which means, no Test facility bundles available on target system.");
        }
    }

    private boolean shouldDeleteRuntime() {
        boolean deleteRuntime = true;
        KeepRuntimeFolderOption[] keepRuntimeFolder = system.getOptions(KeepRuntimeFolderOption.class);
        if (keepRuntimeFolder != null && keepRuntimeFolder.length != 0) {
            deleteRuntime = false;
        }
        return deleteRuntime;
    }

    private Option getInvokerConfiguration() {
        KarafExamSystemConfigurationOption[] internalConfigurationOptions = system
                .getOptions(KarafExamSystemConfigurationOption.class);
        Option invokerConfiguration = systemProperty("pax.exam.invoker").value("junit");
        if (internalConfigurationOptions != null && internalConfigurationOptions.length != 0) {
            invokerConfiguration = systemProperty("pax.exam.invoker")
                    .value(internalConfigurationOptions[0].getInvoker());
        }
        return invokerConfiguration;
    }

    private String shouldRemoteShellBeStarted(ExamSystem subsystem) {
        KarafDistributionConfigurationConsoleOption[] consoleOptions = subsystem
                .getOptions(KarafDistributionConfigurationConsoleOption.class);
        if (consoleOptions == null) {
            return "true";
        }
        for (KarafDistributionConfigurationConsoleOption consoleOption : consoleOptions) {
            if (consoleOption.getStartRemoteShell() != null) {
                return consoleOption.getStartRemoteShell() ? "true" : "false";
            }
        }
        return "true";
    }

    private String shouldLocalConsoleBeStarted(ExamSystem subsystem) {
        KarafDistributionConfigurationConsoleOption[] consoleOptions = subsystem
                .getOptions(KarafDistributionConfigurationConsoleOption.class);
        if (consoleOptions == null) {
            return "true";
        }
        for (KarafDistributionConfigurationConsoleOption consoleOption : consoleOptions) {
            if (consoleOption.getStartLocalConsole() != null) {
                return consoleOption.getStartLocalConsole() ? "true" : "false";
            }
        }
        return "true";
    }

    private boolean shouldMBeanServerBuilderBeEnabled(ExamSystem subsystem) {
        KarafDistributionConfigurationSecurityOption[] securityOptions = subsystem
                .getOptions(KarafDistributionConfigurationSecurityOption.class);
        if (securityOptions == null) {
            return false;
        }
        for (KarafDistributionConfigurationSecurityOption securityOption : securityOptions) {
            if (securityOption.getEnableKarafMBeanServerBuilder() != null) {
                return securityOption.getEnableKarafMBeanServerBuilder();
            }
        }
        return false;
    }

    private void makeScriptsInBinExec(File karafBin) {
        if (!karafBin.exists()) {
            return;
        }
        File[] files = karafBin.listFiles();
        for (File file : files) {
            file.setExecutable(true);
        }
    }

    private File retrieveFinalTargetFolder(ExamSystem subsystem) {
        if (framework.getUnpackDirectory() == null) {
            return subsystem.getConfigFolder();
        } else {
            File targetDir = new File(framework.getUnpackDirectory() + "/" + UUID.randomUUID().toString());
            targetDir = transformToAbsolutePath(targetDir);
            targetDir.mkdirs();
            return targetDir;
        }
    }

    private File transformToAbsolutePath(File file) {
        return new File(file.getAbsolutePath());
    }

    private void appendVmSettingsFromSystem(ArrayList<String> opts, ExamSystem subsystem) {
        VMOption[] options = subsystem.getOptions(VMOption.class);
        for (VMOption option : options) {
            opts.add(option.getOption());
        }
    }

    private void updateUserSetProperties(File karafHome, List<KarafDistributionConfigurationFileOption> options)
            throws IOException {
        HashMap<String, HashMap<String, List<KarafDistributionConfigurationFileOption>>> optionMap = new HashMap<String, HashMap<String, List<KarafDistributionConfigurationFileOption>>>();
        for (KarafDistributionConfigurationFileOption option : options) {
            if (!optionMap.containsKey(option.getConfigurationFilePath())) {
                optionMap.put(option.getConfigurationFilePath(),
                        new HashMap<String, List<KarafDistributionConfigurationFileOption>>());
            }
            HashMap<String, List<KarafDistributionConfigurationFileOption>> optionEntries = optionMap
                    .get(option.getConfigurationFilePath());
            if (!optionEntries.containsKey(option.getKey())) {
                optionEntries.put(option.getKey(), new ArrayList<KarafDistributionConfigurationFileOption>());
            } else {
                // if special file warn, replace and continue
                if (!option.getConfigurationFilePath().equals(FeaturesCfg.FILE_PATH)) {
                    LOGGER.warn("you're trying to add an additional value to a config file; you're current "
                            + "value will be replaced.");
                    optionEntries.put(option.getKey(), new ArrayList<KarafDistributionConfigurationFileOption>());
                }
            }
            optionEntries.get(option.getKey()).add(option);
        }
        Set<String> configFiles = optionMap.keySet();
        for (String configFile : configFiles) {

            KarafPropertiesFile karafPropertiesFile = new KarafPropertiesFile(karafHome, configFile);
            karafPropertiesFile.load();
            Collection<List<KarafDistributionConfigurationFileOption>> optionsToApply = optionMap.get(configFile)
                    .values();
            boolean store = true;
            for (List<KarafDistributionConfigurationFileOption> optionListToApply : optionsToApply) {
                for (KarafDistributionConfigurationFileOption optionToApply : optionListToApply) {
                    if (optionToApply instanceof KarafDistributionConfigurationFilePutOption) {
                        karafPropertiesFile.put(optionToApply.getKey(), optionToApply.getValue());
                    } else if (optionToApply instanceof KarafDistributionConfigurationFileReplacementOption) {
                        karafPropertiesFile.replace(
                                ((KarafDistributionConfigurationFileReplacementOption) optionToApply).getSource());
                        store = false;
                        break;
                    } else {
                        karafPropertiesFile.extend(optionToApply.getKey(), optionToApply.getValue());
                    }
                }
                if (!store) {
                    break;
                }
            }
            if (store) {
                karafPropertiesFile.store();
            }
        }
    }

    private Collection<? extends KarafDistributionConfigurationFileOption> configureSystemPackages(
            ExamSystem subsystem) {
        String systemPackages = JoinUtil.join(subsystem.getOptions(SystemPackageOption.class));
        if (systemPackages.length() == 0) {
            return Arrays.asList();
        }
        return Arrays.asList(new KarafDistributionConfigurationFileExtendOption(
                CustomProperties.SYSTEM_PACKAGES_EXTRA, systemPackages));
    }

    private Collection<? extends KarafDistributionConfigurationFileOption> configureBootDelegation(
            ExamSystem subsystem) {
        BootDelegationOption[] bootDelegationOptions = subsystem.getOptions(BootDelegationOption.class);
        return Arrays.asList(new KarafDistributionConfigurationFileExtendOption(CustomProperties.BOOTDELEGATION,
                JoinUtil.join(bootDelegationOptions)));
    }

    private Collection<? extends KarafDistributionConfigurationFileOption> fromFeatureOptions(
            KarafFeaturesOption... featuresOptions) {
        ArrayList<KarafDistributionConfigurationFileOption> retVal = new ArrayList<KarafDistributionConfigurationFileOption>();

        for (KarafFeaturesOption featuresOption : featuresOptions) {
            retVal.add(new KarafDistributionConfigurationFileExtendOption(FeaturesCfg.REPOSITORIES,
                    featuresOption.getURL()));
            retVal.add(new KarafDistributionConfigurationFileExtendOption(FeaturesCfg.BOOT,
                    JoinUtil.join(featuresOption.getFeatures())));
        }
        return retVal;
    }

    private void setupSystemProperties(File karafHome, ExamSystem _system) throws IOException {
        File customPropertiesFile = new File(karafHome + "/etc/system.properties");
        SystemPropertyOption[] customProps = _system.getOptions(SystemPropertyOption.class);
        Properties karafPropertyFile = new Properties();
        karafPropertyFile.load(new FileInputStream(customPropertiesFile));
        for (SystemPropertyOption systemPropertyOption : customProps) {
            karafPropertyFile.put(systemPropertyOption.getKey(), systemPropertyOption.getValue());
        }
        for (PropagateSystemPropertyOption option : system.getOptions(PropagateSystemPropertyOption.class)) {
            String key = option.getKey();
            String value = System.getProperty(key);
            if (value != null) {
                karafPropertyFile.put(key, value);
            }
        }

        karafPropertyFile.store(new FileOutputStream(customPropertiesFile), "updated by pax-exam");
    }

    private void updateLogProperties(File karafHome, ExamSystem _system) throws IOException {
        DoNotModifyLogOption[] modifyLog = _system.getOptions(DoNotModifyLogOption.class);
        if (modifyLog != null && modifyLog.length != 0) {
            LOGGER.info("Log file should not be modified by the test framework");
            return;
        }
        String realLogLevel = retrieveRealLogLevel(_system);
        File customPropertiesFile = new File(karafHome + "/etc/org.ops4j.pax.logging.cfg");
        Properties karafPropertyFile = new Properties();
        karafPropertyFile.load(new FileInputStream(customPropertiesFile));
        karafPropertyFile.put("log4j.rootLogger", realLogLevel + ", out, stdout, osgi:*");
        karafPropertyFile.store(new FileOutputStream(customPropertiesFile), "updated by pax-exam");
    }

    private String retrieveRealLogLevel(ExamSystem _system) {
        LogLevelOption[] logLevelOptions = _system.getOptions(LogLevelOption.class);
        return logLevelOptions != null && logLevelOptions.length != 0 ? logLevelOptions[0].getLogLevel().toString()
                : "WARN";
    }

    private String[] buildKarafClasspath(File karafHome) {
        List<String> cp = new ArrayList<String>();
        File[] jars = new File(karafHome + "/lib").listFiles((FileFilter) new WildcardFileFilter("*.jar"));
        for (File jar : jars) {
            cp.add(jar.toString());
        }
        // do the same for lib/boot
        File[] bootJars = new File(karafHome + "/lib/boot").listFiles((FileFilter) new WildcardFileFilter("*.jar"));
        if (bootJars != null) {
            for (File jar : bootJars) {
                cp.add(jar.toString());
            }
        }
        // do the same for lib/ext
        File[] extJars = new File(karafHome + "/lib/ext").listFiles((FileFilter) new WildcardFileFilter("*.jar"));
        for (File jar : extJars) {
            cp.add(jar.toString());
        }
        return cp.toArray(new String[] {});
    }

    /**
     * Since we might get quite deep use a simple breath first search algorithm
     */
    private File searchKarafBase(File _targetFolder) {
        Queue<File> searchNext = new LinkedList<File>();
        searchNext.add(_targetFolder);
        while (!searchNext.isEmpty()) {
            File head = searchNext.poll();
            if (!head.isDirectory()) {
                continue;
            }
            boolean isSystem = false;
            boolean etc = false;
            for (File file : head.listFiles()) {
                if (file.isDirectory() && file.getName().equals("system")) {
                    isSystem = true;
                }
                if (file.isDirectory() && file.getName().equals("etc")) {
                    etc = true;
                }
            }
            if (isSystem && etc) {
                return head;
            }
            searchNext.addAll(Arrays.asList(head.listFiles()));
        }
        throw new IllegalStateException("No karaf base dir found in extracted distribution.");
    }

    @Override
    public synchronized TestContainer stop() {
        LOGGER.debug("Shutting down the test container (Pax Runner)");
        try {
            if (started) {
                target.stop();
                RemoteBundleContextClient remoteBundleContextClient = target.getClientRBC();
                if (remoteBundleContextClient != null) {
                    remoteBundleContextClient.stop();

                }
                if (runner != null) {
                    runner.shutdown();
                }
                try {
                    UnicastRemoteObject.unexportObject(rgstry, true);
                    /*
                     * NOTE: javaRunner.waitForExit() works for Equinox and Felix, but not for Knopflerfish,
                     * need to investigate why. OTOH, it may be better to kill the process as we're doing
                     * now, just to be on the safe side.
                     */
                } catch (NoSuchObjectException exc) {
                    throw new TestContainerException(exc);
                }

            } else {
                throw new RuntimeException("Container never came up");
            }
        } finally {
            started = false;
            target = null;
            if (shouldDeleteRuntime()) {
                system.clear();
                try {
                    FileUtils.forceDelete(targetFolder);
                } catch (IOException e) {
                    forceCleanup();
                }
            }
        }
        return this;
    }

    private void forceCleanup() {
        LOGGER.info("Can't remove runtime system; shedule it for exit of the jvm.");
        try {
            FileUtils.forceDeleteOnExit(targetFolder);
        } catch (IOException e1) {
            LOGGER.error("Well, this should simply not happen...");
        }
    }

    private void waitForState(final long bundleId, final int state, final RelativeTimeout timeout) {
        target.getClientRBC().waitForState(bundleId, state, timeout);
    }

    @Override
    public synchronized void call(TestAddress address) {
        target.call(address);
    }

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

    @Override
    public synchronized long install(String location, InputStream stream) {
        return target.install(location, stream);
    }

    @Override
    public String toString() {
        return "KarafTestContainer{" + framework.getFrameworkURL() + "}";
    }

    @Override
    public long installProbe(InputStream stream) {
        return target.installProbe(stream);
    }

    @Override
    public void uninstallProbe() {
        target.uninstallProbe();
    }

}