You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/04/27 09:59:52 UTC
[sling-org-apache-sling-feature-launcher] 01/16: Move feature model
to whiteboard git
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-launcher.git
commit 80949a541e11f6b30298046c46c1e991a0900e8a
Author: Carsten Ziegeler <cz...@adobe.com>
AuthorDate: Fri Nov 3 15:06:50 2017 +0100
Move feature model to whiteboard git
---
pom.xml | 128 +++++++++
.../feature/launcher/impl/FeatureProcessor.java | 137 +++++++++
.../sling/feature/launcher/impl/Installation.java | 137 +++++++++
.../feature/launcher/impl/LauncherConfig.java | 122 ++++++++
.../apache/sling/feature/launcher/impl/Main.java | 313 +++++++++++++++++++++
.../launcher/impl/launchers/AbstractRunner.java | 278 ++++++++++++++++++
.../launcher/impl/launchers/FrameworkLauncher.java | 88 ++++++
.../launcher/impl/launchers/FrameworkRunner.java | 70 +++++
.../sling/feature/launcher/spi/Launcher.java | 26 ++
.../launcher/spi/LauncherPrepareContext.java | 32 +++
.../feature/launcher/spi/LauncherRunContext.java | 58 ++++
11 files changed, 1389 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6bf9943
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,128 @@
+<?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>32</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.feature.launcher</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Apache Sling Feature Launcher</name>
+ <description>
+ A application launcher using Apache Sling Features
+ </description>
+
+ <properties>
+ <sling.java.version>8</sling.java.version>
+ </properties>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-launcher</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/support/feature-launcher</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/tooling/support/feature-launcher</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <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>org.apache.sling.feature,org.apache.sling.feature.support,org.apache.sling.commons.johnzon,org.apache.felix.converter,commons-cli,slf4j-api,slf4j-simple,osgi.core</includeArtifactIds>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>org.apache.sling.feature.launcher.impl.Main</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.feature</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.feature.support</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.converter</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ <version>1.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.johnzon</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.8.9</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java b/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
new file mode 100644
index 0000000..14eaf9c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java
@@ -0,0 +1,137 @@
+/*
+ * 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.launcher.impl;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.launcher.impl.LauncherConfig.StartupMode;
+import org.apache.sling.feature.support.ArtifactHandler;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.apache.sling.feature.support.FeatureUtil;
+import org.apache.sling.feature.support.json.ApplicationJSONReader;
+import org.apache.sling.feature.support.json.ApplicationJSONWriter;
+
+public class FeatureProcessor {
+
+ /**
+ * Initialize the launcher
+ * Read the features and prepare the application
+ * @param config The current configuration
+ * @param artifactManager The artifact manager
+ */
+ public static Application createApplication(final LauncherConfig config,
+ final ArtifactManager artifactManager)
+ throws IOException {
+ final Application app;
+ if ( config.getApplicationFile() != null ) {
+ String absoluteArg = config.getApplicationFile();
+ if ( absoluteArg.indexOf(":") < 2 ) {
+ absoluteArg = new File(absoluteArg).getAbsolutePath();
+ }
+ final ArtifactHandler appArtifact = artifactManager.getArtifactHandler(absoluteArg);
+
+ try (final FileReader r = new FileReader(appArtifact.getFile())) {
+ app = ApplicationJSONReader.read(r);
+ }
+
+ } else {
+ app = FeatureUtil.assembleApplication(null, artifactManager, FeatureUtil.getFeatureFiles(config.getHomeDirectory(), config.getFeatureFiles()).toArray(new String[0]));
+ }
+
+ // write application back
+ final File file = new File(config.getHomeDirectory(), "resources" + File.separatorChar + "provisioning" + File.separatorChar + "application.json");
+ file.getParentFile().mkdirs();
+
+ try (final FileWriter writer = new FileWriter(file)) {
+ ApplicationJSONWriter.write(writer, app);
+ } catch ( final IOException ioe) {
+ Main.LOG().error("Error while writing application file: {}", ioe.getMessage(), ioe);
+ System.exit(1);
+ }
+
+ return app;
+ }
+
+ /**
+ * Prepare the launcher
+ * - add all bundles to the bundle map of the installation object
+ * - add all other artifacts to the install directory (only if startup mode is INSTALL)
+ * - process configurations
+ */
+ public static void prepareLauncher(final LauncherConfig config,
+ final ArtifactManager artifactManager,
+ final Application app) throws Exception {
+ for(final Map.Entry<Integer, List<Artifact>> entry : app.getBundles().getBundlesByStartLevel().entrySet()) {
+ for(final Artifact a : entry.getValue()) {
+ final ArtifactHandler handler = artifactManager.getArtifactHandler(":" + a.getId().toMvnPath());
+ final File artifactFile = handler.getFile();
+
+ config.getInstallation().addBundle(entry.getKey(), artifactFile);
+ }
+ }
+ int index = 1;
+ for(final Extension ext : app.getExtensions()) {
+ if ( ext.getType() == ExtensionType.ARTIFACTS ) {
+ for(final Artifact a : ext.getArtifacts() ) {
+ if ( config.getStartupMode() == StartupMode.PURE ) {
+ throw new Exception("Artifacts other than bundle are not supported by framework launcher.");
+ }
+ final ArtifactHandler handler = artifactManager.getArtifactHandler(":" + a.getId().toMvnPath());
+ config.getInstallation().addInstallableArtifact(handler.getFile());
+ }
+ } else {
+ if ( ext.getName().equals(Extension.NAME_REPOINIT) ) {
+ if ( ext.getType() != ExtensionType.TEXT ) {
+ throw new Exception(Extension.NAME_REPOINIT + " extension must be of type text and not json");
+ }
+ final Configuration cfg = new Configuration("org.apache.sling.jcr.repoinit.RepositoryInitializer", "repoinit" + String.valueOf(index));
+ index++;
+ cfg.getProperties().put("scripts", ext.getText());
+ config.getInstallation().addConfiguration(cfg.getName(), cfg.getFactoryPid(), cfg.getProperties());
+ } else {
+ if ( ext.isRequired() ) {
+ throw new Exception("Unknown required extension " + ext.getName());
+ }
+ }
+ }
+ }
+
+ for(final Configuration cfg : app.getConfigurations()) {
+ if ( cfg.isFactoryConfiguration() ) {
+ config.getInstallation().addConfiguration(cfg.getName(), cfg.getFactoryPid(), cfg.getProperties());
+ } else {
+ config.getInstallation().addConfiguration(cfg.getPid(), null, cfg.getProperties());
+ }
+ }
+
+ for(final Map.Entry<String, String> prop : app.getFrameworkProperties()) {
+ if ( !config.getInstallation().getFrameworkProperties().containsKey(prop.getKey()) ) {
+ config.getInstallation().getFrameworkProperties().put(prop.getKey(), prop.getValue());
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java b/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
new file mode 100644
index 0000000..2dff508
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Installation.java
@@ -0,0 +1,137 @@
+/*
+ * 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.launcher.impl;
+
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class holds the configuration of the launcher.
+ */
+public class Installation implements LauncherRunContext {
+
+ /** The map with the framework properties. */
+ private final Map<String, String> fwkProperties = new HashMap<>();
+
+ /** Bundle map */
+ private final Map<Integer, List<File>> bundleMap = new HashMap<>();
+
+ /** Artifacts to be installed */
+ private final List<File> installables = new ArrayList<>();
+
+ /** Configurations, they are installed on first start. */
+ private final List<Object[]> configurations = new ArrayList<>();
+
+ /** The list of app jars. */
+ private final List<File> appJars = new ArrayList<>();
+
+ /**
+ * Add an application jar.
+ * @param jar The application jar
+ */
+ public void addAppJar(final File jar) {
+ this.appJars.add(jar);
+ }
+
+ /**
+ * Get the list of application jars.
+ * @return The list of app jars
+ */
+ public List<File> getAppJars() {
+ return this.appJars;
+ }
+
+ /**
+ * Add a bundle with the given start level
+ * @param startLevel The start level
+ * @param file The bundle file
+ */
+ public void addBundle(final Integer startLevel, final File file) {
+ List<File> files = bundleMap.get(startLevel);
+ if ( files == null ) {
+ files = new ArrayList<>();
+ bundleMap.put(startLevel, files);
+ }
+ files.add(file);
+ }
+
+ /**
+ * Add an artifact to be installed by the installer
+ * @param file The file
+ */
+ public void addInstallableArtifact(final File file) {
+ this.installables.add(file);
+ }
+
+ /**
+ * Add a configuration
+ * @param pid The pid
+ * @param factoryPid The factory pid
+ * @param properties The propertis
+ */
+ public void addConfiguration(final String pid, final String factoryPid, final Dictionary<String, Object> properties) {
+ this.configurations.add(new Object[] {pid, factoryPid, properties});
+ }
+
+ /**
+ * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getFrameworkProperties()
+ */
+ @Override
+ public Map<String, String> getFrameworkProperties() {
+ return this.fwkProperties;
+ }
+
+ /**
+ * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getBundleMap()
+ */
+ @Override
+ public Map<Integer, List<File>> getBundleMap() {
+ return this.bundleMap;
+ }
+
+ /**
+ * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getConfigurations()
+ */
+ @Override
+ public List<Object[]> getConfigurations() {
+ return this.configurations;
+ }
+
+ /**
+ * @see org.apache.sling.feature.launcher.spi.LauncherRunContext#getInstallableArtifacts()
+ */
+ @Override
+ public List<File> getInstallableArtifacts() {
+ return this.installables;
+ }
+
+ /**
+ * Clear all in-memory objects
+ */
+ public void clear() {
+ this.configurations.clear();
+ this.fwkProperties.clear();
+ this.bundleMap.clear();
+ this.installables.clear();
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/LauncherConfig.java b/src/main/java/org/apache/sling/feature/launcher/impl/LauncherConfig.java
new file mode 100644
index 0000000..56095b9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/LauncherConfig.java
@@ -0,0 +1,122 @@
+/*
+ * 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.launcher.impl;
+
+import org.apache.sling.feature.support.ArtifactManagerConfig;
+import org.apache.sling.feature.support.spi.ArtifactProviderContext;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class holds the configuration of the launcher.
+ */
+public class LauncherConfig
+ extends ArtifactManagerConfig
+ implements ArtifactProviderContext {
+
+ public enum StartupMode {
+ INSTALLER,
+ PURE
+ };
+
+ private static final String HOME = "launcher";
+
+ private static final String CACHE_DIR = "cache";
+
+ /** The feature files or directories. */
+ private volatile String[] featureFiles;
+
+ /** The application file. */
+ private volatile String appFile;
+
+ private volatile StartupMode startupMode = StartupMode.PURE;
+
+ private final Installation installation = new Installation();
+
+ /**
+ * Create a new configuration object.
+ * Set the default values
+ */
+ public LauncherConfig() {
+ this.setCacheDirectory(new File(getHomeDirectory(), CACHE_DIR));
+ }
+
+ public void setApplicationFile(final String value) {
+ appFile = value;
+ }
+
+ public String getApplicationFile() {
+ return this.appFile;
+ }
+
+ /**
+ * Set the list of feature files or directories.
+ * @param value The array with the feature file names.
+ */
+ public void setFeatureFiles(final String[] value) {
+ this.featureFiles = value;
+ if ( value != null && value.length == 0 ) {
+ this.featureFiles = null;
+ }
+ }
+
+ /**
+ * Get the list of feature files.
+ * @return The array of names.
+ * @throws IOException
+ */
+ public String[] getFeatureFiles() {
+ return this.featureFiles;
+ }
+
+
+ /**
+ * Get the home directory.
+ * @return The home directory.
+ */
+ public File getHomeDirectory() {
+ return new File(HOME);
+ }
+
+ /**
+ * Get the startup mode.
+ *
+ * @return The current startup mode.
+ */
+ public StartupMode getStartupMode() {
+ return this.startupMode;
+ }
+
+ /**
+ * Sets the startup mode to {@link StartupMode#INSTALLER}.
+ */
+ public void setUseInstaller() {
+ this.startupMode = StartupMode.INSTALLER;
+ }
+
+ public Installation getInstallation() {
+ return this.installation;
+ }
+
+ /**
+ * Clear all in-memory objects
+ */
+ public void clear() {
+ this.installation.clear();
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/Main.java b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
new file mode 100644
index 0000000..31050fe
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/Main.java
@@ -0,0 +1,313 @@
+/*
+ * 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.launcher.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.cli.BasicParser;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.launcher.impl.launchers.FrameworkLauncher;
+import org.apache.sling.feature.launcher.spi.Launcher;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.support.ArtifactHandler;
+import org.apache.sling.feature.support.ArtifactManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the launcher main class.
+ * It parses command line parameters and prepares the launcher.
+ */
+public class Main {
+
+ private static Logger LOGGER;
+
+ public static Logger LOG() {
+ if ( LOGGER == null ) {
+ LOGGER = LoggerFactory.getLogger("launcher");
+ }
+ return LOGGER;
+ }
+
+ /** Split a string into key and value */
+ private static String[] split(final String val) {
+ final int pos = val.indexOf('=');
+ if ( pos == -1 ) {
+ return new String[] {val, "true"};
+ }
+ return new String[] {val.substring(0, pos), val.substring(pos + 1)};
+ }
+
+ /**
+ * Parse the command line parameters and update a configuration object.
+ * @param args Command line parameters
+ * @return Configuration object.
+ */
+ private static void parseArgs(final LauncherConfig config, final String[] args) {
+ Main.LOG().info("Assembling configuration...");
+ final Options options = new Options();
+
+ final Option repoOption = new Option("u", true, "Set repository url");
+ final Option modelOption = new Option("f", true, "Set feature files/directories");
+ final Option appOption = new Option("a", true, "Set application file");
+ final Option fwkProperties = new Option("D", true, "Set framework properties");
+ fwkProperties.setArgs(20);
+ final Option debugOption = new Option("v", true, "Verbose");
+ debugOption.setArgs(0);
+ final Option installerOption = new Option("I", true, "Use OSGi installer for additional artifacts.");
+ installerOption.setArgs(0);
+ options.addOption(repoOption);
+ options.addOption(appOption);
+ options.addOption(modelOption);
+ options.addOption(fwkProperties);
+ options.addOption(debugOption);
+ options.addOption(installerOption);
+
+ final CommandLineParser clp = new BasicParser();
+ try {
+ final CommandLine cl = clp.parse(options, args);
+
+ if ( cl.hasOption(repoOption.getOpt()) ) {
+ final String value = cl.getOptionValue(repoOption.getOpt());
+ config.setRepositoryUrls(value.split(","));
+ }
+ if ( cl.hasOption(modelOption.getOpt()) ) {
+ final String value = cl.getOptionValue(modelOption.getOpt());
+ config.setFeatureFiles(value.split(","));
+ }
+ if ( cl.hasOption(fwkProperties.getOpt()) ) {
+ for(final String value : cl.getOptionValues(fwkProperties.getOpt())) {
+ final String[] keyVal = split(value);
+
+ config.getInstallation().getFrameworkProperties().put(keyVal[0], keyVal[1]);
+ }
+ }
+ if ( cl.hasOption(debugOption.getOpt()) ) {
+ System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
+ LOGGER = null;
+ }
+ if ( cl.hasOption(installerOption.getOpt()) ) {
+ config.setUseInstaller();
+ }
+ if ( cl.hasOption(appOption.getOpt()) ) {
+ config.setApplicationFile(cl.getOptionValue(appOption.getOpt()));
+ }
+ } catch ( final ParseException pe) {
+ Main.LOG().error("Unable to parse command line: {}", pe.getMessage(), pe);
+ System.exit(1);
+ }
+ }
+
+ public static void main(final String[] args) {
+ // setup logging
+ System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
+ System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
+ System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true");
+ System.setProperty("org.slf4j.simpleLogger.showLogName", "false");
+ Main.LOG().info("");
+ Main.LOG().info("Apache Sling Application Launcher");
+ Main.LOG().info("---------------------------------");
+
+ // check if launcher has already been created
+ final LauncherConfig launcherConfig = new LauncherConfig();
+ parseArgs(launcherConfig, args);
+
+ ArtifactManager artifactManager = null;
+ try {
+
+ Main.LOG().info("Initializing...");
+ try {
+ artifactManager = ArtifactManager.getArtifactManager(launcherConfig);
+ } catch ( final IOException ioe) {
+ Main.LOG().error("Unable to setup artifact manager: {}", ioe.getMessage(), ioe);
+ System.exit(1);
+ }
+ Main.LOG().info("Artifact Repositories: {}", Arrays.toString(launcherConfig.getRepositoryUrls()));
+ Main.LOG().info("Assembling provisioning model...");
+
+ try {
+ final Launcher launcher = new FrameworkLauncher();
+ final Application app = FeatureProcessor.createApplication(launcherConfig, artifactManager);
+
+ Main.LOG().info("");
+ Main.LOG().info("Assembling launcher...");
+ final ArtifactManager aMgr = artifactManager;
+ final LauncherPrepareContext ctx = new LauncherPrepareContext() {
+
+ @Override
+ public File getArtifactFile(final ArtifactId artifact) throws IOException {
+ final ArtifactHandler handler = aMgr.getArtifactHandler(":" + artifact.toMvnPath());
+ return handler.getFile();
+ }
+
+ @Override
+ public void addAppJar(final File jar) {
+ launcherConfig.getInstallation().addAppJar(jar);
+ }
+ };
+ launcher.prepare(ctx, app);
+
+ FeatureProcessor.prepareLauncher(launcherConfig, artifactManager, app);
+
+ } catch ( final Exception iae) {
+ Main.LOG().error("Error while assembling launcher: {}", iae.getMessage(), iae);
+ System.exit(1);
+ }
+
+ Main.LOG().info("Using {} local artifacts, {} cached artifacts, and {} downloaded artifacts",
+ launcherConfig.getLocalArtifacts(), launcherConfig.getCachedArtifacts(), launcherConfig.getDownloadedArtifacts());
+ } finally {
+ if ( artifactManager != null ) {
+ artifactManager.shutdown();
+ }
+ }
+
+ try {
+ run(launcherConfig);
+ } catch ( final Exception iae) {
+ Main.LOG().error("Error while running launcher: {}", iae.getMessage(), iae);
+ System.exit(1);
+ }
+ }
+
+ private static final String STORAGE_PROPERTY = "org.osgi.framework.storage";
+
+ private static final String START_LEVEL_PROP = "org.osgi.framework.startlevel.beginning";
+
+ /**
+ * Run launcher.
+ * @param config The configuration
+ * @throws Exception If anything goes wrong
+ */
+ private static void run(final LauncherConfig config) throws Exception {
+ Main.LOG().info("");
+ Main.LOG().info("Starting launcher...");
+ Main.LOG().info("Launcher Home: {}", config.getHomeDirectory().getAbsolutePath());
+ Main.LOG().info("Cache Directory: {}", config.getCacheDirectory().getAbsolutePath());
+ Main.LOG().info("Startup Mode: {}", config.getStartupMode());
+ Main.LOG().info("");
+
+ final Installation installation = config.getInstallation();
+
+ // set sling home, and use separate locations for launchpad and properties
+ installation.getFrameworkProperties().put("sling.home", config.getHomeDirectory().getAbsolutePath());
+ installation.getFrameworkProperties().put("sling.launchpad", config.getHomeDirectory().getAbsolutePath() + "/launchpad");
+ installation.getFrameworkProperties().put("sling.properties", "conf/sling.properties");
+
+
+ // additional OSGi properties
+ // move storage inside launcher
+ if ( installation.getFrameworkProperties().get(STORAGE_PROPERTY) == null ) {
+ installation.getFrameworkProperties().put(STORAGE_PROPERTY, config.getHomeDirectory().getAbsolutePath() + File.separatorChar + "framework");
+ }
+ // set start level to 30
+ if ( installation.getFrameworkProperties().get(START_LEVEL_PROP) == null ) {
+ installation.getFrameworkProperties().put(START_LEVEL_PROP, "30");
+ }
+
+ final Launcher launcher = new FrameworkLauncher();
+ launcher.run(installation, createClassLoader(installation));
+
+ config.clear();
+ }
+
+ /**
+ * Create the class loader.
+ * @param installation The launcher configuration
+ * @throws Exception If anything goes wrong
+ */
+ public static ClassLoader createClassLoader(final Installation installation) throws Exception {
+ final List<URL> list = new ArrayList<>();
+ for(final File f : installation.getAppJars()) {
+ try {
+ list.add(f.toURI().toURL());
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ list.add(Main.class.getProtectionDomain().getCodeSource().getLocation());
+
+ final URL[] urls = list.toArray(new URL[list.size()]);
+
+ if ( Main.LOG().isDebugEnabled() ) {
+ Main.LOG().debug("App classpath: ");
+ for (int i = 0; i < urls.length; i++) {
+ Main.LOG().debug(" - {}", urls[i]);
+ }
+ }
+
+ // create a paranoid class loader, loading from parent last
+ final ClassLoader cl = new URLClassLoader(urls) {
+ @Override
+ public final Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ // First check if it's already loaded
+ Class<?> clazz = findLoadedClass(name);
+
+ if (clazz == null) {
+
+ try {
+ clazz = findClass(name);
+ } catch (ClassNotFoundException cnfe) {
+ ClassLoader parent = getParent();
+ if (parent != null) {
+ // Ask to parent ClassLoader (can also throw a CNFE).
+ clazz = parent.loadClass(name);
+ } else {
+ // Propagate exception
+ throw cnfe;
+ }
+ }
+ }
+
+ if (resolve) {
+ resolveClass(clazz);
+ }
+
+ return clazz;
+ }
+
+ @Override
+ public final URL getResource(final String name) {
+
+ URL resource = findResource(name);
+ ClassLoader parent = this.getParent();
+ if (resource == null && parent != null) {
+ resource = parent.getResource(name);
+ }
+
+ return resource;
+ }
+ };
+
+ Thread.currentThread().setContextClassLoader(cl);
+
+ return cl;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
new file mode 100644
index 0000000..0be1804
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/AbstractRunner.java
@@ -0,0 +1,278 @@
+/*
+ * 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.launcher.impl.launchers;
+
+import org.apache.sling.feature.launcher.impl.Main;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Common functionality for the framework start.
+ */
+public class AbstractRunner {
+
+ private volatile ServiceTracker<Object, Object> configAdminTracker;
+
+ private volatile ServiceTracker<Object, Object> installerTracker;
+
+ private final List<Object[]> configurations;
+
+ private final List<File> installables;
+
+ public AbstractRunner(final List<Object[]> configurations, final List<File> installables) {
+ this.configurations = new ArrayList<>(configurations);
+ this.installables = installables;
+ }
+
+ protected void setupFramework(final Framework framework, final Map<Integer, List<File>> bundlesMap)
+ throws BundleException {
+ if ( !configurations.isEmpty() ) {
+ this.configAdminTracker = new ServiceTracker<>(framework.getBundleContext(),
+ "org.osgi.service.cm.ConfigurationAdmin",
+ new ServiceTrackerCustomizer<Object, Object>() {
+
+ @Override
+ public Object addingService(final ServiceReference<Object> reference) {
+ // get config admin
+ final Object cm = framework.getBundleContext().getService(reference);
+ if ( cm != null ) {
+ try {
+ configure(cm);
+ } finally {
+ framework.getBundleContext().ungetService(reference);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<Object> reference, Object service) {
+ // nothing to do
+ }
+
+ @Override
+ public void removedService(ServiceReference<Object> reference, Object service) {
+ // nothing to do
+ }
+ });
+ this.configAdminTracker.open();
+ }
+ if ( installables != null && !installables.isEmpty() ) {
+ this.installerTracker = new ServiceTracker<>(framework.getBundleContext(),
+ "org.apache.sling.installer.api.OsgiInstaller",
+ new ServiceTrackerCustomizer<Object, Object>() {
+
+ @Override
+ public Object addingService(final ServiceReference<Object> reference) {
+ // get installer
+ final Object installer = framework.getBundleContext().getService(reference);
+ if ( installer != null ) {
+ try {
+ install(installer);
+ } finally {
+ framework.getBundleContext().ungetService(reference);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<Object> reference, Object service) {
+ // nothing to do
+ }
+
+ @Override
+ public void removedService(ServiceReference<Object> reference, Object service) {
+ // nothing to do
+ }
+ });
+ this.installerTracker.open();
+ }
+ try {
+ this.install(framework, bundlesMap);
+ } catch ( final IOException ioe) {
+ throw new BundleException("Unable to install bundles.", ioe);
+ }
+ }
+
+ private void configure(final Object configAdmin) {
+ try {
+ final Method createConfig = configAdmin.getClass().getDeclaredMethod("getConfiguration", String.class, String.class);
+ final Method createFactoryConfig = configAdmin.getClass().getDeclaredMethod("getFactoryConfiguration", String.class, String.class, String.class);
+
+ Method updateMethod = null;
+ for(final Object[] obj : this.configurations) {
+ final Object cfg;
+ if ( obj[1] != null ) {
+ cfg = createFactoryConfig.invoke(configAdmin, obj[1], obj[0], null);
+ } else {
+ cfg = createConfig.invoke(configAdmin, obj[0], null);
+ }
+ if ( updateMethod == null ) {
+ updateMethod = cfg.getClass().getDeclaredMethod("update", Dictionary.class);
+ }
+ updateMethod.invoke(cfg, obj[2]);
+ }
+ } catch ( final Exception e) {
+ Main.LOG().error("Unable to create configurations", e);
+ throw new RuntimeException(e);
+ }
+ final Thread t = new Thread(() -> { configAdminTracker.close(); configAdminTracker = null; });
+ t.setDaemon(false);
+ t.start();
+ this.configurations.clear();
+ }
+
+ private boolean isSystemBundleFragment(final Bundle installedBundle) {
+ final String fragmentHeader = getFragmentHostHeader(installedBundle);
+ return fragmentHeader != null
+ && fragmentHeader.indexOf(Constants.EXTENSION_DIRECTIVE) > 0;
+ }
+
+ /**
+ * Gets the bundle's Fragment-Host header.
+ */
+ private String getFragmentHostHeader(final Bundle b) {
+ return b.getHeaders().get( Constants.FRAGMENT_HOST );
+ }
+
+ /**
+ * Install the bundles
+ * @param bundleMap The map with the bundles indexed by start level
+ * @throws IOException, BundleException If anything goes wrong.
+ */
+ private void install(final Framework framework, final Map<Integer, List<File>> bundleMap)
+ throws IOException, BundleException {
+ final BundleContext bc = framework.getBundleContext();
+ int defaultStartLevel = getProperty(bc, "felix.startlevel.bundle", 1);
+ for(final Integer startLevel : sortStartLevels(bundleMap.keySet(), defaultStartLevel)) {
+ Main.LOG().debug("Installing bundles with start level {}", startLevel);
+
+ for(final File file : bundleMap.get(startLevel)) {
+ Main.LOG().debug("- {}", file.getName());
+
+ // use reference protocol. This avoids copying the binary to the cache directory
+ // of the framework
+ final Bundle bundle = bc.installBundle("reference:" + file.toURI().toURL(), null);
+ if ( startLevel > 0 ) {
+ bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
+ }
+
+ // fragment?
+ if ( !isSystemBundleFragment(bundle) ) {
+ final String fragmentHostHeader = getFragmentHostHeader(bundle);
+ if (fragmentHostHeader != null) {
+ for (final Bundle b : bc.getBundles()) {
+ if (fragmentHostHeader.equals(b.getSymbolicName())) {
+ final FrameworkWiring fw = framework.adapt(FrameworkWiring.class);
+ fw.refreshBundles(Collections.singleton(b));
+ break;
+ }
+ }
+
+ } else {
+ bundle.start();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sort the start levels in the ascending order. The only exception is the start level
+ * "0", which should be put at the position configured in {@code felix.startlevel.bundle}.
+ *
+ * @param startLevels integer start levels
+ * @return sorted start levels
+ */
+ private static Iterable<Integer> sortStartLevels(final Collection<Integer> startLevels, final int defaultStartLevel) {
+ final List<Integer> result = new ArrayList<>(startLevels);
+ Collections.sort(result, (o1, o2) -> {
+ int i1 = o1 == 0 ? defaultStartLevel : o1;
+ int i2 = o2 == 0 ? defaultStartLevel : o2;
+ return Integer.compare(i1, i2);
+ });
+ return result;
+ }
+
+ private static int getProperty(BundleContext bc, String propName, int defaultValue) {
+ String val = bc.getProperty(propName);
+ if (val == null) {
+ return defaultValue;
+ } else {
+ return Integer.parseInt(val);
+ }
+ }
+
+ private void install(final Object installer) {
+ try {
+ final Class<?> installableResourceClass = installer.getClass().getClassLoader().loadClass("org.apache.sling.installer.api.InstallableResource");
+ final Object resources = Array.newInstance(installableResourceClass, this.installables.size());
+ final Method registerResources = installer.getClass().getDeclaredMethod("registerResources", String.class, resources.getClass());
+ final Constructor<?> constructor = installableResourceClass.getDeclaredConstructor(String.class,
+ InputStream.class,
+ Dictionary.class,
+ String.class,
+ String.class,
+ Integer.class);
+
+ for(int i=0; i<this.installables.size();i++) {
+ final File f = this.installables.get(i);
+ final Dictionary<String, Object> dict = new Hashtable<>();
+ dict.put("resource.uri.hint", f.toURI().toString());
+ final Object rsrc = constructor.newInstance(f.getAbsolutePath(),
+ new FileInputStream(f),
+ dict,
+ f.getName(),
+ "file",
+ null);
+ Array.set(resources, i, rsrc);
+ }
+ registerResources.invoke(installer, "cloudlauncher", resources);
+ } catch ( final Exception e) {
+ Main.LOG().error("Unable to contact installer and install additional artifacts", e);
+ throw new RuntimeException(e);
+ }
+ final Thread t = new Thread(() -> { installerTracker.close(); installerTracker = null; });
+ t.setDaemon(false);
+ t.start();
+ this.installables.clear();
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
new file mode 100644
index 0000000..8bb5fdc
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkLauncher.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.feature.launcher.impl.launchers;
+
+import org.apache.sling.feature.Application;
+import org.apache.sling.feature.launcher.impl.Main;
+import org.apache.sling.feature.launcher.spi.Launcher;
+import org.apache.sling.feature.launcher.spi.LauncherPrepareContext;
+import org.apache.sling.feature.launcher.spi.LauncherRunContext;
+import org.apache.sling.feature.support.util.SubstVarUtil;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Launcher directly using the OSGi launcher API.
+ */
+public class FrameworkLauncher implements Launcher {
+
+
+ @Override
+ public void prepare(final LauncherPrepareContext context, final Application app) throws Exception {
+ context.addAppJar(context.getArtifactFile(app.getFramework()));
+ }
+
+ /**
+ * Run the launcher
+ * @throws If anything goes wrong
+ */
+ @Override
+ public void run(final LauncherRunContext context, final ClassLoader cl) throws Exception {
+ Map<String, String> properties = new HashMap<>();
+ context.getFrameworkProperties().forEach((key, value) -> {
+ properties.put(key, SubstVarUtil.substVars(value, key,null, context.getFrameworkProperties()));
+ });
+ if ( Main.LOG().isDebugEnabled() ) {
+ Main.LOG().debug("Bundles:");
+ for(final Integer key : context.getBundleMap().keySet()) {
+ Main.LOG().debug("-- Start Level {}", key);
+ for(final File f : context.getBundleMap().get(key)) {
+ Main.LOG().debug(" - {}", f.getName());
+ }
+ }
+ Main.LOG().debug("Settings: ");
+ for(final Map.Entry<String, String> entry : properties.entrySet()) {
+ Main.LOG().debug("- {}={}", entry.getKey(), entry.getValue());
+ }
+ Main.LOG().debug("Configurations: ");
+ for(final Object[] entry : context.getConfigurations()) {
+ if ( entry[1] != null ) {
+ Main.LOG().debug("- Factory {} - {}", entry[1], entry[0]);
+ } else {
+ Main.LOG().debug("- {}", entry[0]);
+ }
+ }
+ Main.LOG().debug("");
+ }
+ long time = System.currentTimeMillis();
+
+ final Class<?> runnerClass = cl.loadClass(this.getClass().getPackage().getName() + ".FrameworkRunner");
+ final Constructor<?> constructor = runnerClass.getDeclaredConstructor(Map.class, Map.class, List.class, List.class);
+ constructor.setAccessible(true);
+ constructor.newInstance(properties,
+ context.getBundleMap(),
+ context.getConfigurations(),
+ context.getInstallableArtifacts());
+
+ Main.LOG().debug("Startup took: " + (System.currentTimeMillis() - time));
+ // nothing else to do, constructor starts everything
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
new file mode 100644
index 0000000..5d95cef
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/impl/launchers/FrameworkRunner.java
@@ -0,0 +1,70 @@
+/*
+ * 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.launcher.impl.launchers;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * Launcher directly using the OSGi launcher API.
+ */
+public class FrameworkRunner extends AbstractRunner {
+
+ public FrameworkRunner(final Map<String, String> frameworkProperties,
+ final Map<Integer, List<File>> bundlesMap,
+ final List<Object[]> configurations,
+ final List<File> installables) throws Exception {
+ super(configurations, installables);
+
+ final ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
+ FrameworkFactory factory = null;
+ for(FrameworkFactory f : loader) {
+ factory = f;
+ break;
+ }
+ if ( factory == null ) {
+ throw new Exception("Unable to locate framework factory.");
+ }
+
+ // create the framework
+ final Framework framework = factory.newFramework(frameworkProperties);
+ // initialize the framework
+ framework.init();
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ try {
+ framework.stop();
+ } catch (final BundleException e) {
+ // ignore
+ }
+ }
+ });
+
+ this.setupFramework(framework, bundlesMap);
+
+ // finally start
+ framework.start();
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/Launcher.java b/src/main/java/org/apache/sling/feature/launcher/spi/Launcher.java
new file mode 100644
index 0000000..a1ced11
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/Launcher.java
@@ -0,0 +1,26 @@
+/*
+ * 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.launcher.spi;
+
+import org.apache.sling.feature.Application;
+
+public interface Launcher {
+
+ void prepare(LauncherPrepareContext context, Application app) throws Exception;
+
+ void run(LauncherRunContext context, ClassLoader cl) throws Exception;
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/LauncherPrepareContext.java b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherPrepareContext.java
new file mode 100644
index 0000000..6d6f02b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherPrepareContext.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.feature.launcher.spi;
+
+import org.apache.sling.feature.ArtifactId;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This is the context for the launcher
+ */
+public interface LauncherPrepareContext {
+
+ void addAppJar(File jar);
+
+ File getArtifactFile(ArtifactId artifact) throws IOException;
+}
diff --git a/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
new file mode 100644
index 0000000..20c95a9
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/launcher/spi/LauncherRunContext.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.feature.launcher.spi;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is the context for the launcher
+ */
+public interface LauncherRunContext {
+
+ /**
+ * Map of framework properties to be set when the framework is created.
+ * @return The map with the framework properties.
+ */
+ Map<String, String> getFrameworkProperties();
+
+ /**
+ * Bundle map, key is the start level, value is a list of files.
+ * @return The bundle map, might be empty
+ */
+ Map<Integer, List<File>> getBundleMap();
+
+ /**
+ * List of configurations.
+ * The value in each is an object array with three values
+ * <ol>
+ * <li>The PID
+ * <li>The factory PID or {@code null}
+ * <li>The dictionary with the properties
+ * </ol>
+ * We can't use a custom object due to class loading restrictions.
+ * @return The list, might be empty
+ */
+ List<Object[]> getConfigurations();
+
+ /**
+ * List of installable artifacts.
+ * @return The list of files. The list might be empty.
+ */
+ List<File> getInstallableArtifacts();
+}
--
To stop receiving notification emails like this one, please contact
davidb@apache.org.