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:

bundle lifecycle

State Description

INSTALLED

This is the initial state of a bundle after the installation into the runtime.

RESOLVED

This state is reached during the first start of the bundle. It means that the bundle layer is loaded successfully.

STARTING

The bundle is in the start process.

ACTIVE

The bundle is successfully started and ready to operate.

STOPPING

The bundle is shutting down.

UNINSTALLED

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:

BundleActivator in manifest.json
{
  ...
  "activator" : "AClassName"
  ...
}

A base template of a BundleActivator file looks like this:

BundleActivator Template
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.

BundleActivator returning a Deferred
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

BundleEvent.­types.­INSTALLED

ct/­framework/­BundleEvent/­INSTALLED

Fired when a bundle is installed.

BundleEvent.­types.­STARTED

ct/­framework/­BundleEvent/­STARTED

Fired when a bundle is started.

BundleEvent.­types.­STOPPED

ct/­framework/­BundleEvent/­STOPPED

Fired when a bundle is stopped.

BundleEvent.­types.­UNINSTALLED

ct/­framework/­BundleEvent/­UNINSTALLED

Fired when a bundle is uninstalled.

BundleEvent.­types.­RESOLVING

ct/­framework/­BundleEvent/­RESOLVING

Fired when a bundle enters the resolve phase.

BundleEvent.­types.­RESOLVED

ct/­framework/­BundleEvent/­RESOLVED

Fired when all classes (JavaScript sources) are resolved.

BundleEvent.­types.­UNRESOLVED

ct/­framework/­BundleEvent/­UNRESOLVED

Fired during the uninstallation of a bundle.

BundleEvent.­types.­STARTING

ct/­framework/­BundleEvent/­STARTING

Fired at the beginning of a bundle start.

BundleEvent.­types.­STOPPING

ct/­framework/­BundleEvent/­STOPPING

Fired at the beginning of a bundle stop.

FrameworkEvent.­types.­STARTED

ct/­framework/­FrameworkEvent/­STARTED

Fired after the framework and all preinstalled bundles are started.

FrameworkEvent.­types.­ERROR

ct/­framework/­FrameworkEvent/­ERROR

Fired when an error occurs within the framework.

FrameworkEvent.­types.­STARTLEVEL_CHANGED

ct/­framework/­FrameworkEvent/­STARTLEVEL_CHANGED

Fired during startup of the framework.

FrameworkEvent.­types.­WARNING

ct/­framework/­FrameworkEvent/­WARNING

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.

FrameworkEvent.­types.­STOPPED

ct/­framework/­FrameworkEvent/­STOPPED

Fired when the framework is stopped

The BundleContext can be used to listen to bundle events using the addBundleListener method.

Listening for bundle events
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.

Listening for framework events
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.

BundleContext.getProperty
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.

Bundle.getHeaders
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.

Bundle.getI18n
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.

BundleContext.getBundles
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

(name=test)

Property name must have the value test (case sensitive)

(name=*)

Property name must have a value (must exist).

(keywords=wizard)

Property keywords must have the value wizard. keywords is of type array. This means that one value of the array must match the condition.

(name=test*)

Property name must start with test

(startLevel <= 10)

Property startLevel must be less than or equal to 10.

(startLevel >= 10)

Property startLevel must be greater than or equal to 10.

(! (startLevel = 10))

Property startLevel must not be equal to 10. This is the Not Operator.

(& (startLevel < 10) (startLevel > 8))

Property startLevel must be less than 10 and greater than 8. This is the And Operator.

(| (startLevel < 10) (startLevel > 10))

Property startLevel must be less than 10 or greater than 10. This is the Or Operator.

(name~=test)

Property name must be equal to test (case insensitive), so it matches test, TEST, TesT for example.

(name~=test*)

Property name must start with test but (case insensitive), so it matches testab, TESTab, TesTaB for example.

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.

Bundle.start
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.

Bundle.stop
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 interface
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:

BundleContext.installBundle
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.

Bundle.uninstall
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");
      });
  }
}