org.wso2.carbon.container.CarbonTestContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.container.CarbonTestContainer.java

Source

/*
 *  Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  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.wso2.carbon.container;

import org.apache.commons.io.FileUtils;
import org.ops4j.net.FreePort;
import org.ops4j.pax.exam.ExamSystem;
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.options.SystemPropertyOption;
import org.ops4j.pax.exam.options.ValueOption;
import org.ops4j.pax.exam.options.extra.RepositoryOption;
import org.ops4j.pax.exam.rbc.client.RemoteBundleContextClient;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.container.options.CarbonDistributionBaseOption;
import org.wso2.carbon.container.options.CopyFileOption;
import org.wso2.carbon.container.options.CopyOSGiLibBundleOption;
import org.wso2.carbon.container.options.DebugOption;
import org.wso2.carbon.container.options.KeepDirectoryOption;
import org.wso2.carbon.container.runner.CarbonRunner;
import org.wso2.carbon.container.runner.Runner;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
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.List;
import java.util.UUID;

import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
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;

/**
 * Test Container class to configure the distribution and start the server.
 *
 * @since 5.2.0
 */
public class CarbonTestContainer implements TestContainer {

    private static final Logger logger = LoggerFactory.getLogger(CarbonTestContainer.class);

    private static final String CARBON_TEST_CONTAINER = "CarbonTestContainer";
    private static final String EXAM_INJECT_PROPERTY = "pax.exam.inject";
    private static final String LIB_DIRECTORY = "lib";

    private final Runner runner;
    private final ExamSystem system;
    private CarbonDistributionBaseOption carbonHomeDirectoryOption;
    private RBCRemoteTarget target;
    private Path targetDirectory;
    private Registry registry;
    private boolean started;

    public CarbonTestContainer(ExamSystem system, CarbonDistributionBaseOption carbonHomeDirectoryOption) {
        this.carbonHomeDirectoryOption = carbonHomeDirectoryOption;
        this.system = system;
        this.runner = new CarbonRunner();
    }

