Programmatic service access

The App Runtime provides as a service registry on which service provider bundles can register and unregister service objects. Service consumer bundles can search and bind these services to use the provided functionality. A service object is an object that provides methods and attributes which can be used by client bundles. Consider an illustrative example:

service registry

The 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, searching for an EventService and a LogService. It will use these services to log some messages and to broadcast a new location. Both bundles are using the service registry provided by the App Runtime to establish communication between each other.

Using the service registry 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 App 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 another 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. Do not 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 BundleContext.getServiceReferences(). 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. For more information about filter expressions, see Searching 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 getServiceReferences() 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 BundleContext.getService(). If a client no longer needs a service, it has to release the service instance using BundleContext.ungetService().

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 service events are fired by the App Runtime and are also transported via the EventService, so the topics are listed, too.

The BundleContext can be used to listen to service events 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. To listen only for events of services providing a special interface, use the property objectClass in the filter expression. For more details about filter expressions, see Searching 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 service factory

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 App Runtime on the service factory. The created service instance is cached for each bundle by the App Runtime and reused if a bundle is using the service again. If a bundle is no longer using a service, the App Runtime is calling ungetService() on the service factory to release the service instance.

register service factory
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
        }
      }
    }
}