Module Life Cycle
This section describes the dynamic aspects of bundles. It starts with the explanation of the life cycle states. After that, writing code to install/start/stop/uninstall bundles during runtime is described.
Bundle Life Cycle
Bundles follow a dedicated life cycle. The following graphic illustrates the bundles' 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 perform work |
|
The bundle is shutting down |
|
The bundle is fully uninstalled from the runtime and can not 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 if a bundle is installed |
|
|
fired if a bundle is successfully started |
|
|
fired if a bundle is successfully stopped |
|
|
fired if a bundle is uninstalled |
|
|
fired if a bundle steps in the resolve phase |
|
|
fired if all classes (js sources) are resolved |
|
|
fired during an 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 if 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 case where it is fired is if a bundle has expressed an optional dependency to another bundle and this bundle is not installed. |
|
|
fired if the full 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");
}
}
Or, as an alternative, it can obtain the bundle object from the BundleContext and use the 'getHeaders' method. This method does not look into the system bundle properties and returns only a value if 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, via 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];
}
}
The 'getBundles' method accepts a filter string, which 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 well 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 samples of possible filter expressions, which should help to understand the syntax.
Sample | Description |
---|---|
|
Property 'name' must have the value 'test' (case sensitive) |
|
Property 'name' must have a value (must exist). |
|
Property 'keywords' must have the value 'wizard'. 'keywords' is of type array. This means that one value of the array must match the condition. |
|
Property 'name' must start with 'test' |
|
Property 'startLevel' must be less or equal to 10 |
|
Property 'startLevel' must be greater or equal to 10 |
|
Property 'startLevel' must not be equal to 10, This is the Not Operator |
|
Property 'startLevel' must be lower 10 and greater 8. This is the And Operator |
|
Property 'startLevel' lower 10 or greater 10. This is the Or Operator |
|
Property 'name' must be equal to 'test' (case insensitive), so this matches test,TEST, TesT and so on. |
|
Property 'name' must start with 'test' but (case insensitive), so this matches testab, TESTab, TesTaB and so on. |
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 sample shows the easiest 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 fully 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");
});
}
}