Module Lifecycle
This section describes the dynamic aspects of bundles. It starts with the explanation of the lifecycle states. After that it is described how to write code to install, start, stop, and uninstall bundles.
Bundle Lifecycle
Bundles follow a dedicated lifecycle. The following diagram illustrates a bundle’s states and their transitions:
State | Description |
---|---|
|
This is the initial state of a bundle after the installation into the runtime. |
|
This state is reached during the first start of the bundle. It means that the bundle layer is loaded successfully. |
|
The bundle is in the start process. |
|
The bundle is successfully started and ready to operate. |
|
The bundle is shutting down. |
|
The bundle is fully uninstalled from the runtime and cannot be restarted again. |
Reacting on the Start/Stop of a bundle
One way to perform logic after a bundle is started, and until a bundle is stopped, is to a add a BundleActivator
to the bundle.
A BundleActivator
is informed by the JS OSGi runtime about the start and stop of the bundle in which it is declared.
The BundleActivator
can use a BundleContext
to interact with the JS OSGi runtime.
A BundleActivator
is added to a bundle by declaring the following in the manifest.json
file:
{
...
"activator" : "AClassName"
...
}
A base template of a BundleActivator
file looks like this:
export default class{
start(bundleContext){
// do something
}
stop:(bundleContext){
// clean up
}
}
Since version 2.0.1, a BundleActivator
can return a Promise in the start
method.
The JS OSGi runtime waits until the Promise is resolved before setting the state of the bundle to ACTIVE
.
import Promise from "apprt-core/Promise";
export default class{
start(){
return new Promise((resolve)=>{
setTimeout(function(){
resolve(true);
},100);
});
}
}
The main class for interacting with the JS OSGi runtime is the BundleContext
, which provides all methods to manipulate bundle states, register for framework events or use the service layer (see next section).
Listening to Bundle Events
Following bundle and framework events are fired by the OSGi runtime. These are also transported via the EventService, so the topics are listed, too.
Type | Topic | Description |
---|---|---|
|
|
Fired when a bundle is installed. |
|
|
Fired when a bundle is started. |
|
|
Fired when a bundle is stopped. |
|
|
Fired when a bundle is uninstalled. |
|
|
Fired when a bundle enters the resolve phase. |
|
|
Fired when all classes (JavaScript sources) are resolved. |
|
|
Fired during the uninstallation of a bundle. |
|
|
Fired at the beginning of a bundle start. |
|
|
Fired at the beginning of a bundle stop. |
|
|
Fired after the framework and all preinstalled bundles are started. |
|
|
Fired when an error occurs within the framework. |
|
|
Fired during startup of the framework. |
|
|
Fired if the framework considers a state as warning and not as error. One situatution where it is fired is if a bundle has defined an optional dependency to another bundle and this bundle is not installed. |
|
|
Fired when the framework is stopped |
The BundleContext can be used to listen to bundle events using the addBundleListener
method.
import BundleEvent from "apprt/BundleEvent";
export default class {
start(bundleContext){
this._bundleEventListenerHandle = bundleContext.addBundleListener(this._onBundleEvent,this);
}
stop(bundleContext){
bundleContext.removeBundleListener(this._bundleEventListenerHandle);
this._bundleEventListenerHandle = null;
}
_onBundleEvent(evt){
let types = BundleEvent.types;
if (types.STARTED === evt.getType()){
let bundle = evt.getBundle();
}
}
}
The same approach can be used to register to framework events, by applying the addFrameworkListener
method.
import FrameworkEvent from "apprt/launch/FrameworkEvent";
export default class {
start(bundleContext){
this._frameworkEventListenerHandle = bundleContext.addFrameworkListener(this._onFrameworkEvent,this);
}
stop(bundleContext){
bundleContext.removeBundleListener(this._frameworkEventListenerHandle);
this._frameworkEventListenerHandle = null;
}
_onFrameworkEvent(evt){
let types = FrameworkEvent.types;
if (types.STARTED === evt.getType()){
}
}
}
Access manifest properties
If a BundleActivator needs to access manifest properties, it can use the getProperty
method of the BundleContext.
export default class {
start(bundleContext){
//NOTE: getProperty first looks in the manifest of the bundle but
// if the key is not defined, it looks in the manifest of the system bundle, too!
let vendor = bundleContext.getProperty("vendor");
}
}
As an alternative it can obtain the bundle object from the BundleContext and use the getHeaders
.
This method ignores system bundle properties and only returns values specified in the bundle.
export default class{
start(bundleContext){
let bundle = bundleContext.getBundle();
let manifest = bundle.getHeaders();
let vendor = manifest.get("vendor");
// for some properties exist methods in the apprt/Bundle class
// the state as integer
let state = bundle.getState();
// the state as string
let stateName = bundle.getStateTitle();
// a unique number of the bundle (internal number identifier, not stable between framework starts)
let id = bundle.getBundleId();
// value of "Bundle-SymbolicName"
let symName = bundle.getSymbolicName();
// apprt/Version instance of the bundle
let version = bundle.getVersion();
// value of "Bundle-Namespace"
let ns = bundle.getNamespace();
}
}
Retrieve Internationalization (i18n) values
If a BundleActivator needs to access i18n values, it can use the bundle object.
export default class{
start(bundleContext){
let bundle = bundleContext.getBundle();
// gets a ct/I18N object
let i18n = bundle.getI18n();
// gets value of key 'i18nkey' from the nls/bundle.js files
let i18nvalue = i18n.get().i18nkey;
}
}
Search for bundles
The BundleContext also provides methods to search for bundles installed in the JS OSGi runtime using the getBundles
method.
export default class {
start(){
// returns array of all bundles
let allbundles = bundleContext.getBundles();
// search bundles with symbolic name "test"
let bundles = bundleContext.getBundles("(name=test)");
// expect one test bundle
let testBundle = bundles[0];
}
}
getBundles
accepts a filter string that can define complex filter expressions.
This expression is used to filter bundles based on their manifest properties.
Filter expressions are specified by the RFC-1960 and known as LDAP filter syntax. It is adopted by the OSGi specification and used in different places to express search queries for bundles or services.
Here are some examples of filter expressions to better understand the syntax.
Sample | Description |
---|---|
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
|
Property |
Manually trigger the start of a bundle
If a BundleActivator needs to start a bundle manually, it can use the start
method of a bundle object.
export default class {
start(){
// seach bundles with symbolic name "test"
let bundles = bundleContext.getBundles("(name=test)");
// expect one test bundle
let testBundle = bundles[0];
// start the test bundle
testBundle.start().then(()=>{
console.debug("test bundle started");
});
}
}
Manually trigger the stop of a bundle
If a BundleActivator needs to stop a bundle, it can use the stop
method of a bundle object.
A bundle should not stop itself.
export default class {
start(){
// search bundles with symbolic name "test"
let bundles = bundleContext.getBundles("(name=test)");
// expect one test bundle
let testBundle = bundles[0];
// stop the test bundle
testBundle.stop().then(function(){
console.debug("test bundle stopped");
});
}
}
Install a bundle
If a BundleActivator needs to install new bundles into the system, it can use the installBundle
method of the BundleContext.
This is a very powerful feature, because it allows lazy adding of features to a running application.
This method expects three parameters and can be used in different ways.
installBundle(
location, // URL pointing to the bundle directory
manifest, // manifest.json object (optional)
name // the name of the bundle (optional), overwrites the name provided in the manifest.json
);
The following code sample shows the most simple way to install a new bundle:
export default class {
start(){
// install a test bundle
let promise = bundleContext.installBundle("http://localhost:8080/js/externalBundles/test");
// The installBundle method, now fetches the manifest.json provided
// at http://localhost:8080/js/externalBundles/test/manifest.json
// interprets it and constructs a bundle instance
promise.then((testBundle) => {
// after successfull installation start the bundle
testBundle.start();
});
}
}
Uninstall a bundle
If a BundleActivator needs to uninstall a bundle, it can use the uninstall
method of a bundle object.
export default class {
start(){
// search bundles with symbolic name "test"
let bundles = bundleContext.getBundles("(name=test)");
// expect one test bundle
let testBundle = bundles[0];
// uninstall the test bundle
testBundle.uninstall().then(()=>{
console.debug("test bundle uninstalled");
});
}
}