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

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.karaf.tooling.exam.container.internal.adaptions.KarafManipulator;
import org.apache.karaf.tooling.exam.container.internal.adaptions.KarafManipulatorFactory;
import org.apache.karaf.tooling.exam.container.internal.runner.Runner;
import org.apache.karaf.tooling.exam.options.*;
import org.apache.karaf.tooling.exam.options.configs.CustomProperties;
import org.apache.karaf.tooling.exam.options.configs.FeaturesCfg;
import org.ops4j.pax.exam.*;
import org.ops4j.pax.exam.container.remote.RBCRemoteTarget;
import org.ops4j.pax.exam.options.*;
import org.ops4j.pax.exam.options.extra.FeaturesScannerProvisionOption;
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;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

import static org.apache.karaf.tooling.exam.options.KarafDistributionOption.editConfigurationFileExtend;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.rbc.Constants.*;

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_INVOKER_PROPERTY = "pax.exam.invoker";
    private static final String EXAM_INJECT_PROPERTY = "pax.exam.inject";

    private final Runner runner;
    private final RMIRegistry registry;
    private final ExamSystem system;
    private KarafDistributionBaseConfigurationOption framework;
    @SuppressWarnings("unused")
    private KarafManipulator versionAdaptions;

    private boolean deleteRuntime = true;
    private boolean started = false;
    private RBCRemoteTarget target;

    private File targetFolder;

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

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

            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());
            }

            ExamSystem subsystem = system.fork(
                    options(
                            systemProperty(RMI_HOST_PROPERTY).value(registry.getHost()),
                            systemProperty(RMI_PORT_PROPERTY).value("" + registry.getPort()),
                            systemProperty(RMI_NAME_PROPERTY).value(name),
                            invokerConfiguration,
                            systemProperty(EXAM_INJECT_PROPERTY).value("true"),
                            editConfigurationFileExtend("etc/system.properties", "jline.shutdownhook", "true")
                    ));
            target = new RBCRemoteTarget(name, registry.getPort(), subsystem.getTimeout());

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

            URL sourceDistribution = new URL(framework.getFrameworkURL());

            KeepRuntimeFolderOption[] keepRuntimeFolder = subsystem.getOptions(KeepRuntimeFolderOption.class);
            if (keepRuntimeFolder != null && keepRuntimeFolder.length != 0) {
                deleteRuntime = false;
            }

            targetFolder = retrieveFinalTargetFolder(subsystem);
            extractKarafDistribution(sourceDistribution, targetFolder);

            File javaHome = new File(System.getProperty("java.home"));
            File karafBase = searchKarafBase(targetFolder);
            File distributionInfo = new File(karafBase + "/etc/distribution.info");
            File karafBin = new File(karafBase + "/bin");
            File featuresXmlFile = new File(targetFolder + "/examfeatures.xml");
            File karafHome = karafBase;
            File deploy = new File(karafBase + "/deploy");
            String karafData = karafHome + "/data";

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

            ArrayList<String> javaOpts = Lists.newArrayList();
            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"};
            String[] karafOpts = new String[]{};
            ArrayList<String> opts =
                    Lists.newArrayList("-Dkaraf.startLocalConsole=" + shouldLocalConsoleBeStarted(subsystem),
                            "-Dkaraf.startRemoteShell=" + shouldRemoteShellBeStarted(subsystem));

            String[] classPath = buildKarafClasspath(karafHome);
            String main = "org.apache.karaf.main.Main";
            String options = "";
            String[] environment = new String[]{};
            String[] fileEndings = new String[]{"jar", "war", "zip", "kar", "xml"};

            updateLogProperties(karafHome, subsystem);
            updateUserSetProperties(karafHome, subsystem);
            copyBootClasspathLibraries(karafHome, subsystem);
            setupExamProperties(karafHome, subsystem);
            makeScriptsInBinExec(karafBin);

            int startLevel = Constants.DEFAULT_START_LEVEL;
            ExamBundlesStartLevel examBundlesStartLevel = system.getSingleOption(ExamBundlesStartLevel.class);
            if (examBundlesStartLevel != null) {
                startLevel = examBundlesStartLevel.getStartLevel();
            }

            ExamFeaturesFile examFeaturesFile;
            if (framework.isUseDeployFolder()) {
                copyReferencedArtifactsToDeployFolder(deploy, subsystem, fileEndings);
                examFeaturesFile = new ExamFeaturesFile("", startLevel);
            } else {
                StringBuilder extension = extractExtensionString(subsystem);
                examFeaturesFile = new ExamFeaturesFile(extension.toString(), startLevel);
            }
            examFeaturesFile.writeToFile(featuresXmlFile);

            examFeaturesFile.adaptDistributionToStartExam(karafHome, featuresXmlFile);

            long startedAt = System.currentTimeMillis();

            runner.exec(environment, karafBase, javaHome.toString(), javaOpts.toArray(new String[]{}),
                    javaEndorsedDirs, javaExtDirs, karafHome.toString(), karafData, karafOpts,
                    opts.toArray(new String[]{}), classPath, main, options);

            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.apache.karaf.tooling.exam.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.");
            }

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

    private void copyBootClasspathLibraries(File karafHome, ExamSystem subsystem) throws MalformedURLException,
            IOException {
        BootClasspathLibraryOption[] bootClasspathLibraryOptions =
                subsystem.getOptions(BootClasspathLibraryOption.class);
        for (BootClasspathLibraryOption bootClasspathLibraryOption : bootClasspathLibraryOptions) {
            UrlReference libraryUrl = bootClasspathLibraryOption.getLibraryUrl();
            FileUtils.copyURLToFile(
                    new URL(libraryUrl.getURL()),
                    createFileNameWithRandomPrefixFromUrlAtTarget(libraryUrl.getURL(), new File(karafHome + "/lib"),
                            new String[]{"jar"}));
        }
    }

    @SuppressWarnings("rawtypes")
    private StringBuilder extractExtensionString(ExamSystem subsystem) {
        ProvisionOption[] provisionOptions = subsystem.getOptions(ProvisionOption.class);
        StringBuilder extension = new StringBuilder();
        for (ProvisionOption provisionOption : provisionOptions) {
            if (provisionOption.getURL().startsWith("link") || provisionOption.getURL().startsWith("scan-features")) {
                // well those we've already handled at another location...
                continue;
            }
            extension.append("<bundle>").append(provisionOption.getURL()).append("</bundle>\n");
        }
        return extension;
    }

    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 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 target = new File(framework.getUnpackDirectory() + "/" + UUID.randomUUID().toString());
            target = transformToAbsolutePath(target);
            target.mkdirs();
            return target;
        }
    }

    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());
        }
    }

    @SuppressWarnings("rawtypes")
    private void copyReferencedArtifactsToDeployFolder(File deploy, ExamSystem subsystem, String[] fileEndings) {
        ProvisionOption[] options = subsystem.getOptions(ProvisionOption.class);
        for (ProvisionOption option : options) {
            try {
                FileUtils.copyURLToFile(new URL(option.getURL()),
                        createFileNameWithRandomPrefixFromUrlAtTarget(option.getURL(), deploy, fileEndings));
            } catch (Exception e) {
                // well, this can happen...
            }
        }
    }

    private File createFileNameWithRandomPrefixFromUrlAtTarget(String url, File deploy, String[] fileEndings) {
        String prefix = UUID.randomUUID().toString();
        String realEnding = extractPossibleFileEndingIfMavenArtifact(url, fileEndings);
        String fileName = new File(url).getName();
        return new File(deploy + "/" + prefix + "_" + fileName + "." + realEnding);
    }

    private String extractPossibleFileEndingIfMavenArtifact(String url, String[] fileEndings) {
        String realEnding = "jar";
        for (String ending : fileEndings) {
            if (url.indexOf("/" + ending + "/") > 0) {
                realEnding = ending;
                break;
            }
        }
        return realEnding;
    }

    private void updateUserSetProperties(File karafHome, ExamSystem subsystem) throws IOException {
        List<KarafDistributionConfigurationFileOption> options = Lists.newArrayList(
                subsystem.getOptions(KarafDistributionConfigurationFileOption.class));
        options.addAll(extractFileOptionsBasedOnFeaturesScannerOptions(subsystem));
        options.addAll(configureBootDelegation(subsystem));
        options.addAll(configureSystemBundles(subsystem));
        HashMap<String, HashMap<String, List<KarafDistributionConfigurationFileOption>>> optionMap = Maps.newHashMap();
        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> configureSystemBundles(ExamSystem subsystem) {
        SystemPackageOption[] systemPackageOptions = subsystem.getOptions(SystemPackageOption.class);
        String systemPackageString = "";
        for (SystemPackageOption systemPackageOption : systemPackageOptions) {
            if (!systemPackageString.equals("")) {
                systemPackageString += ",";
            }
            systemPackageString += systemPackageOption.getValue();
        }
        if (systemPackageString.equals("")) {
            return Lists.newArrayList();
        }
        return Lists.newArrayList(new KarafDistributionConfigurationFileExtendOption(
                CustomProperties.SYSTEM_PACKAGES_EXTRA, systemPackageString));
    }

    private Collection<? extends KarafDistributionConfigurationFileOption>
    configureBootDelegation(ExamSystem subsystem) {
        BootDelegationOption[] bootDelegationOptions = subsystem.getOptions(BootDelegationOption.class);
        String bootDelegationString = "";
        for (BootDelegationOption bootDelegationOption : bootDelegationOptions) {
            bootDelegationString += ",";
            bootDelegationString += bootDelegationOption.getValue();
        }
        return Lists.newArrayList(new KarafDistributionConfigurationFileExtendOption(CustomProperties.BOOTDELEGATION,
                bootDelegationString));
    }

    private Collection<? extends KarafDistributionConfigurationFileOption>
    extractFileOptionsBasedOnFeaturesScannerOptions(ExamSystem subsystem) {
        ArrayList<KarafDistributionConfigurationFileOption> retVal = Lists.newArrayList();
        FeaturesScannerProvisionOption[] features = subsystem.getOptions(FeaturesScannerProvisionOption.class);
        for (FeaturesScannerProvisionOption feature : features) {
            String fullFeatureUrl = feature.getURL();
            String[] split = fullFeatureUrl.split("\\!/");
            String url = split[0].replaceAll("scan-features:", "");
            retVal.add(new KarafDistributionConfigurationFileExtendOption(FeaturesCfg.REPOSITORIES, "," + url));
            retVal.add(new KarafDistributionConfigurationFileExtendOption(FeaturesCfg.BOOT, "," + split[1]));
        }
        return retVal;
    }

    private void setupExamProperties(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());
        }
        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());
        }
        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 system = false;
            boolean etc = false;
            for (File file : head.listFiles()) {
                if (file.isDirectory() && file.getName().equals("system")) {
                    system = true;
                }
                if (file.isDirectory() && file.getName().equals("etc")) {
                    etc = true;
                }
            }
            if (system && etc) {
                return head;
            }
            searchNext.addAll(Arrays.asList(head.listFiles()));
        }
        throw new IllegalStateException("No karaf base dir found in extracted distribution.");
    }

    private void extractKarafDistribution(URL sourceDistribution, File targetFolder) throws IOException {
        if (sourceDistribution.getProtocol().equals("file")) {
            if (sourceDistribution.getFile().indexOf(".zip") > 0) {
                extractZipDistribution(sourceDistribution, targetFolder);
            } else if (sourceDistribution.getFile().indexOf(".tar.gz") > 0) {
                extractTarGzDistribution(sourceDistribution, targetFolder);
            } else {
                throw new IllegalStateException(
                        "Unknow packaging of distribution; only zip or tar.gz could be handled.");
            }
            return;
        }
        if (sourceDistribution.toExternalForm().indexOf("/zip") > 0) {
            extractZipDistribution(sourceDistribution, targetFolder);
        } else if (sourceDistribution.toExternalForm().indexOf("/tar.gz") > 0) {
            extractTarGzDistribution(sourceDistribution, targetFolder);
        } else {
            throw new IllegalStateException(
                    "Unknow packaging of distribution; only zip or tar.gz could be handled.");
        }
    }

    private void extractTarGzDistribution(URL sourceDistribution, File targetFolder) throws IOException,
            FileNotFoundException {
        File uncompressedFile = File.createTempFile("uncompressedTarGz-", ".tar");
        extractGzArchive(sourceDistribution.openStream(), uncompressedFile);
        extract(new TarArchiveInputStream(new FileInputStream(uncompressedFile)), targetFolder);
        FileUtils.forceDelete(uncompressedFile);
    }

    private void extractZipDistribution(URL sourceDistribution, File targetFolder) throws IOException {
        extract(new ZipArchiveInputStream(sourceDistribution.openStream()), targetFolder);
    }

    private void extractGzArchive(InputStream tarGz, File tar) throws IOException {
        BufferedInputStream in = new BufferedInputStream(tarGz);
        FileOutputStream out = new FileOutputStream(tar);
        GzipCompressorInputStream gzIn = new GzipCompressorInputStream(in);
        final byte[] buffer = new byte[1000];
        int n = 0;
        while (-1 != (n = gzIn.read(buffer))) {
            out.write(buffer, 0, n);
        }
        out.close();
        gzIn.close();
    }

    private void extract(ArchiveInputStream is, File targetDir) throws IOException {
        try {
            if (targetDir.exists()) {
                FileUtils.forceDelete(targetDir);
            }
            targetDir.mkdirs();
            ArchiveEntry entry = is.getNextEntry();
            while (entry != null) {
                String name = entry.getName();
                name = name.substring(name.indexOf("/") + 1);
                File file = new File(targetDir, name);
                if (entry.isDirectory()) {
                    file.mkdirs();
                } else {
                    file.getParentFile().mkdirs();
                    OutputStream os = new FileOutputStream(file);
                    try {
                        IOUtils.copy(is, os);
                    } finally {
                        IOUtils.closeQuietly(os);
                    }
                }
                entry = is.getNextEntry();
            }
        } finally {
            is.close();
        }
    }

    @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();
                }
            } else {
                throw new RuntimeException("Container never came up");
            }
        } finally {
            started = false;
            target = null;
            if (deleteRuntime) {
                system.clear();
                try {
                    FileUtils.forceDelete(targetFolder);
                } catch (IOException e) {
                    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...");
                    }
                }
            }
        }
        return this;
    }

    private void waitForState(final long bundleId, final int state, final RelativeTimeout timeout)
            throws TimeoutException {
        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() + "}";
    }

}
