Dynamic Service Layer

The Dynamic Service Layer is the core communication infrastructure of the OSGi runtime. This layer provides an in-memory service registry on which service provider bundles can register/unregister service objects. Service consumer bundles can find/bind these services to use the provided functionality. In OSGi terms, a service is a plain object which provides methods and/or attributes which can be used by client bundles. Consider an illustrative example:

service registry

The core system bundle acts as a service provider bundle and registers some services, in this case a LogService and an EventService. The locateme bundle acts as a service consumer bundle and searches for an EventService and a LogService and uses them to log some messages and to broadcast a new geolocation. Both bundles are using the service registry provided by the OSGi runtime to establish communication between each other. It is important to understand that the Service Registry pattern has the following main advantages:

  • Minimizing lifetime dependencies (start-up order is not important)

  • No direct dependencies between bundles (locateme only assumes that there is an EventService, but does not know that it is provided by the system bundle)

  • Fully dynamic: Services can be registered and unregistered in the system at any time

  • Defining a clean way how a bundle can provide new functionality to the system (register a service)

Register a service

To register a service in the JS OSGi runtime, the method registerService of the BundleContext is used. A service is registered using well-defined interface names, service properties, and the service instance. The service properties are a very important feature, because they are used to provide metadata about a service instance. A service interface specification might require that a service implementation must provide well-defined service properties next to the interface name. This means that service properties are part of the service interface and therefore should be defined with care.

BundleContext.registerService
import MyService from "./MyService";

export default class {
    start(bundleContext){
      // create the service instance
      let serviceInstance = new MyService();

      // define service metadata properties, usable to filter service instances
      let serviceProperties = {
        // because of some speed tests
        // now this service reacts on function calls in ~100ms
        "averageSpeedInMilliSeconds": 100
      };

      // the interface names provided by the service
      // you can also consider this as the names under which the service can be found in the registry
      let interfaces = ["interfacenamespace.MyServiceInterface"];

     this._serviceregistration = bundleContext.registerService(interfaces, serviceInstance, serviceProperties);
    }
}

The method BundleContext.registerService returns an apprt/ServiceRegistration instance that can be used to unregister the service and to change the service properties during runtime. A ServiceRegistration instance should be considered as bundle-private. This means that you should never give access to such instances to enother bundle. A bundle is fully responsible for correct clean up of service registrations.

Unregister a service

Use the unregister method of a ServiceRegistration instance to unregister a service.

ServiceRegistration.unregister
export default class {
     ...
    stop(bundleContext){
      // get ServiceRegistration object (stored during service registration)
      let registration = this._serviceregistration;
      // clear the reference
      this._serviceregistration = null;
      // call unregister
      registration.unregister();
    }
}

Change the service properties

If a provider bundle needs to change the service properties, it can use the ServiceRegistration.setProperties method. It is not recommended to overuse this method, because the service properties are part of the interface definition. Therefore each consumer bundle must check if it is still correct to use a once bound service, which can result in long rebind/restart flows.

ServiceRegistration.setProperties
export default class {
    ...
    amethod: function(){
      // get ServiceRegistration object (stored during service registration)
      let registration = this._serviceregistration;

      // update properties
      registration.setProperties({
        // some statistic calculations say here is a slow connection
        "averageSpeedInMilliSeconds": 1000
      });
    }
    ...
}

Find a service

A consumer bundle can search the service registry to find specific services by using the method getServiceReferences of the BundleContext. The search result is highly dynamic because a service can be removed or added at any time.

BundleContext.getServiceReferences
export default class {
    ...
    start(bundleContext){
      let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");
      matchingServiceReferences.forEach((serviceReference)=>{
        console.debug("find service: " + serviceReference);
      });
    }
}

The getServiceReferences method also allows to use filter expressions to filter services by their properties. To get more details about filter expressions, read Search for bundles.

BundleContext.getServiceReferences using filter
export default class {
    start(bundleContext){
      // look for MyServiceInterface providers, which react faster than 200 milliseconds
      var matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface", "(averageSpeedInMilliSeconds<200)");
    }
}

The service references returned by the getServiceReferences method are ordered after following conditions:

  • Service ranking

  • Insertion order

Service-Ranking is a special service property that can be set during the registration of a service. The default value is 0. The service with the highest ranking number is on the head of the returned list. The service with the lowest ranking is on the end of the list. Services which have equal ranking numbers are sorted by their insertion time. Services inserted first are on the head of the list.

Access service properties

The ServiceReference objects returned by the getServiceReferences method can be used to obtain service metadata. With the method ServiceReference.getProperty any metadata value can be retrieved. The method ServiceReference.getProperties returns a plain object which can be used to read the properties. The method ServiceReference.getPropertyKeys returns an array with the property names. If you want to know the origin of a service you can use the method ServiceReference.getBundle.

