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 and 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:
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 a 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 at any time in the system
-
Clear 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, of course, 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.
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 BundleContext.registerService method returns a apprt/ServiceRegistration instance, which can be used to unregister a service and to change the service properties during runtime. A ServiceRegistration instance should be considered as bundle private, which means you should never give such instances to other bundles. 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.
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 use this method often, because the service properties are part of the interface definition.
Therefore each consumer bundle must check if it is still correct to use a bound service, which can result in long rebind/restart flows.
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.
export default class {
...
start(bundleContext){
let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");
matchingServiceReferences.forEach((serviceReference)=>{
console.debug("find service: " + serviceReference);
});
}
}
The getServiceReferences method also provides the use of filter expressions to filter services by their properties. To get more details about filter expressions, see Search for bundles.
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, which 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 the same ranking number 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 wish to know the source bundle of a service you can use the method ServiceReference.getBundle.
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.
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 fully responsible for 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 on 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 |
---|---|---|
|
|
fired if a new service is registered |
|
|
fired if a service should be unregistered (consumers should unget instances) |
|
|
fired if the service properties of a service are changed |
The BundleContext can be used to listen to ServiceEvents using the addServiceListener method.
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.
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 is used to allow 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, the 'getService' method is called by the JS OSGi runtime at 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 not longer using a service, the JS OSGi runtime is calling the method "ungetService" of the service factory to release the service instance.
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
}
}
}
}