Updating the Sling Launcher
Status: IMPLEMENTED
Created: 10. January 2009
Author: fmeschbe
Updated: 13. January 2009, fmeschbe, Prototype implementation
1 Problem Scope
A Sling instance is launched by the Sling launcher, which is a single JAR (standalone Java Application) or WAR (Web Application) file. This file cannot currently be updated without physical access to the system, stopping the system, replacing the file and restarting the system.
It would be a good thing, if we could use existing administration API such as the Felix Web Console or the Felix (Remote) Shell to update this launcher. This concept is about how we could slightly restructure the launcher to enable just this functionality.
2 Idea
We have a single JAR file, which contains the OSGi framework (Apache Felix), the OSGi core libraries and the actual launcher for the OSGi framework. This single JAR file the launcher JAR is contained as a JAR file in either the standalone Java Application JAR or the Web Application WAR file.
The Standalone Java Application JAR File contains a single main class, which reads the command line and instantiates the actual launcher in the launcher JAR and waits for its termination.
The Web Application WAR File contains a single servlet, which instantiates the actual launcher servlet in the launcher JAR and reacts to its termination.
Both the main class and the servlet are very tiny and have the following tasks:
- Read the initial configuration for the launcher. This initial configuration becomes the overwrite properties for the sling properties used as the framework properties. The initial configuraiton is read as follows:
- The main class uses the command line arguments and the system properties for the initial configuration
- The servlet uses the servlet and servlet context init-params for the initial configuration
- After getting the initial configuration, the main class will just have the framework started. The servlet will create and initialize a servlet provided by the launcher JAR, which also sets up the OSGi HttpService bridge.
- After starting the framework through the launcher, the main class does nothing more. The servlet will forward the service requests to the launched servlet.
- To initiate an external shutdown, the main class or servlet terminate the launcher:
- The main class just calls the stop method and waits for its termination
- The servlet calls
destroy()
on the launched internal servlet - During the lifetime of Sling, the framework may be stopped from within by calling the
stop
orupdate
methods. To inform the main class or servlet of this situation, the launcher takes toRunnable
instances: One is called by the launcher when the framework has been stopped. The other is called if the framework has been stopped, but should be restarted again. TheseRunnable
instances are only called when the framework has completely been shut down. - If instructed by having the
Runnable
being called, which is notified of a required restart, the main class or servlet will start from scratch at the first step.
3 Launcher JAR update
Since the launcher JAR is a single JAR file, it may be updated. But it must also be possible to make sure the new code is actually being used after restart. To implement this requirement the main class or servlet follow this algorithm:
- Place or replace the launcher JAR file in the filesystem inside ${sling.home}
- Create an
URLClassLoader
with this JAR file and the class loader of the main class or servlet as the parent class loader - The main class uses reflection to instantiate and start the launcher. The main servlet uses reflection only to instantiate the launcher servlet. After that the normal servlet API is used for further interaction.
After the launcher terminates either by the stop
or update
method being called on the system bundle or by being requested from the main class or servlet, the class loader is just dropped and thus given to the garbage collector. It may be, that there will have to be some cleanup for the garbage collector to be operational. Notable areas are the Java VM JavaBeans cache and any caches of the Java Logging API.
4 Launcher support for stop and update
The launcher JAR must have two basic features:
- Know when the framework is stopped by the
stop
orupdate
method being called on the system bundle - Take the appropriate measure after the framework has been stopped
To implement these two features, the launcher JAR overwrites the stop
and update
methods of the Felix
class, which implements the system bundle. The update(InputStream)
method is overwritten to take the new launcher JAR file and place it such, that it can be used to restart. All method overwrites, start a separate thread which waits for the framework to terminate. When the framework has been terminated, the main class or servlet is notified.
The main class or servlet provide the launcher with a Notifiable
instances. The Notifiable.stopped()
is called if the framework has been stopped. The Notifiable.updated(File)
is called if the framework has been stopped for it to be restarted, where the file argument may be the new launcher JAR to use.
The Notifiable.stopped
method enables the main class or servlet to react to the situation that the framework has gone and take appropriate actions. The Notifiable.updated(File)
method enables the main class or servlet to drop the framework and classloader and restart the framework in a new class loader with a potentially new launcher JAR.
5 Prototype
I have prepared a prototype of this concept in my white board at http://svn.apache.org/repos/asf/incubator/sling/whiteboard/fmeschbe/launchpad.
The launchpad/base module creates two artifacts: The primary artifact is included with the final application. The secondary artifact is the actual launcher JAR file.
6.1 Primary Artifact
The primary artifact contains three elements:
Notifiable
interface with two methodsstopped
– called when the framework has been stopped – andupdated
– called when the framework has been updated and needs to be restarted. This interface is to be implemented by the main class or servlet.Loader
creates theURLClassLoader
with the launcher JAR and instantiates the launcher class. This class is implemented as a utility class with a two methodsloadLauncher
andcleanupVM
:
/** * Creates an URLClassLoader from a _launcher JAR_ file in the given * slingHome directory and loads and returns the launcher class * identified by the launcherClassName. * @param launcherClassName The fully qualified name of a class * implementing the Launcher interface. This class must have * a public constructor taking no arguments. * @param slingHome The value to be used as ${slingHome}. This may * be null in which case the sling folder in the current working * directory is assumed. If this name is empty, the current working * directory is assumed to be used as ${slingHome}. * @return the Launcher instance loaded from the newly created classloader * @throws NullPointerException if launcherClassName is null * @throws IllegalArgumentException if the launcherClassName cannot be * instantiated. The cause of the failure is contained as the cause * of the exception. */ public static Launcher loadLauncher(String launcherClassName, String slingHome); /** * Tries to remove as many traces of class loaded by the framework from * the Java VM as possible. Most notably the following traces are removed: * <ul> * <li>JavaBeans property caches * <li>Java Logging caches * </ul> * <p> * This method must be called when the notifier is called. */ public static void cleanupVM();
Launcher
interface which is implemented by classes in the secondary artifact. This interface has a method to support the standaline Java Application case (setCommandLine
). To support the Web Application, the launcher class is expected to implement thejavax.servlet.Servlet
interface. TheLauncher
interface is defined as follows:
// set sling home for the framework public void setSlingHome(String slingHome); // set the Notifiable to be informed on stop/update public void setNotifiable(Notifiable notifiable); // sets command line arguments, mainly used by the main class public void setCommandLine(String[] args); // starts the framework public void start(); // stops the framework // this method only returns when the framework has actually been stopped. // this method may be used by the main class or servlet to initiate a // shutdown of the framework public void stop();
6.2 Secondary Artifact
The secondary artifact is just what is the launchpad/base artifact today. The extension is just the support for the new API defined in the primary artifact. This secondary artifact is included as the launcher JAR in the final application.
6.3 Building the prototype
To build an test drive the prototype do the following:
1. Checkout the prototype:
$ svn checkout http://svn.apache.org/repos/asf/incubator/sling/whiteboard/fmeschbe/launchpad prototype
2. Build and locally install the base artifact
$ cd prototype/base $ mvn clean install
This install three artifacts: the primary artifact later used as the launcher JAR and two secondary artifacts with classifier shared
and sources
. The shared
artifact is used by the app and webapp modules to get the Loader
class and the Notifiable
and Launcher
interfaces.
3. Build the standalone application
$ cd ../app $ mvn -PwithBase,withBundles,withShell clean install
The module has three profiles, which are not enabled by default:
- withBase – Include the launcher JAR with the build
- withBundles – Include the Sling Bundles with the build
- withShell – Add the Apache Felix Shell and Shell Remote bundles
You may omit the withShell profile, but generally want to include the withBase and withBundles artifacts. These profiles will also later be enabled by default.
The standalone application can of course directly be used by launching it with
$ java -jar target/org.apache.sling.launchpad.app-5-incubator-SNAPSHOT.jar
4. Build the Web Application
The web application depends on the standalone application to share the list of bundles included. The web application is easily built by
$ cd ../webapp $ mvn clean install
This creates the web application.
Note: The web application also contains support for SLING-711. This results in modified behaviour for the definition for the defualt sling.home value. Previously the system property sling.home was considered if no sling.home servlet init-param or context init-param was available with a final fallback to "sling". Now the system property is ignored altogether and the default value is derived from the servlet context path as sling/<path> where <path> is the servlet context path with all slashes replaced by underscores and the root context being a single underscore. For example: If Sling is running in the context /sling the defualt sling.home is sling/_sling.