blob: 312ba8f4fd5ce50db32accf67494f6e60d931dd5 [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.app;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
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.sling.launchpad.base.shared.Launcher;
import org.apache.sling.launchpad.base.shared.Loader;
import org.apache.sling.launchpad.base.shared.Notifiable;
import org.apache.sling.launchpad.base.shared.SharedConstants;
/**
* The <code>Main</code> is the externally visible Standalone Java Application
* launcher for Sling. Please refer to the full description <i>The Sling
* Launchpad</i> on the Sling Web Site for a full description of this class.
* <p>
* Logging goes to standard output for informational messages and to standard
* error for error messages.
* <p>
* This class goes into the secondary artifact with the classifier <i>app</i> to
* be used as the main class when starting the Java Application.
*
* @see <a href="http://sling.apache.org/site/the-sling-launchpad.html">The
* Sling Launchpad</a>
*/
public class Main {
// 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
* {@link ControlAction} to be taken in the {@link #doControlAction()}
* method.
*/
protected static final String PROP_CONTROL_ACTION = "sling.control.action";
/**
* 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";
/**
* The main entry point to the Sling Launcher Standalone Java Application.
* This method is generally only called by the Java VM to launch Sling.
*
* @param args The command line arguments supplied when starting the Sling
* Launcher through the Java VM.
*/
public static void main(final String[] args) {
final Map<String, String> rawArgs = parseCommandLine(args);
// support usage first
if (doHelp(rawArgs)) {
System.exit(0);
}
final Map<String, String> props = convertCommandLineArgs(rawArgs);
if (props == null) {
System.exit(1);
}
final Main main = new Main(props);
// check for control commands
int rc = main.doControlAction();
if (rc >= 0) {
main.terminateVM(rc);
}
// finally start Sling
if (!main.doStart()) {
error("Failed to start Sling; terminating", null);
main.terminateVM(1);
}
}
/**
* The map of command line arguments where the keys are the actual
* property names as known to the OSGi Framework and its installed
* bundles.
*/
private final Map<String, String> commandLineArgs;
/**
* Whether to install the shutdown hook.
*
* @see #PROP_SHUTDOWN_HOOK
* @see #installShutdownHook(Map)
* @see #addShutdownHook()
*/
private boolean installShutdownHook;
/**
* The shutdown hook installed into the Java VM after Sling has been
* started. The hook is removed again when Sling is being shut down
* or the {@link Notified notifier} is notified of the framework shutdown.
*
* @see #addShutdownHook()
* @see #removeShutdownHook()
*/
private Thread shutdownHook;
/**
* The absolute path to the home directory of the launched Sling
* application. This corresponds to the value of the <code>sling.home</code>
* framework property.
*
* @see #getSlingHome(Map)
*/
private final String slingHome;
/**
* The {@link Loader} class used to create the Framework class loader and
* to launch the framework.
*/
private Loader loader;
/**
* The actual launcher accessed through the {@link #loader} to launch
* the OSGi Framework.
*/
private Launcher sling;
/**
* Flag to indicate if Sling has already been started.
*/
private boolean started = false;
/**
* Creates an instance of this main loader class. The provided arguments are
* used to configure the OSGi framework being launched with the
* {@link #doStart(URL)} method.
*
* @param args The map of configuration properties to be supplied to the
* OSGi framework. The keys in this map are assumed to be usefull
* without translation to the launcher and the OSGi Framework. If
* this parameter is <code>null</code> and empty map without
* configuration is assumed.
*/
protected Main(Map<String, String> args) {
this.commandLineArgs = (args == null)
? new HashMap<String, String>()
: args;
this.installShutdownHook = installShutdownHook(this.commandLineArgs);
// sling.home from the command line or system properties, else default
String home = getSlingHome(commandLineArgs);
final File slingHomeFile = new File(home);
if (!slingHomeFile.isAbsolute()) {
home = slingHomeFile.getAbsolutePath();
}
this.slingHome = home;
}
/**
* After instantiating this class, this method may be called to help with
* the communication with a running Sling instance. To setup this
* communication the configuration properties supplied to the constructor
* are evaluated as follows:
* <p>
* <table>
* <tr>
* <td><code>{@value #PROP_CONTROL_SOCKET}</code></td>
* <td>Specifies the socket to use for the control connection. This
* specification is of the form <i>host:port</i> where the host can be a
* host name or IP Address and may be omitted (along with the separating
* colon) and port is just the numeric port number at which to listen. The
* default is <i>localhost:63000</i>. It is suggested to not use an
* externally accessible interface for security reasons because there is no
* added security on this control channel for now.</td>
* </tr>
* <tr>
* <td><code>{@value #PROP_CONTROL_ACTION}</code></td>
* <td>The actual action to execute:
* <ul>
* <b>start</b> -- Start the listener on the configured socket and expect
* commands there. This action is useful only when launching the Sling
* application since this action helps manage a running system.
* </ul>
* <ul>
* <b>stop</b> -- Connects to the listener running on the configured socket
* and send the command to terminate the Sling Application. If this command
* is used, it is expected the Sling Application will not start.
* </ul>
* <ul>
* <b>status</b> -- Connects to the listener running on the configured
* socket and query about its status. If this command is used, it is
* expected the Sling Application will not start.
* </ul>
* </td>
* </tr>
* </table>
* <p>
* After this method has executed the <code>j</code> and
* {@link #PROP_CONTROL_ACTION} properties have been removed from the
* configuration properties.
* <p>
* While the {@link #doStart()} and {@link #doStop()} methods may be called
* multiple times this method should only be called once after creating this
* class's instance.
*
* @return An code indicating whether the Java VM is expected to be
* terminated or not. If <code>-1</code> is returned, the VM should
* continue as intended, maybe starting the Sling Application. This
* code is returned if the start action (or no action at all) is
* supplied. Otherwise the VM should terminate with the returned
* code as its exit code. For the stop action, this will be zero.
* For the status action, this will be a LSB compliant code for
* daemon status check: 0 (application running), 1 (Program Dead),
* 3 (Program Not Running), 4 (Unknown Problem).
* @see <a
* href="http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html">Init Script Actions</a>
* for a definition of the LSB status codes
*/
protected int doControlAction() {
final ControlAction action = getControlAction();
if (action != null) {
final ControlListener sl = new ControlListener(this,
commandLineArgs.remove(PROP_CONTROL_SOCKET));
switch (action) {
case START:
if (!sl.listen()) {
// assume service already running
return 0;
}
break;
case STATUS:
return sl.statusServer();
case STOP:
return sl.shutdownServer();
case THREADS:
return sl.dumpThreads();
}
}
return -1;
}
/**
* Terminates the VM which was started by calling the
* {@link #main(String[])} method of this class using the
* <code>status</code> value as the application exit status code.
* <p>
* This method does not return.
*
* @param status The application status exit code.
*/
// default accessor to enable overwriting for unit tests
void terminateVM(final int status) {
System.exit(status);
}
private ControlAction getControlAction() {
Object action = this.commandLineArgs.remove(PROP_CONTROL_ACTION);
if (action != null) {
if (action instanceof ControlAction) {
return (ControlAction) action;
}
try {
return ControlAction.valueOf(action.toString().toUpperCase());
} catch (IllegalArgumentException iae) {
error("Illegal control action value: " + action, null);
}
}
return null;
}
/**
* Starts the application with the configuration supplied with the
* configuration properties when this instance has been created.
* <p>
* Calling this method multiple times before calling {@link #doStop()} will
* cause a message to be printed and <code>true</code> being returned.
*
* @return <code>true</code> if startup was successful or the application
* is considered to be started already. Otherwise an error message
* has been logged and <code>false</code> is returned.
*/
protected boolean doStart() {
// ensure up-to-date launcher jar
return doStart(getClass().getResource(
SharedConstants.DEFAULT_SLING_LAUNCHER_JAR));
}
protected boolean doStart(final URL launcherJar) {
// prevent duplicate start
if ( this.started) {
info("Apache Sling has already been started", null);
return true;
}
info("Starting Apache Sling in " + slingHome, null);
this.started = true;
Loader loaderTmp = null;
try {
final File launchpadHome = getLaunchpadHome(slingHome,
commandLineArgs);
loaderTmp = new Loader(launchpadHome) {
@Override
protected void info(String msg) {
Main.info(msg, null);
}
};
} catch (IllegalArgumentException iae) {
error(
"Cannot launch: Launchpad folder cannot be used: "
+ iae.getMessage(), null);
return false;
}
this.loader = loaderTmp;
if (launcherJar != null) {
try {
loader.installLauncherJar(launcherJar);
} catch (IOException ioe) {
error("Cannot launch: Cannot install " + launcherJar
+ " for use", ioe);
return false;
}
} else {
info("No Launcher JAR to install", null);
}
Object object = null;
try {
object = loader.loadLauncher(SharedConstants.DEFAULT_SLING_MAIN);
} catch (IllegalArgumentException iae) {
error("Cannot launch: Failed loading Sling class "
+ SharedConstants.DEFAULT_SLING_MAIN, iae);
return false;
}
if (object instanceof Launcher) {
// configure the launcher
Launcher sling = (Launcher) object;
sling.setNotifiable(new Notified());
sling.setCommandLine(commandLineArgs);
sling.setSlingHome(slingHome);
// launch it
info("Starting launcher ...", null);
if (sling.start()) {
info("Startup completed", null);
this.sling = sling;
addShutdownHook();
return true;
}
error("Cannot launch: Launcher.start() returned false", null);
} else {
error("Cannot launch: Class " + SharedConstants.DEFAULT_SLING_MAIN + " is not a Launcher class", null);
}
return false;
}
/**
* Maybe called by the application to cause the Sling Application to
* properly terminate by stopping the OSGi Framework.
* <p>
* After calling this method the Sling Application can be started again
* by calling the {@link #doStart()} method.
* <p>
* Calling this method multiple times without calling the {@link #doStart()}
* method in between has no effect after the Sling Application has been
* terminated.
*/
protected void doStop() {
removeShutdownHook();
// now really shutdown sling
if (this.sling != null) {
info("Stopping Apache Sling", null);
this.sling.stop();
this.sling = null;
}
// clean and VM caches
if (this.loader != null) {
this.loader.cleanupVM();
this.loader = null;
}
// further cleanup
this.started = false;
}
private void addShutdownHook() {
if (this.installShutdownHook && this.shutdownHook == null) {
this.shutdownHook = new Thread(new ShutdownHook(), "Apache Sling Terminator");
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
private void removeShutdownHook() {
// remove the shutdown hook, will fail if called from the
// shutdown hook itself. Otherwise this prevents shutdown
// from being called again
Thread shutdownHook = this.shutdownHook;
this.shutdownHook = null;
if (shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (Throwable t) {
// don't care for problems removing the hook
}
}
}
/**
* Define the sling.home parameter implementing the algorithme defined on
* the wiki page to find the setting according to this algorithm:
* <ol>
* <li>Configuration property <code>sling.home</code></li>
* <li>System property <code>sling.home</code></li>
* <li>Environment variable <code>SLING_HOME</code></li>
* <li>Default value <code>sling</code></li>
* </ol>
*
* @param commandLine The command line arguments
* @return The value to use for sling.home
*/
private static String getSlingHome(Map<String, String> commandLine) {
String source = null;
String slingHome = commandLine.get(SharedConstants.SLING_HOME);
if (slingHome != null) {
source = "command line";
} else {
slingHome = System.getProperty(SharedConstants.SLING_HOME);
if (slingHome != null) {
source = "system property sling.home";
} else {
slingHome = System.getenv(ENV_SLING_HOME);
if (slingHome != null) {
source = "environment variable SLING_HOME";
} else {
source = "default";
slingHome = SharedConstants.SLING_HOME_DEFAULT;
}
}
}
info("Setting sling.home=" + slingHome + " (" + source + ")", null);
return slingHome;
}
/**
* Return the absolute path to sling home
*/
public String getSlingHome() {
return this.slingHome;
}
/**
* Define the sling.launchpad parameter implementing the algorithm defined
* on the wiki page to find the setting according to this algorithm:
* <ol>
* <li>Configuration property <code>sling.launchpad</code>. This path is
* resolved against the <code>slingHome</code> folder if relative.</li>
* <li>Default to same as <code>sling.home</code></li>
* </ol>
*
* @param slingHome The absolute path to the Sling Home folder (aka the
* <code>sling.home</code>.
* @param commandLineArgs The configuration properties from where to get the
* <code>sling.launchpad</code> property.
* @return The absolute <code>File</code> indicating the launchpad folder.
*/
private static File getLaunchpadHome(final String slingHome,
final Map<String, String> commandLineArgs) {
final String launchpadHomeParam = commandLineArgs.get(SharedConstants.SLING_LAUNCHPAD);
if (launchpadHomeParam == null || launchpadHomeParam.length() == 0) {
commandLineArgs.put(SharedConstants.SLING_LAUNCHPAD, slingHome);
return new File(slingHome);
}
File launchpadHome = new File(launchpadHomeParam);
if (!launchpadHome.isAbsolute()) {
launchpadHome = new File(slingHome, launchpadHomeParam);
}
commandLineArgs.put(SharedConstants.SLING_LAUNCHPAD,
launchpadHome.getAbsolutePath());
return launchpadHome;
}
// ---------- logging
// emit an informational message to standard out
protected static void info(String message, Throwable t) {
log(System.out, "*INFO *", message, t);
}
// emit an error message to standard err
protected 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();
out.print(linePrefix);
out.println(message);
if (t != null) {
t.printStackTrace(new PrintStream(out) {
@Override
public void print(String x) {
super.print(linePrefix);
super.print(x);
}
});
}
}
/**
* Parses the command line arguments into a map of strings indexed by
* strings. This method supports single character option names only at the
* moment. Each pair of an option name and its value is stored into the
* map. If a single dash '-' character is encountered the rest of the command
* line are interpreted as option names and are stored in the map unmodified
* as entries with the same key and value.
* <table>
* <tr><th>Command Line</th><th>Mapping</th></tr>
* <tr><td>x</td><td>x -> x</td></tr>
* <tr><td>-y z</td><td>y -> z</td></tr>
* <tr><td>-yz</td><td>y -> z</td></tr>
* <tr><td>-y -z</td><td>y -> y, z -> z</td></tr>
* <tr><td>-y x - -z a</td><td>y -> x, -z -> -z, a -> a</td></tr>
* </table>
*
* @param args The command line to parse
*
* @return The map of command line options and their values
*/
// default accessor to enable unit tests without requiring reflection
static Map<String, String> parseCommandLine(String... args) {
Map<String, String> commandLine = new HashMap<String, String>();
boolean readUnparsed = false;
for (int argc = 0; args != null && argc < args.length; argc++) {
String arg = args[argc];
if (readUnparsed) {
commandLine.put(arg, arg);
} else if (arg.startsWith("-")) {
if (arg.length() == 1) {
readUnparsed = true;
} else {
String key = String.valueOf(arg.charAt(1));
if (arg.length() > 2) {
final String val;
final int indexOfEq = arg.indexOf('=');
if (indexOfEq != -1) {
//Handle case -Da=b
key = arg.substring(1, indexOfEq);
val = arg.substring(indexOfEq + 1);
} else {
val = arg.substring(2);
}
commandLine.put(key, val);
} else {
argc++;
if (argc < args.length
&& (args[argc].equals("-") || !args[argc].startsWith("-"))) {
String val = args[argc];
//Special handling for -D a=b
if(key.equals("D")){
final int indexOfEq = val.indexOf('=');
if (indexOfEq != -1) {
//Handle case -D a=b. Add key as Da
key = "D" + val.substring(0, indexOfEq);
val = val.substring(indexOfEq + 1);
}
}
commandLine.put(key, val);
} else {
commandLine.put(key, key);
argc--;
}
}
}
} else {
commandLine.put(arg, arg);
}
}
return commandLine;
}
/** prints a simple usage plus optional error message */
private static boolean doHelp(Map<String, String> args) {
if (args.remove("h") != null) {
System.out.println("usage: "
+ Main.class.getName()
+ " [ start | stop | status ] [ -j adr ] [ -l loglevel ] [ -f logfile ] [ -c slinghome ] [ -i launchpadhome ] [ -a address ] [ -p port ] { -Dn=v } [ -h ]");
System.out.println(" start listen for control connection (uses -j)");
System.out.println(" stop terminate running Apache Sling (uses -j)");
System.out.println(" status check whether Apache Sling is running (uses -j)");
System.out.println(" threads request a thread dump from Apache Sling (uses -j)");
System.out.println(" -j adr host and port to use for control connection in the format '[host:]port' (default 127.0.0.1:0)");
System.out.println(" -l loglevel the initial loglevel (0..4, FATAL, ERROR, WARN, INFO, DEBUG)");
System.out.println(" -f logfile the log file, \"-\" for stdout (default logs/error.log)");
System.out.println(" -c slinghome the sling context directory (default sling)");
System.out.println(" -i launchpadhome the launchpad directory (default slinghome)");
System.out.println(" -a address the interfact to bind to (use 0.0.0.0 for any)");
System.out.println(" -p port the port to listen to (default 8080)");
System.out.println(" -r path the root servlet context path for the http service (default is /)");
System.out.println(" -n don't install the shutdown hook");
System.out.println(" -Dn=v 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");
System.out.println(" -h prints this usage message");
return true;
}
return false;
}
private static boolean installShutdownHook(Map<String, String> props) {
String prop = props.remove(PROP_SHUTDOWN_HOOK);
if (prop == null) {
prop = System.getProperty(PROP_SHUTDOWN_HOOK);
}
return (prop == null) ? true : Boolean.valueOf(prop);
}
// default accessor to enable unit tests without requiring reflection
static Map<String, String> convertCommandLineArgs(
Map<String, String> rawArgs) {
final HashMap<String, String> props = new HashMap<String, String>();
boolean errorArg = false;
for (Entry<String, String> arg : rawArgs.entrySet()) {
if (arg.getKey().length() == 1 || arg.getKey().startsWith("D")) {
String value = arg.getValue();
switch (arg.getKey().charAt(0)) {
case 'j':
if (value == arg.getKey()) {
errorArg("-j", "Missing host:port value");
errorArg = true;
continue;
}
props.put(PROP_CONTROL_SOCKET, value);
break;
case 'l':
if (value == arg.getKey()) {
errorArg("-l", "Missing log level value");
errorArg = true;
continue;
}
props.put(PROP_LOG_LEVEL, value);
break;
case 'f':
if (value == arg.getKey()) {
errorArg("-f", "Missing log file value");
errorArg = true;
continue;
} else if ("-".equals(value)) {
value = "";
}
props.put(PROP_LOG_FILE, value);
break;
case 'c':
if (value == arg.getKey()) {
errorArg("-c", "Missing directory value");
errorArg = true;
continue;
}
props.put(SharedConstants.SLING_HOME, value);
break;
case 'i':
if (value == arg.getKey()) {
errorArg("-i", "Missing launchpad directory value");
errorArg = true;
continue;
}
props.put(SharedConstants.SLING_LAUNCHPAD, value);
break;
case 'a':
if (value == arg.getKey()) {
errorArg("-a", "Missing address value");
errorArg = true;
continue;
}
props.put(PROP_HOST, value);
break;
case 'p':
if (value == arg.getKey()) {
errorArg("-p", "Missing port value");
errorArg = true;
continue;
}
try {
// just to verify it is a number
Integer.parseInt(value);
props.put(PROP_PORT, value);
} catch (RuntimeException e) {
errorArg("-p", "Bad port: " + value);
errorArg = true;
}
break;
case 'r':
if (value == arg.getKey()) {
errorArg("-r", "Missing root path value");
errorArg = true;
continue;
}
props.put(PROP_CONTEXT_PATH, value);
break;
case 'n':
props.put(PROP_SHUTDOWN_HOOK, Boolean.FALSE.toString());
break;
case 'D':
if (value == arg.getKey()) {
errorArg("-D", "Missing property assignment");
errorArg = true;
continue;
}
if (arg.getKey().length() > 1) {
//Dfoo=bar arg.key=Dfoo and arg.value=bar
props.put(arg.getKey().substring(1), arg.getValue());
} else {
//D foo=bar arg.key=D and arg.value=foo=bar
String[] parts = value.split("=");
int valueIdx = (parts.length > 1) ? 1 : 0;
props.put(parts[0], parts[valueIdx]);
}
break;
default:
errorArg("-" + arg.getKey(), "Unrecognized option");
errorArg = true;
break;
}
} else if ("start".equals(arg.getKey())
|| "stop".equals(arg.getKey())
|| "status".equals(arg.getKey())
|| "threads".equals(arg.getKey())) {
props.put(PROP_CONTROL_ACTION, arg.getValue());
} else {
errorArg(arg.getKey(), "Unrecognized option");
errorArg = true;
}
}
return errorArg ? null : props;
}
private static void errorArg(String option, String message) {
error(String.format("%s: %s (use -h for more information)", option,
message), null);
}
/**
* Removes well-known stray threads and thread groups and removes framework
* thread context class loaders. Well-known stray threads and thread groups
* are:
* <ul>
* <li>The FileCleaningTracker$Reaper thread of the commons-io library</li>
* <li>The QuartzScheduler:ApacheSling thread group. See <a
* href="https://issues.apache.org/jira/browse/SLING-2535">SLING-2535
* QuartzScheduler:ApacheSling thread group remaining after stopping the
* scheduler bundle</a></li>
* </ul>
*/
@SuppressWarnings("deprecation")
static void cleanupThreads() {
// the current thread is the SlingNotifier thread part of
// the main thread group, whose parent is the system thread
// group. We only care for the main thread group here
ThreadGroup tg = Thread.currentThread().getThreadGroup();
Thread[] active = new Thread[tg.activeCount()];
tg.enumerate(active);
for (Thread thread : active) {
if (thread != null) {
if (thread.getName().equals("FileCleaningTracker$Reaper")) {
// I know, but this thread is stray ...
// Commons-IO bundle (or consumer of it) should
// actually stop it
thread.stop();
} else {
ClassLoader loader = thread.getContextClassLoader();
if (loader != null && loader.getClass().getName().startsWith("org.apache.felix.framework.")) {
thread.setContextClassLoader(null);
}
}
}
}
// SLING-2535 - Scheduler thread group
ThreadGroup[] groups = new ThreadGroup[tg.activeGroupCount()];
tg.enumerate(groups);
for (ThreadGroup group : groups) {
if (group != null && group.getName().equals("QuartzScheduler:ApacheSling")) {
group.destroy();
}
}
}
private class ShutdownHook implements Runnable {
@Override
public void run() {
Main.info("Java VM is shutting down", null);
Main.this.doStop();
}
}
private class Notified implements Notifiable {
/**
* The framework has been stopped by calling the
* <code>Bundle.stop()</code> on the system bundle. This actually
* terminates the Sling Standalone application.
*/
@Override
public void stopped() {
/**
* This method is called if the framework is stopped from within by
* calling stop on the system bundle or if the framework is stopped
* because the VM is going down and the shutdown hook has initiated
* the shutdown. In any case we ensure the reference to the framework
* is removed and remove the shutdown hook (but don't care if that
* fails).
*/
Main.info("Apache Sling has been stopped", null);
Main.this.sling = null;
Main.this.doStop();
}
/**
* The framework has been stopped with the intent to be restarted by
* calling either of the <code>Bundle.update</code> methods on the
* system bundle.
* <p>
* If an <code>InputStream</code> was provided, this has been copied to
* a temporary file, which will be used in place of the existing
* launcher jar file.
*
* @param updateFile The temporary file to replace the existing launcher
* jar file. If <code>null</code> the existing launcher jar
* will be used again.
*/
@Override
public void updated(File updateFile) {
Main.this.sling = null;
Main.this.doStop();
Main.cleanupThreads();
if (updateFile == null) {
Main.info("Restarting Framework and Apache Sling", null);
if (!Main.this.doStart(null)) {
Main.error("Failed to restart Sling; terminating", null);
Main.this.terminateVM(1);
}
} else {
Main.info(
"Restarting Framework with update from " + updateFile, null);
boolean started = false;
try {
started = Main.this.doStart(updateFile.toURI().toURL());
} catch (MalformedURLException mue) {
Main.error("Cannot get URL for file " + updateFile, mue);
} finally {
updateFile.delete();
}
if (!started) {
Main.error("Failed to restart Sling; terminating", null);
Main.this.terminateVM(1);
}
}
}
}
}