blob: 6d17038ce4a52334aa5011978d259336eeb3d074 [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.launchpad.base.app;
import static org.apache.felix.framework.util.FelixConstants.LOG_LEVEL_PROP;
import java.io.PrintStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.felix.framework.Logger;
import org.apache.sling.launchpad.api.LaunchpadContentProvider;
import org.apache.sling.launchpad.base.impl.ClassLoaderResourceProvider;
import org.apache.sling.launchpad.base.impl.Sling;
import org.apache.sling.launchpad.base.shared.Launcher;
import org.apache.sling.launchpad.base.shared.Notifiable;
import org.apache.sling.launchpad.base.shared.SharedConstants;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
/**
* The <code>Main</code> class is a simple Java Application which interprests
* the command line and creates the {@link Sling} launcher class and thus starts
* the OSGi framework. In addition a shutdown thread is registered to ensure
* proper shutdown on VM termination.
* <p>
* The supported command line options are:
* <dl>
* <dt>-l loglevel</dt>
* <dd>Sets the initial loglevel as an integer in the range 0 to 4 or as one of
* the well known level strings FATAL, ERROR, WARN, INFO or DEBUG. This option
* overwrites the <code>org.apache.sling.osg.log.level</code> setting the
* <code>sling.properties</code> file.</dd>
* <dt>-f logfile</dt>
* <dd>The log file, \"-\" for stdout (default logs/error.log). This option
* overwrites the <code>org.apache.sling.osg.log.file</code> setting the
* <code>sling.properties</code> file.</dd>
* <dt>-c slinghome</dt>
* <dd>The directory in which Sling locates its initial configuration file
* <code>sling.properties</code> and where files of Sling itself such as the
* Apache Felix bundle archive or the JCR repository files are stored (default
* sling).</dd>
* <dt>-a address</dt>
* <dd>The interfact to bind to (use 0.0.0.0 for any). This option is not
* implemented yet.</dd>
* <dt>-p port</dt>
* <dd>The port to listen (default 8080) to handle HTTP requests. This option
* overwrites the <code>org.osgi.service.http.port</code> setting the
* <code>sling.properties</code> file.</dd>
* <dt>-h</dt>
* <dd>Prints a simple usage message listing all available command line options.
* </dd>
* </dl>
*/
public class MainDelegate implements Launcher {
/** Mapping between log level numbers and names */
private static final String[] logLevels = { "FATAL", "ERROR", "WARN",
"INFO", "DEBUG" };
/** 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 Sling system property name setting the bootstrap log level */
private static final String PROP_BOOT_LOG_LEVEL = "sling.launchpad.log.level";
/** Default log level setting if no set on command line (value is "INFO"). */
private static final int DEFAULT_LOG_LEVEL = Logger.LOG_INFO;
/**
* The configuration property setting the port on which the HTTP service
* listens
*/
private static final String PROP_PORT = "org.osgi.service.http.port";
private Notifiable notifiable;
/** The parsed command line mapping (Sling) option name to option value */
private Map<String, String> commandLine;
private String slingHome;
private Sling sling;
@Override
public void setNotifiable(Notifiable notifiable) {
this.notifiable = notifiable;
}
@Override
public void setCommandLine(Map<String, String> args) {
commandLine = new HashMap<String, String>();
parseCommandLine(args, commandLine);
}
@Override
public void setSlingHome(String slingHome) {
this.slingHome = slingHome;
}
@Override
public boolean start() {
Map<String, String> props = new HashMap<String, String>();
// parse the command line (exit in case of failure)
if (commandLine == null) {
setCommandLine(new HashMap<String, String>());
}
// if sling.home was set on the command line, set it in the properties
if (slingHome != null) {
props.put(SharedConstants.SLING_HOME, slingHome);
} else if (commandLine.containsKey(SharedConstants.SLING_HOME)) {
props.put(SharedConstants.SLING_HOME,
commandLine.get(SharedConstants.SLING_HOME));
}
// ensure sling.launchpad is set
if (!commandLine.containsKey(SharedConstants.SLING_LAUNCHPAD)) {
commandLine.put(SharedConstants.SLING_LAUNCHPAD, slingHome);
}
// check sling.properties in the command line
final String slingPropertiesProp = commandLine.remove(SharedConstants.SLING_PROPERTIES);
if (slingPropertiesProp != null) {
props.put(SharedConstants.SLING_PROPERTIES, slingPropertiesProp);
}
// set up and configure Felix Logger
int logLevel;
if (!commandLine.containsKey(PROP_LOG_LEVEL)) {
logLevel = DEFAULT_LOG_LEVEL;
} else {
logLevel = toLogLevelInt(commandLine.get(PROP_LOG_LEVEL),
DEFAULT_LOG_LEVEL);
commandLine.put(LOG_LEVEL_PROP, String.valueOf(logLevel));
}
final Logger logger = new SlingLogger();
// default log level: prevent tons of needless WARN from the framework
logger.setLogLevel(Logger.LOG_ERROR);
if ( System.getProperty(PROP_BOOT_LOG_LEVEL) != null ) {
try {
logger.setLogLevel(
Integer.parseInt(System.getProperty(PROP_BOOT_LOG_LEVEL)));
} catch (final NumberFormatException ex) {
// just ignore
}
}
try {
LaunchpadContentProvider resProvider = new ClassLoaderResourceProvider(
getClass().getClassLoader());
// creating the instance launches the framework and we are done here
// ..
sling = new Sling(notifiable, logger, resProvider, props) {
// overwrite the loadPropertiesOverride method to inject the
// command line arguments unconditionally. These will not be
// persisted in any properties file, though
@Override
protected void loadPropertiesOverride(
Map<String, String> properties) {
if (commandLine != null) {
properties.putAll(commandLine);
}
// Display port number on console, in case HttpService doesn't. This is logged as late as
// possible in order to pick up defaults from the Sling property files, although system
// property substitutions will be missed.
info("HTTP server port: " + properties.get(PROP_PORT), null);
}
};
// we successfully started it
return true;
} catch (BundleException be) {
error("Failed to Start OSGi framework", be);
}
// we failed to start
return false;
}
@Override
public void stop() {
if (sling != null) {
sling.destroy();
sling = null;
}
}
/**
* Parses the command line in <code>args</code> and sets appropriate Sling
* configuration options in the <code>props</code> map.
*/
private static void parseCommandLine(Map<String, String> args,
Map<String, String> props) {
/*
* NOTE: We expect command line args to be suitable to use as
* properties to launch Sling. Any standalone Java application
* command line args have to be translated into Sling launcher
* properties by the Main class. For deployment of the Launchpad
* JAR into older launchers we keep converting existing command
* line args for now. New command line arguments must solely be
* known and converted in the Main class and not here.
*/
for (Entry<String, String> arg : args.entrySet()) {
if (arg.getKey().length() == 1) {
String value = arg.getValue();
switch (arg.getKey().charAt(0)) {
case 'l':
if (value == arg.getKey()) {
terminate("Missing log level value", 1);
continue;
}
props.put(PROP_LOG_LEVEL, value);
break;
case 'f':
if (value == arg.getKey()) {
terminate("Missing log file value", 1);
continue;
} else if ("-".equals(value)) {
value = "";
}
props.put(PROP_LOG_FILE, value);
break;
case 'c':
if (value == arg.getKey()) {
terminate("Missing directory value", 1);
continue;
}
props.put(SharedConstants.SLING_HOME, value);
break;
case 'p':
if (value == arg.getKey()) {
terminate("Missing port value", 1);
continue;
}
try {
// just to verify it is a number
Integer.parseInt(value);
props.put(PROP_PORT, value);
} catch (RuntimeException e) {
terminate("Bad port: " + value, 1);
}
break;
case 'a':
if (value == arg.getKey()) {
terminate("Missing address value", 1);
continue;
}
info("Setting the address to bind to is not supported, binding to 0.0.0.0", null);
break;
default:
terminate("Unrecognized option " + arg.getKey(), 1);
break;
}
} else {
info("Setting " + arg.getKey() + "=" + arg.getValue(), null);
props.put(arg.getKey(), arg.getValue());
}
}
}
/** Return the log level code for the string */
private static int toLogLevelInt(String level, int defaultLevel) {
try {
int logLevel = Integer.parseInt(level);
if (logLevel >= 0 && logLevel < logLevels.length) {
return logLevel;
}
} catch (NumberFormatException nfe) {
// might be a log level string
}
for (int i = 0; i < logLevels.length; i++) {
if (logLevels[i].equalsIgnoreCase(level)) {
return i;
}
}
return defaultLevel;
}
// ---------- console logging
/** prints a simple usage plus optional error message and exists with code */
private static void terminate(String message, int code) {
if (message != null) {
error(message + " (use -h for more information)", null);
}
System.exit(code);
}
// emit an debugging message to standard out
static void debug(String message, Throwable t) {
log(System.out, "*DEBUG*", message, t);
}
// emit an informational message to standard out
static void info(String message, Throwable t) {
log(System.out, "*INFO *", message, t);
}
// emit an warning message to standard out
static void warn(String message, Throwable t) {
log(System.out, "*WARN *", message, t);
}
// emit an error message to standard err
static void error(String message, Throwable t) {
log(System.err, "*ERROR*", message, t);
}
private static final DateFormat fmt = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS ");
// helper method to format the message on the correct output channel
// the throwable if not-null is also prefixed line by line with the prefix
private static void log(PrintStream out, String prefix, String message,
Throwable t) {
final StringBuilder linePrefixBuilder = new StringBuilder();
synchronized (fmt) {
linePrefixBuilder.append(fmt.format(new Date()));
}
linePrefixBuilder.append(prefix);
linePrefixBuilder.append(" [");
linePrefixBuilder.append(Thread.currentThread().getName());
linePrefixBuilder.append("] ");
final String linePrefix = linePrefixBuilder.toString();
synchronized (out) {
out.print(linePrefix);
out.println(message);
if (t != null) {
t.printStackTrace(new PrintStream(out) {
@Override
public void println(String x) {
synchronized (this) {
print(linePrefix);
super.println(x);
flush();
}
}
});
}
}
}
private static class SlingLogger extends Logger {
@Override
protected void doLog(Bundle bundle, @SuppressWarnings("rawtypes") ServiceReference sr, int level, String msg, Throwable throwable) {
// unwind throwable if it is a BundleException
if ((throwable instanceof BundleException) && (((BundleException) throwable).getNestedException() != null)) {
throwable = ((BundleException) throwable).getNestedException();
}
final StringBuilder sb = new StringBuilder();
if (sr != null) {
sb.append("SvcRef ");
sb.append(sr);
sb.append(" ");
} else if (bundle != null) {
sb.append("Bundle '");
sb.append(String.valueOf(bundle.getBundleId()));
sb.append("' ");
}
sb.append(msg);
if ( throwable != null ) {
sb.append(" (");
sb.append(throwable);
sb.append(")");
}
final String s = sb.toString();
switch (level) {
case LOG_DEBUG:
debug("DEBUG: " + s);
break;
case LOG_INFO:
info("INFO: " + s, throwable);
break;
case LOG_WARNING:
warn("WARNING: " + s, throwable);
break;
case LOG_ERROR:
error("ERROR: " + s, throwable);
break;
default:
warn("UNKNOWN[" + level + "]: " + s, null);
}
}
}
}