SLING-11685 use feature launcher assembly to launch (#4)

unpack the feature launcher assembly and use the contained script
diff --git a/pom.xml b/pom.xml
index e9de3d1..f110a09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,12 @@
         </dependency>
         <dependency>
             <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-archiver</artifactId>
+            <version>4.2.7</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-interactivity-api</artifactId>
             <version>1.1</version>
             <scope>compile</scope>
diff --git a/src/it/simple-for-SLING-10956-it/pom.xml b/src/it/simple-for-SLING-10956-it/pom.xml
new file mode 100644
index 0000000..35db3e7
--- /dev/null
+++ b/src/it/simple-for-SLING-10956-it/pom.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>simple-for-SLING-10956-it</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <description>A simple IT verifying starting and stopping.</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.6.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.6.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.12</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <version>3.2.0</version>
+                <executions>
+                    <execution>
+                        <id>reserve-network-port</id>
+                        <goals>
+                            <goal>reserve-network-port</goal>
+                        </goals>
+                        <phase>process-resources</phase>
+                        <configuration>
+                            <portNames>
+                                <portName>http.port</portName>
+                            </portNames>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>slingfeature-maven-plugin</artifactId>
+                <version>1.3.6</version>
+                <extensions>true</extensions>
+                <!-- TODO - seems we can't attach non-aggregates -->
+                <configuration>
+                    <aggregates>
+                        <aggregate>
+                            <classifier>main</classifier>
+                            <filesInclude>model.json</filesInclude>
+                        </aggregate>
+                    </aggregates>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>attach-features</goal>
+                            <goal>aggregate-features</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>@project.groupId@</groupId>
+                <artifactId>@project.artifactId@</artifactId>
+                <version>@project.version@</version>
+                <configuration>
+                    <featureLauncherVersion>1.2.0</featureLauncherVersion>
+                    <launches>
+                        <launch>
+                            <id>model</id>
+                            <feature>
+                                <groupId>org.apache.sling</groupId>
+                                <artifactId>simple-for-SLING-10956-it</artifactId>
+                                <version>1.0-SNAPSHOT</version>
+                                <classifier>main</classifier>
+                                <type>slingosgifeature</type>
+                            </feature>
+                            <launcherArguments>
+                                <vmOptions>
+                                    <value>-DTEST_VM_OPTION=TEST_VM_OPTION_VALUE</value>
+                                </vmOptions>
+                                <frameworkProperties>
+                                    <org.osgi.service.http.port>${http.port}</org.osgi.service.http.port>
+                                </frameworkProperties>
+                                <variables>
+                                    <TEST_VARIABLE>TEST_VALUE</TEST_VARIABLE>
+                                </variables>
+                            </launcherArguments>
+                            <startTimeoutSeconds>180</startTimeoutSeconds>
+                        </launch>
+                    </launches>
+                    <toLaunch>
+                    </toLaunch>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>start</goal>
+                            <goal>stop</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>2.22.2</version>
+                <configuration>
+                    <systemPropertyVariables>
+                        <HTTP_PORT>${http.port}</HTTP_PORT>
+                        <build.log.file>${project.build.directory}/../build.log</build.log.file>
+                    </systemPropertyVariables>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/src/it/simple-for-SLING-10956-it/src/main/features/model.json b/src/it/simple-for-SLING-10956-it/src/main/features/model.json
new file mode 100644
index 0000000..ee05e71
--- /dev/null
+++ b/src/it/simple-for-SLING-10956-it/src/main/features/model.json
@@ -0,0 +1,14 @@
+{
+  "id":"${project.groupId}:${project.artifactId}:model:${project.version}",
+  "bundles": [
+    "org.apache.felix/org.apache.felix.scr/2.1.20",
+    "org.apache.felix/org.apache.felix.log/1.2.2",
+    "org.apache.felix/org.apache.felix.configadmin/1.9.16",
+    "org.osgi/org.osgi.util.promise/1.1.1",
+    "org.osgi/org.osgi.util.function/1.1.0",
+    "org.osgi/org.osgi.util.converter/1.0.1",
+    "org.apache.commons/commons-lang3/3.9",
+    "org.apache.felix/org.apache.felix.http.jetty/4.0.16",
+    "org.apache.felix/org.apache.felix.http.servlet-api/1.1.2"
+  ]
+}
\ No newline at end of file
diff --git a/src/it/simple-for-SLING-10956-it/src/test/java/org/apache/sling/it/SLING10956/AppRunningIT.java b/src/it/simple-for-SLING-10956-it/src/test/java/org/apache/sling/it/SLING10956/AppRunningIT.java
new file mode 100644
index 0000000..d7671bd
--- /dev/null
+++ b/src/it/simple-for-SLING-10956-it/src/test/java/org/apache/sling/it/SLING10956/AppRunningIT.java
@@ -0,0 +1,84 @@
+/*
+ * 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.it.SLING10956;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.MethodOrderer.Alphanumeric;
+import org.junit.jupiter.api.TestMethodOrder;
+ import  org.apache.http.impl.client.*;
+import  org.apache.http.client.methods.*;
+
+@TestMethodOrder(Alphanumeric.class)
+public class AppRunningIT {
+
+    @Test
+    public void aaSlingAppIsUp() throws Exception {
+
+        int port = Integer.getInteger("HTTP_PORT", 8080);
+
+        try ( CloseableHttpClient httpclient = HttpClients.createDefault() ) {
+            HttpGet get = new HttpGet("http://localhost:" + port + "/");
+            for ( int i = 0; i < 30; i++ ) {
+                try ( CloseableHttpResponse response = httpclient.execute(get) ) {
+                    System.out.println("Status line = " + response.getStatusLine().toString());
+                    int statusCode = response.getStatusLine().getStatusCode();
+                    if ( (statusCode / 100 < 5 ) ) {
+                        System.out.println("App is ready");
+                        return;
+                    }
+                    Thread.sleep(1000l);
+                }
+            }
+
+            fail("App is not yet ready, failing");
+        }
+    }
+
+    @Test
+    public void bbCheckLauncherEnvironmentVarInLogs() throws Exception {
+        final String logFilename = System.getProperty("build.log.file");
+
+        // This verifies the launcherArguments vmOptions and variables from our test pom
+        final Pattern expected = Pattern.compile(".*\\-DTEST_VM_OPTION=TEST_VM_OPTION_VALUE.*");
+
+        try (Stream<String> lines = Files.lines(Paths.get(logFilename))) {
+            final Optional<String> expectedLine = lines.filter(line -> expected.matcher(line).matches()).findFirst();
+            assertTrue(expectedLine.isPresent(), "Expected pattern " + expected + " to be found in log file " + logFilename);
+        }
+    }
+
+    @Test
+    public void bbCheckLauncherCommandLineInLogs() throws Exception {
+        final String logFilename = System.getProperty("build.log.file");
+
+        // This verifies the launcherArguments vmOptions and variables from our test pom
+        final Pattern expected = Pattern.compile(".*\\-V, TEST_VARIABLE=TEST_VALUE.*");
+
+        try (Stream<String> lines = Files.lines(Paths.get(logFilename))) {
+            final Optional<String> expectedLine = lines.filter(line -> expected.matcher(line).matches()).findFirst();
+            assertTrue(expectedLine.isPresent(), "Expected pattern " + expected + " to be found in log file " + logFilename);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/it/simple-for-SLING-10956-it/verify.groovy b/src/it/simple-for-SLING-10956-it/verify.groovy
new file mode 100644
index 0000000..e9b7465
--- /dev/null
+++ b/src/it/simple-for-SLING-10956-it/verify.groovy
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+File touchFile = new File( basedir, "build.log" );
+
+assert touchFile.isFile()
diff --git a/src/it/simple-it/pom.xml b/src/it/simple-it/pom.xml
index 04286ed..903dd04 100644
--- a/src/it/simple-it/pom.xml
+++ b/src/it/simple-it/pom.xml
@@ -101,6 +101,9 @@
                 <artifactId>@project.artifactId@</artifactId>
                 <version>@project.version@</version>
                 <configuration>
+                    <!-- pin this at the 1.1.26 version to test launching 
+                         using the pre SLING-10956 technique -->
+                    <featureLauncherVersion>1.1.26</featureLauncherVersion>
                     <launches>
                         <launch>
                             <id>model</id>
diff --git a/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java b/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java
index c1f9970..2278d6f 100644
--- a/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java
+++ b/src/main/java/org/apache/sling/maven/feature/launcher/StartMojo.java
@@ -24,12 +24,18 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.lang.ProcessBuilder.Redirect;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.VersionRange;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.model.Dependency;
 import org.apache.maven.plugin.AbstractMojo;
@@ -40,6 +46,10 @@
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.utils.Os;
+import org.codehaus.plexus.archiver.UnArchiver;
+import org.codehaus.plexus.archiver.manager.ArchiverManager;
+import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.artifact.DefaultArtifact;
@@ -54,7 +64,10 @@
  */
 @Mojo( name = "start", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST )
 public class StartMojo extends AbstractMojo {
-    
+
+    private static final String JAVA_HOME = "JAVA_HOME";
+    private static final String JAVA_OPTS = "JAVA_OPTS";
+
     /**
      * The directory in which the features are launched (below its child directory {@code launchers/<launch-id>}).
      */
@@ -101,21 +114,55 @@
     
     @Component
     private ProcessTracker processes;
-    
-    @Override
+
+    /**
+     * To look up UnArchiver implementations
+     */
+    @Component
+    private ArchiverManager archiverManager;
+
     public void execute() throws MojoExecutionException, MojoFailureException {
 
-        Artifact launcherArtifact = new DefaultArtifact("org.apache.sling:org.apache.sling.feature.launcher:" + featureLauncherVersion);
-
         try {
+            // the feature launcher before version 1.1.28 used a single jar, while versions
+            //  after that provide an assembly per SLING-10956
+            VersionRange beforeAssemblyRange = VersionRange.createFromVersionSpec("(,1.1.26]");
+            boolean useAssembly = !beforeAssemblyRange.containsVersion(new DefaultArtifactVersion(featureLauncherVersion));
+
             RepositorySystemSession repositorySession = mavenSession.getRepositorySession();
-            File launcher = resolver
-                .resolveArtifact(repositorySession, new ArtifactRequest(launcherArtifact, remoteRepos, null))
-                .getArtifact()
-                .getFile();
-            
             File workDir = new File(outputDirectory, "launchers");
             workDir.mkdirs();
+
+            File launcher;
+            if (useAssembly) {
+                // fetch the assembly artifact
+                Artifact launcherAssemblyArtifact = new DefaultArtifact("org.apache.sling:org.apache.sling.feature.launcher:tar.gz:" + featureLauncherVersion);
+                File assemblyArchive = resolver
+                        .resolveArtifact(repositorySession, new ArtifactRequest(launcherAssemblyArtifact, remoteRepos, null))
+                        .getArtifact()
+                        .getFile();
+
+                // unpack the file
+                UnArchiver unArchiver = archiverManager.getUnArchiver( assemblyArchive );
+                unArchiver.setSourceFile(assemblyArchive);
+                unArchiver.setDestFile(workDir);
+                unArchiver.extract();
+
+                // system property
+                Path relPath = Paths.get(launcherAssemblyArtifact.getArtifactId() + "-" + launcherAssemblyArtifact.getVersion(), "bin");
+                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+                    relPath = relPath.resolve("launcher.bat");
+                } else {
+                    relPath = relPath.resolve("launcher");
+                }
+                launcher = workDir.toPath().resolve(relPath).toFile();
+            } else {
+                Artifact launcherArtifact = new DefaultArtifact("org.apache.sling:org.apache.sling.feature.launcher:" + featureLauncherVersion);
+                launcher = resolver
+                        .resolveArtifact(repositorySession, new ArtifactRequest(launcherArtifact, remoteRepos, null))
+                        .getArtifact()
+                        .getFile();
+            }
             
             for ( Launch launch : launches ) {
                 if (launch.isSkip()) {
@@ -129,24 +176,59 @@
                 
                 ArtifactResult result = resolver.resolveArtifact(repositorySession, new ArtifactRequest(artifact, remoteRepos, null));
                 File featureFile = result.getArtifact().getFile();
-                
-                List<String> args = new ArrayList<>();
-                String javahome = System.getenv("JAVA_HOME");
+
+                String javahome = System.getenv(JAVA_HOME);
                 if (javahome == null || javahome.isEmpty()) {
                     // SLING-9843 fallback to java.home system property if JAVA_HOME env variable is not set
                     getLog().warn("The JAVA_HOME env variable was not set, falling back to the java.home system property");
                     javahome = System.getProperty("java.home");
                 }
-                args.add(javahome + File.separatorChar + "bin" + File.separatorChar + "java");
-                // SLING-9994 - if any extra vm options were supplied, apply them here
-                String[] vmOptions = launch.getLauncherArguments().getVmOptions();
-                for (String vmOption : vmOptions) {
-                    if (vmOption != null && !vmOption.isEmpty()) {
-                        args.add(vmOption);
+                List<String> args = new ArrayList<>();
+                if (useAssembly) {
+                    // use the post v1.1.28 launcher script
+
+                    Map<String, String> newEnv = new HashMap<>(launch.getEnvironmentVariables());
+                    newEnv.put(JAVA_HOME, javahome);
+
+                    // SLING-9994 - if any extra vm options were supplied, apply them here
+                    StringBuilder javaOptsBuilder = null;
+                    String[] vmOptions = launch.getLauncherArguments().getVmOptions();
+                    for (String vmOption : vmOptions) {
+                        if (vmOption != null && !vmOption.isEmpty()) {
+                            if (javaOptsBuilder == null) {
+                                javaOptsBuilder = new StringBuilder();
+                            } else {
+                                javaOptsBuilder.append(" ");
+                            }
+                            javaOptsBuilder.append(vmOption);
+                        }
                     }
+                    if (javaOptsBuilder != null) {
+                        // pass vmOptions through JAVA_OPTS environment variable?
+                        if (newEnv.containsKey(JAVA_OPTS)) {
+                            // if the original value existed append it to our buffer
+                            javaOptsBuilder.append(" ").append(newEnv.get(JAVA_OPTS));
+                        }
+                        newEnv.put(JAVA_OPTS, javaOptsBuilder.toString());
+                    }
+
+                    args.add(launcher.getAbsolutePath());
+
+                    launch.setEnvironmentVariables(newEnv);
+                } else {
+                    // use the pre v1.1.28 single jar technique
+
+                    args.add(javahome + File.separatorChar + "bin" + File.separatorChar + "java");
+                    // SLING-9994 - if any extra vm options were supplied, apply them here
+                    String[] vmOptions = launch.getLauncherArguments().getVmOptions();
+                    for (String vmOption : vmOptions) {
+                        if (vmOption != null && !vmOption.isEmpty()) {
+                            args.add(vmOption);
+                        }
+                    }
+                    args.add("-jar");
+                    args.add(launcher.getAbsolutePath());
                 }
-                args.add("-jar");
-                args.add(launcher.getAbsolutePath());
                 args.add("-f");
                 args.add(featureFile.getAbsolutePath());
                 args.add("-p");
@@ -168,7 +250,10 @@
                 pb.redirectInput(Redirect.INHERIT);
                 pb.directory(workDir);
                 launch.getEnvironmentVariables().entrySet()
-                    .forEach( e -> pb.environment().put(e.getKey(), e.getValue()) );
+                    .forEach( e -> {
+                            getLog().info("Setting environment variable '" + e.getKey() + "' to '" + e.getValue() + "'");
+                            pb.environment().put(e.getKey(), e.getValue());
+                        } );
                 
                 getLog().info("Starting launch with id '" + launch.getId() + "', args=" + args);
                 
@@ -205,7 +290,7 @@
                 processes.startTracking(launch.getId(), process);
             }
 
-        } catch (ArtifactResolutionException | IOException e) {
+        } catch (NoSuchArchiverException | InvalidVersionSpecificationException | ArtifactResolutionException | IOException e) {
             throw new MojoExecutionException(e.getMessage(), e);
         } catch ( InterruptedException e ) {
             Thread.currentThread().interrupt();