You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/04/27 09:59:52 UTC

[sling-org-apache-sling-feature-launcher] 01/16: Move feature model to whiteboard git

This is an automated email from the ASF dual-hosted git repository.

davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-launcher.git

commit 80949a541e11f6b30298046c46c1e991a0900e8a
Author: Carsten Ziegeler <cz...@adobe.com>
AuthorDate: Fri Nov 3 15:06:50 2017 +0100

    Move feature model to whiteboard git
---
 pom.xml                                            | 128 +++++++++
 .../feature/launcher/impl/FeatureProcessor.java    | 137 +++++++++
 .../sling/feature/launcher/impl/Installation.java  | 137 +++++++++
 .../feature/launcher/impl/LauncherConfig.java      | 122 ++++++++
 .../apache/sling/feature/launcher/impl/Main.java   | 313 +++++++++++++++++++++
 .../launcher/impl/launchers/AbstractRunner.java    | 278 ++++++++++++++++++
 .../launcher/impl/launchers/FrameworkLauncher.java |  88 ++++++
 .../launcher/impl/launchers/FrameworkRunner.java   |  70 +++++
 .../sling/feature/launcher/spi/Launcher.java       |  26 ++
 .../launcher/spi/LauncherPrepareContext.java       |  32 +++
 .../feature/launcher/spi/LauncherRunContext.java   |  58 ++++
 11 files changed, 1389 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6bf9943
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+    <!--
+        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.
+    -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>32</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.feature.launcher</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling Feature Launcher</name>
+    <description>
+        A application launcher using Apache Sling Features
+    </description>
+
+    <properties>
+        <sling.java.version>8</sling.java.version>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-launcher</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-launcher</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/tooling/support/feature-launcher</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                <execution>
+                    <id>unpack-dependencies</id>
+                    <phase>prepare-package</phase>
+                    <goals>
+                        <goal>unpack-dependencies</goal>
+                    </goals>
+                    <configuration>
+                        <excludes>META-INF/**</excludes>
+                        <outputDirectory>${project.build.directory}/classes</outputDirectory>
+                        <overWriteReleases>false</overWriteReleases>
+                        <overWriteSnapshots>true</overWriteSnapshots>
+                        <includeArtifactIds>org.apache.sling.feature,org.apache.sling.feature.support,org.apache.sling.commons.johnzon,org.apache.felix.converter,commons-cli,slf4j-api,slf4j-simple,osgi.core</includeArtifactIds>
+                    </configuration>
+                </execution>
+            </executions>
+            </plugin>
+                <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.sling.feature.launcher.impl.Main</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.support</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.converter</artifactId>
+            <version>0.1.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+             <groupId>commons-cli</groupId>
+             <artifactId>commons-cli</artifactId>
+             <version>1.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>2.8.9</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java b/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
new file mode 100644
index 0000000..14eaf9c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
@@ -0,0 +1,137 @@
+/*
+ * 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.sling.feature.launcher.impl;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.launcher.impl.LauncherConfig.StartupMode;
+import org.apache.sling.feature.support.ArtifactHandler;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.apache.sling.feature.support.FeatureUtil;
+import org.apache.sling.feature.support.json.ApplicationJSONReader;
+import org.apache.sling.feature.support.json.ApplicationJSONWriter;
+
+public class FeatureProcessor {
+
+    /**
+     * Initialize the launcher
+     * Read the features and prepare the application
+     * @param config The current configuration
+     * @param artifactManager The artifact manager
+     */
+    public static Application createApplication(final LauncherConfig config,
+            final ArtifactManager artifactManager)
+    throws IOException {
+        final Application app;
+        if ( config.getApplicationFile() != null ) {
+            String absoluteArg = config.getApplicationFile();
+            if ( absoluteArg.indexOf(":") < 2 ) {
+                absoluteArg = new File(absoluteArg).getAbsolutePath();
+            }
+            final ArtifactHandler appArtifact = artifactManager.getArtifactHandler(absoluteArg);
+
+            try (final FileReader r = new FileReader(appArtifact.getFile())) {
+                app = ApplicationJSONReader.read(r);
+            }
+
+        } else {
+           app = FeatureUtil.assembleApplication(null, artifactManager, FeatureUtil.getFeatureFiles(config.getHomeDirectory(), config.getFeatureFiles()).toArray(new String[0]));
+        }
+
+        // write application back
+        final File file = new File(config.getHomeDirectory(), "resources" + File.separatorChar + "provisioning" + File.separatorChar + "application.json");
+        file.getParentFile().mkdirs();
+
+        try (final FileWriter writer = new FileWriter(file)) {
+            ApplicationJSONWriter.write(writer, app);
+        } catch ( final IOException ioe) {
+            Main.LOG().error("Error while writing application file: {}", ioe.getMessage(), ioe);
+            System.exit(1);
+        }
+
+        return app;
+    }
+
+    /**
+     * Prepare the launcher
+     * - add all bundles to the bundle map of the installation object
+     * - add all other artifacts to the install directory (only if startup mode is INSTALL)
+     * - process configurations
+     */
+    public static void prepareLauncher(final LauncherConfig config,
+            final ArtifactManager artifactManager,
+            final Application app) throws Exception {
+        for(final Map.Entry<Integer, List<Artifact>> entry : app.getBundles().getBundlesByStartLevel().entrySet()) {
+            for(final Artifact a : entry.getValue()) {
+                final ArtifactHandler handler = artifactManager.getArtifactHandler(":" + a.getId().toMvnPath());
+                final File artifactFile = handler.getFile();
+
+                config.getInstallation().addBundle(entry.getKey(), artifactFile);
+            }
+        }
+        int index = 1;
+        for(final Extension ext : app.getExtensions()) {
+            if ( ext.getType() == ExtensionType.ARTIFACTS ) {
+                for(final Artifact a : ext.getArtifacts() ) {
+                    if ( config.getStartupMode() == StartupMode.PURE ) {
+                        throw new Exception("Artifacts other than bundle are not supported by framework launcher.");
+                    }
+                    final ArtifactHandler handler = artifactManager.getArtifactHandler(":" + a.getId().toMvnPath());
+                    config.getInstallation().addInstallableArtifact(handler.getFile());
+                }
+            } else {
+                if ( ext.getName().equals(Extension.NAME_REPOINIT) ) {
+                    if ( ext.getType() != ExtensionType.TEXT ) {
+                        throw new Exception(Extension.NAME_REPOINIT + " extension must be of type text and not json");
+                    }
+                    final Configuration cfg = new Configuration("org.apache.sling.jcr.repoinit.RepositoryInitializer", "repoinit" + String.valueOf(index));
+                    index++;
+                    cfg.getProperties().put("scripts", ext.getText());
+                    config.getInstallation().addConfiguration(cfg.getName(), cfg.getFactoryPid(), cfg.getProperties());
+                } else {
+                    if ( ext.isRequired() ) {
+                        throw new Exception("Unknown required extension " + ext.getName());
+                    }
+                }
+            }
+        }
+
+        for(final Configuration cfg : app.getConfigurations()) {
+            if ( cfg.isFactoryConfiguration() ) {
+                config.getInstallation().addConfiguration(cfg.getName(), cfg.getFactoryPid(), cfg.getProperties());
+            } else {
+                config.getInstallation().addConfiguration(cfg.getPid(), null, cfg.getProperties());
+            }
+        }
+
+        for(final Map.Entry<String, String> prop : app.getFrameworkProperties()) {
+            if ( !config.getInstallation().getFrameworkProperties().containsKey(prop.getKey()) ) {
+                config.getInstallation().getFrameworkProperties().put(prop.getKey(), prop.getValue());
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java b/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
new file mode 100644
index 0000000..2dff508
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
@@ -0,0 +1,137 @@
+/*
+ * 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.sling.feature.launcher.impl;
+
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class holds the configuration of the launcher.
+ */
+public class Installation implements LauncherRunContext {
+
+    /** The map with the framework properties. */
+    private final Map<String, String> fwkProperties = new HashMap<>();
+
+    /** Bundle map */
+    private final Map<Integer, List<File>> bundleMap = new HashMap<>();
+
+    /** Artifacts to be installed */
+    private final List<File> installables = new ArrayList<>();
+
+    /** Configurations, they are installed on first start. */
+    private final List<Object[]> configurations = new ArrayList<>();
+
+    /** The list of app jars. */
+    private final List<File> appJars = new ArrayList<>();
+
+    /**
+     * Add an application jar.
+     * @param jar The application jar
+     */
+    public void addAppJar(final File jar) {
+        this.appJars.add(jar);
+    }
+
+    /**
+     * Get the list of application jars.
+     * @return The list of app jars
+     */
+    public List<File> getAppJars() {
+        return this.appJars;
+    }
+
+    /**
+     * Add a bundle with the given start level
+     * @param startLevel The start level
+     * @param file The bundle file
+     */
+    public void addBundle(final Integer startLevel, final File file) {
+        List<File> files = bundleMap.get(startLevel);
+        if ( files == null ) {
+            files = new ArrayList<>();
+            bundleMap.put(startLevel, files);
+        }
+        files.add(file);
+    }
+
+    /**
+     * Add an artifact to be installed by the installer
+     * @param file The file
+     */
+    public void addInstallableArtifact(final File file) {
+        this.installables.add(file);
+    }
+
+    /**
+     * Add a configuration
+     * @param pid The pid
+     * @param factoryPid The factory pid
+     * @param properties The propertis
+     */
+    public void addConfiguration(final String pid, final String factoryPid, final Dictionary<String, Object> properties) {
+        this.configurations.add(new Object[] {pid, factoryPid, properties});
+    }
+
+    /**
+     * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getFrameworkProperties()
+     */
+    @Override
+    public Map<String, String> getFrameworkProperties() {
+        return this.fwkProperties;
+    }
+
+    /**
+     * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getBundleMap()
+     */
+    @Override
+    public Map<Integer, List<File>> getBundleMap() {
+        return this.bundleMap;
+    }
+
+    /**
+     * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getConfigurations()
+     */
+    @Override
+    public List<Object[]> getConfigurations() {
+        return this.configurations;
+    }
+
+    /**
+     * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getInstallableArtifacts()
+     */
+    @Override
+    public List<File> getInstallableArtifacts() {
+        return this.installables;
+    }
+
+    /**
+     * Clear all in-memory objects
+     */
+    public void clear() {
+        this.configurations.clear();
+        this.fwkProperties.clear();
+        this.bundleMap.clear();
+        this.installables.clear();
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/LauncherConfig.java b/src/main/java/org/apache/sling/feature/launcher/impl/LauncherConfig.java
new file mode 100644
index 0000000..56095b9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/LauncherConfig.java
@@ -0,0 +1,122 @@
+/*
+ * 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.sling.feature.launcher.impl;
+
+import org.apache.sling.feature.support.ArtifactManagerConfig;
+import org.apache.sling.feature.support.spi.ArtifactProviderContext;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class holds the configuration of the launcher.
+ */
+public class LauncherConfig
+    extends ArtifactManagerConfig
+    implements ArtifactProviderContext {
+
+    public enum StartupMode {
+        INSTALLER,
+        PURE
+    };
+
+    private static final String HOME = "launcher";
+
+    private static final String CACHE_DIR = "cache";
+
+    /** The feature files or directories. */
+    private volatile String[] featureFiles;
+
+    /** The application file. */
+    private volatile String appFile;
+
+    private volatile StartupMode startupMode = StartupMode.PURE;
+
+    private final Installation installation = new Installation();
+
+    /**
+     * Create a new configuration object.
+     * Set the default values
+     */
+    public LauncherConfig() {
+        this.setCacheDirectory(new File(getHomeDirectory(), CACHE_DIR));
+    }
+
+    public void setApplicationFile(final String value) {
+        appFile = value;
+    }
+
+    public String getApplicationFile() {
+        return this.appFile;
+    }
+
+    /**
+     * Set the list of feature files or directories.
+     * @param value The array with the feature file names.
+     */
+    public void setFeatureFiles(final String[] value) {
+        this.featureFiles = value;
+        if ( value != null && value.length == 0 ) {
+            this.featureFiles = null;
+        }
+    }
+
+    /**
+     * Get the list of feature files.
+     * @return The array of names.
+     * @throws IOException
+     */
+    public String[] getFeatureFiles() {
+        return this.featureFiles;
+    }
+
+
+    /**
+     * Get the home directory.
+     * @return The home directory.
+     */
+    public File getHomeDirectory() {
+        return new File(HOME);
+    }
+
+    /**
+     * Get the startup mode.
+     *
+     * @return The current startup mode.
+     */
+    public StartupMode getStartupMode() {
+        return this.startupMode;
+    }
+
+    /**
+     * Sets the startup mode to {@link StartupMode#INSTALLER}.
+     */
+    public void setUseInstaller() {
+        this.startupMode = StartupMode.INSTALLER;
+    }
+
+    public Installation getInstallation() {
+        return this.installation;
+    }
+
+    /**
+     * Clear all in-memory objects
+     */
+    public void clear() {
+        this.installation.clear();
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Main.java b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
new file mode 100644
index 0000000..31050fe
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
@@ -0,0 +1,313 @@
+/*
+ * 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.sling.feature.launcher.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.cli.BasicParser;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.launcher.impl.launchers.FrameworkLauncher;
+import org.apache.sling.feature.launcher.spi.Launcher;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.support.ArtifactHandler;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the launcher main class.
+ * It parses command line parameters and prepares the launcher.
+ */
+public class Main {
+
+    private static Logger LOGGER;
+
+    public static Logger LOG() {
+        if ( LOGGER == null ) {
+            LOGGER = LoggerFactory.getLogger("launcher");
+        }
+        return LOGGER;
+    }
+
+    /** Split a string into key and value */
+    private static String[] split(final String val) {
+        final int pos = val.indexOf('=');
+        if ( pos == -1 ) {
+            return new String[] {val, "true"};
+        }
+        return new String[] {val.substring(0, pos), val.substring(pos + 1)};
+    }
+
+    /**
+     * Parse the command line parameters and update a configuration object.
+     * @param args Command line parameters
+     * @return Configuration object.
+     */
+    private static void parseArgs(final LauncherConfig config, final String[] args) {
+        Main.LOG().info("Assembling configuration...");
+        final Options options = new Options();
+
+        final Option repoOption =  new Option("u", true, "Set repository url");
+        final Option modelOption =  new Option("f", true, "Set feature files/directories");
+        final Option appOption =  new Option("a", true, "Set application file");
+        final Option fwkProperties = new Option("D", true, "Set framework properties");
+        fwkProperties.setArgs(20);
+        final Option debugOption = new Option("v", true, "Verbose");
+        debugOption.setArgs(0);
+        final Option installerOption = new Option("I", true, "Use OSGi installer for additional artifacts.");
+        installerOption.setArgs(0);
+        options.addOption(repoOption);
+        options.addOption(appOption);
+        options.addOption(modelOption);
+        options.addOption(fwkProperties);
+        options.addOption(debugOption);
+        options.addOption(installerOption);
+
+        final CommandLineParser clp = new BasicParser();
+        try {
+            final CommandLine cl = clp.parse(options, args);
+
+            if ( cl.hasOption(repoOption.getOpt()) ) {
+                final String value = cl.getOptionValue(repoOption.getOpt());
+                config.setRepositoryUrls(value.split(","));
+            }
+            if ( cl.hasOption(modelOption.getOpt()) ) {
+                final String value = cl.getOptionValue(modelOption.getOpt());
+                config.setFeatureFiles(value.split(","));
+            }
+            if ( cl.hasOption(fwkProperties.getOpt()) ) {
+                for(final String value : cl.getOptionValues(fwkProperties.getOpt())) {
+                    final String[] keyVal = split(value);
+
+                    config.getInstallation().getFrameworkProperties().put(keyVal[0], keyVal[1]);
+                }
+            }
+            if ( cl.hasOption(debugOption.getOpt()) ) {
+                System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
+                LOGGER = null;
+            }
+            if ( cl.hasOption(installerOption.getOpt()) ) {
+                config.setUseInstaller();
+            }
+            if ( cl.hasOption(appOption.getOpt()) ) {
+                config.setApplicationFile(cl.getOptionValue(appOption.getOpt()));
+            }
+        } catch ( final ParseException pe) {
+            Main.LOG().error("Unable to parse command line: {}", pe.getMessage(), pe);
+            System.exit(1);
+        }
+    }
+
+    public static void main(final String[] args) {
+        // setup logging
+        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
+        System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
+        System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true");
+        System.setProperty("org.slf4j.simpleLogger.showLogName", "false");
+        Main.LOG().info("");
+        Main.LOG().info("Apache Sling Application Launcher");
+        Main.LOG().info("---------------------------------");
+
+        // check if launcher has already been created
+        final LauncherConfig launcherConfig = new LauncherConfig();
+        parseArgs(launcherConfig, args);
+
+        ArtifactManager artifactManager = null;
+        try {
+
+            Main.LOG().info("Initializing...");
+            try {
+                artifactManager = ArtifactManager.getArtifactManager(launcherConfig);
+            } catch ( final IOException ioe) {
+                Main.LOG().error("Unable to setup artifact manager: {}", ioe.getMessage(), ioe);
+                System.exit(1);
+            }
+            Main.LOG().info("Artifact Repositories: {}", Arrays.toString(launcherConfig.getRepositoryUrls()));
+            Main.LOG().info("Assembling provisioning model...");
+
+            try {
+                final Launcher launcher = new FrameworkLauncher();
+                final Application app = FeatureProcessor.createApplication(launcherConfig, artifactManager);
+
+                Main.LOG().info("");
+                Main.LOG().info("Assembling launcher...");
+                final ArtifactManager aMgr = artifactManager;
+                final LauncherPrepareContext ctx = new LauncherPrepareContext() {
+
+                    @Override
+                    public File getArtifactFile(final ArtifactId artifact) throws IOException {
+                        final ArtifactHandler handler = aMgr.getArtifactHandler(":" + artifact.toMvnPath());
+                        return handler.getFile();
+                    }
+
+                    @Override
+                    public void addAppJar(final File jar) {
+                        launcherConfig.getInstallation().addAppJar(jar);
+                    }
+                };
+                launcher.prepare(ctx, app);
+
+                FeatureProcessor.prepareLauncher(launcherConfig, artifactManager, app);
+
+            } catch ( final Exception iae) {
+                Main.LOG().error("Error while assembling launcher: {}", iae.getMessage(), iae);
+                System.exit(1);
+            }
+
+            Main.LOG().info("Using {} local artifacts, {} cached artifacts, and {} downloaded artifacts",
+                    launcherConfig.getLocalArtifacts(), launcherConfig.getCachedArtifacts(), launcherConfig.getDownloadedArtifacts());
+        } finally {
+            if ( artifactManager != null ) {
+                artifactManager.shutdown();
+            }
+        }
+
+        try {
+            run(launcherConfig);
+        } catch ( final Exception iae) {
+            Main.LOG().error("Error while running launcher: {}", iae.getMessage(), iae);
+            System.exit(1);
+        }
+    }
+
+    private static final String STORAGE_PROPERTY = "org.osgi.framework.storage";
+
+    private static final String START_LEVEL_PROP = "org.osgi.framework.startlevel.beginning";
+
+    /**
+     * Run launcher.
+     * @param config The configuration
+     * @throws Exception If anything goes wrong
+     */
+    private static void run(final LauncherConfig config) throws Exception {
+        Main.LOG().info("");
+        Main.LOG().info("Starting launcher...");
+        Main.LOG().info("Launcher Home: {}", config.getHomeDirectory().getAbsolutePath());
+        Main.LOG().info("Cache Directory: {}", config.getCacheDirectory().getAbsolutePath());
+        Main.LOG().info("Startup Mode: {}", config.getStartupMode());
+        Main.LOG().info("");
+
+        final Installation installation = config.getInstallation();
+
+        // set sling home, and use separate locations for launchpad and properties
+        installation.getFrameworkProperties().put("sling.home", config.getHomeDirectory().getAbsolutePath());
+        installation.getFrameworkProperties().put("sling.launchpad", config.getHomeDirectory().getAbsolutePath() + "/launchpad");
+        installation.getFrameworkProperties().put("sling.properties", "conf/sling.properties");
+
+
+        // additional OSGi properties
+        // move storage inside launcher
+        if ( installation.getFrameworkProperties().get(STORAGE_PROPERTY) == null ) {
+            installation.getFrameworkProperties().put(STORAGE_PROPERTY, config.getHomeDirectory().getAbsolutePath() + File.separatorChar + "framework");
+        }
+        // set start level to 30
+        if ( installation.getFrameworkProperties().get(START_LEVEL_PROP) == null ) {
+            installation.getFrameworkProperties().put(START_LEVEL_PROP, "30");
+        }
+
+        final Launcher launcher = new FrameworkLauncher();
+        launcher.run(installation, createClassLoader(installation));
+
+        config.clear();
+    }
+
+    /**
+     * Create the class loader.
+     * @param installation The launcher configuration
+     * @throws Exception If anything goes wrong
+     */
+    public static ClassLoader createClassLoader(final Installation installation) throws Exception {
+        final List<URL> list = new ArrayList<>();
+        for(final File f : installation.getAppJars()) {
+            try {
+                list.add(f.toURI().toURL());
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+        list.add(Main.class.getProtectionDomain().getCodeSource().getLocation());
+
+        final URL[] urls = list.toArray(new URL[list.size()]);
+
+        if ( Main.LOG().isDebugEnabled() ) {
+            Main.LOG().debug("App classpath: ");
+            for (int i = 0; i < urls.length; i++) {
+                Main.LOG().debug(" - {}", urls[i]);
+            }
+        }
+
+        // create a paranoid class loader, loading from parent last
+        final ClassLoader cl = new URLClassLoader(urls) {
+            @Override
+            public final Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+                // First check if it's already loaded
+                Class<?> clazz = findLoadedClass(name);
+
+                if (clazz == null) {
+
+                    try {
+                        clazz = findClass(name);
+                    } catch (ClassNotFoundException cnfe) {
+                        ClassLoader parent = getParent();
+                        if (parent != null) {
+                            // Ask to parent ClassLoader (can also throw a CNFE).
+                            clazz = parent.loadClass(name);
+                        } else {
+                            // Propagate exception
+                            throw cnfe;
+                        }
+                    }
+                }
+
+                if (resolve) {
+                    resolveClass(clazz);
+                }
+
+                return clazz;
+            }
+
+            @Override
+            public final URL getResource(final String name) {
+
+                URL resource = findResource(name);
+                ClassLoader parent = this.getParent();
+                if (resource == null && parent != null) {
+                    resource = parent.getResource(name);
+                }
+
+                return resource;
+            }
+        };
+
+        Thread.currentThread().setContextClassLoader(cl);
+
+        return cl;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
new file mode 100644
index 0000000..0be1804
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
@@ -0,0 +1,278 @@
+/*
+ * 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.sling.feature.launcher.impl.launchers;
+
+import org.apache.sling.feature.launcher.impl.Main;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Common functionality for the framework start.
+ */
+public class AbstractRunner {
+
+    private volatile ServiceTracker<Object, Object> configAdminTracker;
+
+    private volatile ServiceTracker<Object, Object> installerTracker;
+
+    private final List<Object[]> configurations;
+
+    private final List<File> installables;
+
+    public AbstractRunner(final List<Object[]> configurations, final List<File> installables) {
+        this.configurations = new ArrayList<>(configurations);
+        this.installables = installables;
+    }
+
+    protected void setupFramework(final Framework framework, final Map<Integer, List<File>> bundlesMap)
+    throws BundleException {
+        if ( !configurations.isEmpty() ) {
+            this.configAdminTracker = new ServiceTracker<>(framework.getBundleContext(),
+                    "org.osgi.service.cm.ConfigurationAdmin",
+                    new ServiceTrackerCustomizer<Object, Object>() {
+
+                        @Override
+                        public Object addingService(final ServiceReference<Object> reference) {
+                            // get config admin
+                            final Object cm = framework.getBundleContext().getService(reference);
+                            if ( cm != null ) {
+                                try {
+                                    configure(cm);
+                                } finally {
+                                    framework.getBundleContext().ungetService(reference);
+                                }
+                            }
+                            return null;
+                        }
+
+                        @Override
+                        public void modifiedService(ServiceReference<Object> reference, Object service) {
+                            // nothing to do
+                        }
+
+                        @Override
+                        public void removedService(ServiceReference<Object> reference, Object service) {
+                            // nothing to do
+                        }
+            });
+            this.configAdminTracker.open();
+        }
+        if ( installables != null && !installables.isEmpty() ) {
+            this.installerTracker = new ServiceTracker<>(framework.getBundleContext(),
+                    "org.apache.sling.installer.api.OsgiInstaller",
+                    new ServiceTrackerCustomizer<Object, Object>() {
+
+                        @Override
+                        public Object addingService(final ServiceReference<Object> reference) {
+                            // get installer
+                            final Object installer = framework.getBundleContext().getService(reference);
+                            if ( installer != null ) {
+                                try {
+                                    install(installer);
+                                } finally {
+                                    framework.getBundleContext().ungetService(reference);
+                                }
+                            }
+                            return null;
+                        }
+
+                        @Override
+                        public void modifiedService(ServiceReference<Object> reference, Object service) {
+                            // nothing to do
+                        }
+
+                        @Override
+                        public void removedService(ServiceReference<Object> reference, Object service) {
+                            // nothing to do
+                        }
+            });
+            this.installerTracker.open();
+        }
+        try {
+            this.install(framework, bundlesMap);
+        } catch ( final IOException ioe) {
+            throw new BundleException("Unable to install bundles.", ioe);
+        }
+    }
+
+    private void configure(final Object configAdmin) {
+        try {
+            final Method createConfig = configAdmin.getClass().getDeclaredMethod("getConfiguration", String.class, String.class);
+            final Method createFactoryConfig = configAdmin.getClass().getDeclaredMethod("getFactoryConfiguration", String.class, String.class, String.class);
+
+            Method updateMethod = null;
+            for(final Object[] obj : this.configurations) {
+                final Object cfg;
+                if ( obj[1] != null ) {
+                    cfg = createFactoryConfig.invoke(configAdmin, obj[1], obj[0], null);
+                } else {
+                    cfg = createConfig.invoke(configAdmin, obj[0], null);
+                }
+                if ( updateMethod == null ) {
+                    updateMethod = cfg.getClass().getDeclaredMethod("update", Dictionary.class);
+                }
+                updateMethod.invoke(cfg, obj[2]);
+            }
+        } catch ( final Exception e) {
+            Main.LOG().error("Unable to create configurations", e);
+            throw new RuntimeException(e);
+        }
+        final Thread t = new Thread(() -> { configAdminTracker.close(); configAdminTracker = null; });
+        t.setDaemon(false);
+        t.start();
+        this.configurations.clear();
+    }
+
+    private boolean isSystemBundleFragment(final Bundle installedBundle) {
+        final String fragmentHeader = getFragmentHostHeader(installedBundle);
+        return fragmentHeader != null
+            && fragmentHeader.indexOf(Constants.EXTENSION_DIRECTIVE) > 0;
+    }
+
+    /**
+     * Gets the bundle's Fragment-Host header.
+     */
+    private String getFragmentHostHeader(final Bundle b) {
+        return b.getHeaders().get( Constants.FRAGMENT_HOST );
+    }
+
+    /**
+     * Install the bundles
+     * @param bundleMap The map with the bundles indexed by start level
+     * @throws IOException, BundleException If anything goes wrong.
+     */
+    private void install(final Framework framework, final Map<Integer, List<File>> bundleMap)
+    throws IOException, BundleException {
+        final BundleContext bc = framework.getBundleContext();
+        int defaultStartLevel = getProperty(bc, "felix.startlevel.bundle", 1);
+        for(final Integer startLevel : sortStartLevels(bundleMap.keySet(), defaultStartLevel)) {
+            Main.LOG().debug("Installing bundles with start level {}", startLevel);
+
+            for(final File file : bundleMap.get(startLevel)) {
+                Main.LOG().debug("- {}", file.getName());
+
+                // use reference protocol. This avoids copying the binary to the cache directory
+                // of the framework
+                final Bundle bundle = bc.installBundle("reference:" + file.toURI().toURL(), null);
+                if ( startLevel > 0 ) {
+                    bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
+                }
+
+                // fragment?
+                if ( !isSystemBundleFragment(bundle) ) {
+                    final String fragmentHostHeader = getFragmentHostHeader(bundle);
+                    if (fragmentHostHeader != null) {
+                        for (final Bundle b : bc.getBundles()) {
+                            if (fragmentHostHeader.equals(b.getSymbolicName())) {
+                                final FrameworkWiring fw = framework.adapt(FrameworkWiring.class);
+                                fw.refreshBundles(Collections.singleton(b));
+                                break;
+                            }
+                        }
+
+                    } else {
+                        bundle.start();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sort the start levels in the ascending order. The only exception is the start level
+     * "0", which should be put at the position configured in {@code felix.startlevel.bundle}.
+     *
+     * @param startLevels integer start levels
+     * @return sorted start levels
+     */
+    private static Iterable<Integer> sortStartLevels(final Collection<Integer> startLevels, final int defaultStartLevel) {
+        final List<Integer> result = new ArrayList<>(startLevels);
+        Collections.sort(result, (o1, o2) -> {
+            int i1 = o1 == 0 ? defaultStartLevel : o1;
+            int i2 = o2 == 0 ? defaultStartLevel : o2;
+            return Integer.compare(i1, i2);
+        });
+        return result;
+    }
+
+    private static int getProperty(BundleContext bc, String propName, int defaultValue) {
+        String val = bc.getProperty(propName);
+        if (val == null) {
+            return defaultValue;
+        } else {
+            return Integer.parseInt(val);
+        }
+    }
+
+    private void install(final Object installer) {
+        try {
+            final Class<?> installableResourceClass = installer.getClass().getClassLoader().loadClass("org.apache.sling.installer.api.InstallableResource");
+            final Object resources = Array.newInstance(installableResourceClass, this.installables.size());
+            final Method registerResources = installer.getClass().getDeclaredMethod("registerResources", String.class, resources.getClass());
+            final Constructor<?> constructor = installableResourceClass.getDeclaredConstructor(String.class,
+                    InputStream.class,
+                    Dictionary.class,
+                    String.class,
+                    String.class,
+                    Integer.class);
+
+            for(int i=0; i<this.installables.size();i++) {
+                final File f = this.installables.get(i);
+                final Dictionary<String, Object> dict = new Hashtable<>();
+                dict.put("resource.uri.hint", f.toURI().toString());
+                final Object rsrc = constructor.newInstance(f.getAbsolutePath(),
+                        new FileInputStream(f),
+                        dict,
+                        f.getName(),
+                        "file",
+                        null);
+                Array.set(resources, i, rsrc);
+            }
+            registerResources.invoke(installer, "cloudlauncher", resources);
+        } catch ( final Exception e) {
+            Main.LOG().error("Unable to contact installer and install additional artifacts", e);
+            throw new RuntimeException(e);
+        }
+        final Thread t = new Thread(() -> { installerTracker.close(); installerTracker = null; });
+        t.setDaemon(false);
+        t.start();
+        this.installables.clear();
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
new file mode 100644
index 0000000..8bb5fdc
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
@@ -0,0 +1,88 @@
+/*
+ * 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.sling.feature.launcher.impl.launchers;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.launcher.impl.Main;
+import org.apache.sling.feature.launcher.spi.Launcher;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+import org.apache.sling.feature.support.util.SubstVarUtil;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Launcher directly using the OSGi launcher API.
+ */
+public class FrameworkLauncher implements Launcher {
+
+
+    @Override
+    public void prepare(final LauncherPrepareContext context, final Application app) throws Exception {
+        context.addAppJar(context.getArtifactFile(app.getFramework()));
+    }
+
+    /**
+     * Run the launcher
+     * @throws If anything goes wrong
+     */
+    @Override
+    public void run(final LauncherRunContext context, final ClassLoader cl) throws Exception {
+        Map<String, String> properties = new HashMap<>();
+        context.getFrameworkProperties().forEach((key, value) -> {
+            properties.put(key, SubstVarUtil.substVars(value, key,null, context.getFrameworkProperties()));
+        });
+        if ( Main.LOG().isDebugEnabled() ) {
+            Main.LOG().debug("Bundles:");
+            for(final Integer key : context.getBundleMap().keySet()) {
+                Main.LOG().debug("-- Start Level {}", key);
+                for(final File f : context.getBundleMap().get(key)) {
+                    Main.LOG().debug("  - {}", f.getName());
+                }
+            }
+            Main.LOG().debug("Settings: ");
+            for(final Map.Entry<String, String> entry : properties.entrySet()) {
+                Main.LOG().debug("- {}={}", entry.getKey(), entry.getValue());
+            }
+            Main.LOG().debug("Configurations: ");
+            for(final Object[] entry : context.getConfigurations()) {
+                if ( entry[1] != null ) {
+                    Main.LOG().debug("- Factory {} - {}", entry[1], entry[0]);
+                } else {
+                    Main.LOG().debug("- {}", entry[0]);
+                }
+            }
+            Main.LOG().debug("");
+        }
+        long time = System.currentTimeMillis();
+
+        final Class<?> runnerClass = cl.loadClass(this.getClass().getPackage().getName() + ".FrameworkRunner");
+        final Constructor<?> constructor = runnerClass.getDeclaredConstructor(Map.class, Map.class, List.class, List.class);
+        constructor.setAccessible(true);
+        constructor.newInstance(properties,
+                context.getBundleMap(),
+                context.getConfigurations(),
+                context.getInstallableArtifacts());
+
+        Main.LOG().debug("Startup took: " + (System.currentTimeMillis() - time));
+        // nothing else to do, constructor starts everything
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
new file mode 100644
index 0000000..5d95cef
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
@@ -0,0 +1,70 @@
+/*
+ * 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.sling.feature.launcher.impl.launchers;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * Launcher directly using the OSGi launcher API.
+ */
+public class FrameworkRunner extends AbstractRunner {
+
+    public FrameworkRunner(final Map<String, String> frameworkProperties,
+            final Map<Integer, List<File>> bundlesMap,
+            final List<Object[]> configurations,
+            final List<File> installables) throws Exception {
+        super(configurations, installables);
+
+        final ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
+        FrameworkFactory factory = null;
+        for(FrameworkFactory f : loader) {
+            factory = f;
+            break;
+        }
+        if ( factory == null ) {
+            throw new Exception("Unable to locate framework factory.");
+        }
+
+        // create the framework
+        final Framework framework = factory.newFramework(frameworkProperties);
+        // initialize the framework
+        framework.init();
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                try {
+                    framework.stop();
+                } catch (final BundleException e) {
+                    // ignore
+                }
+            }
+        });
+
+        this.setupFramework(framework, bundlesMap);
+
+        // finally start
+        framework.start();
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/Launcher.java b/src/main/java/org/apache/sling/feature/launcher/spi/Launcher.java
new file mode 100644
index 0000000..a1ced11
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/Launcher.java
@@ -0,0 +1,26 @@
+/*
+ * 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.sling.feature.launcher.spi;
+
+import org.apache.sling.feature.Application;
+
+public interface Launcher {
+
+    void prepare(LauncherPrepareContext context, Application app) throws Exception;
+
+    void run(LauncherRunContext context, ClassLoader cl) throws Exception;
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/LauncherPrepareContext.java b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherPrepareContext.java
new file mode 100644
index 0000000..6d6f02b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherPrepareContext.java
@@ -0,0 +1,32 @@
+/*
+ * 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.sling.feature.launcher.spi;
+
+import org.apache.sling.feature.ArtifactId;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This is the context for the launcher
+ */
+public interface LauncherPrepareContext {
+
+    void addAppJar(File jar);
+
+    File getArtifactFile(ArtifactId artifact) throws IOException;
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
new file mode 100644
index 0000000..20c95a9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
@@ -0,0 +1,58 @@
+/*
+ * 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.sling.feature.launcher.spi;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is the context for the launcher
+ */
+public interface LauncherRunContext {
+
+    /**
+     * Map of framework properties to be set when the framework is created.
+     * @return The map with the framework properties.
+     */
+    Map<String, String> getFrameworkProperties();
+
+    /**
+     * Bundle map, key is the start level, value is a list of files.
+     * @return The bundle map, might be empty
+     */
+    Map<Integer, List<File>> getBundleMap();
+
+    /**
+     * List of configurations.
+     * The value in each is an object array with three values
+     * <ol>
+     *  <li>The PID
+     *  <li>The factory PID or {@code null}
+     *  <li>The dictionary with the properties
+     * </ol>
+     * We can't use a custom object due to class loading restrictions.
+     * @return The list, might be empty
+     */
+    List<Object[]> getConfigurations();
+
+    /**
+     * List of installable artifacts.
+     * @return The list of files. The list might be empty.
+     */
+    List<File> getInstallableArtifacts();
+}

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.