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:

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 perform work

STOPPING

The bundle is shutting down

UNINSTALLED

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:

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 if a bundle is installed

BundleEvent.­types.­STARTED

ct/­framework/­BundleEvent/­STARTED

fired if a bundle is successfully started

BundleEvent.­types.­STOPPED

ct/­framework/­BundleEvent/­STOPPED

fired if a bundle is successfully stopped

BundleEvent.­types.­UNINSTALLED

ct/­framework/­BundleEvent/­UNINSTALLED

fired if a bundle is uninstalled

BundleEvent.­types.­RESOLVING

ct/­framework/­BundleEvent/­RESOLVING

fired if a bundle steps in the resolve phase

BundleEvent.­types.­RESOLVED

ct/­framework/­BundleEvent/­RESOLVED

fired if all classes (js sources) are resolved

BundleEvent.­types.­UNRESOLVED

ct/­framework/­BundleEvent/­UNRESOLVED

fired during an 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 if 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 case where it is fired is if a bundle has expressed an optional dependency to another bundle and this bundle is not installed.

FrameworkEvent.­types.­STOPPED

ct/­framework/­FrameworkEvent/­STOPPED

fired if the full 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");
   }
}

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.

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, via 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];
   }
}

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

(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 or equal to 10

(startLevel >= 10)

Property 'startLevel' must be greater 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 lower 10 and greater 8. This is the And Operator

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

Property 'startLevel' lower 10 or greater 10. This is the Or Operator

(name~=test)

Property 'name' must be equal to 'test' (case insensitive), so this matches test,TEST, TesT and so on.

(name~=test*)

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.

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 sample shows the easiest 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 fully 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");
      });
  }
}