You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by an...@apache.org on 2019/12/27 23:57:35 UTC

[sling-org-apache-sling-launchpad-base] branch feature/SLING-8955-Feature-Launcher-Support created (now e4fd8cb)

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

andysch pushed a change to branch feature/SLING-8955-Feature-Launcher-Support
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-launchpad-base.git.


      at e4fd8cb  Added support for Feature Launcher and changed version to 3.0.0

This branch includes the following new commits:

     new e4fd8cb  Added support for Feature Launcher and changed version to 3.0.0

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



[sling-org-apache-sling-launchpad-base] 01/01: Added support for Feature Launcher and changed version to 3.0.0

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

andysch pushed a commit to branch feature/SLING-8955-Feature-Launcher-Support
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-launchpad-base.git

commit e4fd8cb2df4da025e297bf0cf73233bb6e0ffc5d
Author: Andreas Schaefer <sc...@iMac.local>
AuthorDate: Fri Dec 27 15:56:52 2019 -0800

    Added support for Feature Launcher and changed version to 3.0.0
---
 pom.xml                                            |  13 +-
 .../launchpad/app/{Main.java => AbstractMain.java} | 172 ++--
 .../sling/launchpad/app/ControlListener.java       |   4 +-
 .../apache/sling/launchpad/app/FeatureMain.java    | 221 +++++
 .../java/org/apache/sling/launchpad/app/Main.java  | 954 +--------------------
 .../org/apache/sling/launchpad/app/MainTest.java   |   2 +-
 6 files changed, 342 insertions(+), 1024 deletions(-)

diff --git a/pom.xml b/pom.xml
index bab110f..c342f60 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
        This version is a concatenation of the framework version and a version for the launchpad base itself.
        For a release only the qualifier (launchpad base version) is increased.
      -->
-    <version>6.0.2-2.6.37-SNAPSHOT</version>
+    <version>6.0.2-3.0.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Launchpad Base</name>
@@ -124,7 +124,7 @@
                             <goal>unpack-dependencies</goal>
                         </goals>
                         <configuration>
-                            <includeArtifactIds>org.apache.sling.commons.osgi</includeArtifactIds>
+                            <includeArtifactIds>commons-io,org.apache.sling.launchpad.base,org.apache.sling.feature.extension.content,org.apache.sling.feature.launcher,org.apache.sling.commons.osgi</includeArtifactIds>
                             <excludeTransitive>true</excludeTransitive>
                             <outputDirectory>
                                 ${project.build.directory}/classes
@@ -160,7 +160,7 @@
                             <archive>
                                 <manifest>
                                     <mainClass>
-                                        org.apache.sling.launchpad.app.Main
+                                        org.apache.sling.launchpad.app.FeatureMain
                                     </mainClass>
                                     <addDefaultImplementationEntries>
                                         true
@@ -174,6 +174,7 @@
                                 <include>org/apache/sling/commons/osgi/bundleversion/**</include>
                                 <include>org/osgi/framework/Version*</include>
                                 <include>META-INF/**</include>
+                                <include>org/apache/sling/feature/**</include>
                             </includes>
                         </configuration>
                     </execution>
@@ -268,6 +269,12 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.launcher</artifactId>
+            <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
         </dependency>
diff --git a/src/main/java/org/apache/sling/launchpad/app/Main.java b/src/main/java/org/apache/sling/launchpad/app/AbstractMain.java
similarity index 91%
copy from src/main/java/org/apache/sling/launchpad/app/Main.java
copy to src/main/java/org/apache/sling/launchpad/app/AbstractMain.java
index 312ba8f..e03cc3f 100644
--- a/src/main/java/org/apache/sling/launchpad/app/Main.java
+++ b/src/main/java/org/apache/sling/launchpad/app/AbstractMain.java
@@ -16,9 +16,17 @@
  */
 package org.apache.sling.launchpad.app;
 
+import org.apache.sling.launchpad.base.shared.Launcher;
+import org.apache.sling.launchpad.base.shared.Loader;
+import org.apache.sling.launchpad.base.shared.Notifiable;
+import org.apache.sling.launchpad.base.shared.SharedConstants;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.DateFormat;
@@ -28,11 +36,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.apache.sling.launchpad.base.shared.Launcher;
-import org.apache.sling.launchpad.base.shared.Loader;
-import org.apache.sling.launchpad.base.shared.Notifiable;
-import org.apache.sling.launchpad.base.shared.SharedConstants;
-
 /**
  * The <code>Main</code> is the externally visible Standalone Java Application
  * launcher for Sling. Please refer to the full description <i>The Sling
@@ -47,7 +50,7 @@ import org.apache.sling.launchpad.base.shared.SharedConstants;
  * @see <a href="http://sling.apache.org/site/the-sling-launchpad.html">The
  *      Sling Launchpad</a>
  */