BundleContext.getProperty
export default class {
    start(bundleContext){
      let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");
      matchingServiceReferences.forEach((serviceReference)=>{

        // access single property
        let speed = serviceReference.getProperty("averageSpeedInMilliSeconds");

        console.debug("find service: " + serviceReference + " it is very fast around: " + speed);

        // get property keys as array
        let keys = serviceReference.getPropertyKeys();

        // get all properties as object
        let props = serviceReference.getProperties();

        keys.forEach((key)=>{
          console.debug("key: " +key + " value: " + props[key]);
        });

        // get the bundle providing the service
        let serviceProviderBundle = serviceReference.getBundle();
        ...
      });
    }
}

Bind/Unbind a service

A ServiceReference can be used to get the real service instance by using the BundleContext.getService method. If a client no longer needs a service, it has to release the service instance using the BundleContext.ungetService method.

BundleContext.getService/ungetService
export default class {
    start(bundleContext){
      // find possible service references
      let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");

      // get first service reference
      let serviceReference = matchingServiceReferences[0];

      try{
        // get service instance
        let service = bundleContext.getService(serviceReference);
        // do something with the service
        ...
      }finally{
        // unget the service if it is no longer used
        bundleContext.ungetService(serviceReference);
      }
    }
}

The client is responsible for the correct resource management of the used services. This means that a client bundle must make sure that it releases a bound service instance. Because a service can be unregistered at any time, it can happen that a bound service is unregistered and not functional. Therefore, a client bundle has to register a ServiceListener to react correctly in such dynamic situations.

Listen to service events

The following ServiceEvents are fired by the OSGi Runtime and are also transported via the EventService, so the topics are listed, too.

Type Topic Description

ServiceEvent.­types.­REGISTERED

ct/­framework/­ServiceEvent/­REGISTERED

Fired when a new service is registered.

ServiceEvent.­types.­UNREGISTERING

ct/­framework/­ServiceEvent/­UNREGISTERING

Fired when a service should be unregistered (consumers should unget instances).

ServiceEvent.­types.­MODIFIED

ct/­framework/­ServiceEvent/­MODIFIED

Fired when the service properties of a service are changed.

The BundleContext can be used to listen to ServiceEvents using the addServiceListener method.

Listening for service events
import ServiceEvent from "apprt/ServiceEvent";
export default class {
    start(bundleContext){
      // register event listener function
      // without a filter it listens for any service event in the runtime
      this._serviceEventListenerHandle = bundleContext.addServiceListener(null,this._onServiceEvent,this);
    },
    stop : function(bundleContext){
      // unregister event listener
      bundleContext.removeServiceListener(this._serviceEventListenerHandle);
      this._serviceEventListenerHandle= null;
    },
    _onServiceEvent: function(evt){
      var types = ServiceEvent.types;
      var serviceReference = evt.getServiceReference();
      switch (evt.getType()){
        case types.REGISTERED:
        ...
        break;
        case types.UNREGISTERING:
        ...
        break;
        case types.MODIFIED:
        ...
        break;
      }
    }
}

The addServiceListener method accepts a filter expression, which allows to register service listeners for specific events. If you wish to listen only for events of services providing a special interface, you have to use the property 'objectClass' in the filter expression. To get more details about filter expressions, see Search for Bundles.

Listening for specific service events
export default class {
    start(bundleContext){
      // registers only for events of services with interface 'interfacenamespace.MyServiceInterface'
      this._serviceEventListenerHandle = bundleContext.addServiceListener("(objectClass=interfacenamespace.MyServiceInterface)",this._onServiceEvent,this);
    }
    _onServiceEvent(evt){
      ...
    }
}

Register a ServiceFactory

Next to the normal service registration, there exists a special service registration pattern, which allows the creation of services dependent on the using bundles. Instead of directly registering the service instance, a service factory is registered. A service factory must provide the methods getService and ungetService. If a new bundle is using the registered service, getService is called by the JS OSGi runtime on the service factory. The created service instance is cached for each bundle by the JS OSGi runtime and reused if a bundle is using the service again. If a bundle is no longer using a service, the JS OSGi runtime is calling ungetService on the service factory to release the service instance.

register ServiceFactory
import MyService from "./MyService";

export default class {
    start(bundleContext){
      ...
      // sample of registration of an inlined service factory
      this._serviceregistration = bundleContext.registerService(["my.Interface"], {

        getService(usingBundle,serviceregistration){
          // do something with the bundle information...
          // a MyService is created for each using bundle
          return new MyService();
        },

        ungetService(usingBundle,serviceregistration,instance){
          // do something with the bundle information...
          // clean up the instance resources
        }
      }
    }
}