You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by an...@apache.org on 2020/02/13 16:29:35 UTC

[sling-org-apache-sling-kickstart] branch master created (now f386743)

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

andysch pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-kickstart.git.


      at f386743  Renamed Quickstart to Kickstart

This branch includes the following new commits:

     new f386743  Renamed Quickstart to Kickstart

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-kickstart] 01/01: Renamed Quickstart to Kickstart

Posted by an...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f38674383ea57c1b585beb22f011779774d0ca45
Author: Andreas Schaefer <sc...@iMac.local>
AuthorDate: Thu Feb 6 13:55:37 2020 -0800

    Renamed Quickstart to Kickstart
---
 Readme.md                                          |   27 +
 pom.xml                                            |  308 ++++++
 .../apache/sling/kickstart/app/SlingStarter.java   |  322 ++++++
 .../sling/kickstart/control/ControlAction.java     |   53 +
 .../sling/kickstart/control/ControlListener.java   |  628 +++++++++++
 .../sling/kickstart/control/ControlTarget.java     |   32 +
 .../org.apache.sling.feature.launcher.spi.Launcher |    1 +
 ...eature.launcher.spi.extensions.ExtensionHandler |    2 +
 src/main/resources/feature-sling12.json            | 1139 ++++++++++++++++++++
 .../feature/starter/it/LaunchpadReadyRule.java     |  121 +++
 .../apache/sling/feature/starter/it/SmokeIT.java   |  195 ++++
 .../sling/feature/starter/it/package-info.java     |   30 +
 12 files changed, 2858 insertions(+)

diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..2fc61a3
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,27 @@
+# Sling Feature Starter
+
+This project is the Feature Model based version of the **sling-org-apache-sling-starter**
+module and creates an executable JAR file for now.
+It is also a test case for the Slingstart Feature Maven Plugin as it uses it
+to launch a Launchpad Ready Rule and Smoke tests.
+
+## Build
+
+This plugin depends on the **Sling Start Feature Maven Plugin** (also in the Sling
+Whiteboard) which is then used to run the IT tests:
+
+1. Go to **sling-slingstart-feature-maven-plugin** module in Sling Whiteboard
+2. Build with: `mvn clean install`
+3. Go back to **sling-org-apache-sling-feature-starter**
+4. Build and Launch it with: `mvn clean install`
+5. Sling will come up and run the IT tests and then shut down. Sling can be
+   kept running after the end of the IT tests by providing the property
+   **block.sling.at.the.end** with the value **true**
+
+## Usage
+
+After the resulting jar file **org.apache.sling.feature.starter-<version>.jar**
+can be executed with:
+```
+java -jar org.apache.sling.feature.starter-<version>.jar ...
+```
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fc5d676
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,308 @@
+<?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>35</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.kickstart</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>slingkickstart</packaging>
+
+    <name>Apache Sling Kickstart Launcher</name>
+    <description>
+        An Executable JAR file to launch Sling
+    </description>
+
+    <!--    <scm>-->
+    <!--        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-starter.git</connection>-->
+    <!--        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-starter.git</developerConnection>-->
+    <!--        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-feature-starter.git</url>-->
+    <!--        <tag>HEAD</tag>-->
+    <!--    </scm>-->
+
+    <properties>
+        <sling.java.version>8</sling.java.version>
+        <picocli.version>3.6.0</picocli.version>
+        <appassembler-maven-plugin.version>2.0.0</appassembler-maven-plugin.version>
+        <org.apache.sling.feature.extension.content.version>1.0.4</org.apache.sling.feature.extension.content.version>
+        <org.apache.sling.feature.launcher.version>1.1.2</org.apache.sling.feature.launcher.version>
+        <org.apache.sling.feature.io.version>1.2.2</org.apache.sling.feature.io.version>
+        <org.apache.felix.converter.version>1.0.8</org.apache.felix.converter.version>
+
+        <sling.java.version>8</sling.java.version>
+        <IT.expected.bundles.count>126</IT.expected.bundles.count>
+
+        <block.sling.at.the.end>false</block.sling.at.the.end>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>ianal-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>verify-legal-files</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-clean-plugin</artifactId>
+                <configuration>
+                    <filesets>
+                        <fileset>
+                            <directory>${basedir}</directory>
+                            <includes>
+                                <include>sling/**</include>
+                                <include>coverage.ec</include>
+                            </includes>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+            <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>
+                                commons-io,
+                                org.apache.sling.feature.extension.content,
+                                org.apache.sling.feature.launcher,
+                                osgi.core,commons-lang,
+                                org.apache.sling.feature,
+                                org.apache.sling.feature.io,
+                                org.apache.felix.converter,
+                                picocli,
+                                slf4j-api,
+                                slf4j-simple
+                            </includeArtifactIds>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.sling.kickstart.app.SlingStarter</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>readme.md</exclude>
+                        <exclude>src/main/resources/META-INF/services/**</exclude>
+                        <exclude>**/*.properties</exclude>
+                        <exclude>launcher/**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>reserve-network-port</id>
+                        <goals>
+                            <!-- pre-integration-test is too late -->
+                            <goal>reserve-network-port</goal>
+                        </goals>
+                        <phase>process-resources</phase>
+                        <configuration>
+                            <portNames>
+                                <portName>http.port</portName>
+                                <portName>sling.control.port</portName>
+                            </portNames>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>sling-kickstart-maven-plugin</artifactId>
+                <version>0.0.1-SNAPSHOT</version>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <id>start-container-before-IT</id>
+                        <goals>
+                            <goal>start</goal>
+                        </goals>
+                    </execution>
+<!--                    <execution>-->
+<!--                        <id>stop-container-after-IT</id>-->
+<!--                        <goals>-->
+<!--                            <goal>stop</goal>-->
+<!--                        </goals>-->
+<!--                        <configuration>-->
+<!--                            &lt;!&ndash; Let the Test Server run to manually verify the setup. TODO: remove later &ndash;&gt;-->
+<!--                            <shouldBlockUntilKeyIsPressed>${block.sling.at.the.end}</shouldBlockUntilKeyIsPressed>-->
+<!--                        </configuration>-->
+<!--                    </execution>-->
+                </executions>
+                <configuration>
+                    <launchpadJar>${project.build.directory}/${project.artifactId}-${project.version}.jar</launchpadJar>
+                    <parallelExecution>false</parallelExecution>
+                    <servers>
+                        <server>
+                            <port>${http.port}</port>
+                            <controlPort>${sling.control.port}</controlPort>
+                            <debug>true</debug>
+                        </server>
+                    </servers>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <systemPropertyVariables>
+                        <launchpad.http.port>${http.port}</launchpad.http.port>
+                        <IT.expected.bundles.count>${IT.expected.bundles.count}</IT.expected.bundles.count>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <!-- Extend RAT configuration from parent pom -->
+                    <groupId>org.apache.rat</groupId>
+                    <artifactId>apache-rat-plugin</artifactId>
+                    <configuration>
+                        <excludes combine.children="append">
+                            <!-- Exclude sling instance -->
+                            <exclude>sling/**</exclude>
+                        </excludes>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+    <dependencies>
+        <!--
+         | CLI
+        -->
+        <dependency>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli</artifactId>
+            <version>${picocli.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <version>7.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>6.0.2</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.extension.content</artifactId>
+            <version>${org.apache.sling.feature.extension.content.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.launcher</artifactId>
+            <version>${org.apache.sling.feature.launcher.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.io</artifactId>
+            <version>${org.apache.sling.feature.io.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.converter</artifactId>
+            <version>${org.apache.felix.converter.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+        <!-- Testing dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.10</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <version>1.11.2</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/kickstart/app/SlingStarter.java b/src/main/java/org/apache/sling/kickstart/app/SlingStarter.java
new file mode 100644
index 0000000..beda54f
--- /dev/null
+++ b/src/main/java/org/apache/sling/kickstart/app/SlingStarter.java
@@ -0,0 +1,322 @@
+/*
+ * 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.kickstart.app;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.feature.launcher.impl.Main;
+import org.apache.sling.kickstart.control.ControlAction;
+import org.apache.sling.kickstart.control.ControlListener;
+import org.apache.sling.kickstart.control.ControlTarget;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+
+@Command(
+    name = "java -jar <Sling Feature Starter JAR File>",
+    description = "Apache Sling Feature Starter",
+    footer = "Copyright(c) 2019 The Apache Software Foundation."
+)
+public class SlingStarter implements Runnable, ControlTarget {
+
+    @Option(names = { "-s", "--mainFeature" }, description = "main feature file (file path or URL) replacing the provided Sling Feature File", required = false)
+    private String mainFeatureFile;
+
+    @Option(names = { "-af", "--additionalFeature" }, description = "additional feature files", required = false)
+    private List<String> additionalFeatureFile;
+
+    @Option(names = { "-j", "--control" }, description = "host and port to use for control connection in the format '[host:]port' (default 127.0.0.1:0)", required = false)
+    private String controlAddress;
+
+    @Option(names = { "-l", "--logLevel" }, description = "the initial loglevel (0..4, FATAL, ERROR, WARN, INFO, DEBUG)", required = false)
+    private String logLevel;
+
+    @Option(names = { "-f", "--logFile" }, description = "the log file, \"-\" for stdout (default logs/error.log)", required = false)
+    private String logFile;
+
+    @Option(names = { "-c", "--slingHome" }, description = "the sling context directory (default sling)", required = false)
+    private String slingHome;
+
+    //AS TODO: does this still apply here
+    @Option(names = { "-i", "--launcherHome" }, description = "the launcher home directory (default launcher)", required = false)
+    private String launcherHome;
+
+    @Option(names = { "-a", "--address" }, description = "the interface to bind to (use 0.0.0.0 for any)", required = false)
+    private String address;
+
+    @Option(names = { "-p", "--port" }, description = "the port to listen to (default 8080)", required = false)
+    private String port;
+
+    @Option(names = { "-r", "--context" }, description = "the root servlet context path for the http service (default is /)", required = false)
+    private String contextPath;
+
+    @Option(names = { "-n", "--noShutdownHook" }, description = "don't install the shutdown hook")
+    private boolean noShutdownHook;
+
+    @Option(names = { "-v", "--verbose" }, description = "the feature launcher is verbose on launch", required = false)
+    private boolean verbose;
+
+    @Option(names = {"-D", "--define"}, description = "sets property n to value v. Make sure to use this option *after* the jar filename. " +
+        "The JVM also has a -D option which has a different meaning", required = false)
+    private Map<String, String> properties = new HashMap<>();
+
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display the usage message.")
+    private boolean helpRequested;
+
+    @Parameters(paramLabel = "COMMAND", description = "Optional Command for Server Instance Interaction, can be one of: 'start', 'stop', 'status' or 'threads'", arity = "0..1")
+    private String command;
+
+    // The name of the environment variable to consult to find out
+    // about sling.home
+    private static final String ENV_SLING_HOME = "SLING_HOME";
+
+    /**
+     * The name of the configuration property indicating the socket to use for
+     * the control connection. The value of this property is either just a port
+     * number (in which case the host is assumed to be <code>localhost</code>)
+     * or a host name (or IP address) and port number separated by a colon.
+     */
+    protected static final String PROP_CONTROL_SOCKET = "sling.control.socket";
+
+    /** The Sling configuration property name setting the initial log level */
+    private static final String PROP_LOG_LEVEL = "org.apache.sling.commons.log.level";
+
+    /** The Sling configuration property name setting the initial log file */
+    private static final String PROP_LOG_FILE = "org.apache.sling.commons.log.file";
+
+    /**
+     * The configuration property setting the port on which the HTTP service
+     * listens
+     */
+    private static final String PROP_PORT = "org.osgi.service.http.port";
+
+    /**
+     * The configuration property setting the context path where the HTTP service
+     * mounts itself.
+     */
+    private static final String PROP_CONTEXT_PATH = "org.apache.felix.http.context_path";
+
+    /**
+     * Host name or IP Address of the interface to listen on.
+     */
+    private static final String PROP_HOST = "org.apache.felix.http.host";
+
+    /**
+     * Name of the configuration property (or system property) indicating
+     * whether the shutdown hook should be installed or not. If this property is
+     * not set or set to {@code true} (case insensitive), the shutdown hook
+     * properly shutting down the framework is installed on startup. Otherwise,
+     * if this property is set to any value other than {@code true} (case
+     * insensitive) the shutdown hook is not installed.
+     * <p>
+     * The respective command line option is {@code -n}.
+     */
+    private static final String PROP_SHUTDOWN_HOOK = "sling.shutdown.hook";
+
+    private boolean started = false;
+
+    @Override
+    public void run() {
+        try {
+            URL mainFeatureURL = checkFeatureFile(mainFeatureFile);
+            if(mainFeatureURL == null) {
+                mainFeatureURL = getClass().getResource("/feature-sling12.json");
+            }
+            List<String> argumentList = new ArrayList<>();
+            argumentList.add("-f");
+            argumentList.add(mainFeatureURL.toString());
+            if(additionalFeatureFile != null) {
+                for (String additional : additionalFeatureFile) {
+                    URL additionalURL = checkFeatureFile(additional);
+                    if (additionalURL != null) {
+                        argumentList.add("-f");
+                        argumentList.add(additionalURL.toString());
+                    }
+                }
+            }
+            if(StringUtils.isNotEmpty(logLevel)) {
+                addArgument(argumentList, PROP_LOG_LEVEL, logLevel);
+            }
+            if(StringUtils.isNotEmpty(logFile)) {
+                addArgument(argumentList, PROP_LOG_FILE, logFile);
+            }
+            if(StringUtils.isNotEmpty(port)) {
+                addArgument(argumentList, PROP_PORT, port);
+            }
+            if(StringUtils.isNotEmpty(address)) {
+                addArgument(argumentList, PROP_HOST, address);
+            }
+            if(StringUtils.isNotEmpty(contextPath)) {
+                addArgument(argumentList, PROP_CONTEXT_PATH, contextPath);
+            }
+            if(verbose) {
+                argumentList.add("-v");
+            }
+            System.out.println("Before Launching Feature Launcher, arguments: " + argumentList);
+            // Now we have to handle any Start Option
+            ControlAction controlAction = getControlAction(command);
+            int answer = doControlAction(controlAction, controlAddress);
+            if (answer >= 0) {
+                doTerminateVM(answer);
+                return;
+            }
+
+            // finally start Sling
+            if (!doStart(argumentList)) {
+                error("Failed to start Sling; terminating", null);
+                doTerminateVM(1);
+                return;
+            }
+        } catch(Throwable t) {
+            System.out.println("Caught an Exception: " + t.getLocalizedMessage());
+            t.printStackTrace();
+        }
+    }
+
+    private void addArgument(List<String> list, String key, String value) {
+        list.add("-D");
+        list.add(key + "=" + value);
+    }
+
+    private URL checkFeatureFile(String featureFile) {
+        URL answer = null;
+        if(featureFile != null && !featureFile.isEmpty()) {
+            try {
+                URL check = new URL(featureFile);
+                check.toURI();
+                answer = check;
+            } catch (MalformedURLException | URISyntaxException e) {
+                // Try it as a file
+                File check = new File(featureFile);
+                if (!check.exists() || !check.canRead()) {
+                    throw new RuntimeException("Given Feature File is not a valid URL or File: '" + featureFile + "'", e);
+                }
+                try {
+                    answer = check.toURI().toURL();
+                } catch (MalformedURLException ex) {
+                    throw new RuntimeException("Given Feature File cannot be converted to an URL: '" + featureFile + "'", e);
+                }
+            }
+        }
+        return answer;
+    }
+
+    public static void main(String[] args) {
+        CommandLine.run(new SlingStarter(), args);
+    }
+
+    private int doControlAction(ControlAction controlAction, String controlAddress) {
+        final ControlListener sl = new ControlListener(
+            this,
+            controlAddress
+        );
+        switch (controlAction) {
+            case FOREGROUND:
+                if (!sl.listen()) {
+                    return -1;
+                }
+                break;
+            case START:
+                if (!sl.listen()) {
+                    // assume service already running
+                    return 0;
+                }
+                break;
+            case STOP:
+                return sl.shutdownServer();
+            case STATUS:
+                return sl.statusServer();
+            case THREADS:
+                return sl.dumpThreads();
+        }
+        return -1;
+    }
+
+    private boolean doStart(List<String> argumentList) {
+        // prevent duplicate start
+        if ( this.started) {
+            info("Apache Sling has already been started", new Exception("Where did this come from"));
+            return true;
+        }
+
+        info("Starting Apache Sling in " + slingHome, null);
+        this.started = true;
+        System.out.println("Start Command: '" + command + "'");
+        try {
+            Main.main(argumentList.toArray(new String[]{}));
+        } catch(Error | RuntimeException e) {
+            error("Launching Sling Feature failed", e);
+            return false;
+        }
+        return true;
+    }
+
+    private ControlAction getControlAction(String command) {
+        ControlAction answer = ControlAction.FOREGROUND;
+        try {
+            answer = ControlAction.valueOf(command.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException("Given Control Action is not valid: '" + command.toUpperCase() + "'");
+        } catch (NullPointerException e) {
+            // Ignore as we set the default to FOREGROUND anyhow
+        }
+        return answer;
+    }
+
+    @Override
+    public String getHome() {
+        return slingHome;
+    }
+
+    @Override
+    public void doStop() {
+        info("Stop Application", null);
+        System.exit(0);
+    }
+
+    @Override
+    public void doTerminateVM(int status) {
+        info("Terminate VM, status: " + status, null);
+        System.exit(status);
+    }
+
+    @Override
+    public void info(String message, Throwable t) {
+        System.out.println(message);
+        if(t != null) {
+            t.printStackTrace();
+        }
+    }
+
+    @Override
+    public void error(String message, Throwable t) {
+        System.err.println(message);
+        if(t != null) {
+            t.printStackTrace(System.err);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/kickstart/control/ControlAction.java b/src/main/java/org/apache/sling/kickstart/control/ControlAction.java
new file mode 100644
index 0000000..feea4ca
--- /dev/null
+++ b/src/main/java/org/apache/sling/kickstart/control/ControlAction.java
@@ -0,0 +1,53 @@
+/*
+ * 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.kickstart.control;
+
+/**
+ * The <code>ControlAction</code> defines values to used as the action for the
+ * Feature Launcher control with the {@link Main#doControlAction()} method.
+ */
+public enum ControlAction {
+
+    /**
+     * Indicates the Feature Launcher application should be started and a listener should
+     * be installed to accept control commands.
+     */
+    START,
+
+    /**
+     * Indicates to connect to a running Feature Launcher application having installed a
+     * listener and send that application the command to shutdown.
+     */
+    STOP,
+
+    /**
+     * Indicates to connect to a running Feature Launcher application having installed a
+     * listener and ask that application about its state.
+     */
+    STATUS,
+
+    FOREGROUND,
+
+    /**
+     * Indicates to connect to a running Feature Launcher application having installed a
+     * listener and ask for a thread dump.
+     */
+    THREADS;
+
+}
diff --git a/src/main/java/org/apache/sling/kickstart/control/ControlListener.java b/src/main/java/org/apache/sling/kickstart/control/ControlListener.java
new file mode 100644
index 0000000..d621842
--- /dev/null
+++ b/src/main/java/org/apache/sling/kickstart/control/ControlListener.java
@@ -0,0 +1,628 @@
+/*
+ * 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.kickstart.control;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStreamWriter;
+import java.lang.management.LockInfo;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MonitorInfo;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.math.BigInteger;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The <code>ControlListener</code> class is a helper class for the {@link ControlTarget}
+ * class to support in Sling standalone application process communication. This
+ * class implements the client and server sides of a TCP/IP based communication
+ * channel to control a running Sling application.
+ * <p>
+ * The server side listens for commands on a configurable host and port &endash;
+ * <code>localhost:63000</code> by default &endash; supporting the following
+ * commands:
+ * <table>
+ * <tr>
+ * <th>Command</th>
+ * <th>Description</th>
+ * </tr>
+ * <tr>
+ * <td><code>status</code></td>
+ * <td>Request status information. Currently only <i>OK</i> is sent back. If no
+ * connection can be created to the server the client assumes Sling is not
+ * running.</td>
+ * </tr>
+ * <tr>
+ * <td><code>stop</code></td>
+ * <td>Requests Sling to shutdown.</td>
+ * </tr>
+ * </table>
+ */
+public class ControlListener implements Runnable {
+
+    // command sent by the client to cause Sling to shutdown
+    static final String COMMAND_STOP = "stop";
+
+    // command sent by the client to check for the status of the server
+    static final String COMMAND_STATUS = "status";
+
+    // command sent by the client to request a thread dump
+    static final String COMMAND_THREADS = "threads";
+
+    // the response sent by the server if the command executed successfully
+    private static final String RESPONSE_OK = "OK";
+
+    // the status response sent by the server when shutting down
+    private static final String RESPONSE_STOPPING = "STOPPING";
+
+    // The default interface to listen on
+    private static final String DEFAULT_LISTEN_INTERFACE = "127.0.0.1";
+
+    // The default port to listen on and to connect to - we select it randomly
+    private static final int DEFAULT_LISTEN_PORT = 0;
+
+    // The reference to the Main class to shutdown on request
+    private final ControlTarget controlTarget;
+
+    private final String listenSpec;
+
+    private String secretKey;
+    private InetSocketAddress socketAddress;
+
+    private volatile Thread shutdownThread = null;
+
+    /**
+     * Creates an instance of this control support class.
+     * <p>
+     * The host (name or address) and port number of the socket is defined by
+     * the <code>listenSpec</code> parameter. This parameter is defined as
+     * <code>[ host ":" ] port</code>. If the parameter is empty or
+     * <code>null</code> it defaults to <i>localhost:0</i>. If the host name
+     * is missing it defaults to <i>localhost</i>.
+     *
+     * @param controlTarget The Main class reference. This is only required if this
+     *            instance is used for the server side to listen for remote stop
+     *            commands. Otherwise this argument may be <code>null</code>.
+     * @param listenSpec The specification for the host and port for the socket
+     *            connection. See above for the format of this parameter.
+     */
+    public ControlListener(final ControlTarget controlTarget, final String listenSpec) {
+        this.controlTarget = controlTarget;
+        this.listenSpec = listenSpec; // socketAddress = this.getSocketAddress(listenSpec, selectNewPort);
+    }
+
+    /**
+     * Implements the server side of the control connection starting a thread
+     * listening on the host and port configured on setup of this instance.
+     */
+    public boolean listen() {
+        final File configFile = getConfigFile();
+        if (configFile.canRead() && statusServer() == 0) {
+            // server already running, fail
+            controlTarget.error("Sling already active in " + this.controlTarget.getHome(), null);
+            return false;
+        }
+        configFile.delete();
+
+        final Thread listener = new Thread(this);
+        listener.setDaemon(true);
+        listener.setName("Apache Sling Control Listener (inactive)");
+        listener.start();
+        return true;
+    }
+
+    /**
+     * Implements the client side of the control connection sending the command
+     * to shutdown Sling.
+     */
+    public int shutdownServer() {
+        return sendCommand(COMMAND_STOP);
+    }
+
+    /**
+     * Implements the client side of the control connection sending the command
+     * to check whether Sling is active.
+     */
+    public int statusServer() {
+        return sendCommand(COMMAND_STATUS);
+    }
+
+    /**
+     * Implements the client side of the control connection sending the command
+     * to retrieve a thread dump.
+     */
+    public int dumpThreads() {
+        return sendCommand(COMMAND_THREADS);
+    }
+
+    // ---------- Runnable interface
+
+    /**
+     * Implements the server thread receiving commands from clients and acting
+     * upon them.
+     */
+    @Override
+    public void run() {
+        this.configure(false);
+
+        final ServerSocket server;
+        try {
+            server = new ServerSocket();
+            server.bind(this.socketAddress);
+            writePortToConfigFile(getConfigFile(),
+                new InetSocketAddress(server.getInetAddress(), server.getLocalPort()), this.secretKey);
+            Thread.currentThread().setName(
+                "Apache Sling Control Listener@" + server.getInetAddress() + ":" + server.getLocalPort());
+            controlTarget.info("Apache Sling Control Listener started", null);
+        } catch (final IOException ioe) {
+            controlTarget.error("Failed to start Apache Sling Control Listener", ioe);
+            return;
+        }
+
+        long delay = 0;
+
+        try {
+            while (true) {
+
+                final Socket s;
+                try {
+                    s = server.accept();
+                } catch (IOException ioe) {
+                    // accept terminated, most probably due to Socket.close()
+                    // just end the loop and exit
+                    break;
+                }
+
+                // delay processing after unsuccessful attempts
+                if (delay > 0) {
+                    controlTarget.info(s.getRemoteSocketAddress() + ": Delay: " + (delay / 1000), null);
+                    try {
+                        Thread.sleep(delay);
+                    } catch (InterruptedException e) {
+                    }
+                }
+
+                try {
+                    final String commandLine = readLine(s);
+                    if (commandLine == null) {
+                        final String msg = "ERR: missing command";
+                        writeLine(s, msg);
+                        continue;
+                    }
+
+                    final int blank = commandLine.indexOf(' ');
+                    if (blank < 0) {
+                        final String msg = "ERR: missing key";
+                        writeLine(s, msg);
+                        continue;
+                    }
+
+                    if (!secretKey.equals(commandLine.substring(0, blank))) {
+                        final String msg = "ERR: wrong key";
+                        writeLine(s, msg);
+                        delay = (delay > 0) ? delay * 2 : 1000L;
+                        continue;
+                    }
+
+                    final String command = commandLine.substring(blank + 1);
+                    controlTarget.info(s.getRemoteSocketAddress() + ">" + command, null);
+
+                    if (COMMAND_STOP.equals(command)) {
+                        if (this.shutdownThread != null) {
+                            writeLine(s, RESPONSE_STOPPING);
+                        } else {
+                            this.shutdownThread = new Thread("Apache Sling Control Listener: Shutdown") {
+                                @Override
+                                public void run() {
+                                    controlTarget.doStop();
+                                    try {
+                                        server.close();
+                                    } catch (final IOException ignore) {
+                                    }
+                                }
+                            };
+                            this.shutdownThread.start();
+                            writeLine(s, RESPONSE_OK);
+                        }
+
+                    } else if (COMMAND_STATUS.equals(command)) {
+                        writeLine(s, (this.shutdownThread == null) ? RESPONSE_OK : RESPONSE_STOPPING);
+
+                    } else if (COMMAND_THREADS.equals(command)) {
+                        dumpThreads(s);
+
+                    } else {
+                        final String msg = "ERR:" + command;
+                        writeLine(s, msg);
+
+                    }
+                } finally {
+                    try {
+                        s.close();
+                    } catch (IOException ignore) {
+                    }
+                }
+            }
+        } catch (final IOException ioe) {
+            controlTarget.error("Failure reading from client", ioe);
+        } finally {
+            try {
+                server.close();
+            } catch (final IOException ignore) {
+            }
+        }
+
+        getConfigFile().delete();
+
+        // everything has stopped and when this thread terminates,
+        // the VM should stop. If there are still some non-daemon threads
+        // active, this will not happen, so we force this here ...
+        controlTarget.info("Apache Sling terminated, exiting Java VM", null);
+        this.controlTarget.doTerminateVM(0);
+    }
+
+    // ---------- socket support
+
+    private void dumpThreads(final Socket socket) throws IOException {
+
+        final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        final ThreadInfo[] threadInfos = threadBean.dumpAllThreads(true, true);
+
+        for (ThreadInfo thread : threadInfos) {
+            printThread(socket, thread);
+
+            // add locked synchronizers
+            final LockInfo[] locks = thread.getLockedSynchronizers();
+            writeLine(socket, "-");
+            writeLine(socket, "-   Locked ownable synchronizers:");
+            if (locks.length > 0) {
+                for (LockInfo li : locks) {
+                    writeLine(socket, String.format("-        - locked %s",
+                        formatLockInfo(
+                            li.getClassName(),
+                            li.getIdentityHashCode()
+                        )
+                    ));
+                }
+            } else {
+                writeLine(socket, "-        - None");
+            }
+
+            // empty separator line
+            writeLine(socket, "-");
+        }
+
+        final long[] deadLocked;
+        if (threadBean.isSynchronizerUsageSupported()) {
+            deadLocked = threadBean.findDeadlockedThreads();
+        } else {
+            deadLocked = threadBean.findMonitorDeadlockedThreads();
+        }
+        if (deadLocked != null) {
+            final ThreadInfo[] dl = threadBean.getThreadInfo(deadLocked, true, true);
+            final Set<ThreadInfo> dlSet = new HashSet<ThreadInfo>(Arrays.asList(dl));
+            int deadlockCount = 0;
+            for (ThreadInfo current : dl) {
+                if (dlSet.remove(current)) {
+
+                    // find and record a single deadlock
+                    ArrayList<ThreadInfo> loop = new ArrayList<ThreadInfo>();
+                    do {
+                        loop.add(current);
+                        for (ThreadInfo cand : dl) {
+                            if (cand.getThreadId() == current.getLockOwnerId()) {
+                                current = (dlSet.remove(cand)) ? cand : null;
+                                break;
+                            }
+                        }
+                    } while (current != null);
+
+                    deadlockCount++;
+
+                    // print the deadlock
+                    writeLine(socket, "-Found one Java-level deadlock:");
+                    writeLine(socket, "-=============================");
+                    for (ThreadInfo thread : loop) {
+                        writeLine(socket, String.format("-\"%s\" #%d",
+                            thread.getThreadName(),
+                            thread.getThreadId()
+                        ));
+                        writeLine(socket, String.format("-  waiting on %s",
+                            formatLockInfo(
+                                thread.getLockInfo().getClassName(),
+                                thread.getLockInfo().getIdentityHashCode()
+                            )
+                        ));
+                        writeLine(socket, String.format("-  which is held by \"%s\" #%d",
+                            thread.getLockOwnerName(),
+                            thread.getLockOwnerId()
+                        ));
+                    }
+                    writeLine(socket, "-");
+
+                    writeLine(socket, "-Java stack information for the threads listed above:");
+                    writeLine(socket, "-===================================================");
+
+                    for (ThreadInfo thread : loop) {
+                        printThread(socket, thread);
+                    }
+                    writeLine(socket, "-");
+                }
+            }
+
+//            "Thread-8":
+//                waiting to lock monitor 7f89fb80da08 (object 7f37a0968, a java.lang.Object),
+//                which is held by "Thread-7"
+//              "Thread-7":
+//                waiting to lock monitor 7f89fb80b0b0 (object 7f37a0958, a java.lang.Object),
+//                which is held by "Thread-8"
+
+            writeLine(socket, String.format("-Found %d deadlocks.",
+                deadlockCount
+            ));
+        }
+
+        writeLine(socket, RESPONSE_OK);
+    }
+
+    private String formatLockInfo(final String className, final int objectId) {
+        return String.format("<%08x> (a %s)", objectId, className);
+    }
+
+    private void printThread(final Socket socket, final ThreadInfo thread) throws IOException {
+        writeLine(socket, String.format("-\"%s\" #%d",
+            thread.getThreadName(),
+            thread.getThreadId()
+        ));
+
+        writeLine(socket, String.format("-    java.lang.Thread.State: %s",
+            thread.getThreadState()
+        ));
+
+        final MonitorInfo[] monitors = thread.getLockedMonitors();
+        final StackTraceElement[] trace = thread.getStackTrace();
+        for (int i=0; i < trace.length; i++) {
+            StackTraceElement ste = trace[i];
+            if (ste.isNativeMethod()) {
+                writeLine(socket, String.format("-        at %s.%s(Native Method)",
+                    ste.getClassName(),
+                    ste.getMethodName()
+                ));
+            } else {
+                writeLine(socket, String.format("-        at %s.%s(%s:%d)",
+                    ste.getClassName(),
+                    ste.getMethodName(),
+                    ste.getFileName(),
+                    ste.getLineNumber()
+                ));
+            }
+
+            if (i == 0 && thread.getLockInfo() != null) {
+                writeLine(socket, String.format("-        - waiting on %s%s",
+                    formatLockInfo(
+                        thread.getLockInfo().getClassName(),
+                        thread.getLockInfo().getIdentityHashCode()
+                    ),
+                    (thread.getLockOwnerId() >= 0)
+                        ? String.format(" owned by \"%s\" #%d",
+                            thread.getLockOwnerName(),
+                            thread.getLockOwnerId()
+                        ):""
+                ));
+            }
+
+            for (MonitorInfo mi : monitors) {
+                if (i == mi.getLockedStackDepth()) {
+                    writeLine(socket, String.format("-        - locked %s",
+                        formatLockInfo(
+                            mi.getClassName(),
+                            mi.getIdentityHashCode()
+                        )
+                    ));
+                }
+            }
+        }
+    }
+
+    /**
+     * Sends the given command to the server indicated by the configured
+     * socket address and logs the reply.
+     *
+     * @param command The command to send
+     *
+     * @return A code indicating success of sending the command.
+     */
+    private int sendCommand(final String command) {
+        if (configure(true)) {
+            if (this.secretKey == null) {
+                controlTarget.info("Missing secret key to protect sending '" + command + "' to " + this.socketAddress, null);
+                return 4; // LSB code for unknown status
+            }
+
+            Socket socket = null;
+            try {
+                socket = new Socket();
+                socket.connect(this.socketAddress);
+                writeLine0(socket, this.secretKey + " " + command);
+                final String result = readLine(socket);
+                controlTarget.info("Sent '" + command + "' to " + this.socketAddress + ": " + result, null);
+                return 0; // LSB code for everything's fine
+            } catch (final ConnectException ce) {
+                controlTarget.info("No Apache Sling running at " + this.socketAddress, null);
+                return 3; // LSB code for programm not running
+            } catch (final IOException ioe) {
+                controlTarget.error("Failed sending '" + command + "' to " + this.socketAddress, ioe);
+                return 1; // LSB code for programm dead
+            } finally {
+                if (socket != null) {
+                    try {
+                        socket.close();
+                    } catch (IOException ignore) {
+                    }
+                }
+            }
+        }
+        controlTarget.info("No socket address to send '" + command + "' to", null);
+        return 4; // LSB code for unknown status
+    }
+
+    private String readLine(final Socket socket) throws IOException {
+        final BufferedReader br = new BufferedReader(new InputStreamReader(
+            socket.getInputStream(), "UTF-8"));
+
+        StringBuilder b = new StringBuilder();
+        boolean more = true;
+        while (more) {
+            String s = br.readLine();
+            if (s != null && s.startsWith("-")) {
+                s = s.substring(1);
+            } else {
+                more = false;
+            }
+            if (b.length() > 0) {
+                b.append("\r\n");
+            }
+            b.append(s);
+        }
+
+        return b.toString();
+    }
+
+    private void writeLine(final Socket socket, final String line) throws IOException {
+        controlTarget.info(socket.getRemoteSocketAddress() + "<" + line, null);
+        this.writeLine0(socket, line);
+    }
+
+    private void writeLine0(final Socket socket, final String line) throws IOException {
+        final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
+        bw.write(line);
+        bw.write("\r\n");
+        bw.flush();
+    }
+
+    /**
+     * Read the port from the config file
+     * @return The port or null
+     */
+    private boolean configure(final boolean fromConfigFile) {
+        boolean result = false;
+        if (fromConfigFile) {
+            final File configFile = this.getConfigFile();
+            if (configFile.canRead()) {
+                try ( final LineNumberReader lnr = new LineNumberReader(new FileReader(configFile))) {
+                    this.socketAddress = getSocketAddress(lnr.readLine());
+                    this.secretKey = lnr.readLine();
+                    result = true;
+                } catch (final IOException ignore) {
+                    // ignore
+                }
+            }
+        } else {
+            this.socketAddress = getSocketAddress(this.listenSpec);
+            this.secretKey = generateKey();
+            result = true;
+        }
+
+        return result;
+    }
+
+    private static String generateKey() {
+         return new BigInteger(165, new SecureRandom()).toString(32);
+    }
+
+    /**
+     * Return the control port file
+     */
+    private File getConfigFile() {
+        final File configDir = new File(this.controlTarget.getHome(), "conf");
+        return new File(configDir, "controlport");
+    }
+
+    private InetSocketAddress getSocketAddress(String listenSpec) {
+        try {
+
+            final String address;
+            final int port;
+            if (listenSpec == null) {
+                address = DEFAULT_LISTEN_INTERFACE;
+                port = DEFAULT_LISTEN_PORT;
+            } else {
+                final int colon = listenSpec.indexOf(':');
+                if (colon < 0) {
+                    address = DEFAULT_LISTEN_INTERFACE;
+                    port = Integer.parseInt(listenSpec);
+                } else {
+                    address = listenSpec.substring(0, colon);
+                    port = Integer.parseInt(listenSpec.substring(colon + 1));
+                }
+            }
+
+            final InetSocketAddress addr = new InetSocketAddress(address, port);
+            if (!addr.isUnresolved()) {
+                return addr;
+            }
+
+            controlTarget.error("Unknown host in '" + listenSpec, null);
+        } catch (final NumberFormatException nfe) {
+            controlTarget.error("Cannot parse port number from '" + listenSpec + "'",
+                null);
+        }
+
+        return null;
+    }
+
+    private void writePortToConfigFile(final File configFile, final InetSocketAddress socketAddress,
+            final String secretKey) {
+        configFile.getParentFile().mkdirs();
+        FileWriter fw = null;
+        try {
+            fw = new FileWriter(configFile);
+            fw.write(socketAddress.getAddress().getHostAddress());
+            fw.write(':');
+            fw.write(String.valueOf(socketAddress.getPort()));
+            fw.write('\n');
+            fw.write(secretKey);
+            fw.write('\n');
+        } catch (final IOException ignore) {
+            // ignore
+        } finally {
+            if (fw != null) {
+                try {
+                    fw.close();
+                } catch (final IOException ignore) {
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/kickstart/control/ControlTarget.java b/src/main/java/org/apache/sling/kickstart/control/ControlTarget.java
new file mode 100644
index 0000000..3f714ba
--- /dev/null
+++ b/src/main/java/org/apache/sling/kickstart/control/ControlTarget.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.kickstart.control;
+
+public interface ControlTarget {
+
+    public String getHome();
+
+    public void doStop();
+
+    public void doTerminateVM(int status);
+
+    void info(String message, Throwable t);
+
+    void error(String message, Throwable t);
+}
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.Launcher b/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.Launcher
new file mode 100644
index 0000000..21483ff
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.Launcher
@@ -0,0 +1 @@
+org.apache.sling.feature.launcher.impl.launchers.FrameworkLauncher
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler b/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler
new file mode 100644
index 0000000..e936f9a
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler
@@ -0,0 +1,2 @@
+org.apache.sling.feature.launcher.impl.extensions.handlers.RepoInitHandler
+org.apache.sling.feature.launcher.impl.extensions.handlers.ContentPackageHandler
diff --git a/src/main/resources/feature-sling12.json b/src/main/resources/feature-sling12.json
new file mode 100644
index 0000000..85b68c4
--- /dev/null
+++ b/src/main/resources/feature-sling12.json
@@ -0,0 +1,1139 @@
+{
+  "id":"org.apache.sling:org.apache.sling.feature.model.starter:slingosgifeature:sling12:12-SNAPSHOT",
+  "title":"Apache Sling Feature Module Starter Application",
+  "description":"The Sling Starter application built from Feature Models",
+  "vendor":"The Apache Software Foundation",
+  "license":"Apache License, Version 2.0",
+  "variables":{
+    "composum.nodes.version":"1.11.3",
+    "oak.version":"1.16.0",
+    "jackson.version":"2.9.9",
+    "slf4j.version":"1.7.25",
+    "jackrabbit.version":"2.18.2"
+  },
+  "bundles":[
+    {
+      "id":"org.apache.aries:org.apache.aries.util:1.1.3",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.commons:commons-lang3:3.8.1",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.configadmin:1.9.14",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.eventadmin:1.5.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.geronimo.specs:geronimo-annotation_1.3_spec:1.1",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.geronimo.specs:geronimo-atinject_1.0_spec:1.1",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.geronimo.specs:geronimo-ws-metadata_2.0_spec:1.1.3",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.servicemix.bundles:org.apache.servicemix.bundles.jaxb-impl:2.2.11_1",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.servicemix.bundles:org.apache.servicemix.bundles.saaj-impl:1.3.23_2",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.servicemix.specs:org.apache.servicemix.specs.jaxb-api-2.2:2.9.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.servicemix.specs:org.apache.servicemix.specs.jaxws-api-2.2:2.9.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.servicemix.specs:org.apache.servicemix.specs.saaj-api-1.3:2.8.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.log:5.1.10",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.logservice:1.0.6",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.installer.core:3.9.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.installer.factory.configuration:1.2.2",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.installer.provider.file:1.1.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.javax.activation:0.1.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.settings:1.3.10",
+      "start-order":"1"
+    },
+    {
+      "id":"org.jvnet.staxex:stax-ex:1.7.6",
+      "start-order":"1"
+    },
+    {
+      "id":"org.osgi:org.osgi.util.function:1.1.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.osgi:org.osgi.util.promise:1.1.0",
+      "start-order":"1"
+    },
+    {
+      "id":"org.slf4j:jcl-over-slf4j:1.7.25",
+      "start-order":"1"
+    },
+    {
+      "id":"org.slf4j:log4j-over-slf4j:1.7.25",
+      "start-order":"1"
+    },
+    {
+      "id":"org.slf4j:slf4j-api:1.7.25",
+      "start-order":"1"
+    },
+    {
+      "id":"com.composum.sling.core:composum-sling-core-commons:1.11.3",
+      "start-order":"20"
+    },
+    {
+      "id":"com.composum.sling.core:composum-sling-core-console:1.11.3",
+      "start-order":"20"
+    },
+    {
+      "id":"com.composum.sling.core:composum-sling-core-jslibs:1.11.3",
+      "start-order":"20"
+    },
+    {
+      "id":"com.composum.sling.core:composum-sling-package-manager:1.11.3",
+      "start-order":"20"
+    },
+    {
+      "id":"com.composum.sling.core:composum-sling-user-management:1.11.3",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.jackrabbit.vault:org.apache.jackrabbit.vault:3.2.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.healthcheck.api:2.0.2",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.healthcheck.core:2.0.6",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.healthcheck.generalchecks:2.0.4",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.healthcheck.webconsoleplugin:2.0.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.hc.api:1.0.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.hc.support:1.0.6",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.launchpad.base:6.0.2-2.6.36",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.jaas:1.0.2",
+      "start-order":"10"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-api:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-blob-plugins:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-blob:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-commons:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-core-spi:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-core:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-jcr:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-lucene:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-query-spi:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-security-spi:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-store-composite:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-store-document:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-store-spi:1.16.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.oak.server:1.2.2",
+      "start-order":"16"
+    },
+    {
+      "id":"org.apache.jackrabbit:oak-segment-tar:1.16.0",
+      "run-modes":"oak_tar",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.repoinit:1.1.10",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.provisioning.model:1.8.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.repoinit.parser:1.2.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.antlr:antlr4-runtime:4.7.1",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.servicemix.bundles:org.apache.servicemix.bundles.rhino:1.7.10_1",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.api:2.2.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.core:2.0.56",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.el-api:1.0.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.javascript:3.0.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.jsp-api:1.0.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.jsp.taglib:2.4.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.jsp:2.3.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly.compiler.java:1.1.2-1.4.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly.compiler:1.1.2-1.4.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly.js.provider:1.0.28",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly.models.provider:1.0.8",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly.repl:1.0.6",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly.runtime:1.1.0-1.4.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.scripting.sightly:1.1.2-1.4.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.caconfig.api:1.1.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.caconfig.impl:1.4.14",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.caconfig.spi:1.3.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.discovery.api:1.0.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.discovery.base:2.0.8",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.discovery.commons:1.0.20",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.discovery.oak:1.2.28",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.discovery.support:1.0.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.event.dea:1.1.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.event:4.2.12",
+      "start-order":"20"
+    },
+    {
+      "id":"com.fasterxml.jackson.core:jackson-annotations:2.9.9",
+      "start-order":"20"
+    },
+    {
+      "id":"com.fasterxml.jackson.core:jackson-core:2.9.9",
+      "start-order":"20"
+    },
+    {
+      "id":"com.fasterxml.jackson.core:jackson-databind:2.9.9",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.models.jacksonexporter:1.0.8",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.validation.api:1.0.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.validation.core:1.0.4",
+      "start-order":"20"
+    },
+    {
+      "id":"commons-codec:commons-codec:1.12",
+      "start-order":"20"
+    },
+    {
+      "id":"commons-collections:commons-collections:3.2.2",
+      "start-order":"20"
+    },
+    {
+      "id":"javax.mail:mail:1.5.0-b01",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.commons:commons-collections4:4.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.commons:commons-math:2.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.geronimo.bundles:jstl:1.2_1",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.httpcomponents:httpclient-osgi:4.5.6",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.httpcomponents:httpcore-osgi:4.4.10",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.adapter:2.1.10",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.auth.form:1.0.14",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.bundleresource.impl:2.3.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.classloader:1.4.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.compiler:2.3.6",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.fsclassloader:1.0.10",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.mime:2.2.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.osgi:2.4.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.scheduler:2.7.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.threads:3.2.18",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.engine:2.6.18",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.fsresource:2.1.14",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.i18n:2.5.14",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.installer.console:1.0.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.installer.hc:2.0.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.installer.provider.jcr:3.1.26",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.contentloader:2.3.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.resource:3.0.18",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.models.api:1.3.8",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.models.impl:1.4.10",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.resourceresolver:1.6.8",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.serviceuser.webconsole:1.0.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.serviceusermapper:1.4.4",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.servlets.get:2.1.40",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.servlets.post:2.3.30",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.servlets.resolver:2.5.2",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.xss:2.1.8",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.metatype:1.2.2",
+      "start-order":"4"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.scr:2.1.16",
+      "start-order":"4"
+    },
+    {
+      "id":"commons-fileupload:commons-fileupload:1.3.3",
+      "start-order":"5"
+    },
+    {
+      "id":"commons-io:commons-io:2.6",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.aries.jmx:org.apache.aries.jmx.api:1.1.5",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.aries.jmx:org.apache.aries.jmx.core:1.1.8",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.aries.jmx:org.apache.aries.jmx.whiteboard:1.2.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.bundlerepository:2.0.10",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.http.whiteboard:4.0.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.inventory:1.0.6",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.prefs:1.1.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.webconsole.plugins.ds:2.1.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.webconsole.plugins.event:1.1.8",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.webconsole.plugins.memoryusage:1.0.8",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.webconsole.plugins.obr:1.0.4",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.webconsole.plugins.packageadmin:1.0.4",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.webconsole:4.3.8",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.api:2.20.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.auth.core:1.4.2",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.johnzon:1.1.2",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.log.webconsole:1.0.0",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.extensions.threaddump:0.2.2",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.extensions.webconsolebranding:1.0.2",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.extensions.webconsolesecurityprovider:1.2.2",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.starter.content:1.0.4",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.http.sslfilter:1.2.6",
+      "start-order":"10"
+    },
+    {
+      "id":"org.apache.pdfbox:fontbox:2.0.16",
+      "start-order":"10"
+    },
+    {
+      "id":"org.apache.pdfbox:jempbox:1.8.16",
+      "start-order":"10"
+    },
+    {
+      "id":"org.apache.pdfbox:pdfbox:2.0.16",
+      "start-order":"10"
+    },
+    {
+      "id":"org.apache.tika:tika-core:1.21",
+      "start-order":"10"
+    },
+    {
+      "id":"org.apache.tika:tika-parsers:1.21",
+      "start-order":"10"
+    },
+    {
+      "id":"com.google.guava:guava:15.0",
+      "start-order":"15"
+    },
+    {
+      "id":"io.dropwizard.metrics:metrics-core:3.2.6",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-api:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-data:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-jcr-commons:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-jcr-rmi:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-spi-commons:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-spi:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.jackrabbit:jackrabbit-webdav:2.18.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.commons.metrics:1.2.6",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.api:2.4.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.base:3.0.6",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.davex:1.3.10",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.jackrabbit.accessmanager:3.0.4",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.jackrabbit.usermanager:2.2.8",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.jcr-wrapper:2.0.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.registration:1.0.6",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.webconsole:1.0.2",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.webdav:2.3.8",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.resource.filter:1.0.0",
+      "start-order":"15"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.sample.slingshot:0.9.0",
+      "start-order":"20"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.http.jetty:4.0.8",
+      "run-modes":":standalone",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.felix:org.apache.felix.http.servlet-api:1.1.2",
+      "run-modes":":standalone",
+      "start-order":"5"
+    },
+    {
+      "id":"org.apache.sling:org.apache.sling.jcr.packageinit:0.0.1-SNAPSHOT",
+      "start-order":"20"
+    }
+  ],
+  "configurations":{
+    "org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment~composum":{
+      "whitelist.bundles":[
+        "com.composum.core.commons",
+        "com.composum.core.pckgmgr",
+        "com.composum.core.pckginstall"
+      ],
+      "whitelist.name":"composum"
+    },
+    "org.apache.felix.hc.generalchecks.BundlesStartedCheck":{
+      "hc.tags":[
+        "bundles"
+      ]
+    },
+    "org.apache.felix.hc.generalchecks.CpuCheck":{
+      "hc.tags":[
+        "cpu",
+        "system-resources"
+      ],
+      "cpuPercentageThresholdWarn":95
+    },
+    "org.apache.felix.hc.generalchecks.DiskSpaceCheck":{
+      "hc.tags":[
+        "diskspace",
+        "system-resources"
+      ],
+      "diskPaths":[
+        "."
+      ]
+    },
+    "org.apache.felix.hc.generalchecks.FrameworkStartCheck":{
+      "hc.tags":[
+        "systemalive"
+      ],
+      "targetStartLevel:Integer":"30"
+    },
+    "org.apache.felix.hc.generalchecks.MemoryCheck":{
+      "hc.tags":[
+        "memory",
+        "system-resources"
+      ],
+      "heapUsedPercentageThresholdCritical":100,
+      "heapUsedPercentageThresholdWarn":95
+    },
+    "org.apache.felix.hc.generalchecks.ServicesCheck":{
+      "hc.tags":[
+        "systemalive"
+      ],
+      "services.list":[
+        "org.apache.sling.jcr.api.SlingRepository",
+        "org.apache.sling.engine.auth.Authenticator",
+        "org.apache.sling.api.resource.ResourceResolverFactory",
+        "org.apache.sling.api.servlets.ServletResolver",
+        "javax.script.ScriptEngineManager"
+      ]
+    },
+    "org.apache.felix.hc.generalchecks.ThreadUsageCheck":{
+      "hc.tags":[
+        "threads",
+        "cpu",
+        "system-resources"
+      ]
+    },
+    "org.apache.felix.hc.core.impl.filter.ServiceUnavailableFilter~startupandshutdown":{
+      "osgi.http.whiteboard.filter.regex":"(?!/system/).*",
+      "avoid404DuringStartup":true,
+      "service.ranking:Integer":"2147483647",
+      "includeExecutionResult":false,
+      "osgi.http.whiteboard.context.select":"(osgi.http.whiteboard.context.name=*)",
+      "tags":[
+        "systemalive"
+      ],
+      "autoDisableFilter":true,
+      "responseTextFor503":"classpath:org.apache.sling.starter.content:content/content/startup/index.html"
+    },
+    "org.apache.felix.hc.core.impl.servlet.HealthCheckExecutorServlet~default":{
+      "servletPath":"/system/health"
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~hc-support":{
+      "user.mapping":[
+        "org.apache.sling.hc.support=sling-readall"
+      ]
+    },
+    "org.apache.felix.jaas.ConfigurationSpi":{
+      "jaas.defaultRealmName":"jackrabbit.oak",
+      "jaas.configProviderName":"FelixJaasProvider"
+    },
+    "org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl":{
+      "org.apache.jackrabbit.oak.authentication.configSpiName":"FelixJaasProvider"
+    },
+    "org.apache.jackrabbit.oak.security.user.RandomAuthorizableNodeName":{
+      "length:Integer":"21"
+    },
+    "org.apache.jackrabbit.oak.security.user.UserConfigurationImpl":{
+      "groupsPath":"/home/groups",
+      "defaultDepth":"1",
+      "importBehavior":"besteffort",
+      "usersPath":"/home/users"
+    },
+    "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider":{
+      "userPrivilegeNames":[
+        "jcr:all"
+      ],
+      "groupPrivilegeNames":[
+        "jcr:read"
+      ],
+      "enabledActions":[
+        "org.apache.jackrabbit.oak.spi.security.user.action.AccessControlAction"
+      ]
+    },
+    "org.apache.felix.jaas.Configuration.factory~GuestLoginModule":{
+      "jaas.controlFlag":"optional",
+      "jaas.classname":"org.apache.jackrabbit.oak.spi.security.authentication.GuestLoginModule",
+      "jaas.ranking:Integer":"300"
+    },
+    "org.apache.felix.jaas.Configuration.factory~LoginModuleImpl":{
+      "jaas.controlFlag":"required",
+      "jaas.classname":"org.apache.jackrabbit.oak.security.authentication.user.LoginModuleImpl"
+    },
+    "org.apache.felix.jaas.Configuration.factory~TokenLoginModule":{
+      "jaas.controlFlag":"sufficient",
+      "jaas.classname":"org.apache.jackrabbit.oak.security.authentication.token.TokenLoginModule",
+      "jaas.ranking:Integer":"200"
+    },
+    "org.apache.jackrabbit.oak.segment.SegmentNodeStoreService":{
+      "name":"Default NodeStore"
+    },
+    "org.apache.sling.scripting.core.impl.ScriptCacheImpl":{
+      "org.apache.sling.scripting.cache.additional_extensions":[
+        "js"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~scripting":{
+      "user.mapping":[
+        "org.apache.sling.scripting.core=sling-scripting",
+        "org.apache.sling.scripting.sightly.js.provider=sling-scripting"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~sling-caconfig":{
+      "user.mapping":[
+        "org.apache.sling.caconfig.impl=sling-readall"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~sling.discovery":{
+      "user.mapping":[
+        "org.apache.sling.discovery.commons=sling-discovery",
+        "org.apache.sling.discovery.base=sling-discovery",
+        "org.apache.sling.discovery.oak=sling-discovery"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~sling.event":{
+      "user.mapping":[
+        "org.apache.sling.event=sling-event",
+        "org.apache.sling.event.dea=sling-event"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~validation":{
+      "user.mapping":[
+        "org.apache.sling.validation.core=sling-validation"
+      ]
+    },
+    "org.apache.sling.commons.log.LogManager":{
+      "org.apache.sling.commons.log.packagingDataEnabled":true,
+      "org.apache.sling.commons.log.pattern":"%d{dd.MM.yyyy HH:mm:ss.SSS} *%level* [%thread] %logger %msg%n",
+      "org.apache.sling.commons.log.level":"info",
+      "org.apache.sling.commons.log.file":"logs/error.log",
+      "org.apache.sling.commons.log.file.number:Integer":"7",
+      "org.apache.sling.commons.log.file.size":"'.'yyyy-MM-dd"
+    },
+    "org.apache.sling.engine.impl.log.RequestLogger":{
+      "access.log.enabled":true,
+      "request.log.outputtype:Integer":"0",
+      "access.log.output":"log.access",
+      "request.log.output":"log.request",
+      "request.log.enabled":true,
+      "access.log.outputtype:Integer":"0"
+    },
+    "org.apache.sling.jcr.davex.impl.servlets.SlingDavExServlet":{
+      "alias":"/server"
+    },
+    "org.apache.sling.jcr.webdav.impl.servlets.SimpleWebDavServlet":{
+      "dav.root":"/dav"
+    },
+    "org.apache.sling.commons.log.LogManager.factory.config~access.log":{
+      "org.apache.sling.commons.log.pattern":"%msg%n",
+      "org.apache.sling.commons.log.names":[
+        "log.access"
+      ],
+      "org.apache.sling.commons.log.level":"info",
+      "org.apache.sling.commons.log.file":"logs/access.log"
+    },
+    "org.apache.sling.commons.log.LogManager.factory.config~request.log":{
+      "org.apache.sling.commons.log.pattern":"%msg%n",
+      "org.apache.sling.commons.log.names":[
+        "log.request"
+      ],
+      "org.apache.sling.commons.log.level":"info",
+      "org.apache.sling.commons.log.file":"logs/request.log"
+    },
+    "org.apache.sling.jcr.base.internal.LoginAdminWhitelist.fragment~sling":{
+      "whitelist.bundles":[
+        "org.apache.sling.discovery.commons",
+        "org.apache.sling.discovery.base",
+        "org.apache.sling.discovery.oak",
+        "org.apache.sling.extensions.webconsolesecurityprovider",
+        "org.apache.sling.i18n",
+        "org.apache.sling.jcr.base",
+        "org.apache.sling.jcr.contentloader",
+        "org.apache.sling.jcr.jackrabbit.usermanager",
+        "org.apache.sling.jcr.oak.server",
+        "org.apache.sling.jcr.repoinit",
+        "org.apache.sling.jcr.webconsole",
+        "org.apache.sling.servlets.post",
+        "org.apache.sling.serviceuser.webconsole"
+      ],
+      "whitelist.name":"sling"
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~i18n":{
+      "user.mapping":[
+        "org.apache.sling.i18n=sling-i18n"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~jcr-install":{
+      "user.mapping":[
+        "org.apache.sling.installer.provider.jcr=sling-jcr-install"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~jcr-resource":{
+      "user.mapping":[
+        "org.apache.sling.jcr.resource:validation=sling-readall"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~observation":{
+      "user.mapping":[
+        "org.apache.sling.jcr.resource:observation=sling-readall"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~resourceresolver":{
+      "user.mapping":[
+        "org.apache.sling.resourceresolver:mapping=sling-mapping",
+        "org.apache.sling.resourceresolver:hierarchy=sling-readall",
+        "org.apache.sling.resourceresolver:observation=sling-readall",
+        "org.apache.sling.resourceresolver:console=sling-readall"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~servletsresolver":{
+      "user.mapping":[
+        "org.apache.sling.servlets.resolver:console=sling-readall",
+        "org.apache.sling.servlets.resolver:scripts=sling-scripting"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~xss":{
+      "user.mapping":[
+        "org.apache.sling.xss=sling-xss"
+      ]
+    },
+    "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~sling.slingshot":{
+      "user.mapping":[
+        "org.apache.sling.sample.slingshot=slingshot-service"
+      ]
+    }
+  },
+  "framework-properties":{
+    "sling.run.mode.install.options":"oak_tar,oak_mongo",
+    "sling.jre.java.xml":",javax.xml;version=\"2.1.0\",javax.xml.datatype;uses:=\"javax.xml.namespace\";version=\"2.1.0\",javax.xml.namespace;version=\"2.1.0\",javax.xml.parsers;uses:=\"javax.xml.validation,org.w3c.dom,org.xml.sax,org.xml.sax.helpers\";version=\"2.1.0\",javax.xml.stream;uses:=\"javax.xml.namespace,javax.xml.stream.events,javax.xml.stream.util,javax.xml.transform\";version=\"1.0.0\",javax.xml.stream.events;uses:=\"javax.xml.namespace,javax.xml.stream\";version=\"1.0.0\",j [...]
+    "felix.systempackages.calculate.uses":"true",
+    "localIndexDir":"${sling.home}/repository/index",
+    "org.osgi.framework.system.packages":"org.osgi.framework;version=\"1.9\",org.osgi.framework.dto;version=\"1.8\";uses:=\"org.osgi.dto\",org.osgi.framework.hooks.bundle;version=\"1.1\";uses:=\"org.osgi.framework\",org.osgi.framework.hooks.resolver;version=\"1.0\";uses:=\"org.osgi.framework.wiring\",org.osgi.framework.hooks.service;version=\"1.1\";uses:=\"org.osgi.framework\",org.osgi.framework.hooks.weaving;version=\"1.1\";uses:=\"org.osgi.framework.wiring\",org.osgi.framework.launch;v [...]
+    "repository.home":"${sling.home}/repository",
+    "felix.systempackages.substitution":"true",
+    "sling.jre-jpms":"{dollar}{felix.jpms.java.base}{dollar}{felix.jpms.java.compiler}{dollar}{felix.jpms.java.datatransfer}{dollar}{felix.jpms.java.desktop}{dollar}{felix.jpms.java.instrument}{dollar}{felix.jpms.java.logging}{dollar}{felix.jpms.java.management}{dollar}{felix.jpms.java.management.rmi}{dollar}{felix.jpms.java.naming}{dollar}{felix.jpms.java.net.http}{dollar}{felix.jpms.java.prefs}{dollar}{felix.jpms.java.rmi}{dollar}{felix.jpms.java.scripting}{dollar}{felix.jpms.java.se}{ [...]
+    "sling.jpms.java.xml":"{dollar}{sling.jre.java.xml},javax.xml.catalog;uses:=\"javax.xml.namespace\";version=\"1.0.0\"",
+    "sling.jre-1.8":",java.applet;version=\"{dollar}{felix.detect.java.version}\",java.awt;version=\"{dollar}{felix.detect.java.version}\",java.awt.color;version=\"{dollar}{felix.detect.java.version}\",java.awt.datatransfer;version=\"{dollar}{felix.detect.java.version}\",java.awt.dnd;version=\"{dollar}{felix.detect.java.version}\",java.awt.event;version=\"{dollar}{felix.detect.java.version}\",java.awt.font;version=\"{dollar}{felix.detect.java.version}\",java.awt.geom;version=\"{dollar}{f [...]
+  },
+  "assembled-features:ARTIFACTS|TRANSIENT":[
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:boot:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:composum_composum-nodes:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:healthcheck:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:launchpad:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:oak:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:repoinit:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:scripting_sling:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling-caconfig:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling-discovery:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling-event:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling-models-jacksonexporter_models-jacksonexporter:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling-validation:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:sling_slingshot:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:standalone:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.feature.model.starter:slingfeature:webapp:12-SNAPSHOT",
+    "org.apache.sling:org.apache.sling.jcr.packageinit:slingosgifeature:jcr-packageinit:0.0.1-SNAPSHOT"
+  ],
+  "repoinit:TEXT|true":[
+    "# general",
+    "create path (sling:OrderedFolder) /content",
+    "set ACL for everyone",
+    "allow   jcr:read\ton /content",
+    "end",
+    "",
+    "# sling-mapping",
+    "create service user sling-mapping",
+    "",
+    "set ACL for sling-mapping",
+    "allow   jcr:read    on /",
+    "end",
+    "",
+    "# sling-readall",
+    "create service user sling-readall",
+    "",
+    "set ACL for sling-readall",
+    "allow   jcr:read    on /",
+    "end",
+    "",
+    "# sling-xss",
+    "create service user sling-xss",
+    "",
+    "create path (sling:Folder) /apps/sling/xss",
+    "",
+    "set ACL for sling-xss",
+    "allow   jcr:read    on /apps/sling/xss",
+    "end",
+    "",
+    "# sling-i18n",
+    "create service user sling-i18n",
+    "",
+    "set ACL for sling-i18n",
+    "allow   jcr:read    on /",
+    "end",
+    "",
+    "# sling-jcr-install",
+    "create service user sling-jcr-install",
+    "",
+    "# used for config OSGi writeback",
+    "create path (sling:Folder) /apps/sling/install",
+    "",
+    "set ACL for sling-jcr-install",
+    "allow\tjcr:read\ton\t/",
+    "allow\trep:write\ton /apps/sling/install",
+    "end",
+    "",
+    "#<<< SLING-5848 - Define service user and ACLs for Scripting",
+    "create service user sling-scripting",
+    "",
+    "create path (sling:Folder) /libs",
+    "create path (sling:Folder) /apps",
+    "",
+    "set ACL for sling-scripting",
+    "deny    jcr:all     on /",
+    "allow   jcr:read    on /libs,/apps",
+    "end",
+    "# SLING-5848 - Define service user and ACLs for Scripting >>>",
+    "",
+    "create path (sling:Folder) /conf",
+    "",
+    "create service user sling-discovery",
+    "",
+    "create path (sling:Folder) /var/discovery",
+    "create path (sling:Folder) /var/discovery/oak",
+    "",
+    "set ACL for sling-discovery",
+    "allow   jcr:read,rep:write    on /var/discovery",
+    "end",
+    "",
+    "create service user sling-event",
+    "",
+    "create path (sling:Folder) /var",
+    "create path (sling:Folder) /var/eventing",
+    "",
+    "set ACL for sling-event",
+    "allow   jcr:read,rep:write    on /var/eventing",
+    "end",
+    "",
+    "create service user sling-validation",
+    "",
+    "create path (sling:Folder) /apps",
+    "create path (sling:Folder) /libs",
+    "",
+    "set ACL for sling-validation",
+    "allow   jcr:read    on /apps",
+    "allow   jcr:read    on /libs",
+    "end",
+    "",
+    "create service user slingshot-service",
+    "create user slingshot1 with password slingshot1",
+    "create user slingshot2 with password slingshot2",
+    "",
+    "create path (sling:Folder) /content/slingshot",
+    "create path (sling:Folder) /content/slingshot/users",
+    "create path (sling:Folder) /content/slingshot/users/slingshot1",
+    "create path (sling:Folder) /content/slingshot/users/slingshot2",
+    "",
+    "set ACL for slingshot-service",
+    "allow   jcr:read,rep:write    on /content/slingshot",
+    "end",
+    "",
+    "set ACL for slingshot1",
+    "allow   jcr:read,rep:write    on /content/slingshot/users/slingshot1",
+    "end",
+    "",
+    "set ACL for slingshot2",
+    "allow   jcr:read,rep:write    on /content/slingshot/users/slingshot2",
+    "end"
+  ]
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/starter/it/LaunchpadReadyRule.java b/src/test/java/org/apache/sling/feature/starter/it/LaunchpadReadyRule.java
new file mode 100644
index 0000000..f82bc3a
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/starter/it/LaunchpadReadyRule.java
@@ -0,0 +1,121 @@
+/*
+ * 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.starter.it;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.ConnectException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.rules.ExternalResource;
+
+public class LaunchpadReadyRule extends ExternalResource {
+
+    private static final int TRIES = 60;
+    private static final int WAIT_BETWEEN_TRIES_MILLIS = 1000;
+
+    private final List<Check> checks = new ArrayList<>();
+
+    public LaunchpadReadyRule(int launchpadPort) {
+
+        checks.add(new Check("http://localhost:" + launchpadPort + "/server/default/jcr:root/content"));
+        checks.add(new Check("http://localhost:" + launchpadPort + "/starter/index.html") {
+            @Override
+            public String runCheck(HttpResponse response) throws Exception {
+                try (InputStreamReader isr = new InputStreamReader(response.getEntity().getContent());
+                        BufferedReader reader = new BufferedReader(isr)) {
+
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        if (line.contains("Do not remove this comment, used for Starter integration tests")) {
+                            return null;
+                        }
+                    }
+                }
+
+                return "Did not find 'ready' marker in the response body";
+            }
+        });
+    }
+
+    @Override
+    protected void before() throws Throwable {
+
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+            for (Check check : checks) {
+                runCheck(client, check);
+            }
+        }
+    }
+
+    private void runCheck(CloseableHttpClient client, Check check) throws Exception {
+
+        String lastFailure = null;
+        HttpGet get = new HttpGet(check.getUrl());
+        
+        for (int i = 0; i < TRIES; i++) {
+            try (CloseableHttpResponse response = client.execute(get)) {
+
+                if (response.getStatusLine().getStatusCode() != 200) {
+                    lastFailure = "Status code is " + response.getStatusLine();
+                    Thread.sleep(WAIT_BETWEEN_TRIES_MILLIS);
+                    continue;
+                }
+
+                lastFailure = check.runCheck(response);
+                if (lastFailure == null) {
+                    return;
+                }
+            } catch ( ConnectException e ) {
+                lastFailure = e.getClass().getName() + " : " + e.getMessage();
+            }
+
+            Thread.sleep(WAIT_BETWEEN_TRIES_MILLIS);
+        }
+        
+        throw new RuntimeException(String.format("Launchpad not ready. Failed check for URL %s with message '%s'",
+                check.getUrl(), lastFailure));
+    }
+
+    static class Check {
+        private String url;
+
+        public Check(String url) {
+            this.url = url;
+        }
+
+        public String getUrl() {
+            return url;
+        }
+
+        /**
+         * @param response the HttpResponse
+         * @return null if check check was successful, an error description otherwise
+         * @throws Exception
+         */
+        public String runCheck(HttpResponse response) throws Exception {
+            return null;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/starter/it/SmokeIT.java b/src/test/java/org/apache/sling/feature/starter/it/SmokeIT.java
new file mode 100644
index 0000000..785e29e
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/starter/it/SmokeIT.java
@@ -0,0 +1,195 @@
+/*
+ * 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.starter.it;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.felix.utils.json.JSONParser;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class SmokeIT {
+
+    private static final int LAUNCHPAD_PORT = Integer.getInteger("launchpad.http.port", 8080);
+    private static final int EXPECTED_BUNDLES_COUNT = Integer.getInteger("IT.expected.bundles.count", Integer.MAX_VALUE);
+
+    @ClassRule
+    public static LaunchpadReadyRule LAUNCHPAD = new LaunchpadReadyRule(LAUNCHPAD_PORT);
+    private HttpClientContext httpClientContext;
+
+    @Before
+    public void prepareHttpContext() {
+
+        CredentialsProvider credsProvider = new BasicCredentialsProvider();
+        UsernamePasswordCredentials creds = new UsernamePasswordCredentials("admin", "admin");
+        credsProvider.setCredentials(new AuthScope("localhost", LAUNCHPAD_PORT), creds);
+
+        BasicAuthCache authCache = new BasicAuthCache();
+        BasicScheme basicAuth = new BasicScheme();
+        authCache.put(new HttpHost("localhost", LAUNCHPAD_PORT, "http"), basicAuth);
+
+        httpClientContext = HttpClientContext.create();
+        httpClientContext.setCredentialsProvider(credsProvider);
+        httpClientContext.setAuthCache(authCache);
+    }
+
+    private CloseableHttpClient newClient() {
+
+        return HttpClientBuilder.create()
+                .setDefaultCredentialsProvider(httpClientContext.getCredentialsProvider())
+                .build();
+    }
+
+    @Test
+    public void verifyAllBundlesStarted() throws Exception {
+
+        try ( CloseableHttpClient client = newClient() ) {
+
+            HttpGet get = new HttpGet("http://localhost:" + LAUNCHPAD_PORT + "/system/console/bundles.json");
+
+            // pass the context to ensure preemptive basic auth is used
+            // https://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html
+            try ( CloseableHttpResponse response = client.execute(get, httpClientContext) ) {
+
+                if ( response.getStatusLine().getStatusCode() != 200 ) {
+                    fail("Unexpected status line " + response.getStatusLine());
+                }
+
+                Header contentType = response.getFirstHeader("Content-Type");
+                assertThat("Content-Type header", contentType.getValue(), CoreMatchers.startsWith("application/json"));
+
+                Map<String, Object> obj = new JSONParser(response.getEntity().getContent()).getParsed();
+
+                @SuppressWarnings("unchecked")
+                List<Object> status = (List<Object>) obj.get("s");
+
+                @SuppressWarnings("unchecked")
+                List<Object> bundles = (List<Object>) obj.get("data");
+                if(bundles.size() < EXPECTED_BUNDLES_COUNT) {
+                    fail("Expected at least " + EXPECTED_BUNDLES_COUNT + " bundles, got " + bundles.size());
+                }
+
+                BundleStatus bs = new BundleStatus(status);
+
+                if ( bs.resolvedBundles != 0 || bs.installedBundles != 0 ) {
+
+                    StringBuilder out = new StringBuilder();
+                    out.append("Expected all bundles to be active, but instead got ")
+                        .append(bs.resolvedBundles).append(" resolved bundles, ")
+                        .append(bs.installedBundles).append(" installed bundlles: ");
+
+                    for ( int i = 0 ; i < bundles.size(); i++ ) {
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> bundle = (Map<String, Object>) bundles.get(i);
+
+                        String bundleState = (String) bundle.get("state");
+                        String bundleSymbolicName = (String) bundle.get("symbolicName");
+                        String bundleVersion = (String) bundle.get("version");
+
+                        switch ( bundleState ) {
+                            case "Active":
+                            case "Fragment":
+                                continue;
+
+                            default:
+                                out.append("\n- ").append(bundleSymbolicName).append(" ").append(bundleVersion).append(" is in state " ).append(bundleState);
+                        }
+                    }
+
+                    fail(out.toString());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void ensureRepositoryIsStarted() throws Exception {
+        try ( CloseableHttpClient client = newClient() ) {
+
+            HttpGet get = new HttpGet("http://localhost:" + LAUNCHPAD_PORT + "/server/default/jcr:root/content");
+
+            try ( CloseableHttpResponse response = client.execute(get) ) {
+
+                if ( response.getStatusLine().getStatusCode() != 200 ) {
+                    fail("Unexpected status line " + response.getStatusLine());
+                }
+
+                Header contentType = response.getFirstHeader("Content-Type");
+                assertThat("Content-Type header", contentType.getValue(), equalTo("text/xml"));
+
+                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+                dbf.setNamespaceAware(true);
+                DocumentBuilder db = dbf.newDocumentBuilder();
+                Document document = db.parse(response.getEntity().getContent());
+
+                Element docElement = document.getDocumentElement();
+                NamedNodeMap attrs = docElement.getAttributes();
+
+                Node nameAttr = attrs.getNamedItemNS("http://www.jcp.org/jcr/sv/1.0", "name");
+                assertThat("no 'name' attribute found", nameAttr, notNullValue());
+                assertThat("Invalid name attribute value", nameAttr.getNodeValue(), equalTo("content"));
+            }
+        }
+    }
+
+    static class BundleStatus {
+
+        long totalBundles;
+        long activeBundles;
+        long activeFragments;
+        long resolvedBundles;
+        long installedBundles;
+
+        public BundleStatus(List<Object> array) {
+
+            totalBundles = (Long)array.get(0);
+            activeBundles = (Long)array.get(1);
+            activeFragments = (Long)array.get(2);
+            resolvedBundles = (Long)array.get(3);
+            installedBundles = (Long)array.get(4);
+
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/feature/starter/it/package-info.java b/src/test/java/org/apache/sling/feature/starter/it/package-info.java
new file mode 100644
index 0000000..f70e5e8
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/starter/it/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/**
+ * <h1>Smoke tests for the Sling Launchpad</h1>
+ * 
+ * <p>This package contains a minimal set of tests for the Sling launchpad. The
+ * tests validate that the launchpad is correctly assembled and that there are
+ * no obvious mistakes such as bundles that can't be wired. More extensive
+ * tests must be placed in specific test projects.</p>
+ * 
+ * <p>The launchpad tests don't depend on other Sling bundles,to prevent circular
+ * dependencies. As such, there is some duplication of code ( at least intent, if 
+ * not implementation ) with some of the testing projects. This is another 
+ * argument for keeping the tests minimal.</p>
+ */
+package org.apache.sling.launchpad;
\ No newline at end of file