/* * 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); } } } } }