blob: 478f8c0cd0fc228f7918501d300380fe9c1dedd8 [file] [log] [blame]
/*
* 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.PrintWriter;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.sling.feature.ArtifactId;
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 {
public static final String OPT_OSGI_FRAMEWORK_ARTIFACT = "fa";
public static final String OPT_FELIX_FRAMEWORK_VERSION = "fv";
public static final String OPT_EXTENSION_CONFIGURATION = "ec";
public static final String OPT_HOME_DIR = "p";
public static final String OPT_CACHE_DIR = "c";
public static final String OPT_VERBOSE = "v";
public static final String OPT_VARIABLE_VALUES = "V";
public static final String OPT_FRAMEWORK_PROPERTIES = "D";
public static final String OPT_FEATURE_FILES = "f";
public static final String OPT_REPOSITORY_URLS = "u";
public static final String OPT_CONFIG_CLASH = "CC";
public static final String OPT_ARTICACT_CLASH = "C";
public static final String OPT_PRINT_CONTAINER_ENV_HELP = "cenv";
public static final String OPT_LAUNCH_FEATURE_ID = "i";
public static final String OPT_CACHE_ONLY = "CO";
private static Logger LOGGER;
private static Options options;
private static final List<String> logLevels = Arrays.asList("trace", "debug", "info", "warn",
"error", "off");
private static Logger LOG() {
if (LOGGER == null) {
LOGGER = LoggerFactory.getLogger("launcher");
}
return LOGGER;
}
/** Split a string into key and value */
static String[] splitKeyVal(final String keyVal) {
final int pos = keyVal.indexOf('=');
if (pos == -1) {
return new String[] { keyVal, "true" };
}
return new String[] { keyVal.substring(0, pos), keyVal.substring(pos + 1) };
}
static Map.Entry<String, Map<String, String>> splitMap2(final String val) {
String[] split1 = val.split(":");
if (split1.length < 2) {
return new AbstractMap.SimpleEntry<>(split1[0], Collections.emptyMap());
}
Map<String, String> m = splitMap(split1[1]);
return new AbstractMap.SimpleEntry<>(split1[0], m);
}
private static Map<String, String> splitMap(String value) {
Map<String, String> m = new HashMap<>();
for (String kv : value.split(",")) {
String[] keyval = splitKeyVal(kv);
m.put(keyval[0], keyval[1]);
}
return m;
}
private static Optional<String> extractValueFromOption(CommandLine cl, String opt) {
return extractValueFromOption(cl, opt, null);
}
private static Optional<String> extractValueFromOption(CommandLine cl, String opt,
String defaultVaue) {
return Optional.ofNullable(cl.getOptionValue(opt, defaultVaue));
}
private static Optional<List<String>> extractValuesFromOption(CommandLine cl, String opt) {
String[] values = cl.getOptionValues(opt);
if (Objects.isNull(values)) {
return Optional.empty();
}
return Optional.of(Stream.of(values).collect(Collectors.toList()));
}
/**
* Parse the command line parameters and update a configuration object.
*
* @param args Command line parameters
* @param config Configuration object
* @return Options object.
*/
protected static void parseArgs(final LauncherConfig config, final String[] args) {
final Option artifactClashOverride = Option.builder(OPT_ARTICACT_CLASH)
.longOpt("artifact-clash")
.desc("Set artifact clash override")
.optionalArg(true)
.numberOfArgs(Option.UNLIMITED_VALUES)
.build();
final Option configClashOverride = Option.builder(OPT_CONFIG_CLASH)
.longOpt("config-clash")
.desc("Set config clash override")
.optionalArg(true)
.numberOfArgs(Option.UNLIMITED_VALUES)
.build();
final Option repoOption = Option.builder(OPT_REPOSITORY_URLS)
.longOpt("repository-urls")
.desc("Set repository urls")
.optionalArg(true)
.valueSeparator(',')
.numberOfArgs(Option.UNLIMITED_VALUES)
.build();
final Option featureOption = Option.builder(OPT_FEATURE_FILES)
.longOpt("feature-files")
.desc("Set feature files")
.optionalArg(true)
.valueSeparator(',')
.numberOfArgs(Option.UNLIMITED_VALUES)
.build();
final Option featureIdOption = Option.builder(OPT_LAUNCH_FEATURE_ID)
.longOpt("launch-feature-id")
.desc("Set the id for the launch feature")
.optionalArg(true)
.numberOfArgs(1)
.build();
final Option fwkProperties = Option.builder(OPT_FRAMEWORK_PROPERTIES)
.longOpt("framework-property")
.desc("Set framework property, format: -D key1=val1 -D key2=val2")
.hasArg()
.optionalArg(true)
.build();
final Option varValue = Option.builder(OPT_VARIABLE_VALUES)
.longOpt("variable-value")
.desc("Set variable value, format: -V key1=val1 -V key2=val2")
.optionalArg(true)
.numberOfArgs(Option.UNLIMITED_VALUES)
.build();
final Option debugOption = Option.builder(OPT_VERBOSE)
.longOpt("verbose")
.desc("Verbose")
.optionalArg(true)
.numberOfArgs(1)
.build();
final Option cacheOnlyOption = Option.builder(OPT_CACHE_ONLY)
.longOpt("cacheOnly")
.desc("Cache only the required dependencies. Don't start the framework.")
.optionalArg(true)
.build();
final Option cacheOption = Option.builder(OPT_CACHE_DIR)
.longOpt("cache_dir")
.desc("Set cache dir")
.optionalArg(true)
.numberOfArgs(1)
.build();
final Option homeOption = Option.builder(OPT_HOME_DIR)
.longOpt("home_dir")
.desc("Set home dir")
.optionalArg(true)
.numberOfArgs(1)
.build();
final Option extensionConfiguration = Option.builder(OPT_EXTENSION_CONFIGURATION)
.longOpt("extension_configuration")
.desc("Provide extension configuration, format: extensionName:key1=val1,key2=val2")
.optionalArg(true)
.numberOfArgs(Option.UNLIMITED_VALUES)
.build();
final Option frameworkVersionOption = Option.builder(OPT_FELIX_FRAMEWORK_VERSION)
.longOpt("felix-framework-version")
.desc("Set Apache Felix framework version (default "
.concat(Bootstrap.FELIX_FRAMEWORK_VERSION) + ")")
.optionalArg(true)
.numberOfArgs(1)
.build();
final Option frameworkArtifactOption = Option.builder(OPT_OSGI_FRAMEWORK_ARTIFACT)
.longOpt("osgi-framework-artifact")
.desc("Set OSGi framework artifact (overrides Apache Felix framework version)")
.optionalArg(true)
.numberOfArgs(1)
.build();
final Option printInsideContainerHelp = Option.builder(OPT_PRINT_CONTAINER_ENV_HELP)
.desc("print additional help information for container env vars.")
.optionalArg(true)
.build();
options = new Options().addOption(artifactClashOverride)
.addOption(configClashOverride)
.addOption(repoOption)
.addOption(featureOption)
.addOption(featureIdOption)
.addOption(fwkProperties)
.addOption(varValue)
.addOption(debugOption)
.addOption(cacheOnlyOption)
.addOption(cacheOption)
.addOption(homeOption)
.addOption(extensionConfiguration)
.addOption(frameworkVersionOption)
.addOption(frameworkArtifactOption)
.addOption(printInsideContainerHelp);
final CommandLineParser clp = new DefaultParser();
try {
final CommandLine cl = clp.parse(options, args);
extractValuesFromOption(cl, OPT_REPOSITORY_URLS).ifPresent(
values -> config.setRepositoryUrls(values.stream().toArray(String[]::new)));
extractValuesFromOption(cl, OPT_ARTICACT_CLASH).ifPresent(values -> values
.forEach(v -> config.getArtifactClashOverrides().add(ArtifactId.parse(v))));
extractValuesFromOption(cl, OPT_CONFIG_CLASH).orElseGet(ArrayList::new)
.forEach(value -> {
final String[] keyVal = split(value);
config.getConfigClashOverrides().put(keyVal[0], keyVal[1]);
});
extractValuesFromOption(cl, OPT_FRAMEWORK_PROPERTIES).orElseGet(ArrayList::new)
.forEach(value -> {
final String[] keyVal = split(value);
config.getInstallation().getFrameworkProperties().put(keyVal[0], keyVal[1]);
});
extractValuesFromOption(cl, OPT_VARIABLE_VALUES).orElseGet(ArrayList::new)
.forEach(value -> {
final String[] keyVal = split(value);
config.getVariables().put(keyVal[0], keyVal[1]);
});
if (cl.hasOption(OPT_VERBOSE)) {
extractValueFromOption(cl, OPT_VERBOSE, "debug").ifPresent(value -> {
if (isLoglevel(value)) {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", value);
}
});
} // if not the `org.slf4j.simpleLogger.defaultLogLevel` is by default `info`
if (cl.hasOption(OPT_CACHE_ONLY)) {
config.setCacheOnly(true);
}
extractValuesFromOption(cl, OPT_FEATURE_FILES).orElseGet(ArrayList::new)
.forEach(config::addFeatureFiles);
extractValueFromOption(cl, OPT_LAUNCH_FEATURE_ID)
.ifPresent(config::setLaunchFeatureId);
extractValueFromOption(cl, OPT_CACHE_DIR).map(File::new)
.ifPresent(config::setCacheDirectory);
extractValueFromOption(cl, OPT_HOME_DIR).map(File::new)
.ifPresent(config::setHomeDirectory);
extractValuesFromOption(cl, OPT_EXTENSION_CONFIGURATION)
.ifPresent(values -> values.forEach(v -> {
Map.Entry<String, Map<String, String>> xc = splitMap2(v);
Map<String, Map<String, String>> ec = config.getExtensionConfiguration();
Map<String, String> c = ec.get(xc.getKey());
if (c == null) {
c = new HashMap<>();
ec.put(xc.getKey(), c);
}
c.putAll(xc.getValue());
}));
extractValueFromOption(cl, OPT_FELIX_FRAMEWORK_VERSION)
.ifPresent(config::setFrameworkVersion);
extractValueFromOption(cl, OPT_OSGI_FRAMEWORK_ARTIFACT)
.ifPresent(config::setFrameworkArtifact);
} catch (final ParseException pe) {
Main.LOG().error("Unable to parse command line: {}", pe.getMessage(), pe);
printHelp();
System.exit(1);
}
}
private static boolean isLoglevel(String value) {
return logLevels.contains(value);
}
static void printHelp() {
if (options == null) {
return;
}
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("launcher", options);
if (options.getOption(OPT_PRINT_CONTAINER_ENV_HELP) != null) {
try (PrintWriter writer = new PrintWriter(System.out);) {
writer.println("");
writer.println(
"If you are running sling-feature-launcher as an container please use env vars.");
writer.println("");
writer.println(" cli-arg - container ENV variable");
writer.println("-------------------------------------");
writer.println(" -" + OPT_ARTICACT_CLASH + " - ARTIFACT_CLASH");
writer.println(" -" + OPT_CONFIG_CLASH + " - CONFIG_CLASH");
writer.println(" -" + OPT_CACHE_DIR + " - CACHE_DIR");
writer.println(" -" + OPT_FRAMEWORK_PROPERTIES + " - FRAMEWORK_PROPERTIES format: `key1=val1` for more `key1=val1 -D key2=val2`");
writer.println(" -" + OPT_FEATURE_FILES + " - FEATURE_FILES");
writer.println(" -" + OPT_HOME_DIR + " - HOME_DIR");
writer.println(" -" + OPT_REPOSITORY_URLS + " - REPOSITORY_URLS");
writer.println(" -" + OPT_VARIABLE_VALUES + " - VARIABLE_VALUES format: `variable1=value1` for more `variable1=val1 -V variable2=val2`");
writer.println(
" -" + OPT_EXTENSION_CONFIGURATION + " - EXTENSION_CONFIGURATION");
writer.println(
" -" + OPT_FELIX_FRAMEWORK_VERSION + " - FELIX_FRAMEWORK_VERSION");
writer.println(
" -" + OPT_OSGI_FRAMEWORK_ARTIFACT + " - OSGI_FRAMEWORK_ARTIFACT");
writer.println(" -" + OPT_VERBOSE + " - VERBOSE {trace, debug, info, warn, error, off}");
writer.println("");
writer.println("Java options could be set using the env var 'JAVA_OPTS'");
writer.flush();
}
}
}
/** 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)};
}
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");
// check if launcher has already been created
final LauncherConfig launcherConfig = new LauncherConfig();
parseArgs(launcherConfig, args);
final Bootstrap bootstrap = new Bootstrap(launcherConfig, Main.LOG());
bootstrap.run();
}
}