-public class Main {
+public class AbstractMain {
 
     // The name of the environment variable to consult to find out
     // about sling.home
@@ -110,11 +113,12 @@ public class Main {
      * @param args The command line arguments supplied when starting the Sling
      *            Launcher through the Java VM.
      */
-    public static void main(final String[] args) {
+    public static void main(Class mainClass, final String[] args) {
         final Map<String, String> rawArgs = parseCommandLine(args);
 
         // support usage first
-        if (doHelp(rawArgs)) {
+        boolean usedHelp = (boolean) callMethod(mainClass, "doHelp", Map.class, rawArgs);
+        if(usedHelp) {
             System.exit(0);
         }
 
@@ -123,8 +127,29 @@ public class Main {
             System.exit(1);
         }
 
-        final Main main = new Main(props);
+        final AbstractMain main = (AbstractMain) callMethod(mainClass, null, Map.class, props);
+
+        launchMain(main);
+    }
+
+    private static Object callMethod(Class clazz, String methodName, Class argumentType, Object argument) {
+        try {
+            Object answer;
+            if (methodName == null || methodName.isEmpty()) {
+                Constructor constructor = clazz.getConstructor(argumentType);
+                answer = constructor.newInstance(argument);
+            } else {
+                Method method = clazz.getMethod(methodName, argumentType);
+                System.out.println("Method found: '" + method + "'");
+                answer = method.invoke(null, argument);
+            }
+            return answer;
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
+            throw new RuntimeException("Unexpected issue with reflection", e);
+        }
+    }
 
+    static void launchMain(AbstractMain main) {
         // check for control commands
         int rc = main.doControlAction();
         if (rc >= 0) {
@@ -143,7 +168,7 @@ public class Main {
      * property names as known to the OSGi Framework and its installed
      * bundles.
      */
-    private final Map<String, String> commandLineArgs;
+    protected final Map<String, String> commandLineArgs;
 
     /**
      * Whether to install the shutdown hook.
@@ -152,7 +177,7 @@ public class Main {
      * @see #installShutdownHook(Map)
      * @see #addShutdownHook()
      */
-    private boolean installShutdownHook;
+    protected boolean installShutdownHook;
 
     /**
      * The shutdown hook installed into the Java VM after Sling has been
@@ -162,7 +187,7 @@ public class Main {
      * @see #addShutdownHook()
      * @see #removeShutdownHook()
      */
-    private Thread shutdownHook;
+    protected Thread shutdownHook;
 
     /**
      * The absolute path to the home directory of the launched Sling
@@ -171,24 +196,24 @@ public class Main {
      *
      * @see #getSlingHome(Map)
      */
-    private final String slingHome;
+    protected final String slingHome;
 
     /**
      * The {@link Loader} class used to create the Framework class loader and
      * to launch the framework.
      */
-    private Loader loader;
+    protected Loader loader;
 
     /**
      * The actual launcher accessed through the {@link #loader} to launch
      * the OSGi Framework.
      */
-    private Launcher sling;
+    protected Launcher sling;
 
     /**
      * Flag to indicate if Sling has already been started.
      */
-    private boolean started = false;
+    protected boolean started = false;
 
     /**
      * Creates an instance of this main loader class. The provided arguments are
@@ -201,7 +226,7 @@ public class Main {
      *            this parameter is <code>null</code> and empty map without
      *            configuration is assumed.
      */
-    protected Main(Map<String, String> args) {
+    protected AbstractMain(Map<String, String> args) {
         this.commandLineArgs = (args == null)
                 ? new HashMap<String, String>()
                 : args;
@@ -303,7 +328,7 @@ public class Main {
 
     /**
      * Terminates the VM which was started by calling the
-     * {@link #main(String[])} method of this class using the
+     * {@link #main(Class, String[])} method of this class using the
      * <code>status</code> value as the application exit status code.
      * <p>
      * This method does not return.
@@ -366,7 +391,7 @@ public class Main {
             loaderTmp = new Loader(launchpadHome) {
                 @Override
                 protected void info(String msg) {
-                    Main.info(msg, null);
+                    AbstractMain.info(msg, null);
                 }
             };
         } catch (IllegalArgumentException iae) {
@@ -457,14 +482,14 @@ public class Main {
         this.started = false;
     }
 
-    private void addShutdownHook() {
+    protected void addShutdownHook() {
         if (this.installShutdownHook && this.shutdownHook == null) {
             this.shutdownHook = new Thread(new ShutdownHook(), "Apache Sling Terminator");
             Runtime.getRuntime().addShutdownHook(this.shutdownHook);
         }
     }
 
-    private void removeShutdownHook() {
+    protected void removeShutdownHook() {
         // remove the shutdown hook, will fail if called from the
         // shutdown hook itself. Otherwise this prevents shutdown
         // from being called again
@@ -550,7 +575,7 @@ public class Main {
      *            <code>sling.launchpad</code> property.
      * @return The absolute <code>File</code> indicating the launchpad folder.
      */
-    private static File getLaunchpadHome(final String slingHome,
+    protected static File getLaunchpadHome(final String slingHome,
             final Map<String, String> commandLineArgs) {
         final String launchpadHomeParam = commandLineArgs.get(SharedConstants.SLING_LAUNCHPAD);
         if (launchpadHomeParam == null || launchpadHomeParam.length() == 0) {
@@ -684,37 +709,7 @@ public class Main {
         return commandLine;
     }
 
-    /** prints a simple usage plus optional error message */
-    private static boolean doHelp(Map<String, String> args) {
-        if (args.remove("h") != null) {
-            System.out.println("usage: "
-                + Main.class.getName()
-                + " [ start | stop | status ] [ -j adr ] [ -l loglevel ] [ -f logfile ] [ -c slinghome ] [ -i launchpadhome ] [ -a address ] [ -p port ] { -Dn=v } [ -h ]");
-
-            System.out.println("    start         listen for control connection (uses -j)");
-            System.out.println("    stop          terminate running Apache Sling (uses -j)");
-            System.out.println("    status        check whether Apache Sling is running (uses -j)");
-            System.out.println("    threads       request a thread dump from Apache Sling (uses -j)");
-            System.out.println("    -j adr        host and port to use for control connection in the format '[host:]port' (default 127.0.0.1:0)");
-            System.out.println("    -l loglevel   the initial loglevel (0..4, FATAL, ERROR, WARN, INFO, DEBUG)");
-            System.out.println("    -f logfile    the log file, \"-\" for stdout (default logs/error.log)");
-            System.out.println("    -c slinghome  the sling context directory (default sling)");
-            System.out.println("    -i launchpadhome  the launchpad directory (default slinghome)");
-            System.out.println("    -a address    the interfact to bind to (use 0.0.0.0 for any)");
-            System.out.println("    -p port       the port to listen to (default 8080)");
-            System.out.println("    -r path       the root servlet context path for the http service (default is /)");
-            System.out.println("    -n            don't install the shutdown hook");
-            System.out.println("    -Dn=v         sets property n to value v. Make sure to use this option *after* " +
-                                                  "the jar filename. The JVM also has a -D option which has a " +
-                                                  "different meaning");
-            System.out.println("    -h            prints this usage message");
-
-            return true;
-        }
-        return false;
-    }
-
-    private static boolean installShutdownHook(Map<String, String> props) {
+    protected static boolean installShutdownHook(Map<String, String> props) {
         String prop = props.remove(PROP_SHUTDOWN_HOOK);
         if (prop == null) {
             prop = System.getProperty(PROP_SHUTDOWN_HOOK);
@@ -904,15 +899,50 @@ public class Main {
         }
     }
 
+    /** prints a simple usage plus optional error message */
+    static boolean doHelp(Class mainClass, String[] additionalLines, Map<String, String> args) {
+        if (args.remove("h") != null) {
+            System.out.println("usage: "
+                + mainClass.getName()
+                + " [ start | stop | status ] [ -j adr ] [ -l loglevel ] [ -f logfile ] [ -c slinghome ] [ -i launchpadhome ] [ -a address ] [ -p port ] { -Dn=v } [ -h ]");
+
+            System.out.println("    start         listen for control connection (uses -j)");
+            System.out.println("    stop          terminate running Apache Sling (uses -j)");
+            System.out.println("    status        check whether Apache Sling is running (uses -j)");
+            System.out.println("    threads       request a thread dump from Apache Sling (uses -j)");
+            System.out.println("    -j adr        host and port to use for control connection in the format '[host:]port' (default 127.0.0.1:0)");
+            System.out.println("    -l loglevel   the initial loglevel (0..4, FATAL, ERROR, WARN, INFO, DEBUG)");
+            System.out.println("    -f logfile    the log file, \"-\" for stdout (default logs/error.log)");
+            System.out.println("    -c slinghome  the sling context directory (default sling)");
+            System.out.println("    -i launchpadhome  the launchpad directory (default slinghome)");
+            System.out.println("    -a address    the interface to bind to (use 0.0.0.0 for any)");
+            System.out.println("    -p port       the port to listen to (default 8080)");
+            System.out.println("    -r path       the root servlet context path for the http service (default is /)");
+            System.out.println("    -n            don't install the shutdown hook");
+            System.out.println("    -Dn=v         sets property n to value v. Make sure to use this option *after* " +
+                "the jar filename. The JVM also has a -D option which has a " +
+                "different meaning");
+            if(additionalLines != null && additionalLines.length > 0) {
+                for(String line: additionalLines) {
+                    System.out.println(line);
+                }
+            }
+            System.out.println("    -h            prints this usage message");
+
+            return true;
+        }
+        return false;
+    }
+
     private class ShutdownHook implements Runnable {
         @Override
         public void run() {
-            Main.info("Java VM is shutting down", null);
-            Main.this.doStop();
+            AbstractMain.info("Java VM is shutting down", null);
+            AbstractMain.this.doStop();
         }
     }
 
-    private class Notified implements Notifiable {
+    protected class Notified implements Notifiable {
 
         /**
          * The framework has been stopped by calling the
@@ -930,10 +960,10 @@ public class Main {
              * fails).
              */
 
-            Main.info("Apache Sling has been stopped", null);
+            AbstractMain.info("Apache Sling has been stopped", null);
 
-            Main.this.sling = null;
-            Main.this.doStop();
+            AbstractMain.this.sling = null;
+            AbstractMain.this.doStop();
         }
 
         /**
@@ -952,35 +982,35 @@ public class Main {
         @Override
         public void updated(File updateFile) {
 
-            Main.this.sling = null;
-            Main.this.doStop();
+            AbstractMain.this.sling = null;
+            AbstractMain.this.doStop();
 
-            Main.cleanupThreads();
+            AbstractMain.cleanupThreads();
 
             if (updateFile == null) {
 
-                Main.info("Restarting Framework and Apache Sling", null);
-                if (!Main.this.doStart(null)) {
-                    Main.error("Failed to restart Sling; terminating", null);
-                    Main.this.terminateVM(1);
+                AbstractMain.info("Restarting Framework and Apache Sling", null);
+                if (!AbstractMain.this.doStart(null)) {
+                    AbstractMain.error("Failed to restart Sling; terminating", null);
+                    AbstractMain.this.terminateVM(1);
                 }
 
             } else {
 
-                Main.info(
+                AbstractMain.info(
                     "Restarting Framework with update from " + updateFile, null);
                 boolean started = false;
                 try {
-                    started = Main.this.doStart(updateFile.toURI().toURL());
+                    started = AbstractMain.this.doStart(updateFile.toURI().toURL());
                 } catch (MalformedURLException mue) {
-                    Main.error("Cannot get URL for file " + updateFile, mue);
+                    AbstractMain.error("Cannot get URL for file " + updateFile, mue);
                 } finally {
                     updateFile.delete();
                 }
 
                 if (!started) {
-                    Main.error("Failed to restart Sling; terminating", null);
-                    Main.this.terminateVM(1);
+                    AbstractMain.error("Failed to restart Sling; terminating", null);
+                    AbstractMain.this.terminateVM(1);
                 }
             }
         }
diff --git a/src/main/java/org/apache/sling/launchpad/app/ControlListener.java b/src/main/java/org/apache/sling/launchpad/app/ControlListener.java
index 6e6284b..26689f8 100644
--- a/src/main/java/org/apache/sling/launchpad/app/ControlListener.java
+++ b/src/main/java/org/apache/sling/launchpad/app/ControlListener.java
@@ -93,7 +93,7 @@ class ControlListener implements Runnable {
     private static final int DEFAULT_LISTEN_PORT = 0;
 
     // The reference to the Main class to shutdown on request
-    private final Main slingMain;
+    private final AbstractMain slingMain;
 
     private final String listenSpec;
 
@@ -117,7 +117,7 @@ class ControlListener implements Runnable {
      * @param listenSpec The specification for the host and port for the socket
      *            connection. See above for the format of this parameter.
      */
-    ControlListener(final Main slingMain, final String listenSpec) {
+    ControlListener(final AbstractMain slingMain, final String listenSpec) {
         this.slingMain = slingMain;
         this.listenSpec = listenSpec; // socketAddress = this.getSocketAddress(listenSpec, selectNewPort);
     }
diff --git a/src/main/java/org/apache/sling/launchpad/app/FeatureMain.java b/src/main/java/org/apache/sling/launchpad/app/FeatureMain.java
new file mode 100644
index 0000000..ffa9301
--- /dev/null
+++ b/src/main/java/org/apache/sling/launchpad/app/FeatureMain.java
@@ -0,0 +1,221 @@
+/*
+ * 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.launchpad.app;
+
+import org.apache.sling.launchpad.base.shared.Launcher;
+import org.apache.sling.launchpad.base.shared.Loader;
+import org.apache.sling.launchpad.base.shared.Notifiable;
+import org.apache.sling.launchpad.base.shared.SharedConstants;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * The <code>FeatureMain</code> is the externally visible Standalone Java Application
+ * launcher for Feature Model base Sling. Please refer to the full description <i>The Sling
+ * Launchpad</i> on the Sling Web Site for a full description of this class.
+ * <p>
+ * Logging goes to standard output for informational messages and to standard
+ * error for error messages.
+ * <p>
+ * This class goes into the secondary artifact with the classifier <i>app</i> to
+ * be used as the main class when starting the Java Application.
+ *
+ * @see <a href="http://sling.apache.org/site/the-sling-launchpad.html">The
+ *      Sling Launchpad</a>
+ */
+public class FeatureMain extends AbstractMain {
+
+    public static void main(final String[] args) {
+        AbstractMain.main(FeatureMain.class, args);
+    }
+
+    /** prints a simple usage plus optional error message */
+    public static boolean doHelp(Map<String, String> args) {
+        return AbstractMain.doHelp(Main.class,
+            new String[] {
+                "    -s             Main Feature File (will override the provided Sling Feature File)",
+                "    -af            Additional Feature Files added to the Main Feature File",
+            },
+            args
+        );
+    }
+
+    public FeatureMain(Map<String, String> args) {
+        super(args);
+    }
+
+    protected int doControlAction() {
+        //AS TODO: It would probably best to have the logic of handling Background Service to be inside the Feature Launcher
+//        final ControlAction action = getControlAction();
+//        if (action != null) {
+//            final ControlListener sl = new ControlListener(this,
+//                commandLineArgs.remove(PROP_CONTROL_SOCKET));
+//            switch (action) {
+//                case START:
+//                    if (!sl.listen()) {
+//                        // assume service already running
+//                        return 0;
+//                    }
+//                    break;
+//                case STATUS:
+//                    return sl.statusServer();
+//                case STOP:
+//                    return sl.shutdownServer();
+//                case THREADS:
+//                    return sl.dumpThreads();
+//            }
+//        }
+
+        return -1;
+    }
+
+    protected boolean doStart(final URL launcherJar) {
+
+        // prevent duplicate start
+        if ( this.started) {
+            info("Apache Sling has already been started", null);
+            return true;
+        }
+
+        info("Starting Apache Sling in " + slingHome, null);
+        this.started = true;
+
+        try {
+            java.net.URL url = getClass().getResource("/feature-sling12.json");
+            String[] arguments = new String[]{
+//                    "-f", tempFile.getAbsolutePath(),
+                "-f", url.toString(),
+                "-D", "org.osgi.service.http.port=8081",
+                "-v"
+            };
+            System.out.println("Before Launching Feature Launcher, arguments: " + Arrays.asList(arguments));
+            org.apache.sling.feature.launcher.impl.Main.main(arguments);
+        } catch(Throwable t) {
+            System.out.println("Caught an Exception: " + t.getLocalizedMessage());
+            t.printStackTrace();
+        }
+
+//        Loader loaderTmp = null;
+//        try {
+//            final File launchpadHome = getLaunchpadHome(slingHome,
+//                commandLineArgs);
+//            loaderTmp = new Loader(launchpadHome) {
+//                @Override
+//                protected void info(String msg) {
+//                    FeatureMain.info(msg, null);
+//                }
+//            };
+//        } catch (IllegalArgumentException iae) {
+//            error(
+//                "Cannot launch: Launchpad folder cannot be used: "
+//                    + iae.getMessage(), null);
+//            return false;
+//        }
+//        this.loader = loaderTmp;
+//
+//        if (launcherJar != null) {
+//            try {
+//                loader.installLauncherJar(launcherJar);
+//            } catch (IOException ioe) {
+//                error("Cannot launch: Cannot install " + launcherJar
+//                    + " for use", ioe);
+//                return false;
+//            }
+//        } else {
+//            info("No Launcher JAR to install", null);
+//        }
+//
+//        Object object = null;
+//        try {
+//            object = loader.loadLauncher(SharedConstants.DEFAULT_SLING_MAIN);
+//        } catch (IllegalArgumentException iae) {
+//            error("Cannot launch: Failed loading Sling class "
+//                + SharedConstants.DEFAULT_SLING_MAIN, iae);
+//            return false;
+//        }
+//
+//        if (object instanceof Launcher) {
+//
+//            // configure the launcher
+//            Launcher sling = (Launcher) object;
+//            sling.setNotifiable(new Notified());
+//            sling.setCommandLine(commandLineArgs);
+//            sling.setSlingHome(slingHome);
+//
+//            // launch it
+//            info("Starting launcher ...", null);
+//            if (sling.start()) {
+//                info("Startup completed", null);
+//                this.sling = sling;
+//                addShutdownHook();
+//                return true;
+//            }
+//
+//            error("Cannot launch: Launcher.start() returned false", null);
+//
+//        } else {
+//
+//            error("Cannot launch: Class " + SharedConstants.DEFAULT_SLING_MAIN + " is not a Launcher class", null);
+//
+//        }
+
+        return false;
+    }
+
+    /**
+     * Maybe called by the application to cause the Sling Application to
+     * properly terminate by stopping the OSGi Framework.
+     * <p>
+     * After calling this method the Sling Application can be started again
+     * by calling the {@link #doStart()} method.
+     * <p>
+     * Calling this method multiple times without calling the {@link #doStart()}
+     * method in between has no effect after the Sling Application has been
+     * terminated.
+     */
+    protected void doStop() {
+        removeShutdownHook();
+
+//        // now really shutdown sling
+//        if (this.sling != null) {
+//            info("Stopping Apache Sling", null);
+//            this.sling.stop();
+//            this.sling = null;
+//        }
+//
+//        // clean and VM caches
+//        if (this.loader != null) {
+//            this.loader.cleanupVM();
+//            this.loader = null;
+//        }
+//
+//        // further cleanup
+//        this.started = false;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/launchpad/app/Main.java b/src/main/java/org/apache/sling/launchpad/app/Main.java
index 312ba8f..38509d2 100644
--- a/src/main/java/org/apache/sling/launchpad/app/Main.java
+++ b/src/main/java/org/apache/sling/launchpad/app/Main.java
@@ -16,22 +16,7 @@
  */
 package org.apache.sling.launchpad.app;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
 import java.util.Map;
-import java.util.Map.Entry;
-
-import org.apache.sling.launchpad.base.shared.Launcher;
-import org.apache.sling.launchpad.base.shared.Loader;
-import org.apache.sling.launchpad.base.shared.Notifiable;
-import org.apache.sling.launchpad.base.shared.SharedConstants;
 
 /**
  * The <code>Main</code> is the externally visible Standalone Java Application
@@ -47,943 +32,18 @@ import org.apache.sling.launchpad.base.shared.SharedConstants;
  * @see <a href="http://sling.apache.org/site/the-sling-launchpad.html">The
  *      Sling Launchpad</a>
  */
-public class Main {
-
-    // The name of the environment variable to consult to find out
-    // about sling.home
-    private static final String ENV_SLING_HOME = "SLING_HOME";
-
-    /**
-     * The name of the configuration property indicating the
-     * {@link ControlAction} to be taken in the {@link #doControlAction()}
-     * method.
-     */
-    protected static final String PROP_CONTROL_ACTION = "sling.control.action";
-
-    /**
-     * The name of the configuration property indicating the socket to use for
-     * the control connection. The value of this property is either just a port
-     * number (in which case the host is assumed to be <code>localhost</code>)
-     * or a host name (or IP address) and port number separated by a colon.
-     */
-    protected static final String PROP_CONTROL_SOCKET = "sling.control.socket";
-
-    /** The Sling configuration property name setting the initial log level */
-    private static final String PROP_LOG_LEVEL = "org.apache.sling.commons.log.level";
-
-    /** The Sling configuration property name setting the initial log file */
-    private static final String PROP_LOG_FILE = "org.apache.sling.commons.log.file";
-
-    /**
-     * The configuration property setting the port on which the HTTP service
-     * listens
-     */
-    private static final String PROP_PORT = "org.osgi.service.http.port";
-
-    /**
-     * The configuration property setting the context path where the HTTP service
-     * mounts itself.
-     */
-    private static final String PROP_CONTEXT_PATH = "org.apache.felix.http.context_path";
-
-    /**
-     * Host name or IP Address of the interface to listen on.
-     */
-    private static final String PROP_HOST = "org.apache.felix.http.host";
+public class Main extends AbstractMain {
 
-    /**
-     * Name of the configuration property (or system property) indicating
-     * whether the shutdown hook should be installed or not. If this property is
-     * not set or set to {@code true} (case insensitive), the shutdown hook
-     * properly shutting down the framework is installed on startup. Otherwise,
-     * if this property is set to any value other than {@code true} (case
-     * insensitive) the shutdown hook is not installed.
-     * <p>
-     * The respective command line option is {@code -n}.
-     */
-    private static final String PROP_SHUTDOWN_HOOK = "sling.shutdown.hook";
-
-    /**
-     * The main entry point to the Sling Launcher Standalone Java Application.
-     * This method is generally only called by the Java VM to launch Sling.
-     *
-     * @param args The command line arguments supplied when starting the Sling
-     *            Launcher through the Java VM.
-     */
     public static void main(final String[] args) {
-        final Map<String, String> rawArgs = parseCommandLine(args);
-
-        // support usage first
-        if (doHelp(rawArgs)) {
-            System.exit(0);
-        }
-
-        final Map<String, String> props = convertCommandLineArgs(rawArgs);
-        if (props == null) {
-            System.exit(1);
-        }
-
-        final Main main = new Main(props);
-
-        // check for control commands
-        int rc = main.doControlAction();
-        if (rc >= 0) {
-            main.terminateVM(rc);
-        }
-
-        // finally start Sling
-        if (!main.doStart()) {
-            error("Failed to start Sling; terminating", null);
-            main.terminateVM(1);
-        }
-    }
-
-    /**
-     * The map of command line arguments where the keys are the actual
-     * property names as known to the OSGi Framework and its installed
-     * bundles.
-     */
-    private final Map<String, String> commandLineArgs;
-
-    /**
-     * Whether to install the shutdown hook.
-     *
-     * @see #PROP_SHUTDOWN_HOOK
-     * @see #installShutdownHook(Map)
-     * @see #addShutdownHook()
-     */
-    private boolean installShutdownHook;
-
-    /**
-     * The shutdown hook installed into the Java VM after Sling has been
-     * started. The hook is removed again when Sling is being shut down
-     * or the {@link Notified notifier} is notified of the framework shutdown.
-     *
-     * @see #addShutdownHook()
-     * @see #removeShutdownHook()
-     */
-    private Thread shutdownHook;
-
-    /**
-     * The absolute path to the home directory of the launched Sling
-     * application. This corresponds to the value of the <code>sling.home</code>
-     * framework property.
-     *
-     * @see #getSlingHome(Map)
-     */
-    private final String slingHome;
-
-    /**
-     * The {@link Loader} class used to create the Framework class loader and
-     * to launch the framework.
-     */
-    private Loader loader;
-
-    /**
-     * The actual launcher accessed through the {@link #loader} to launch
-     * the OSGi Framework.
-     */
-    private Launcher sling;
-
-    /**
-     * Flag to indicate if Sling has already been started.
-     */
-    private boolean started = false;
-
-    /**
-     * Creates an instance of this main loader class. The provided arguments are
-     * used to configure the OSGi framework being launched with the
-     * {@link #doStart(URL)} method.
-     *
-     * @param args The map of configuration properties to be supplied to the
-     *            OSGi framework. The keys in this map are assumed to be usefull
-     *            without translation to the launcher and the OSGi Framework. If
-     *            this parameter is <code>null</code> and empty map without
-     *            configuration is assumed.
-     */
-    protected Main(Map<String, String> args) {
-        this.commandLineArgs = (args == null)
-                ? new HashMap<String, String>()
-                : args;
-
-        this.installShutdownHook = installShutdownHook(this.commandLineArgs);
-
-        // sling.home from the command line or system properties, else default
-        String home = getSlingHome(commandLineArgs);
-        final File slingHomeFile = new File(home);
-        if (!slingHomeFile.isAbsolute()) {
-            home = slingHomeFile.getAbsolutePath();
-        }
-        this.slingHome = home;
-    }
-
-    /**
-     * After instantiating this class, this method may be called to help with
-     * the communication with a running Sling instance. To setup this
-     * communication the configuration properties supplied to the constructor
-     * are evaluated as follows:
-     * <p>
-     * <table>
-     * <tr>
-     * <td><code>{@value #PROP_CONTROL_SOCKET}</code></td>
-     * <td>Specifies the socket to use for the control connection. This
-     * specification is of the form <i>host:port</i> where the host can be a
-     * host name or IP Address and may be omitted (along with the separating
-     * colon) and port is just the numeric port number at which to listen. The
-     * default is <i>localhost:63000</i>. It is suggested to not use an
-     * externally accessible interface for security reasons because there is no
-     * added security on this control channel for now.</td>
-     * </tr>
-     * <tr>
-     * <td><code>{@value #PROP_CONTROL_ACTION}</code></td>
-     * <td>The actual action to execute:
-     * <ul>
-     * <b>start</b> -- Start the listener on the configured socket and expect
-     * commands there. This action is useful only when launching the Sling
-     * application since this action helps manage a running system.
-     * </ul>
-     * <ul>
-     * <b>stop</b> -- Connects to the listener running on the configured socket
-     * and send the command to terminate the Sling Application. If this command
-     * is used, it is expected the Sling Application will not start.
-     * </ul>
-     * <ul>
-     * <b>status</b> -- Connects to the listener running on the configured
-     * socket and query about its status. If this command is used, it is
-     * expected the Sling Application will not start.
-     * </ul>
-     * </td>
-     * </tr>
-     * </table>
-     * <p>
-     * After this method has executed the <code>j</code> and
-     * {@link #PROP_CONTROL_ACTION} properties have been removed from the
-     * configuration properties.
-     * <p>
-     * While the {@link #doStart()} and {@link #doStop()} methods may be called
-     * multiple times this method should only be called once after creating this
-     * class's instance.
-     *
-     * @return An code indicating whether the Java VM is expected to be
-     *         terminated or not. If <code>-1</code> is returned, the VM should
-     *         continue as intended, maybe starting the Sling Application. This
-     *         code is returned if the start action (or no action at all) is
-     *         supplied. Otherwise the VM should terminate with the returned
-     *         code as its exit code. For the stop action, this will be zero.
-     *         For the status action, this will be a LSB compliant code for
-     *         daemon status check: 0 (application running), 1 (Program Dead),
-     *         3 (Program Not Running), 4 (Unknown Problem).
-     * @see <a
-     *      href="http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html">Init Script Actions</a>
-     *      for a definition of the LSB status codes
-     */
-    protected int doControlAction() {
-        final ControlAction action = getControlAction();
-        if (action != null) {
-            final ControlListener sl = new ControlListener(this,
-                commandLineArgs.remove(PROP_CONTROL_SOCKET));
-            switch (action) {
-                case START:
-                    if (!sl.listen()) {
-                        // assume service already running
-                        return 0;
-                    }
-                    break;
-                case STATUS:
-                    return sl.statusServer();
-                case STOP:
-                    return sl.shutdownServer();
-                case THREADS:
-                    return sl.dumpThreads();
-            }
-        }
-
-        return -1;
-    }
-
-    /**
-     * Terminates the VM which was started by calling the
-     * {@link #main(String[])} method of this class using the
-     * <code>status</code> value as the application exit status code.
-     * <p>
-     * This method does not return.
-     *
-     * @param status The application status exit code.
-     */
-    // default accessor to enable overwriting for unit tests
-    void terminateVM(final int status) {
-        System.exit(status);
-    }
-
-    private ControlAction getControlAction() {
-        Object action = this.commandLineArgs.remove(PROP_CONTROL_ACTION);
-        if (action != null) {
-            if (action instanceof ControlAction) {
-                return (ControlAction) action;
-            }
-
-            try {
-                return ControlAction.valueOf(action.toString().toUpperCase());
-            } catch (IllegalArgumentException iae) {
-                error("Illegal control action value: " + action, null);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Starts the application with the configuration supplied with the
-     * configuration properties when this instance has been created.
-     * <p>
-     * Calling this method multiple times before calling {@link #doStop()} will
-     * cause a message to be printed and <code>true</code> being returned.
-     *
-     * @return <code>true</code> if startup was successful or the application
-     *         is considered to be started already. Otherwise an error message
-     *         has been logged and <code>false</code> is returned.
-     */
-    protected boolean doStart() {
-        // ensure up-to-date launcher jar
-        return doStart(getClass().getResource(
-            SharedConstants.DEFAULT_SLING_LAUNCHER_JAR));
-    }
-
-    protected boolean doStart(final URL launcherJar) {
-
-        // prevent duplicate start
-        if ( this.started) {
-            info("Apache Sling has already been started", null);
-            return true;
-        }
-
-        info("Starting Apache Sling in " + slingHome, null);
-        this.started = true;
-
-        Loader loaderTmp = null;
-        try {
-            final File launchpadHome = getLaunchpadHome(slingHome,
-                commandLineArgs);
-            loaderTmp = new Loader(launchpadHome) {
-                @Override
-                protected void info(String msg) {
-                    Main.info(msg, null);
-                }
-            };
-        } catch (IllegalArgumentException iae) {
-            error(
-                "Cannot launch: Launchpad folder cannot be used: "
-                    + iae.getMessage(), null);
-            return false;
-        }
-        this.loader = loaderTmp;
-
-        if (launcherJar != null) {
-            try {
-                loader.installLauncherJar(launcherJar);
-            } catch (IOException ioe) {
-                error("Cannot launch: Cannot install " + launcherJar
-                    + " for use", ioe);
-                return false;
-            }
-        } else {
-            info("No Launcher JAR to install", null);
-        }
-
-        Object object = null;
-        try {
-            object = loader.loadLauncher(SharedConstants.DEFAULT_SLING_MAIN);
-        } catch (IllegalArgumentException iae) {
-            error("Cannot launch: Failed loading Sling class "
-                + SharedConstants.DEFAULT_SLING_MAIN, iae);
-            return false;
-        }
-
-        if (object instanceof Launcher) {
-
-            // configure the launcher
-            Launcher sling = (Launcher) object;
-            sling.setNotifiable(new Notified());
-            sling.setCommandLine(commandLineArgs);
-            sling.setSlingHome(slingHome);
-
-            // launch it
-            info("Starting launcher ...", null);
-            if (sling.start()) {
-                info("Startup completed", null);
-                this.sling = sling;
-                addShutdownHook();
-                return true;
-            }
-
-            error("Cannot launch: Launcher.start() returned false", null);
-
-        } else {
-
-            error("Cannot launch: Class " + SharedConstants.DEFAULT_SLING_MAIN + " is not a Launcher class", null);
-
-        }
-
-        return false;
-    }
-
-    /**
-     * Maybe called by the application to cause the Sling Application to
-     * properly terminate by stopping the OSGi Framework.
-     * <p>
-     * After calling this method the Sling Application can be started again
-     * by calling the {@link #doStart()} method.
-     * <p>
-     * Calling this method multiple times without calling the {@link #doStart()}
-     * method in between has no effect after the Sling Application has been
-     * terminated.
-     */
-    protected void doStop() {
-        removeShutdownHook();
-
-        // now really shutdown sling
-        if (this.sling != null) {
-            info("Stopping Apache Sling", null);
-            this.sling.stop();
-            this.sling = null;
-        }
-
-        // clean and VM caches
-        if (this.loader != null) {
-            this.loader.cleanupVM();
-            this.loader = null;
-        }
-
-        // further cleanup
-        this.started = false;
-    }
-
-    private void addShutdownHook() {
-        if (this.installShutdownHook && this.shutdownHook == null) {
-            this.shutdownHook = new Thread(new ShutdownHook(), "Apache Sling Terminator");
-            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
-        }
-    }
-
-    private void removeShutdownHook() {
-        // remove the shutdown hook, will fail if called from the
-        // shutdown hook itself. Otherwise this prevents shutdown
-        // from being called again
-        Thread shutdownHook = this.shutdownHook;
-        this.shutdownHook = null;
-
-        if (shutdownHook != null) {
-            try {
-                Runtime.getRuntime().removeShutdownHook(shutdownHook);
-            } catch (Throwable t) {
-                // don't care for problems removing the hook
-            }
-        }
-    }
-
-    /**
-     * Define the sling.home parameter implementing the algorithme defined on
-     * the wiki page to find the setting according to this algorithm:
-     * <ol>
-     * <li>Configuration property <code>sling.home</code></li>
-     * <li>System property <code>sling.home</code></li>
-     * <li>Environment variable <code>SLING_HOME</code></li>
-     * <li>Default value <code>sling</code></li>
-     * </ol>
-     *
-     * @param commandLine The command line arguments
-     * @return The value to use for sling.home
-     */
-    private static String getSlingHome(Map<String, String> commandLine) {
-        String source = null;
-
-        String slingHome = commandLine.get(SharedConstants.SLING_HOME);
-        if (slingHome != null) {
-
-            source = "command line";
-
-        } else {
-
-            slingHome = System.getProperty(SharedConstants.SLING_HOME);
-            if (slingHome != null) {
-
-                source = "system property sling.home";
-
-            } else {
-
-                slingHome = System.getenv(ENV_SLING_HOME);
-                if (slingHome != null) {
-
-                    source = "environment variable SLING_HOME";
-
-                } else {
-
-                    source = "default";
-                    slingHome = SharedConstants.SLING_HOME_DEFAULT;
-
-                }
-            }
-        }
-
-        info("Setting sling.home=" + slingHome + " (" + source + ")", null);
-        return slingHome;
-    }
-
-    /**
-     * Return the absolute path to sling home
-     */
-    public String getSlingHome() {
-        return this.slingHome;
-    }
-
-    /**
-     * Define the sling.launchpad parameter implementing the algorithm defined
-     * on the wiki page to find the setting according to this algorithm:
-     * <ol>
-     * <li>Configuration property <code>sling.launchpad</code>. This path is
-     * resolved against the <code>slingHome</code> folder if relative.</li>
-     * <li>Default to same as <code>sling.home</code></li>
-     * </ol>
-     *
-     * @param slingHome The absolute path to the Sling Home folder (aka the
-     *            <code>sling.home</code>.
-     * @param commandLineArgs The configuration properties from where to get the
-     *            <code>sling.launchpad</code> property.
-     * @return The absolute <code>File</code> indicating the launchpad folder.
-     */
-    private static File getLaunchpadHome(final String slingHome,
-            final Map<String, String> commandLineArgs) {
-        final String launchpadHomeParam = commandLineArgs.get(SharedConstants.SLING_LAUNCHPAD);
-        if (launchpadHomeParam == null || launchpadHomeParam.length() == 0) {
-            commandLineArgs.put(SharedConstants.SLING_LAUNCHPAD, slingHome);
-            return new File(slingHome);
-        }
-
-        File launchpadHome = new File(launchpadHomeParam);
-        if (!launchpadHome.isAbsolute()) {
-            launchpadHome = new File(slingHome, launchpadHomeParam);
-        }
-
-        commandLineArgs.put(SharedConstants.SLING_LAUNCHPAD,
-            launchpadHome.getAbsolutePath());
-        return launchpadHome;
-    }
-
-    // ---------- logging
-
-    // emit an informational message to standard out
-    protected static void info(String message, Throwable t) {
-        log(System.out, "*INFO *", message, t);
-    }
-
-    // emit an error message to standard err
-    protected static void error(String message, Throwable t) {
-        log(System.err, "*ERROR*", message, t);
-    }
-
-    private static final DateFormat fmt = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS ");
-
-    // helper method to format the message on the correct output channel
-    // the throwable if not-null is also prefixed line by line with the prefix
-    private static void log(PrintStream out, String prefix, String message,
-            Throwable t) {
-
-        final StringBuilder linePrefixBuilder = new StringBuilder();
-        synchronized (fmt) {
-            linePrefixBuilder.append(fmt.format(new Date()));
-        }
-        linePrefixBuilder.append(prefix);
-        linePrefixBuilder.append(" [");
-        linePrefixBuilder.append(Thread.currentThread().getName());
-        linePrefixBuilder.append("] ");
-        final String linePrefix = linePrefixBuilder.toString();
-
-        out.print(linePrefix);
-        out.println(message);
-        if (t != null) {
-            t.printStackTrace(new PrintStream(out) {
-                @Override
-                public void print(String x) {
-                    super.print(linePrefix);
-                    super.print(x);
-                }
-            });
-        }
-    }
-
-    /**
-     * Parses the command line arguments into a map of strings indexed by
-     * strings. This method supports single character option names only at the
-     * moment. Each pair of an option name and its value is stored into the
-     * map. If a single dash '-' character is encountered the rest of the command
-     * line are interpreted as option names and are stored in the map unmodified
-     * as entries with the same key and value.
-     * <table>
-     * <tr><th>Command Line</th><th>Mapping</th></tr>
-     * <tr><td>x</td><td>x -> x</td></tr>
-     * <tr><td>-y z</td><td>y -> z</td></tr>
-     * <tr><td>-yz</td><td>y -> z</td></tr>
-     * <tr><td>-y -z</td><td>y -> y, z -> z</td></tr>
-     * <tr><td>-y x - -z a</td><td>y -> x, -z -> -z, a -> a</td></tr>
-     * </table>
-     *
-     * @param args The command line to parse
-     *
-     * @return The map of command line options and their values
-     */
-    // default accessor to enable unit tests without requiring reflection
-    static Map<String, String> parseCommandLine(String... args) {
-        Map<String, String> commandLine = new HashMap<String, String>();
-        boolean readUnparsed = false;
-        for (int argc = 0; args != null && argc < args.length; argc++) {
-            String arg = args[argc];
-
-            if (readUnparsed) {
-                commandLine.put(arg, arg);
-            } else if (arg.startsWith("-")) {
-                if (arg.length() == 1) {
-                   readUnparsed = true;
-                } else {
-                    String key = String.valueOf(arg.charAt(1));
-                    if (arg.length() > 2) {
-                        final String val;
-                        final int indexOfEq = arg.indexOf('=');
-                        if (indexOfEq != -1) {
-                            //Handle case -Da=b
-                            key = arg.substring(1, indexOfEq);
-                            val = arg.substring(indexOfEq + 1);
-                        } else {
-                            val = arg.substring(2);
-                        }
-                        commandLine.put(key, val);
-                    } else {
-                        argc++;
-                        if (argc < args.length
-                            && (args[argc].equals("-") || !args[argc].startsWith("-"))) {
-                            String val = args[argc];
-
-                            //Special handling for -D a=b
-                            if(key.equals("D")){
-                                final int indexOfEq = val.indexOf('=');
-                                if (indexOfEq != -1) {
-                                    //Handle case -D a=b. Add key as Da
-                                    key = "D" + val.substring(0, indexOfEq);
-                                    val = val.substring(indexOfEq + 1);
-                                }
-                            }
-                            commandLine.put(key, val);
-                        } else {
-                            commandLine.put(key, key);
-                            argc--;
-                        }
-                    }
-                }
-            } else {
-                commandLine.put(arg, arg);
-            }
-        }
-        return commandLine;
-    }
-
-    /** prints a simple usage plus optional error message */
-    private static boolean doHelp(Map<String, String> args) {
-        if (args.remove("h") != null) {
-            System.out.println("usage: "
-                + Main.class.getName()
-                + " [ start | stop | status ] [ -j adr ] [ -l loglevel ] [ -f logfile ] [ -c slinghome ] [ -i launchpadhome ] [ -a address ] [ -p port ] { -Dn=v } [ -h ]");
-
-            System.out.println("    start         listen for control connection (uses -j)");
-            System.out.println("    stop          terminate running Apache Sling (uses -j)");
-            System.out.println("    status        check whether Apache Sling is running (uses -j)");
-            System.out.println("    threads       request a thread dump from Apache Sling (uses -j)");
-            System.out.println("    -j adr        host and port to use for control connection in the format '[host:]port' (default 127.0.0.1:0)");
-            System.out.println("    -l loglevel   the initial loglevel (0..4, FATAL, ERROR, WARN, INFO, DEBUG)");
-            System.out.println("    -f logfile    the log file, \"-\" for stdout (default logs/error.log)");
-            System.out.println("    -c slinghome  the sling context directory (default sling)");
-            System.out.println("    -i launchpadhome  the launchpad directory (default slinghome)");
-            System.out.println("    -a address    the interfact to bind to (use 0.0.0.0 for any)");
-            System.out.println("    -p port       the port to listen to (default 8080)");
-            System.out.println("    -r path       the root servlet context path for the http service (default is /)");
-            System.out.println("    -n            don't install the shutdown hook");
-            System.out.println("    -Dn=v         sets property n to value v. Make sure to use this option *after* " +
-                                                  "the jar filename. The JVM also has a -D option which has a " +
-                                                  "different meaning");
-            System.out.println("    -h            prints this usage message");
-
-            return true;
-        }
-        return false;
+        AbstractMain.main(Main.class, args);
     }
 
-    private static boolean installShutdownHook(Map<String, String> props) {
-        String prop = props.remove(PROP_SHUTDOWN_HOOK);
-        if (prop == null) {
-            prop = System.getProperty(PROP_SHUTDOWN_HOOK);
-        }
-
-        return (prop == null) ? true : Boolean.valueOf(prop);
+        /** prints a simple usage plus optional error message */
+    public static boolean doHelp(Map<String, String> args) {
+        return AbstractMain.doHelp(Main.class, null, args);
     }
 
-    // default accessor to enable unit tests without requiring reflection
-    static Map<String, String> convertCommandLineArgs(
-            Map<String, String> rawArgs) {
-        final HashMap<String, String> props = new HashMap<String, String>();
-        boolean errorArg = false;
-        for (Entry<String, String> arg : rawArgs.entrySet()) {
-            if (arg.getKey().length() == 1 || arg.getKey().startsWith("D")) {
-                String value = arg.getValue();
-                switch (arg.getKey().charAt(0)) {
-                    case 'j':
-                        if (value == arg.getKey()) {
-                            errorArg("-j", "Missing host:port value");
-                            errorArg = true;
-                            continue;
-                        }
-                        props.put(PROP_CONTROL_SOCKET, value);
-                        break;
-
-                    case 'l':
-                        if (value == arg.getKey()) {
-                            errorArg("-l", "Missing log level value");
-                            errorArg = true;
-                            continue;
-                        }
-                        props.put(PROP_LOG_LEVEL, value);
-                        break;
-
-                    case 'f':
-                        if (value == arg.getKey()) {
-                            errorArg("-f", "Missing log file value");
-                            errorArg = true;
-                            continue;
-                        } else if ("-".equals(value)) {
-                            value = "";
-                        }
-                        props.put(PROP_LOG_FILE, value);
-                        break;
-
-                    case 'c':
-                        if (value == arg.getKey()) {
-                            errorArg("-c", "Missing directory value");
-                            errorArg = true;
-                            continue;
-                        }
-                        props.put(SharedConstants.SLING_HOME, value);
-                        break;
-
-                    case 'i':
-                        if (value == arg.getKey()) {
-                            errorArg("-i", "Missing launchpad directory value");
-                            errorArg = true;
-                            continue;
-                        }
-                        props.put(SharedConstants.SLING_LAUNCHPAD, value);
-                        break;
-
-                    case 'a':
-                        if (value == arg.getKey()) {
-                            errorArg("-a", "Missing address value");
-                            errorArg = true;
-                            continue;
-                        }
-                        props.put(PROP_HOST, value);
-                        break;
-
-                    case 'p':
-                        if (value == arg.getKey()) {
-                            errorArg("-p", "Missing port value");
-                            errorArg = true;
-                            continue;
-                        }
-                        try {
-                            // just to verify it is a number
-                            Integer.parseInt(value);
-                            props.put(PROP_PORT, value);
-                        } catch (RuntimeException e) {
-                            errorArg("-p", "Bad port: " + value);
-                            errorArg = true;
-                        }
-                        break;
-
-                    case 'r':
-                        if (value == arg.getKey()) {
-                            errorArg("-r", "Missing root path value");
-                            errorArg = true;
-                            continue;
-                        }
-                        props.put(PROP_CONTEXT_PATH, value);
-                        break;
-
-                    case 'n':
-                        props.put(PROP_SHUTDOWN_HOOK, Boolean.FALSE.toString());
-                        break;
-
-                    case 'D':
-                        if (value == arg.getKey()) {
-                            errorArg("-D", "Missing property assignment");
-                            errorArg = true;
-                            continue;
-                        }
-                        if (arg.getKey().length() > 1) {
-                            //Dfoo=bar arg.key=Dfoo and arg.value=bar
-                            props.put(arg.getKey().substring(1), arg.getValue());
-                        } else {
-                            //D foo=bar arg.key=D and arg.value=foo=bar
-                            String[] parts = value.split("=");
-                            int valueIdx = (parts.length > 1) ? 1 : 0;
-                            props.put(parts[0], parts[valueIdx]);
-                        }
-                        break;
-
-                    default:
-                        errorArg("-" + arg.getKey(), "Unrecognized option");
-                        errorArg = true;
-                        break;
-                }
-            } else if ("start".equals(arg.getKey())
-                    || "stop".equals(arg.getKey())
-                    || "status".equals(arg.getKey())
-                    || "threads".equals(arg.getKey())) {
-                props.put(PROP_CONTROL_ACTION, arg.getValue());
-            } else {
-                errorArg(arg.getKey(), "Unrecognized option");
-                errorArg = true;
-            }
-        }
-        return errorArg ? null : props;
-    }
-
-    private static void errorArg(String option, String message) {
-        error(String.format("%s: %s (use -h for more information)", option,
-            message), null);
-    }
-
-    /**
-     * Removes well-known stray threads and thread groups and removes framework
-     * thread context class loaders. Well-known stray threads and thread groups
-     * are:
-     * <ul>
-     * <li>The FileCleaningTracker$Reaper thread of the commons-io library</li>
-     * <li>The QuartzScheduler:ApacheSling thread group. See <a
-     * href="https://issues.apache.org/jira/browse/SLING-2535">SLING-2535
-     * QuartzScheduler:ApacheSling thread group remaining after stopping the
-     * scheduler bundle</a></li>
-     * </ul>
-     */
-    @SuppressWarnings("deprecation")
-    static void cleanupThreads() {
-
-        // the current thread is the SlingNotifier thread part of
-        // the main thread group, whose parent is the system thread
-        // group. We only care for the main thread group here
-        ThreadGroup tg = Thread.currentThread().getThreadGroup();
-        Thread[] active = new Thread[tg.activeCount()];
-        tg.enumerate(active);
-        for (Thread thread : active) {
-            if (thread != null) {
-                if (thread.getName().equals("FileCleaningTracker$Reaper")) {
-                    // I know, but this thread is stray ...
-                    // Commons-IO bundle (or consumer of it) should
-                    // actually stop it
-                    thread.stop();
-                } else {
-                    ClassLoader loader = thread.getContextClassLoader();
-                    if (loader != null && loader.getClass().getName().startsWith("org.apache.felix.framework.")) {
-                        thread.setContextClassLoader(null);
-                    }
-                }
-            }
-        }
-
-        // SLING-2535 - Scheduler thread group
-        ThreadGroup[] groups = new ThreadGroup[tg.activeGroupCount()];
-        tg.enumerate(groups);
-        for (ThreadGroup group : groups) {
-            if (group != null && group.getName().equals("QuartzScheduler:ApacheSling")) {
-                group.destroy();
-            }
-        }
+    public Main(Map<String, String> args) {
+        super(args);
     }
-
-    private class ShutdownHook implements Runnable {
-        @Override
-        public void run() {
-            Main.info("Java VM is shutting down", null);
-            Main.this.doStop();
-        }
-    }
-
-    private class Notified implements Notifiable {
-
-        /**
-         * The framework has been stopped by calling the
-         * <code>Bundle.stop()</code> on the system bundle. This actually
-         * terminates the Sling Standalone application.
-         */
-        @Override
-        public void stopped() {
-            /**
-             * This method is called if the framework is stopped from within by
-             * calling stop on the system bundle or if the framework is stopped
-             * because the VM is going down and the shutdown hook has initiated
-             * the shutdown. In any case we ensure the reference to the framework
-             * is removed and remove the shutdown hook (but don't care if that
-             * fails).
-             */
-
-            Main.info("Apache Sling has been stopped", null);
-
-            Main.this.sling = null;
-            Main.this.doStop();
-        }
-
-        /**
-         * The framework has been stopped with the intent to be restarted by
-         * calling either of the <code>Bundle.update</code> methods on the
-         * system bundle.
-         * <p>
-         * If an <code>InputStream</code> was provided, this has been copied to
-         * a temporary file, which will be used in place of the existing
-         * launcher jar file.
-         *
-         * @param updateFile The temporary file to replace the existing launcher
-         *            jar file. If <code>null</code> the existing launcher jar
-         *            will be used again.
-         */
-        @Override
-        public void updated(File updateFile) {
-
-            Main.this.sling = null;
-            Main.this.doStop();
-
-            Main.cleanupThreads();
-
-            if (updateFile == null) {
-
-                Main.info("Restarting Framework and Apache Sling", null);
-                if (!Main.this.doStart(null)) {
-                    Main.error("Failed to restart Sling; terminating", null);
-                    Main.this.terminateVM(1);
-                }
-
-            } else {
-
-                Main.info(
-                    "Restarting Framework with update from " + updateFile, null);
-                boolean started = false;
-                try {
-                    started = Main.this.doStart(updateFile.toURI().toURL());
-                } catch (MalformedURLException mue) {
-                    Main.error("Cannot get URL for file " + updateFile, mue);
-                } finally {
-                    updateFile.delete();
-                }
-
-                if (!started) {
-                    Main.error("Failed to restart Sling; terminating", null);
-                    Main.this.terminateVM(1);
-                }
-            }
-        }
-    }
-
 }
diff --git a/src/test/java/org/apache/sling/launchpad/app/MainTest.java b/src/test/java/org/apache/sling/launchpad/app/MainTest.java
index 440ee89..58d778e 100644
--- a/src/test/java/org/apache/sling/launchpad/app/MainTest.java
+++ b/src/test/java/org/apache/sling/launchpad/app/MainTest.java
@@ -299,7 +299,7 @@ public class MainTest extends TestCase {
 
     public void test_installShutdownHook() throws SecurityException, NoSuchMethodException, IllegalArgumentException,
             IllegalAccessException, InvocationTargetException {
-        final Method m = Main.class.getDeclaredMethod("installShutdownHook", Map.class);
+        final Method m = AbstractMain.class.getDeclaredMethod("installShutdownHook", Map.class);
         m.setAccessible(true);
 
         final String key = "sling.shutdown.hook";