    /**
     * Starts the test container.
     *
     * @return this container object for api
     */
    public synchronized TestContainer start() {
        if (carbonHomeDirectoryOption.getDistributionDirectoryPath() == null
                && carbonHomeDirectoryOption.getDistributionMavenURL() == null
                && carbonHomeDirectoryOption.getDistributionZipPath() == null) {
            throw new TestContainerException("Distribution path need to be set.");
        }
        try {
            String name = system.createID(CARBON_TEST_CONTAINER);
            //get a free port to use for rmi
            FreePort freePort = new FreePort(21000, 21099);
            int port = freePort.getPort();
            logger.debug("using RMI registry at port {}" + name, port);
            registry = LocateRegistry.createRegistry(port);
            String host = InetAddress.getLocalHost().getHostName();

            //Setting RMI related properties
            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),
                    systemProperty(EXAM_INJECT_PROPERTY).value("true")));

            target = new RBCRemoteTarget(name, port, subsystem.getTimeout());
            System.setProperty("java.protocol.handler.pkgs", "org.ops4j.pax.url");

            //setup repositories if there are any
            addRepositories();
            targetDirectory = retrieveFinalTargetDirectory();

            if (carbonHomeDirectoryOption.getDistributionMavenURL() != null) {
                URL sourceDistribution = new URL(carbonHomeDirectoryOption.getDistributionMavenURL().getURL());
                ArchiveExtractor.extract(sourceDistribution, targetDirectory.toFile());
            } else if (carbonHomeDirectoryOption.getDistributionZipPath() != null) {
                Path sourceDistribution = carbonHomeDirectoryOption.getDistributionZipPath();
                ArchiveExtractor.extract(sourceDistribution, targetDirectory.toFile());
            } else if (carbonHomeDirectoryOption.getDistributionDirectoryPath() != null) {
                Path sourceDirectory = carbonHomeDirectoryOption.getDistributionDirectoryPath();
                FileUtils.copyDirectory(sourceDirectory.toFile(), targetDirectory.toFile());
            }

            //install bundles if there are any
            copyOSGiLibBundles(targetDirectory);

            //copy files to the distributions if there are any
            copyFiles(targetDirectory);
            Path carbonBin = targetDirectory.resolve("bin");

            //make the files in the bin directory to be executable
            makeFilesInBinExec(carbonBin.toFile());
            List<String> options = new ArrayList<>();
            String[] environment = new String[] {};

            //set system properties as command line arguments
            setupSystemProperties(options, subsystem);

            //Setup debug configurations if available
            DebugOption debugOption = system.getSingleOption(DebugOption.class);
            if (debugOption != null) {
                options.add(debugOption.getDebugConfiguration());
            }
            runner.exec(environment, targetDirectory, options);
            logger.debug("Wait for test container to finish its initialization " + subsystem.getTimeout());

            //wait for the osgi environment to be active
            waitForState(0, Bundle.ACTIVE, subsystem.getTimeout());
            started = true;
        } catch (IOException e) {
            throw new TestContainerException("Problem starting container", e);
        }
        return this;
    }

    /**
     * Set repositories specified in the Repository Option.
     */
    private void addRepositories() {
        RepositoryOption[] repositories = system.getOptions(RepositoryOption.class);
        if (repositories.length != 0) {
            System.setProperty("org.ops4j.pax.url.mvn.repositories", buildString(repositories));
        }
    }

    /**
     * Copy dependencies specified as carbon OSGi-lib option in system to the LIB_DIRECTORY.
     *
     * @param carbonHome carbon home dir
     */
    private void copyOSGiLibBundles(Path carbonHome) {
        Path targetDirectory = carbonHome.resolve(LIB_DIRECTORY);

        Arrays.asList(system.getOptions(CopyOSGiLibBundleOption.class)).forEach(option -> {
            try {
                copyReferencedArtifactsToDeployDirectory(option.getMavenArtifactUrlReference().getURL(),
                        targetDirectory);
            } catch (IOException e) {
                throw new TestContainerException(String.format("Error while copying artifacts to " + LIB_DIRECTORY),
                        e);
            }
        });
    }

    /**
     * Helper method to copy artifacts to the target directory.
     *
     * @param url             url of the artifact
     * @param targetDirectory target directory
     */
    private void copyReferencedArtifactsToDeployDirectory(String url, Path targetDirectory) throws IOException {
        File target = createUnique(url, targetDirectory.toFile());
        FileUtils.copyURLToFile(new URL(url), target);
    }

    /**
     * Create unique id to the file.
     *
     * @param url    url of the artifact
     * @param deploy deploy directory
     * @return file
     */
    private File createUnique(String url, File deploy) {
        String prefix = UUID.randomUUID().toString();
        String fileName = new File(url).getName();
        return new File(deploy, prefix + "_" + fileName + ".jar");
    }

    /**
     * Copy files specified in the carbon file copy option to the destination path.
     *
     * @param carbonHome carbon home
     */
    private void copyFiles(Path carbonHome) {
        Arrays.asList(system.getOptions(CopyFileOption.class)).forEach(option -> {
            try {
                Files.copy(option.getSourcePath(), carbonHome.resolve(option.getDestinationPath()),
                        StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException e) {
                throw new TestContainerException("Error while copying configuration files", e);
            }
        });
    }

    /**
     * Setup system properties.
     *
     * @param options options
     * @throws IOException
     */
    private void setupSystemProperties(List<String> options, ExamSystem examSystem) throws IOException {
        Arrays.asList(examSystem.getOptions(SystemPropertyOption.class)).forEach(systemPropertyOption -> {
            String property = String.format("-D%s=%s", systemPropertyOption.getKey(),
                    systemPropertyOption.getValue());
            options.add(property);
        });
    }

    /**
     * Make all the files in the bin directory to be executable.
     *
     * @param carbonBin carbonBin
     */
    private void makeFilesInBinExec(File carbonBin) {
        if (!carbonBin.exists()) {
            return;
        }
        File[] files = carbonBin.listFiles();
        if (files != null) {
            Arrays.asList(files).forEach(file -> file.setExecutable(true));
        }
    }

    /**
     * Generate a random path if the unpack directory is not specified.
     *
     * @return the path to the carbon home directory
     * @throws IOException
     */
    private Path retrieveFinalTargetDirectory() throws IOException {
        Path unpackDirectory = carbonHomeDirectoryOption.getUnpackDirectory();
        if (unpackDirectory == null) {
            unpackDirectory = Paths.get("target", UUID.randomUUID().toString());
        }
        boolean isCreated = unpackDirectory.toFile().exists() || unpackDirectory.toFile().mkdir();
        if (!isCreated) {
            throw new TestContainerException(
                    "Couldn't create the directory: " + unpackDirectory.toFile().toString());
        }
        return unpackDirectory;
    }

    /**
     * Check whether the container should delete the test directories or not.
     *
     * @return boolean
     */
    private boolean shouldDeleteRuntime() {
        boolean deleteRuntime = true;
        KeepDirectoryOption keepDirectoryOption = system.getSingleOption(KeepDirectoryOption.class);
        if (keepDirectoryOption != null) {
            deleteRuntime = false;
        }
        return deleteRuntime;
    }

    /**
     * Helper method to build string using options.
     *
     * @param options options to convert to string
     * @return the string build by options
     */
    private String buildString(ValueOption<?>[] options) {
        return buildString(new String[0], options, new String[0]);
    }

    private String buildString(String[] prepend, ValueOption<?>[] options, String[] append) {
        StringBuilder builder = new StringBuilder();

        Arrays.asList(prepend).forEach(s -> {
            builder.append(s);
            builder.append(",");
        });

        Arrays.asList(options).forEach(option -> {
            builder.append(option.getValue());
            builder.append(",");
        });

        Arrays.asList(append).forEach(s -> {
            builder.append(s);
            builder.append(",");
        });

        if (builder.length() > 0) {
            return builder.substring(0, builder.length() - 1);
        } else {
            return "";
        }
    }

    /**
     * Stops the regression container.
     *
     * @return this container object for api
     */
    @Override
    public synchronized TestContainer stop() {
        logger.debug("Shutting down the test container.");
        try {
            if (started) {
                target.stop();
                RemoteBundleContextClient remoteBundleContextClient = target.getClientRBC();
                if (remoteBundleContextClient != null) {
                    remoteBundleContextClient.stop();
                }
                runner.shutdown();
                try {
                    UnicastRemoteObject.unexportObject(registry, true);
                } catch (NoSuchObjectException exc) {
                    throw new TestContainerException(exc);
                }
            } else {
                throw new TestContainerException("Container never started.");
            }
        } finally {
            started = false;
            target = null;
            if (shouldDeleteRuntime()) {
                system.clear();
                try {
                    FileUtils.forceDelete(targetDirectory.toFile());
                } catch (IOException e) {
                    forceCleanup();
                }
            }
        }
        return this;
    }

    /**
     * Force cleanup directories if required.
     */
    private void forceCleanup() {
        try {
            FileUtils.forceDeleteOnExit(targetDirectory.toFile());
        } catch (IOException e) {
            logger.error("Error occured when deleting the Directory.", e);
        }
    }

    /**
     * Wait for the bundle to reach for the specified state.
     *
     * @param bundleId id of the bundle
     * @param state    state of the bundle
     * @param timeout  timeout period
     */
    private synchronized 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 synchronized long installProbe(InputStream stream) {
        return target.installProbe(stream);
    }

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

    @Override
    public String toString() {
        return "CarbonTestContainer";
    }
}