/*
* 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.ace.agent.launcher;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import org.apache.ace.agent.AgentConstants;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
/**
* A simple launcher, that launches the embedded OSGi framework together with a management agent. Additional bundles may
* be installed by putting {@link BundleProvider} services on the classpath.
*/
public class Launcher implements PropertyProvider {
private static final String SYSTEM_PREFIX = "system.";
private static final String FRAMEWORK_PREFIX = "framework.";
/**
* MAIN ENTRY POINT
*/
public static void main(String[] args) throws Exception {
Launcher launcher = new Launcher();
launcher.parseArgs(args);
launcher.run();
}
private Map<String, String> m_configuration;
/**
* @return the value of the property with the given key.
*/
public String getProperty(String key) {
String value = m_configuration.get(key);
if (value == null) {
value = System.getProperty(key);
}
return value;
}
public Map<String, String> parseArgs(String... args) throws Exception {
Options options = new Options();
options.addOption(OptionBuilder.withDescription("the agent ID to use").hasArg().withArgName("ID").withLongOpt("agent").create('a'));
options.addOption(OptionBuilder.withDescription("the Apache ACE server URL(s) to use").hasArg().withArgName("URLs").withLongOpt("serverurl").create('s'));
options.addOption(OptionBuilder.withDescription("enable verbose logging").withLongOpt("verbose").create('v'));
options.addOption(OptionBuilder.withDescription("use configuration file").hasArg().withArgName("FILE").withLongOpt("config").create('c'));
options.addOption(OptionBuilder.withDescription("prints this message").withLongOpt("help").create('h'));
// Start from scratch...
Map<String, String> config = new HashMap<String, String>();
CommandLineParser parser = new BasicParser();
CommandLine command = parser.parse(options, args, false /* stopAtNonOption */);
if (command.hasOption("h")) {
printHelp(options);
System.exit(0);
}
// first map all default properties
propagateConfiguration(loadDefaultProperties(), config);
// overwrite with user properties
if (command.hasOption("c")) {
propagateConfiguration(loadUserProperties(command.getOptionValue("c")), config);
}
// add all non-recognized command line options...
propagateConfiguration(command.getArgs(), config);
// convenience debug override...
if (command.hasOption("v")) {
config.put("verbose", "true");
config.put(AgentConstants.CONFIG_LOGGING_LEVEL, "DEBUG");
}
// handle overrides...
if (command.hasOption("s")) {
config.put(AgentConstants.CONFIG_DISCOVERY_SERVERURLS, command.getOptionValue("s"));
}
if (command.hasOption("a")) {
config.put(AgentConstants.CONFIG_IDENTIFICATION_AGENTID, command.getOptionValue("a"));
}
return (m_configuration = config);
}
/**
* Main execution logic of the launcher; Start a framework, install bundles and pass configuration to the
* {@link AgentFactory}.
*
* @throws Exception
* on failure
*/
public void run() throws Exception {
int rc = 0;
try {
FrameworkFactory frameworkFactory = loadFrameworkFactory();
BundleProvider[] bundleProviders = loadBundleProviders();
logVerbose("Launching OSGi framework\n- factory:\t%s\n- properties:\t%s\n- providers:\t%s\n",
frameworkFactory.getClass().getName(), m_configuration, Arrays.toString(bundleProviders));
Framework framework = frameworkFactory.newFramework(m_configuration);
framework.init();
BundleContext context = framework.getBundleContext();
for (BundleProvider bundleProvider : bundleProviders) {
installBundles(context, bundleProvider);
}
framework.start();
logVerbose("Startup complete...");
framework.waitForStop(0);
}
catch (Exception e) {
e.printStackTrace(System.err);
rc = 1;
}
System.exit(rc);
}
/**
* @return the configuration
*/
final Map<String, String> getConfiguration() {
return m_configuration;
}
private void close(Closeable resource) {
if (resource != null) {
try {
resource.close();
}
catch (Exception exception) {
// Ignore, nothing we can do about this...
}
}
}
private Bundle[] installBundles(BundleContext context, BundleProvider bundleProvider) throws BundleException, IOException {
URL[] bundles = bundleProvider.getBundles(this);
List<Bundle> result = new ArrayList<Bundle>(bundles.length);
for (URL bundle : bundles) {
logVerbose("- installing:\t%s%n", bundle.getFile());
InputStream is = null;
try {
is = bundle.openStream();
result.add(context.installBundle(bundle.toExternalForm(), is));
}
finally {
close(is);
}
}
for (Bundle bundle : result) {
bundle.start();
}
return result.toArray(new Bundle[result.size()]);
}
private boolean isVerbose() {
return (m_configuration.get("verbose") != null) && Boolean.parseBoolean(m_configuration.get("verbose"));
}
/**
* Load {@link BundleProvider}s through the {@link ServiceLoader}.
*
* @return list of providers
* @throws Exception
* on failure
*/
private BundleProvider[] loadBundleProviders() throws Exception {
ServiceLoader<BundleProvider> bundleFactoryLoader = ServiceLoader.load(BundleProvider.class);
Iterator<BundleProvider> bundleFactoryIterator = bundleFactoryLoader.iterator();
List<BundleProvider> bundelFactoryList = new ArrayList<BundleProvider>();
while (bundleFactoryIterator.hasNext()) {
bundelFactoryList.add(bundleFactoryIterator.next());
}
return bundelFactoryList.toArray(new BundleProvider[bundelFactoryList.size()]);
}
private Properties loadDefaultProperties() throws IOException {
InputStream inStream = null;
try {
inStream = getClass().getResourceAsStream("launcher-defaults.properties");
Properties properties = new Properties();
properties.load(inStream);
return properties;
}
finally {
close(inStream);
}
}
private FrameworkFactory loadFrameworkFactory() throws IllegalStateException {
ServiceLoader<FrameworkFactory> frameworkFactoryLoader = ServiceLoader.load(FrameworkFactory.class);
Iterator<FrameworkFactory> frameworkFactoryIterator = frameworkFactoryLoader.iterator();
if (!frameworkFactoryIterator.hasNext()) {
throw new IllegalStateException("Unable to load any FrameworkFactory");
}
return frameworkFactoryIterator.next();
}
private Properties loadUserProperties(String configFile) throws IOException {
try {
Properties properties = new Properties();
properties.load(new FileInputStream(configFile));
return properties;
}
catch (Exception e) {
System.err.println("Can not load or access configuration file : " + configFile);
return null;
}
}
private void printHelp(Options options) {
// Since the main() method is in the same class, we can use the following trick to obtain the JAR name...
String jarName;
try {
jarName = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getName();
}
catch (URISyntaxException exception) {
jarName = getClass().getSimpleName();
}
PrintWriter pw = new PrintWriter(System.out);
new HelpFormatter().printHelp(pw, 120, "java -jar " + jarName, "\nOptions:", options, 4, 2, null, true);
pw.println("\n" +
"The configuration file provides the system and framework properties as well as the\n" +
"agent configuration settings. If you specify a setting both on the command line and in\n" +
"a configuration file, the command line setting takes precedence. System properties can\n" +
"be specified by prefixing the property name with 'system.', for example, to set the\n" +
"system property 'my.setting' one should specify it as 'system.my.setting'. All non-\n" +
"system properties (including the agent configuration) are regarded as framework\n" +
"properties, that is, will be passed to the OSGi framework.\n\n" +
"Common options are (see ACE documentation for extensive list):\n\n" +
"- agent.identification.agentid = <ID>\n" +
" defines the target name as <name>, if not given 'defaultTargetID' will be used.\n" +
" Note that the option '-a' overrides the agent ID in the configuration file!\n" +
"- agent.discovery.serverurls = <URL-1>,<URL-2>,...,<URL-N>\n" +
" defines the location of the Apache ACE server(s), multiple servers can be given\n" +
" by separating their URLs with commas. Multiple URLs are used in best-effort round\n" +
" robin mode, that is, if the first URL fails, the second URL will be used, and so\n" +
" on. If not supplied, the default URL of 'http://localhost:8080' is used.\n" +
" Note that the option '-s' overrides the server URL(s) in the configuration file!\n" +
"- agent.logging.level = (DEBUG|INFO|WARNING|ERROR)\n" +
" defines the log level of the agent. If not defined, 'INFO' is used as log level.\n" +
" Note that passing the option '-v' to the launcher implicitly sets the log level\n" +
" to 'DEBUG';\n" +
"- agent.controller.syncinterval = <N>\n" +
" defines the synchronization interval (in seconds) in which the agent will\n" +
" synchronize its state with the Apache ACE server;\n" +
"- agent.controller.syncdelay = <N>\n" +
" defines the initial delay (in seconds) before the agent will start synchronization\n" +
" with the Apache ACE server;\n" +
"- launcher.bundles.dir = <PATH>\n" +
" defines the path where the launcher can find its initial set of bundles that should\n" +
" be installed upon startup of the agent. If not specified, the launcher will look at\n" +
" a directory called 'bundle' in the same directory as the launcher.\n");
pw.flush();
pw.close();
}
private void propagateConfiguration(Properties properties, Map<String, String> config) {
if (properties == null) {
return;
}
for (Object _key : properties.keySet()) {
String key = (String) _key;
String value = properties.getProperty(key);
if (key.startsWith(SYSTEM_PREFIX)) {
System.setProperty(key.substring(SYSTEM_PREFIX.length()), value);
}
else if (key.startsWith(FRAMEWORK_PREFIX)) {
// Strip off the framework prefix, as we pass all configuration options as fw property...
config.put(key.substring(FRAMEWORK_PREFIX.length()), value);
}
else {
config.put(key, value);
}
}
}
private void propagateConfiguration(String[] properties, Map<String, String> config) {
for (String property : properties) {
String[] kv = property.split("\\s*=\\s*", 2);
String key = kv[0].trim();
String value = (kv.length > 1) ? kv[1].trim() : "";
if (key.startsWith(SYSTEM_PREFIX)) {
// Never let system properties to propagate as fw property...
System.setProperty(key.substring(SYSTEM_PREFIX.length()), value);
}
else if (key.startsWith(FRAMEWORK_PREFIX)) {
// Strip off the framework prefix, as we pass all configuration options as framework option...
config.put(key.substring(FRAMEWORK_PREFIX.length()), value);
}
else {
config.put(key, value);
}
}
}
private void logVerbose(String msg, Object... args) {
if (isVerbose()) {
System.out.printf(msg, args);
}
}
}