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