Renamed Quickstart to Kickstart
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..68b3fa6
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,51 @@
+# Sling Start Feature Maven Plugin
+
+This Maven Plugin is the Feature Model based version of the Slingstart
+Maven Plugin. It does not depend on its predecessor to keep the Provisioning Model
+and Feature Model code bases separate.
+
+## Build
+
+This plugin is built like usual with:
+```
+mvn clean install
+```
+
+## Usage
+
+The plugin can be used (see **sling-org-apache-sling-feature-starter**
+module in Sling Whiteboard) like this:
+```
+<plugin>
+    <groupId>org.apache.sling</groupId>
+    <artifactId>slingstart-feature-maven-plugin</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <extensions>true</extensions>
+    <executions>
+        <execution>
+            <id>start-container</id>
+            <goals>
+                <goal>start</goal>
+                <goal>stop</goal>
+            </goals>
+        </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>
+                <stdOutFile>launchpad.out</stdOutFile>
+            </server>
+        </servers>
+    </configuration>
+</plugin>
+```
+
+## Notes
+
+For now this Plugin only supports the starting and stopping of a Sling
+instance for example to run IT tests.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9372f16
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,342 @@
+<?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>sling-kickstart-maven-plugin</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>maven-plugin</packaging>
+
+    <name>Apache Sling Quickstart Maven Plugin</name>
+    <description>
+        Maven Plugin providing the ability to start/stop Sling Quickstart
+    </description>
+    <url>https://sling.apache.org/components/slingstart-maven-plugin/</url>
+
+    <properties>
+        <maven.version>3.0.5</maven.version>
+        <maven.site.path>${project.artifactId}-archives/${project.artifactId}-LATEST</maven.site.path>
+        <sling.java.version>8</sling.java.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.0</org.apache.sling.feature.launcher.version>
+        <org.apache.sling.feature.io.version>1.1.0</org.apache.sling.feature.io.version>
+        <org.apache.felix.converter.version>1.0.8</org.apache.felix.converter.version>
+        <org.apache.sling.feature.version>1.1.0</org.apache.sling.feature.version>
+        <org.apache.sling.feature.analyser.version>1.1.0</org.apache.sling.feature.analyser.version>
+        <org.apache.sling.feature.modelconverter.version>1.0.8</org.apache.sling.feature.modelconverter.version>
+    </properties>
+
+<!--    <scm>-->
+<!--        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-slingstart-maven-plugin.git</connection>-->
+<!--        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-slingstart-maven-plugin.git</developerConnection>-->
+<!--        <url>https://gitbox.apache.org/repos/asf?p=sling-slingstart-maven-plugin.git</url>-->
+<!--       <tag>HEAD</tag>-->
+<!--    </scm>-->
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>enforce-java</id>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <configuration>
+                            <rules>
+                                <requireJavaVersion>
+                                    <message>${project.name} must be compiled with Java 1.8 or higher.</message>
+                                    <version>1.${sling.java.version}.0</version>
+                                </requireJavaVersion>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.plexus</groupId>
+                <artifactId>plexus-component-metadata</artifactId>
+                <version>1.7.1</version>
+                <executions>
+                    <execution>
+                        <id>generate-metadata</id>
+                        <goals>
+                            <goal>generate-metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-plugin-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>mojo-descriptor</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>descriptor</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>generated-helpmojo</id>
+                        <goals>
+                            <goal>helpmojo</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-scm-publish-plugin</artifactId>
+                <configuration>
+                    <checkoutDirectory>${user.home}/maven-sites/${maven.site.path}</checkoutDirectory>
+                    <tryUpdate>true</tryUpdate>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>src/site/markdown/**</exclude>
+                        <exclude>src/test/resources/**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <!-- for https://issues.apache.org/jira/browse/SUREFIRE-1067 -->
+                <version>2.20.1</version>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <!-- for https://issues.apache.org/jira/browse/SUREFIRE-855 -->
+                <version>2.20.1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <systemPropertyVariables>
+                        <project.version>${project.version}</project.version>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+<!--        <dependency>-->
+<!--            <groupId>org.apache.sling</groupId>-->
+<!--            <artifactId>org.apache.sling.provisioning.model</artifactId>-->
+<!--            <version>1.8.4</version>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature</artifactId>
+            <version>${org.apache.sling.feature.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.analyser</artifactId>
+            <version>${org.apache.sling.feature.analyser.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.modelconverter</artifactId>
+            <version>${org.apache.sling.feature.modelconverter.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.maven</groupId>
+            <artifactId>maven-core</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-plugin-api</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-artifact</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-compat</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.plugin-tools</groupId>
+            <artifactId>maven-plugin-annotations</artifactId>
+            <version>3.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <!-- for converting Maven to OSGi versions -->
+        <dependency>
+            <groupId>biz.aQute.bnd</groupId>
+            <artifactId>biz.aQute.bndlib</artifactId>
+            <version>4.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-interactivity-api</artifactId>
+            <version>1.0-alpha-6</version>
+        </dependency>
+        <!-- We use a class from the config admin implementation to read config files -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.configadmin</artifactId>
+            <version>1.8.10</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.converter</artifactId>
+            <version>1.0.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.configurator</artifactId>
+            <version>1.0.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-archiver</artifactId>
+            <version>2.4.4</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.codehaus.plexus</groupId>
+                    <artifactId>plexus-container-default</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.codehaus.plexus</groupId>
+                    <artifactId>plexus-component-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-utils</artifactId>
+            <version>3.0.17</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.shared</groupId>
+            <artifactId>maven-filtering</artifactId>
+            <version>1.2</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>maven-project</artifactId>
+                    <groupId>org.apache.maven</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- The following artifacts are purely used by the unit tests
+            so these dependencies ensures that they are in the .m2 directory prior to executing the tests.
+            Whenever you modify these, you must also modify the references in org.apache.sling.maven.slingstart.PreparePackageMojoTest! -->
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.classloader</artifactId>
+            <version>1.3.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.contentdetection</artifactId>
+            <version>1.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.mime</artifactId>
+            <version>2.1.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.threads</artifactId>
+            <version>3.2.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-plugin-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </reporting>
+
+</project>
diff --git a/src/main/java/org/apache/sling/maven/kickstart/BuildConstants.java b/src/main/java/org/apache/sling/maven/kickstart/BuildConstants.java
new file mode 100644
index 0000000..8ea845b
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/BuildConstants.java
@@ -0,0 +1,97 @@
+/*
+ * 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.maven.kickstart;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class BuildConstants {
+
+    // CONTEXTS
+    public static final String CONTEXT_GLOBAL = "slingstart:global";
+    public static final String CONTEXT_STANDALONE = "slingstart" + ":standalone";
+    public static final String CONTEXT_WEBAPP = "slingstart" + ":webapp";
+
+    // Model artifact name
+    public static final String MODEL_ARTIFACT_NAME = "slingstart.txt";
+
+    // Types
+
+    public static final String TYPE_JAR = "jar";
+
+    public static final String TYPE_WAR = "war";
+
+    public static final String TYPE_POM = "pom";
+
+    public static final String TYPE_TXT = "txt";
+
+    public static final String PACKAGING_PARTIAL_SYSTEM = "slingfeature";
+
+    public static final String PACKAGING_SLINGQUICKSTART = "slingkickstart";
+
+    // Classifiers
+
+    public static final String CLASSIFIER_PARTIAL_SYSTEM = "slingfeature";
+
+    public static final String CLASSIFIER_BASE = "base";
+
+    public static final String CLASSIFIER_APP = "app";
+
+    public static final String CLASSIFIER_WEBAPP = "webapp";
+
+//    public static final String CLASSIFIER_MAR = ModelArchiveWriter.DEFAULT_EXTENSION;
+
+    // Manifest attributes
+
+    public static final String ATTR_BUILT_BY = "Built-By";
+
+    public static final String ATTR_CREATED_BY = "Created-By";
+
+    public static final String ATTR_IMPLEMENTATION_VERSION = "Implementation-Version";
+
+    public static final String ATTR_IMPLEMENTATION_VENDOR = "Implementation-Vendor";
+
+    public static final String ATTR_IMPLEMENTATION_BUILD = "Implementation-Build";
+
+    public static final String ATTR_IMPLEMENTATION_VENDOR_ID = "Implementation-Vendor-Id";
+
+    public static final String ATTR_IMPLEMENTATION_TITLE = "Implementation-Title";
+
+    public static final String ATTR_SPECIFICATION_TITLE = "Specification-Title";
+
+    public static final String ATTR_SPECIFICATION_VENDOR = "Specification-Vendor";
+
+    public static final String ATTR_SPECIFICATION_VERSION = "Specification-Version";
+
+    public static final String ATTR_MAIN_CLASS = "Main-Class";
+
+    public static final String ATTR_VALUE_MAIN_CLASS = "org.apache.sling.launchpad.app.Main";
+
+    public static final List<String> ATTRS_EXCLUDES = new ArrayList<String>();
+    static {
+        ATTRS_EXCLUDES.add(ATTR_BUILT_BY);
+        ATTRS_EXCLUDES.add(ATTR_CREATED_BY);
+        ATTRS_EXCLUDES.add(ATTR_IMPLEMENTATION_VERSION);
+        ATTRS_EXCLUDES.add(ATTR_IMPLEMENTATION_VENDOR);
+        ATTRS_EXCLUDES.add(ATTR_IMPLEMENTATION_BUILD);
+        ATTRS_EXCLUDES.add(ATTR_IMPLEMENTATION_VENDOR_ID);
+        ATTRS_EXCLUDES.add(ATTR_IMPLEMENTATION_TITLE);
+        ATTRS_EXCLUDES.add(ATTR_SPECIFICATION_TITLE);
+        ATTRS_EXCLUDES.add(ATTR_SPECIFICATION_VENDOR);
+        ATTRS_EXCLUDES.add(ATTR_SPECIFICATION_VERSION);
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/launcher/Main.java b/src/main/java/org/apache/sling/maven/kickstart/launcher/Main.java
new file mode 100644
index 0000000..5567a7e
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/launcher/Main.java
@@ -0,0 +1,101 @@
+/*
+ * 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.maven.kickstart.launcher;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+
+/**
+ * Main class for launching Apache Sling.
+ *
+ */
+public class Main {
+
+    /** Arguments to pass to the real main class */
+    private final String[] startupArgs;
+
+    /** Verbose flag */
+    private final boolean verbose;
+
+    /** App jar */
+    private final File appJar;
+
+    /** Listener port. */
+    private final int listenerPort;
+
+    /** Main class default value */
+    private final static String MAIN_CLASS_DEF = "org.apache.sling.kickstart.app.SlingStarter";
+
+    /** Delimeter string */
+    private final static String DELIM =
+	    "-------------------------------------------------------------------";
+
+    /**
+     * Create a new launcher
+     * First argument is the launchpad jar
+     * Second argument is the listener port
+     * Third argument is verbose
+     */
+    public Main(final String[] args) {
+        if ( args == null || args.length < 3 ) {
+            throw new IllegalArgumentException("Missing configuration: " + args);
+        }
+        this.appJar = new File(args[0]);
+        this.listenerPort = Integer.valueOf(args[1]);
+        this.verbose = Boolean.valueOf(args[2]);
+        System.out.println("App Jar: " + appJar);
+        System.out.println("Listener Port: " + listenerPort);
+        System.out.println("Verbose: " + verbose);
+	    this.startupArgs = new String[args.length-3];
+	    System.arraycopy(args, 3, this.startupArgs, 0, this.startupArgs.length);
+    }
+
+    /**
+     * Startup
+     */
+    public void run() throws Exception {
+        if (verbose) {
+	        System.out.println(DELIM);
+            System.out.println("Slingstart application: " + this.appJar);
+            System.out.println("Main class: " + MAIN_CLASS_DEF);
+            System.out.println("Listener Port: " + String.valueOf(this.listenerPort));
+            System.out.println("Arguments: " + Arrays.toString(this.startupArgs));
+            System.out.println(DELIM);
+        }
+
+        final ClassLoader cl = new URLClassLoader(new URL[] {this.appJar.toURI().toURL()});
+        Thread.currentThread().setContextClassLoader(cl);
+
+        final Class<?> mainClass = cl.loadClass(MAIN_CLASS_DEF);
+        final Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
+        mainMethod.invoke(null, (Object)this.startupArgs);
+    }
+
+    public static void main(final String[] args) {
+        try {
+            final Main m = new Main(args);
+            m.run();
+        } catch ( final Exception e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+}
+
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/AbstractStartStopMojo.java b/src/main/java/org/apache/sling/maven/kickstart/run/AbstractStartStopMojo.java
new file mode 100644
index 0000000..e79277a
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/AbstractStartStopMojo.java
@@ -0,0 +1,82 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.codehaus.plexus.components.interactivity.Prompter;
+import org.codehaus.plexus.components.interactivity.PrompterException;
+
+import java.io.File;
+import java.util.List;
+
+public abstract class AbstractStartStopMojo extends AbstractMojo {
+
+    /**
+     * Set this to "true" to skip starting the launchpad
+     */
+    @Parameter(property = "maven.test.skip", defaultValue = "false")
+    protected boolean skipLaunchpad;
+
+    /**
+     * Parameter containing the list of server configurations
+     */
+    @Parameter
+    protected List<ServerConfiguration> servers;
+
+    /**
+     * The system properties file will contain all started instances with their ports etc.
+     */
+    @Parameter(defaultValue = "${project.build.directory}/launchpad-runner.properties")
+    protected File systemPropertiesFile;
+
+    /**
+     * If {@code true} this mojo blocks until you press the Enter key.
+     */
+    @Parameter
+    protected boolean shouldBlockUntilKeyIsPressed;
+
+    @Component
+    private Prompter prompter;
+    
+    protected abstract void doExecute() throws MojoExecutionException, MojoFailureException;
+    
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        if (this.skipLaunchpad) {
+            this.getLog().info("Executing of this mojo is disabled by configuration.");
+            return;
+        }
+        
+        doExecute();
+    }
+
+    protected void blockIfNecessary() throws MojoFailureException {
+        if (shouldBlockUntilKeyIsPressed) {
+            // http://stackoverflow.com/a/21977269/5155923
+            try {
+                prompter.prompt("Press Enter to continue");
+            } catch (PrompterException e) {
+                throw new MojoFailureException("Could not prompt for user input. Maven is probably running in non-interactive mode! Do not use parameter 'shouldBlockUntilKeyIsPressed' in that case", e);
+            }
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/ControlClient.java b/src/main/java/org/apache/sling/maven/kickstart/run/ControlClient.java
new file mode 100644
index 0000000..f2bbb8c
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/ControlClient.java
@@ -0,0 +1,272 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.apache.maven.plugin.logging.Log;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStreamWriter;
+import java.math.BigInteger;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.security.SecureRandom;
+
+/**
+ * The <code>ControlClient</code> class is a helper class to interact with a started
+ * Sling instance through its ControlListener class.
+ */
+public class ControlClient {
+
+    // 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 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;
+
+    private String secretKey;
+    private InetSocketAddress socketAddress;
+
+    private File directory;
+    private Log logger;
+
+    /**
+     * 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>.
+     */
+    public ControlClient(final File directory, Log logger) {
+        this.directory = directory;
+        this.logger = logger;
+    }
+
+    public int getPort() {
+        return socketAddress != null ? socketAddress.getPort() : -1;
+    }
+
+    public boolean isStarted() {
+        Response response = sendCommand(COMMAND_STATUS);
+        return response.getCode() == 0 && "OK".equals(response.getResult());
+    }
+
+    /**
+     * Implements the client side of the control connection sending the command
+     * to shutdown Sling.
+     */
+    public int shutdownServer() { return sendCommand(COMMAND_STOP).getCode(); }
+
+    /**
+     * Implements the client side of the control connection sending the command
+     * to check whether Sling is active.
+     */
+    public int statusServer() {
+        return sendCommand(COMMAND_STATUS).getCode();
+    }
+
+    /**
+     * Implements the client side of the control connection sending the command
+     * to retrieve a thread dump.
+     */
+    public int dumpThreads() {
+        return sendCommand(COMMAND_THREADS).getCode();
+    }
+
+    /**
+     * 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 Response sendCommand(final String command) {
+        if (configure()) {
+            if (this.secretKey == null) {
+                logger.info("Missing secret key to protect sending '" + command + "' to " + this.socketAddress);
+                return new Response(4);
+            }
+
+            Socket socket = null;
+            try {
+                socket = new Socket();
+                socket.connect(this.socketAddress);
+                writeLine0(socket, this.secretKey + " " + command);
+                final String result = readLine(socket);
+                logger.info("Sent '" + command + "' to " + this.socketAddress + ": " + result);
+                return new Response(0, result);
+            } catch (final ConnectException ce) {
+                logger.info("No Apache Sling running at " + this.socketAddress);
+                return new Response(3, ce);
+            } catch (final IOException ioe) {
+                logger.error("Failed sending '" + command + "' to " + this.socketAddress, ioe);
+                return new Response(1, ioe);
+            } finally {
+                if (socket != null) {
+                    try {
+                        socket.close();
+                    } catch (IOException ignore) {
+                    }
+                }
+            }
+        }
+        logger.info("No socket address to send '" + command + "' to");
+        return new Response(4);
+    }
+
+    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 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() {
+        boolean result = false;
+        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
+            }
+        }
+
+        return result;
+    }
+
+    private File getConfigFile() {
+        final File configDir = new File(directory, "conf");
+        return new File(configDir, "controlport");
+    }
+
+    private static String generateKey() {
+         return new BigInteger(165, new SecureRandom()).toString(32);
+    }
+
+    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;
+            }
+
+            logger.error("Unknown host in '" + listenSpec);
+        } catch (final NumberFormatException nfe) {
+            logger.error("Cannot parse port number from '" + listenSpec + "'");
+        }
+
+        return null;
+    }
+
+    private static class Response {
+        private int code;
+        private String result;
+        private Exception exception;
+
+        public Response(int code) {
+            this.code = code;
+        }
+
+        public Response(int code, String result) {
+            this.code = code;
+            this.result = result;
+        }
+
+        public Response(int code, Exception exception) {
+            this.code = code;
+            this.exception = exception;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public String getResult() {
+            return result;
+        }
+
+        public Exception getException() {
+            return exception;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/LauncherCallable.java b/src/main/java/org/apache/sling/maven/kickstart/run/LauncherCallable.java
new file mode 100644
index 0000000..3374b2d
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/LauncherCallable.java
@@ -0,0 +1,384 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.shared.utils.StringUtils;
+import org.apache.sling.maven.kickstart.launcher.Main;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.lang.ProcessBuilder.Redirect;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A callable for launchpad an instance
+ */
+public class LauncherCallable implements Callable<ProcessDescription> {
+
+    private final LaunchpadEnvironment environment;
+    private final ServerConfiguration configuration;
+    private final Log logger;
+
+    public LauncherCallable(final Log logger,
+                                  final ServerConfiguration configuration,
+                                  final LaunchpadEnvironment environment) {
+        this.logger = logger;
+        this.configuration = configuration;
+        this.environment = environment;
+    }
+
+    /**
+     * @see Callable#call()
+     */
+    @Override
+    public ProcessDescription call() throws Exception {
+        logger.info("call() started");
+        // fail if launchpad with this id is already started
+        if (!ProcessDescriptionProvider.getInstance().isRunConfigurationAvailable(configuration.getId())) {
+            throw new Exception("Launchpad with id " + configuration.getId() + " is not available");
+        }
+
+        // get the launchpad jar
+        final File launchpad = this.environment.prepare(this.configuration.getFolder());
+
+        // Lock the launchpad id
+        final String launchpadKey = ProcessDescriptionProvider.getInstance().getId(configuration.getId());
+
+        // start launchpad
+        ProcessDescription cfg = this.start(launchpad);
+
+        // Add thread hook to shutdown launchpad
+        if (environment.isShutdownOnExit()) {
+            cfg.installShutdownHook();
+        }
+
+        // Add configuration to the config provider
+        ProcessDescriptionProvider.getInstance().addRunConfiguration(cfg, launchpadKey);
+
+        logger.info("Before Check if started");
+        boolean started = false;
+        try {
+            final long endTime = System.currentTimeMillis() + this.environment.getReadyTimeOutSec() * 1000;
+            boolean finished = false;
+            while ( !started && !finished && System.currentTimeMillis() < endTime ) {
+                Thread.sleep(5000);
+                logger.info("Ask Control Listener: " + cfg.getControlClient());
+                started = cfg.getControlClient().isStarted();
+                logger.info("Is Started: " + started);
+                try {
+                    // if we get an exit value, the process has stopped
+                    cfg.getProcess().exitValue();
+                    finished = true;
+                } catch ( final IllegalThreadStateException itse) {
+                    // everything as expected
+                }
+                
+            }
+            logger.info("Check Done, started: " + started + ", finihsed: " + finished);
+
+            if ( finished ) {
+                throw new Exception("Launchpad did exit unexpectedly.");
+            }
+            if ( !started ) {
+                throw new Exception("Launchpad did not start successfully in " + this.environment.getReadyTimeOutSec() + " seconds.");
+            }
+            // now check for the availability of the HTTP port
+            boolean httpAvailable = isLocalhostPortAvailable(Integer.valueOf(this.configuration.getPort()));
+            // repeat until http service is up as well
+            while ( !httpAvailable && System.currentTimeMillis() < endTime ) {
+                Thread.sleep(1000);
+                httpAvailable = isLocalhostPortAvailable(Integer.valueOf(this.configuration.getPort()));
+            }
+            if ( !httpAvailable ) {
+                throw new Exception("Launchpad did not start http service on port " + this.configuration.getPort() + " successfully in " + this.environment.getReadyTimeOutSec() + " seconds.");
+            }
+            this.logger.info("Started Launchpad '" + configuration.getId() +
+                    "' at port " + configuration.getPort()+ " [run modes: " + configuration.getRunmode()+ "]");
+        } finally {
+//            // stop control port
+//            cfg.getControlClient().shutdownServer();
+
+            // call launchpad stop routine if not properly started
+            if (!started) {
+                stop(this.logger, cfg);
+                ProcessDescriptionProvider.getInstance().removeRunConfiguration(cfg.getId());
+                cfg = null;
+            }
+        }
+
+        return cfg;
+    }
+
+    private boolean isLocalhostPortAvailable(int port) throws IOException {
+        // https://stackoverflow.com/questions/46436813/difference-between-a-connection-refused-exception-and-a-timeout-in-httpclient
+        Socket clientSocket = new Socket();
+        try {
+            clientSocket.connect(new InetSocketAddress("127.0.0.1", port), 500);
+            // without that, read() call on the InputStream associated with this Socket is infinite
+            this.logger.debug("Successfully connected to localhost, port " + port);
+            clientSocket.close();
+            return true;
+        } catch (SocketTimeoutException e) {
+            // we ran into a timeout (port most probably blocked by firewall)
+            this.logger.debug("Ran into a timeout while connecting to localhost, port " + port, e);
+            return false;
+        } catch (ConnectException e) {
+            // port not bound
+            this.logger.debug("Could not connect to localhost, port " + port, e);
+            return false;
+        } finally {
+            clientSocket.close();
+        }
+    }
+
+    public boolean isRunning() {
+        return getControlPortFile(this.configuration.getFolder()).exists();
+    }
+
+    private void add(final List<String> args, final String value) {
+        if ( value != null ) {
+            final String[] single = value.trim().split(" ");
+            for(final String v : single) {
+                if ( v.trim().length() > 0 ) {
+                    args.add(v.trim());
+                }
+            }
+        }
+    }
+
+    private ProcessDescription start(final File jar) throws Exception {
+        final ProcessDescription cfg = new ProcessDescription(
+            this.configuration.getId(),
+            this.configuration.getFolder(),
+            this.configuration.getServer() + ":" + this.configuration.getControlPort(),
+            logger
+        );
+
+        final ProcessBuilder builder = new ProcessBuilder();
+        final List<String> args = new ArrayList<String>();
+
+        String javaHome = System.getenv("JAVA_HOME");
+        String javaCmd = javaHome != null ? Paths.get(javaHome, "bin", "java").toString() : "java";
+
+        args.add(javaCmd);
+        add(args, this.configuration.getVmOpts());
+        add(args, this.configuration.getVmDebugOpts(this.environment.getDebug()));
+
+        args.add("-cp");
+        args.add("bin");
+        args.add(Main.class.getName());
+        // first three arguments: jar, listener port, verbose
+        args.add(jar.getPath());
+        args.add(String.valueOf(cfg.getControlClient().getPort()));
+        args.add("true");
+
+        // from here on launchpad properties
+        add(args, this.configuration.getOpts());
+
+        if(this.configuration.getAdditionalFeatureFile() != null) {
+            args.add("-af");
+            args.add(this.configuration.getAdditionalFeatureFile().getAbsolutePath());
+        }
+
+        final String contextPath = this.configuration.getContextPath();
+        if ( contextPath != null && contextPath.length() > 0 && !contextPath.equals("/") ) {
+            args.add("-r");
+            args.add(contextPath);
+        }
+
+        if ( this.configuration.getPort() != null ) {
+            args.add("-p");
+            args.add(this.configuration.getPort());
+        }
+
+        if ( this.configuration.getControlPort() != null ) {
+            args.add("-j");
+            args.add(this.configuration.getControlPort());
+        }
+        if ( this.configuration.getRunmode() != null && this.configuration.getRunmode().length() > 0 ) {
+            args.add("-Dsling.run.modes=" + this.configuration.getRunmode());
+        }
+        logger.info("Is Shutdown On Exit: " + this.environment.isShutdownOnExit());
+        if ( !this.environment.isShutdownOnExit() ) {
+            args.add("start");
+        }
+
+        builder.command(args.toArray(new String[args.size()]));
+        builder.directory(this.configuration.getFolder());
+        builder.redirectErrorStream(true);
+        logger.info("Starting Launchpad " + this.configuration.getId() +  "...");
+        logger.info("Starting Launchpad, arguments: " + args);
+        String stdOutFile = this.configuration.getStdOutFile();
+        if (StringUtils.isNotBlank(stdOutFile)) {
+            File absoluteStdOutFile = new File(builder.directory(), stdOutFile);
+            // make sure to create the parent directories (if they do not exist yet)
+            absoluteStdOutFile.getParentFile().mkdirs();
+            builder.redirectOutput(absoluteStdOutFile);
+            logger.info("Redirecting stdout and stderr to " + absoluteStdOutFile);
+        } else {
+            builder.redirectOutput(Redirect.INHERIT);
+        }
+
+        logger.debug("Launchpad cmd: " + builder.command());
+        logger.debug("Launchpad dir: " + builder.directory());
+
+        try {
+            logger.info("Before Builder start()");
+            cfg.setProcess(builder.start());
+            logger.info("After Builder start(), cfg: " + cfg);
+        } catch (final IOException e) {
+            if (cfg.getProcess() != null) {
+                cfg.getProcess().destroy();
+                cfg.setProcess(null);
+            }
+            throw new Exception("Could not start the Launchpad", e);
+        }
+
+        return cfg;
+    }
+
+    public static void stop(final Log LOG, final ProcessDescription cfg) throws Exception {
+        boolean isNew = false;
+
+        if (cfg.getProcess() != null || isNew ) {
+            LOG.info("Stopping Launchpad '" + cfg.getId() + "'");
+            boolean destroy = true;
+            final int twoMinutes = 2 * 60 * 1000;
+            final File controlPortFile = getControlPortFile(cfg.getDirectory());
+            LOG.debug("Control port file " + controlPortFile + " exists: " + controlPortFile.exists());
+            if ( controlPortFile.exists() ) {
+                // reading control port
+                int controlPort = -1;
+                String secretKey = null;
+                LineNumberReader lnr = null;
+                String serverName = null;
+                try {
+                    lnr = new LineNumberReader(new FileReader(controlPortFile));
+                    final String portLine = lnr.readLine();
+                    final int pos = portLine.indexOf(':');
+                    controlPort = Integer.parseInt(portLine.substring(pos + 1));
+                    if ( pos > 0 ) {
+                        serverName = portLine.substring(0, pos);
+                    }
+                    secretKey = lnr.readLine();
+                } catch ( final NumberFormatException ignore) {
+                    // we ignore this
+                    LOG.debug("Error reading control port file " + controlPortFile, ignore);
+                } catch ( final IOException ignore) {
+                    // we ignore this
+                    LOG.debug("Error reading control port file " + controlPortFile, ignore);
+                } finally {
+                    IOUtils.closeQuietly(lnr);
+                }
+
+                if ( controlPort != -1 ) {
+                    final List<String> hosts = new ArrayList<String>();
+                    if ( serverName != null ) {
+                        hosts.add(serverName);
+                    }
+                    hosts.add("localhost");
+                    hosts.add("127.0.0.1");
+                    LOG.debug("Found control port " + controlPort);
+                    int index = 0;
+                    while ( destroy && index < hosts.size() ) {
+                        final String hostName = hosts.get(index);
+
+                        Socket clientSocket = null;
+                        DataOutputStream out = null;
+                        BufferedReader in = null;
+                        try {
+                            LOG.debug("Trying to connect to " + hostName + ":" + controlPort);
+                            clientSocket = new Socket();
+                            // set a socket timeout
+                            clientSocket.connect(new InetSocketAddress(hostName, controlPort), twoMinutes);
+                            // without that, read() call on the InputStream associated with this Socket is infinite
+                            clientSocket.setSoTimeout(twoMinutes);
+
+                            LOG.debug(hostName + ":" + controlPort + " connection estabilished, sending the 'stop' command...");
+
+                            out = new DataOutputStream(clientSocket.getOutputStream());
+                            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+                            if (secretKey != null) {
+                                out.writeBytes(secretKey);
+                                out.write(' ');
+                            }
+                            out.writeBytes("stop\n");
+                            in.readLine();
+                            destroy = false;
+                            LOG.debug("'stop' command sent to " + hostName + ":" + controlPort);
+                        } catch (final Throwable ignore) {
+                            // catch Throwable because InetSocketAddress and Socket#connect throws unchecked exceptions
+                            // we ignore this for now
+                            LOG.debug("Error sending 'stop' command to " + hostName + ":" + controlPort + " due to: " + ignore.getMessage());
+                        } finally {
+                            IOUtils.closeQuietly(in);
+                            IOUtils.closeQuietly(out);
+                            IOUtils.closeQuietly(clientSocket);
+                        }
+                        index++;
+                    }
+                }
+            }
+            if ( cfg.getProcess() != null ) {
+                final Process process = cfg.getProcess();
+
+                if (!destroy) {
+                    LOG.debug("Waiting for process to stop...");
+                    process.waitFor(twoMinutes, TimeUnit.MILLISECONDS);
+                    if (process.isAlive()) {
+                        LOG.debug("Process timeout out after 2 minutes");
+                        destroy = true;
+                    } else {
+                        LOG.debug("Process stopped");
+                    }
+                }
+
+                if (destroy) {
+                    LOG.debug("Destroying process...");
+                    process.destroy();
+                    process.waitFor(twoMinutes, TimeUnit.MILLISECONDS);
+                    LOG.debug("Process destroyed");
+                }
+
+                cfg.setProcess(null);
+            }
+        } else {
+            LOG.warn("Launchpad already stopped");
+        }
+    }
+
+    private static File getControlPortFile(final File directory) {
+        final File confDir = new File(directory, "conf");
+        final File controlPortFile = new File(confDir, "controlport");
+        return controlPortFile;
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/LaunchpadEnvironment.java b/src/main/java/org/apache/sling/maven/kickstart/run/LaunchpadEnvironment.java
new file mode 100644
index 0000000..e1a5127
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/LaunchpadEnvironment.java
@@ -0,0 +1,150 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.codehaus.plexus.util.FileUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Common settings for all launchpad instances.
+ */
+public class LaunchpadEnvironment {
+
+    /** The work directory created by starting launchpad. */
+    public static final String WORK_DIR_NAME = "sling";
+
+    private final File launchpadJar;
+    private final boolean cleanWorkingDirectory;
+    private final boolean shutdownOnExit;
+    private final int readyTimeOutSec;
+    private final String debug;
+
+    public LaunchpadEnvironment(final File launchpadJar,
+                                final boolean cleanWorkingDirectory,
+                                final boolean shutdownOnExit,
+                                final int readyTimeOutSec,
+                                final String debug) {
+        this.launchpadJar = launchpadJar;
+        this.cleanWorkingDirectory = cleanWorkingDirectory;
+        this.shutdownOnExit = shutdownOnExit;
+        this.readyTimeOutSec = readyTimeOutSec;
+        this.debug = debug;
+    }
+
+    public boolean isShutdownOnExit() {
+        return this.shutdownOnExit;
+    }
+
+    public int getReadyTimeOutSec() {
+        return this.readyTimeOutSec;
+    }
+
+    /**
+     * Check if the launchpad folder exists.
+     */
+    private void ensureFolderExists(final File folder) {
+        if (!folder.exists()) {
+            folder.mkdirs();
+        }
+        if (this.cleanWorkingDirectory) {
+            final File work = new File(folder, WORK_DIR_NAME);
+            org.apache.commons.io.FileUtils.deleteQuietly(work);
+        }
+    }
+
+    private File installLaunchpad(final File folder) throws IOException {
+        if (this.launchpadJar.getParentFile().getAbsolutePath().equals(folder.getAbsolutePath())) {
+            return this.launchpadJar;
+        }
+        try {
+            FileUtils.copyFileToDirectory(this.launchpadJar, folder);
+            return new File(folder, this.launchpadJar.getName());
+        } catch (final IOException ioe) {
+            throw new IOException("Unable to copy " + this.launchpadJar + " to " + folder, ioe);
+        }
+    }
+
+    private void installLauncher(final File folder) throws IOException {
+        final File binDir = new File(folder, "bin");
+        copyResource("org/apache/sling/maven/kickstart/launcher/Main.class", binDir);
+    }
+
+    /**
+     * Prepare a new instance.
+     * @param folder The target folder for the instance
+     * @return The launchpad jar
+     * @throws IOException if an error occurs.
+     */
+    public File prepare(final File folder) throws IOException {
+        this.ensureFolderExists(folder);
+
+        // copy launchpadJar
+        final File launchpad = this.installLaunchpad(folder);
+
+        // install launcher
+        this.installLauncher(folder);
+
+        return launchpad;
+    }
+
+    private void copyResource(final String resource,
+            final File dir)
+    throws IOException {
+        final int lastSlash = resource.lastIndexOf('/');
+        final File baseDir;
+        if ( lastSlash > 0 ) {
+            final String filePath = resource.substring(0, lastSlash).replace('/', File.separatorChar);
+            baseDir = new File(dir, filePath);
+        } else {
+            baseDir = dir;
+        }
+        baseDir.mkdirs();
+        final File file = new File(baseDir, resource.substring(lastSlash + 1));
+        final InputStream is = LaunchpadEnvironment.class.getClassLoader().getResourceAsStream(resource);
+        if ( is == null ) {
+            throw new IOException("Resource not found: " + resource);
+        }
+        final FileOutputStream fos = new FileOutputStream(file);
+        final byte[] buffer = new byte[2048];
+        int l;
+        try {
+            while ( (l = is.read(buffer)) > 0 ) {
+                fos.write(buffer, 0, l);
+            }
+        } finally {
+            if ( fos != null ) {
+                fos.close();
+            }
+            if ( is != null ) {
+                is.close();
+            }
+        }
+    }
+
+    /**
+     * 
+     * @return the global debug parameter for all Sling instances. Set through {@link StartMojo#debug}.
+     */
+    public String getDebug() {
+        return debug;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/PortHelper.java b/src/main/java/org/apache/sling/maven/kickstart/run/PortHelper.java
new file mode 100644
index 0000000..262d41b
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/PortHelper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.apache.maven.plugin.MojoExecutionException;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Simple helper class to find a new port.
+ */
+public class PortHelper {
+
+    private static final Set<Integer> USED_PORTS = new HashSet<Integer>();
+
+    public static synchronized int getNextAvailablePort()
+            throws MojoExecutionException {
+        int unusedPort = 0;
+        do {
+            try {
+                final ServerSocket socket = new ServerSocket( 0 );
+                unusedPort = socket.getLocalPort();
+                socket.close();
+            } catch ( final IOException e ) {
+                throw new MojoExecutionException( "Error getting an available port from system", e );
+            }
+        } while ( USED_PORTS.contains(unusedPort));
+        USED_PORTS.add(unusedPort);
+
+        return unusedPort;
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/ProcessDescription.java b/src/main/java/org/apache/sling/maven/kickstart/run/ProcessDescription.java
new file mode 100644
index 0000000..d903ebd
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/ProcessDescription.java
@@ -0,0 +1,85 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+
+import java.io.File;
+
+/**
+ * A running launchpad process.
+ */
+public class ProcessDescription {
+
+    private final String id;
+    private final File directory;
+    private final ControlClient controlClient;
+    private volatile Process process;
+
+    public ProcessDescription(final String id, final File directory, String listenerSpec, Log logger) throws MojoExecutionException {
+        this.id = id;
+        this.directory = directory;
+        this.controlClient = new ControlClient(
+            directory,
+            logger
+        );
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public ControlClient getControlClient() {
+        return this.controlClient;
+    }
+
+    public Process getProcess() {
+        return process;
+    }
+
+    public void setProcess(final Process process) {
+        this.process = process;
+    }
+
+    /**
+     * Install a shutdown hook
+     */
+    public void installShutdownHook() {
+        final ProcessDescription cfg = this;
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                if ( cfg.getProcess() != null ) {
+                    System.out.println("Terminating launchpad " + cfg.getId());
+                    cfg.getProcess().destroy();
+                    cfg.setProcess(null);
+                }
+            }
+        });
+    }
+
+    @Override
+    public String toString() {
+        return "RunningProcessDescription [id=" + id + ", directory="
+                + directory + ", process=" + process + "]";
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/ProcessDescriptionProvider.java b/src/main/java/org/apache/sling/maven/kickstart/run/ProcessDescriptionProvider.java
new file mode 100644
index 0000000..7bb3374
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/ProcessDescriptionProvider.java
@@ -0,0 +1,106 @@
+/*
+ * 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.maven.kickstart.run;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A singleton which is responsible to provide {@link ProcessDescription}s
+ */
+public class ProcessDescriptionProvider {
+
+    private static final String DEFAULT_KEY = "DEFAULT_LAUNCHPAD";
+
+    private static ProcessDescriptionProvider ourInstance = new ProcessDescriptionProvider();
+    private final Map<String, ProcessDescription> configs = new HashMap<String, ProcessDescription>();
+    private final Map<String, String> lockedIds = new HashMap<String, String>();
+
+    private ProcessDescriptionProvider() {
+        // private constructor
+    }
+
+    public static ProcessDescriptionProvider getInstance() {
+        return ourInstance;
+    }
+
+    /**
+     * Prepare an ID for a launchpad that will be started, before saving the config.
+     * @param launchpadId the id of the launchpad to lock
+     * @return id key used to add to configs
+     */
+    public synchronized String getId(final String launchpadId) throws Exception {
+        final String id = (launchpadId == null ? DEFAULT_KEY : launchpadId);
+        if (configs.containsKey(id) || lockedIds.containsKey(id)) {
+            throw new Exception("Launchpad Id " + id + " is already in use");
+        }
+
+        String ts = String.valueOf(System.currentTimeMillis());
+        lockedIds.put(id, ts);
+        return ts;
+    }
+
+    /**
+     *
+     * @param launchpadId
+     * @param unlockKey
+     * @return
+     */
+    public synchronized boolean cancelId(final String launchpadId, final String unlockKey) {
+        final String id = (launchpadId == null ? DEFAULT_KEY : launchpadId);
+        if (lockedIds.containsKey(id) && lockedIds.get(id).equals(unlockKey)) {
+            lockedIds.remove(id);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     *
+     * @param launchpadId
+     * @return
+     */
+    public synchronized ProcessDescription getRunConfiguration(final String launchpadId) {
+        final String id = (launchpadId == null ? DEFAULT_KEY : launchpadId);
+        return configs.get(id);
+    }
+
+    /**
+     *
+     * @param launchpadId
+     * @return
+     */
+    public synchronized  boolean isRunConfigurationAvailable(final String launchpadId) {
+        return getRunConfiguration(launchpadId) == null && !lockedIds.containsKey(launchpadId);
+    }
+
+    public synchronized void addRunConfiguration(ProcessDescription cfg, final String unlockKey) throws Exception {
+        String id = cfg.getId() == null ? DEFAULT_KEY : cfg.getId();
+        if (!lockedIds.containsKey(id) || !lockedIds.get(id).equals(unlockKey)) {
+            throw new Exception("Cannot add configuration. Id " + id + " doesn't exist");
+        }
+        lockedIds.remove(cfg.getId());
+        configs.put(cfg.getId(), cfg);
+    }
+
+    public synchronized void removeRunConfiguration(final String launchpadId) {
+        final String id = (launchpadId == null ? DEFAULT_KEY : launchpadId);
+        configs.remove(id);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/ServerConfiguration.java b/src/main/java/org/apache/sling/maven/kickstart/run/ServerConfiguration.java
new file mode 100644
index 0000000..8658275
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/ServerConfiguration.java
@@ -0,0 +1,239 @@
+/*
+ * 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.maven.kickstart.run;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * A server configuration
+ */
+public class ServerConfiguration implements Serializable {
+
+    private static final long serialVersionUID = 1922175510880318125L;
+
+    private static final String DEFAULT_VM_OPTS = "-Xmx1024m -XX:MaxPermSize=256m -Djava.awt.headless=true";
+
+    // http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/conninv.html#Invocation
+    private static final String DEFAULT_VM_DEBUG_OPTS = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000";
+
+    /** The unique id. */
+    private String id;
+
+    /** The run mode string. */
+    private String runmode;
+
+    /** The port to use. */
+    private String port;
+
+    /** The control port to use. */
+    private String controlPort;
+
+    /** The context path. */
+    private String contextPath;
+
+    /** The vm options. */
+    private String vmOpts = DEFAULT_VM_OPTS;
+
+    /** 
+     * If set to {@code "true"}, the process will allow a debugger to connect on port 8000. 
+     * If set to some other string, that string will be appended to this server's {@code vmOpts}, allowing you to configure arbitrary debugging options.
+     * If the global configuration property {@link StartMojo#debug} is set on the mojo itself, it will be used instead.
+     */
+    private String debug;
+
+    /** Additional application options. */
+    private String opts;
+
+    /** Number of instances. */
+    private int instances = 1;
+
+    /** The folder to use. */
+    private File folder;
+    
+    /**
+     * The relative filename of the file which receives both the standard output (stdout) and standard error (stderr) of the server processes. 
+     * If null or empty string the server process inherits stdout from the parent process (i.e. the Maven process).
+     * The given filename must be relative to the working directory of the according server.
+     */
+    private String stdOutFile;
+
+    private File additionalFeatureFile;
+
+    /**
+     * Get the instance id
+     * @return The instance id
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Set the instance id
+     * @param id New instance id
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getRunmode() {
+        return runmode;
+    }
+
+    public void setRunmode(final String runmode) {
+        this.runmode = runmode;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(final String port) {
+        this.port = port;
+    }
+
+    public String getContextPath() {
+        return contextPath;
+    }
+
+    public void setContextPath(final String contextPath) {
+        this.contextPath = contextPath;
+    }
+
+    public String getVmOpts() {
+        return vmOpts;
+    }
+
+    public void setVmOpts(final String vmOpts) {
+        this.vmOpts = vmOpts;
+    }
+
+    /**
+     * Returns the debugging options derived from the passed globalDebug parameter and the debug field (where the globalDebug parameter has precedence over the local field)
+     * @param globalDebug the global debug options (may be {@code null}).
+     * @return the debugging options to use or {@code null}. Should be appended to the ones being returned by {@link #getVmOpts()}.
+     * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/conninv.html#Invocation">JPDA Sun VM Invocation Options</a>
+     */
+    public String getVmDebugOpts(String globalDebug) {
+        if (globalDebug != null) {
+            if (Boolean.valueOf(globalDebug).equals(Boolean.TRUE)) {
+                return DEFAULT_VM_DEBUG_OPTS;
+            }
+            return globalDebug;
+        }
+        if (Boolean.valueOf(debug).equals(Boolean.TRUE)) {
+            return DEFAULT_VM_DEBUG_OPTS;
+        }
+        return debug;
+    }
+
+    public void setDebug(final String debug) {
+        this.debug = debug;
+    }
+
+    public String getOpts() {
+        return opts;
+    }
+
+    public void setOpts(final String opts) {
+        this.opts = opts;
+    }
+
+    public int getInstances() {
+        return this.instances;
+    }
+
+    public void setInstances(final int value) {
+        this.instances = value;
+    }
+
+    public File getFolder() {
+        return folder;
+    }
+
+    public void setFolder(final File folder) {
+        this.folder = folder.getAbsoluteFile();
+    }
+
+    public String getControlPort() {
+        return controlPort;
+    }
+
+    public void setControlPort(String controlPort) {
+        this.controlPort = controlPort;
+    }
+
+    public String getStdOutFile() {
+        return stdOutFile;
+    }
+
+    public void setStdOutFile(String stdOutFile) {
+        this.stdOutFile = stdOutFile;
+    }
+
+    public File getAdditionalFeatureFile() {
+        return additionalFeatureFile;
+    }
+
+    public void setAdditionalFeatureFile(File additionalFeatureFile) {
+        this.additionalFeatureFile = additionalFeatureFile;
+    }
+
+    /**
+     * Get the server
+     * @return The server
+     */
+    public String getServer() {
+        // hard coded for now
+        return "localhost";
+    }
+
+    public ServerConfiguration copy() {
+        final ServerConfiguration copy = new ServerConfiguration();
+        // we do not copy the id
+        copy.setRunmode(this.getRunmode());
+        copy.setPort(this.getPort());
+        copy.setContextPath(this.getContextPath());
+        copy.setVmOpts(this.getVmOpts());
+        copy.setDebug(this.debug);
+        copy.setOpts(this.getOpts());
+        copy.setInstances(1);
+        copy.setFolder(this.getFolder());
+        copy.setControlPort(this.getControlPort());
+        copy.setStdOutFile(this.stdOutFile);
+        copy.setAdditionalFeatureFile(this.additionalFeatureFile);
+        return copy;
+    }
+
+    @Override
+    public String toString() {
+        return "ServerConfiguration{" +
+            "id='" + id + '\'' +
+            ", runmode='" + runmode + '\'' +
+            ", port='" + port + '\'' +
+            ", controlPort='" + controlPort + '\'' +
+            ", contextPath='" + contextPath + '\'' +
+            ", vmOpts='" + vmOpts + '\'' +
+            ", debug='" + debug + '\'' +
+            ", opts='" + opts + '\'' +
+            ", instances=" + instances +
+            ", folder=" + folder +
+            ", stdOutFile='" + stdOutFile + '\'' +
+            ", additionalFeatureFile=" + additionalFeatureFile +
+            '}';
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/StartMojo.java b/src/main/java/org/apache/sling/maven/kickstart/run/StartMojo.java
new file mode 100644
index 0000000..d09e8fd
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/StartMojo.java
@@ -0,0 +1,462 @@
+/*
+ * 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.maven.kickstart.run;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
+import org.apache.maven.artifact.resolver.ArtifactResolutionException;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.sling.maven.kickstart.BuildConstants;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Start one or multiple launchpad instance(s).
+ */
+@Mojo(
+        name = "start",
+        defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST,
+        threadSafe = true
+    )
+public class StartMojo extends AbstractStartStopMojo {
+
+    /**
+     * Overwrites debug parameter of all server configurations (if set).
+     * Attaches a debugger to the forked JVM. If set to {@code "true"}, the process will allow a debugger to connect on port 8000.
+     * If set to some other string, that string will be appended to the server's {@code vmOpts}, allowing you to configure arbitrary debugging options.
+     */
+    @Parameter(property = "launchpad.debug")
+    protected String debug;
+
+    /**
+     * Ready timeout in seconds. If the launchpad has not been started in this
+     * time, it's assumed that the startup failed.
+     */
+    @Parameter(property = "launchpad.ready.timeout", defaultValue = "600")
+    private int launchpadReadyTimeOutSec;
+
+    /**
+     * The launchpad jar. This option has precedence over "launchpadDependency".
+     */
+    @Parameter(property = "launchpad.jar")
+    private File launchpadJar;
+
+    /**
+     * The launchpad jar as a dependency. This is only used if "launchpadJar" is not
+     * specified.
+     */
+    @Parameter
+    private Dependency launchpadDependency;
+
+    /**
+     * Clean the working directory before start.
+     */
+    @Parameter(property = "launchpad.clean.workdir", defaultValue = "false")
+    private boolean cleanWorkingDirectory;
+
+    /**
+     * Keep the launchpad running.
+     * @deprecated Use {@link AbstractStartStopMojo# blockUntilKeyIsPressed} instead.
+     */
+    @Deprecated
+    @Parameter(property = "launchpad.keep.running", defaultValue = "false")
+    private boolean keepLaunchpadRunning;
+
+    /**
+     * Set the execution of launchpad instances to be run in parallel (threads)
+     */
+    @Parameter(property = "launchpad.parallelExecution", defaultValue = "true")
+    private boolean parallelExecution;
+
+    /**
+     * The Maven project.
+     */
+    @Parameter(property = "project", readonly = true, required = true)
+    private MavenProject project;
+
+    /**
+     * The Maven session.
+     */
+    @Parameter(property = "session", readonly = true, required = true)
+    private MavenSession mavenSession;
+
+    @Component
+    private ArtifactHandlerManager artifactHandlerManager;
+
+    /**
+     * Used to look up Artifacts in the remote repository.
+     *
+     */
+    @Component
+    private ArtifactResolver resolver;
+
+    /**
+     * Get a resolved Artifact from the coordinates provided
+     *
+     * @return the artifact, which has been resolved.
+     * @throws MojoExecutionException
+     */
+    private Artifact getArtifact(final Dependency d)
+            throws MojoExecutionException {
+        final Artifact prjArtifact = new DefaultArtifact(d.getGroupId(),
+                        d.getArtifactId(),
+                        VersionRange.createFromVersion(d.getVersion()),
+                        d.getScope(),
+                        d.getType(),
+                        d.getClassifier(),
+                        this.artifactHandlerManager.getArtifactHandler(d.getType()));
+        try {
+            this.resolver.resolve(prjArtifact, this.project.getRemoteArtifactRepositories(), this.mavenSession.getLocalRepository());
+        } catch (final ArtifactResolutionException e) {
+            throw new MojoExecutionException("Unable to get artifact for " + d, e);
+        } catch (ArtifactNotFoundException e) {
+            throw new MojoExecutionException("Unable to get artifact for " + d, e);
+        }
+
+        return prjArtifact;
+    }
+
+    
+    @Override
+    protected void doExecute() throws MojoExecutionException, MojoFailureException {
+        // delete properties
+        if ( systemPropertiesFile != null && systemPropertiesFile.exists() ) {
+            FileUtils.deleteQuietly(this.systemPropertiesFile);
+        }
+
+        // get configurations
+        final Collection<ServerConfiguration> configurations = getLaunchpadConfigurations();
+
+        // create the common environment
+        getLog().info("Keep Launchpad Running: " + this.keepLaunchpadRunning);
+        final LaunchpadEnvironment env = new LaunchpadEnvironment(this.findLaunchpadJar(),
+                this.cleanWorkingDirectory,
+                !this.keepLaunchpadRunning,
+                this.launchpadReadyTimeOutSec,
+                this.debug);
+
+        // create callables
+        final Collection<LauncherCallable> tasks = new LinkedList<LauncherCallable>();
+
+        for (final ServerConfiguration launchpadConfiguration : configurations) {
+            validateConfiguration(launchpadConfiguration);
+
+            tasks.add(createTask(launchpadConfiguration, env));
+        }
+
+        // create the launchpad runner properties
+        this.createLaunchpadRunnerProperties(configurations);
+
+        if (parallelExecution) {
+            // ExecutorService for starting launchpad instances in parallel
+            final ExecutorService executor = Executors.newCachedThreadPool();
+            try {
+                final List<Future<ProcessDescription>> resultsCollector = executor.invokeAll(tasks);
+                for (final Future<ProcessDescription> future : resultsCollector) {
+                    try {
+                        if (null == future.get()) {
+                            throw new MojoExecutionException("Cannot start all the instances");
+                        }
+                    } catch (final ExecutionException e) {
+                        throw new MojoExecutionException(e.getLocalizedMessage(), e);
+                    }
+                }
+            } catch ( final InterruptedException e) {
+                throw new MojoExecutionException(e.getLocalizedMessage(), e);
+            }
+        } else {
+            for (final LauncherCallable task : tasks) {
+                try {
+                    if (null == task.call()) {
+                        throw new MojoExecutionException("Cannot start all the instances");
+                    }
+                } catch (final Exception e) {
+                    throw new MojoExecutionException(e.getLocalizedMessage(), e);
+                }
+            }
+        }
+        if (this.keepLaunchpadRunning) {
+            getLog().info("Press CTRL-C to stop launchpad instance(s)...");
+            while ( true && this.isRunning(tasks)) {
+                try {
+                    Thread.sleep(5000);
+                } catch (final InterruptedException ie) {
+                    break;
+                }
+            }
+        }
+        blockIfNecessary();
+    }
+
+    /**
+     * Are all launchpads still running?
+     */
+    private boolean isRunning(final Collection<LauncherCallable> tasks) {
+        for(final LauncherCallable task : tasks) {
+            if ( !task.isRunning() ) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void createLaunchpadRunnerProperties(final Collection<ServerConfiguration> configurations)
+    throws MojoExecutionException {
+        // create properties
+        OutputStream writer = null;
+        final Properties props = new Properties();
+        try {
+            writer = new FileOutputStream(this.systemPropertiesFile);
+
+            // disable sling startup check
+            props.put("launchpad.skip.startupcheck", "true");
+
+            // write out all instances
+            int index = 0;
+            for (final ServerConfiguration launchpadConfiguration : configurations) {
+                index++;
+                props.put("launchpad.instance.id." + String.valueOf(index), launchpadConfiguration.getId());
+                String runMode = launchpadConfiguration.getRunmode();
+                if ( runMode == null ) {
+                    runMode = "";
+                }
+                props.put("launchpad.instance.runmode." + String.valueOf(index), runMode);
+                props.put("launchpad.instance.server." + String.valueOf(index), launchpadConfiguration.getServer());
+                props.put("launchpad.instance.port." + String.valueOf(index), launchpadConfiguration.getPort());
+                props.put("launchpad.instance.contextPath." + String.valueOf(index), launchpadConfiguration.getContextPath());
+                final String url = createServerUrl(launchpadConfiguration);
+                props.put("launchpad.instance.url." + String.valueOf(index), url);
+            }
+            props.put("launchpad.instances", String.valueOf(index));
+
+            props.store(writer, null);
+        } catch (final IOException e) {
+            throw new MojoExecutionException(e.getLocalizedMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
+
+    private static String createServerUrl(final ServerConfiguration qc) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("http://");
+        sb.append(qc.getServer());
+        if ( !qc.getPort().equals("80") ) {
+            sb.append(':');
+            sb.append(qc.getPort());
+        }
+        final String contextPath = qc.getContextPath();
+        if ( contextPath != null && contextPath.trim().length() > 0 && !contextPath.equals("/") ) {
+            if ( !contextPath.startsWith("/") ) {
+                sb.append('/');
+            }
+            if ( contextPath.endsWith("/") ) {
+                sb.append(contextPath, 0, contextPath.length()-1);
+            } else {
+                sb.append(contextPath);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * @param launchpadConfiguration
+     */
+    private LauncherCallable createTask(final ServerConfiguration launchpadConfiguration,
+                                               final LaunchpadEnvironment env)
+    throws MojoExecutionException, MojoFailureException {
+        final String id = launchpadConfiguration.getId();
+        getLog().debug(new StringBuilder("Starting ").append(id).
+                append(" with runmode ").append(launchpadConfiguration.getRunmode()).
+                append(" on port ").append(launchpadConfiguration.getPort()).
+                append(" in folder ").append(launchpadConfiguration.getFolder().getAbsolutePath()).toString());
+
+        // create task
+        return new LauncherCallable(this.getLog(), launchpadConfiguration, env);
+
+    }
+
+    /**
+     * Validate a configuration
+     * @param launchpadConfiguration The launchpad configuration
+     * @throws MojoExecutionException
+     */
+    private void validateConfiguration(final ServerConfiguration launchpadConfiguration)
+    throws MojoExecutionException {
+        if ( launchpadConfiguration.getPort() == null ) {
+            launchpadConfiguration.setPort(String.valueOf(PortHelper.getNextAvailablePort()));
+        }
+
+        if ( launchpadConfiguration.getControlPort() == null ) {
+            launchpadConfiguration.setControlPort(String.valueOf(PortHelper.getNextAvailablePort()));
+        }
+
+        // set the id of the launchpad
+        if ( launchpadConfiguration.getId() == null || launchpadConfiguration.getId().trim().length() == 0 ) {
+            String runMode = launchpadConfiguration.getRunmode();
+            if ( runMode == null ) {
+                runMode = "_";
+            }
+            final String id = new StringBuilder(runMode.replace(',', '_')).append('-').append(launchpadConfiguration.getPort()).toString();
+            launchpadConfiguration.setId(id);
+        }
+
+        // populate folder if not set
+        if (launchpadConfiguration.getFolder() == null) {
+            final File folder = new File(new StringBuilder(this.project.getBuild().getDirectory()).append('/').append(launchpadConfiguration.getId()).toString());
+            launchpadConfiguration.setFolder(folder);
+        }
+        // context path should not be null
+        if ( launchpadConfiguration.getContextPath() == null ) {
+            launchpadConfiguration.setContextPath("");
+        }
+
+        if ( launchpadConfiguration.getInstances() < 0 ) {
+            launchpadConfiguration.setInstances(1);
+        }
+    }
+
+    /**
+     * Finds the launchpad.jar artifact of the project being built.
+     *
+     * @return the launchpad.jar artifact
+     * @throws MojoFailureException if a launchpad.jar artifact was not found
+     */
+    private File findLaunchpadJar() throws MojoFailureException, MojoExecutionException {
+
+        // If a launchpad JAR is specified, use it
+        if (launchpadJar != null) {
+            getLog().info("Using launchpad jar from '" +  launchpadJar + "' given as configuration parameter!");
+            return launchpadJar;
+        }
+
+        // If a launchpad dependency is configured, resolve it
+        if (launchpadDependency != null) {
+            getLog().info("Using launchpad dependency '" +  launchpadDependency + "' given as configuration parameter!");
+            return getArtifact(launchpadDependency).getFile();
+        }
+
+        // If the current project is a slingstart project, use its JAR artifact
+        if (this.project.getPackaging().equals(BuildConstants.PACKAGING_SLINGQUICKSTART)) {
+            File jarFile = project.getArtifact().getFile();
+            if (jarFile != null && jarFile.exists()) {
+                getLog().info("Using launchpad jar being generated as this project's primary artifact: '" +  jarFile + "'!");
+                return jarFile;
+            }
+            else {
+                jarFile = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
+                if (jarFile.exists()) {
+                    getLog().info("Using launchpad jar being generated as this project's primary artifact: '" +  jarFile + "'!");
+                    return jarFile;
+                }
+            }
+        }
+
+        // In case there was a provisioning model found but this is not a slingstart project, the JAR might be attached already through goal "package"
+        for (Artifact attachedArtifact : project.getAttachedArtifacts()) {
+            // find the attached artifact with classifier "standalone"
+            if (BuildConstants.TYPE_JAR.equals(attachedArtifact.getType()) && BuildConstants.CLASSIFIER_APP.equals(attachedArtifact.getClassifier())) {
+                getLog().info("Using launchpad jar being attached as additional project artifact: '" +  attachedArtifact.getFile() + "'!");
+                return attachedArtifact.getFile();
+            }
+        }
+        
+        // also check for jars in known target folders (in case the jar has been created in this project's target folder but not attached to the Maven project)
+//AS TODO: Is Package Mojo still needed / supported ?
+//        File localJarFile = PackageMojo.getNonPrimaryBuildFile(project, ".jar");
+//        if (localJarFile.exists()) {
+//            getLog().info("Using local launchpad jar being created in predefined directory: '" +  localJarFile + "'!");
+//            return localJarFile;
+//        }
+        
+        // Last chance: use the first declared dependency with type "slingstart"
+        final Set<Artifact> dependencies = this.project.getDependencyArtifacts();
+        for (final Artifact dep : dependencies) {
+            if (BuildConstants.PACKAGING_SLINGQUICKSTART.equals(dep.getType())) {
+                final Dependency d = new Dependency();
+                d.setGroupId(dep.getGroupId());
+                d.setArtifactId(dep.getArtifactId());
+                d.setVersion(dep.getVersion());
+                d.setScope(Artifact.SCOPE_RUNTIME);
+                d.setType(BuildConstants.TYPE_JAR);
+                getLog().info("Using launchpad jar from first dependency of type 'slingstart': '"+ d +"'!");
+                return getArtifact(d).getFile();
+            }
+        }
+
+        // Launchpad has not been found, throw an exception
+        throw new MojoFailureException("Could not find the launchpad jar. " +
+                "Either specify the 'launchpadJar' configuration or use this inside a slingstart project.");
+    }
+
+    /**
+     * Get all configurations
+     * @return Collection of configurations.
+     */
+    private Collection<ServerConfiguration> getLaunchpadConfigurations() {
+        final List<ServerConfiguration> configs = new ArrayList<ServerConfiguration>();
+        if ( this.servers != null && !this.servers.isEmpty() ) {
+            for(final ServerConfiguration config : this.servers) {
+                // if instances is set to 0, no instance is added
+                if ( config.getInstances() != 0 ) {
+                    configs.add(config);
+                    for(int i=2; i<=config.getInstances();i++) {
+                        final ServerConfiguration replicaConfig = config.copy();
+                        replicaConfig.setPort(null);
+                        final File folder = replicaConfig.getFolder();
+                        if ( folder != null ) {
+                            replicaConfig.setFolder(new File(folder.getParentFile(), folder.getName() + '-' + String.valueOf(i)));
+                        }
+                        configs.add(replicaConfig);
+                    }
+                    config.setInstances(1);
+                }
+            }
+        } else {
+            // use single default instance
+            configs.add(new ServerConfiguration());
+        }
+        return configs;
+    }
+}
diff --git a/src/main/java/org/apache/sling/maven/kickstart/run/StopMojo.java b/src/main/java/org/apache/sling/maven/kickstart/run/StopMojo.java
new file mode 100644
index 0000000..013360a
--- /dev/null
+++ b/src/main/java/org/apache/sling/maven/kickstart/run/StopMojo.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.maven.kickstart.run;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Stop one or multiple running launchpad instance(s).
+ *
+ */
+@Mojo(
+    name = "stop",
+    defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST,
+    threadSafe = true
+)
+public class StopMojo extends AbstractStartStopMojo {
+
+    @Override
+    protected void doExecute() throws MojoExecutionException, MojoFailureException {
+        
+        // read configurations
+        final Properties launchpadConfigProps = new Properties();
+        Reader reader = null;
+        try {
+            reader = new FileReader(this.systemPropertiesFile);
+            launchpadConfigProps.load(reader);
+        } catch ( final IOException ioe) {
+            throw new MojoExecutionException("Unable to read launchpad runner configuration properties.", ioe);
+        } finally {
+            IOUtils.closeQuietly(reader);
+        }
+
+        final int instances = Integer.valueOf(launchpadConfigProps.getProperty("launchpad.instances"));
+        final List<ProcessDescription> configurations = new ArrayList<ProcessDescription>();
+        for(int i=1;i<=instances;i++) {
+            final String id = launchpadConfigProps.getProperty("launchpad.instance.id." + String.valueOf(i));
+
+            final ProcessDescription config = ProcessDescriptionProvider.getInstance().getRunConfiguration(id);
+            if ( config == null ) {
+                getLog().warn("No launchpad configuration found for instance " + id);
+            } else {
+                configurations.add(config);
+            }
+        }
+
+        blockIfNecessary();
+        if (configurations.size() > 0) {
+            getLog().info(new StringBuilder("Stopping ").append(configurations.size()).append(" Launchpad instances").toString());
+
+            for (final ProcessDescription cfg : configurations) {
+
+                try {
+                    LauncherCallable.stop(this.getLog(), cfg);
+                    ProcessDescriptionProvider.getInstance().removeRunConfiguration(cfg.getId());
+                } catch (Exception e) {
+                    throw new MojoExecutionException("Could not stop launchpad " + cfg.getId(), e);
+                }
+            }
+        } else {
+            getLog().warn("No stored configuration file was found at " + this.systemPropertiesFile + " - no Launchapd will be stopped");
+        }
+    }
+}
diff --git a/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
new file mode 100644
index 0000000..ed2f6c1
--- /dev/null
+++ b/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<lifecycleMappingMetadata>
+    <pluginExecutions>
+        <pluginExecution>
+            <pluginExecutionFilter>
+                <goals>
+                    <goal>start</goal>
+                    <goal>stop</goal>
+                </goals>
+            </pluginExecutionFilter>
+            <action>
+                <ignore/>
+            </action>
+        </pluginExecution>
+    </pluginExecutions>
+</lifecycleMappingMetadata>
diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 0000000..a95cdd9
--- /dev/null
+++ b/src/main/resources/META-INF/plexus/components.xml
@@ -0,0 +1,58 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<component-set>
+  <components>
+    <component>
+      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
+      <role-hint>slingkickstart</role-hint>
+      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
+      <configuration>
+        <lifecycles>
+          <lifecycle>
+            <id>default</id>
+            <phases>
+              <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
+              <compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile>
+              <process-test-resources>
+                  org.apache.maven.plugins:maven-resources-plugin:testResources,
+              </process-test-resources>
+              <test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
+              <test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
+              <package>org.apache.maven.plugins:maven-jar-plugin:jar</package>
+              <install>org.apache.maven.plugins:maven-install-plugin:install</install>
+              <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
+            </phases>
+          </lifecycle>
+        </lifecycles>
+      </configuration>
+    </component>
+    <component>
+      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
+      <role-hint>slingkickstart</role-hint>
+      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
+      <configuration>
+        <type>slingkickstart</type>
+        <includesDependencies>false</includesDependencies>
+        <language>java</language>
+        <extension>jar</extension>
+        <addedToClasspath>false</addedToClasspath>
+      </configuration>
+    </component>
+  </components>
+</component-set>
diff --git a/src/main/resources/subsystem-base/readme.txt b/src/main/resources/subsystem-base/readme.txt
new file mode 100644
index 0000000..29c6711
--- /dev/null
+++ b/src/main/resources/subsystem-base/readme.txt
@@ -0,0 +1,36 @@
+A .subsystem-base jar file is produced from the sling provisioning model by the sling-maven-plugin as an intermediary file that is turned into a subsystem file .esa at runtime by the org.apache.sling.installer.factory.subsystems-base bundle. The actual content of the subsystem at runtime is determined by the current run mode. The transformer creates an .esa file that contains the right resources according to the current runmode.
+
+A subsystem-base file is generated from features in the sling provisioning model of type osgi.subsystem.application, osgi.subsystem.composite or osgi.subsystem.feature. For example:
+
+[feature name=mysubsystem type=osgi.subsystem.feature]
+
+[:subsystem-manifest startLevel=20]
+  Subsystem-Description: Extra subsystem headers can go here
+  Subsystem-Copyright: (c) 2015 for example
+
+[artifacts]
+  com.foo.bar/bundle/1
+
+[artifacts startLevel=10]
+  com.foo.bar/bundle1/1.2.3
+  com.foo.bar/bundle2/1.0.0
+
+[artifacts startLevel=15 runModes=myrunmode]
+  com.foo.bar/bundle3/0.0.1
+
+The above provisioning model description will generate a mysubsystem.subsystem-base jar file. The bundles in the subsystem will *all* have start level 20, from the startLevel in the :subsystem-manifest section. The mysubsystem.subsystem-base file will be placed in the install/20/mysubsystem.subsystem-base location of the generated sling-maven-plugin generated slingstart artifact. The startLevel attribute on the artifacts section are transformed into start-order attributes on the Subsystem-Content header.
+The generated mysubsystem.subsystem-base file will have the following content:
+
+META-INF/MANIFEST.MF
+Potential_Bundles/0/bundle-1.jar
+Potential_Bundles/10/bundle1-1.2.3.jar
+Potential_Bundles/10/bundle2-1.0.0.jar
+Potential_Bundles/15/bundle3-0.0.1.jar
+SUBSYSTEM-MANIFEST-BASE.MF
+readme.txt (this file).
+
+The META-INF/MANIFEST.MF file contains information about which resources should be deployed based on runmode. There is a special identifier _all_ which lists resources that should be deployed in all runmodes.
+The Potential_Bundles locations hold the actual resources in directories that denote their start-order.
+The SUBSYSTEM-MANIFEST-BASE.MF file is used as the basis for the OSGI-INF/SUBSYSTEM.MF file in the .esa file. The Subsystem-SymbolicName, Subsystem-Type, Subsystem-Version and Subsystem-Content are automatically generated. Additional headers listed under the [:subsystem-manifest] section are added to the ultimate SUBSYSTEM.MF. The Subsystem-Content is generated by the SubsystemBaseTransformer based on the current runmode.
+
+This file is included in the .subsystem-base file as a readme as this is not a common file format.