Follow knopflerfish on Twitter

Contents

  1. What is a service?
  2. What is a service factory?
  3. What can services be used for?
  4. How are services accessed??
  5. Releasing services
  6. Best practices for accessing services
  7. The white-board model

What is a service?

An OSGi service is a java object instance, registered into an OSGi framework with a set of properties. Any java object can be registered as a service, but typically it implements a well-known interface.

The OSGi R3 specification, chapter 4.10 is highly recommended reading. Also, the javadoc for BundleContext contains lot of information.

The client of a service is always an OSGi bundle, i.e. a piece of java code possible to start via the BundleActivator interface.

Each bundle may register zero or more services. Each bundle may also use zero or more services. There exists no limit on the number of services, more than the ones given by memory limits or java security permissions.

Both publishing/registering and usage of services can be restricted by using java security permissions.

Registering a very simple object as a service
Long i = new Long(20); Hashtable props = new Hashtable(); props.put("description", "This an long value"); bc.registerService(Long.class.getName(), i, props);

Note: a service can also be registered as several interfaces. In this case, the object must implement all of the interfaces.

What is a service factory?

An OSGi service factory is a special class ServiceFactory, which can create individual instances of service objects for different bundles.

Sometimes a service needs to be differently configured depending on which bundle uses the service. For example, the log service needs to be able to print the logging bundle's id, otherwise the log would be hard to read.

A service factory is registered in exactly the same way as a normal service, using registerService, the only difference is an indirection step before the actual service object is handed out.

The client using the service need not, and should not, care if a service is generated by a factory or by a plain object.

A simple service factory example
class LongFactory implements ServiceFactory { public Object getService(Bundle bundle, ServiceRegistration reg) { // each bundle gets a Long with it's own id return new Long(bundle.getBundleId()); } void ungetService(Bundle bundle, ServiceRegistration reg, Object service) { // nothing needed in this case } } ServiceFactory factory = new LongFactory(); bc.registerService(Long.class.getName(), factory, new Hashtable());

Note: The framework will cache generated service objects. Thus, at most one service can be generated per client bundle.

What can services be used for?

The service concept is a very general-purpose tool, but some examples are:

Generally, services is the preferred method bundles should use to communicate between each other.

How are services accessed?

Services are always accessed via ServiceReferences, which uniquely points to a service object.
To access a service, the following procedure must always be used (possibly wrapped by utility code):
  1. Get a ServiceReference
  2. Get the service object from the reference, using BundleContext.getService()
ServiceReference are typically retreived by:
  1. Explicit usage of BundleContext.getService()
    This returns either the highest ranked service of the given class, or null
  2. Explicit usage of BundleContext.getServices()
    This return the complete set of matching services, or null
  3. Callback from a ServiceListener
  4. Using the utility class ServiceTracker

All cases, except the first, allow the client to specify a set of properties that the service must fullfill. These properties are specified using LDAP filters. Zero or more service references can match a given filter.

A typical filter contains a class name and a set of properties. The example below matches a Http service having a "port" property equal to 80.

An LDAP filter string
(&(objectclass=org.osgi.service.http.HttpService)(port=80))

As soon as a ServiceReference is available, the service object can be accessed using a cast:

  HttpService http = (HttpService)bc.getService(sr);

After this, the service object can be accessed as any other java object. Actually, it is a fully normal java object, more exact, it is the same object as the bundle registering the service created.

Releasing services

As soon as the service isn't needed anymore, the client should release the service by calling the BundleContext.ungetService() method.

  bc.ungetService(sr);

All services are automatically released when the client bundles stops. No special service cleanup is needed in BundleActivator.stop().

Note that service should only be released when really unused. Some services may keep a state for each using bundle, and releasing the service will release this state, which may, or may not, be the intention. Each service documentation must be consulted. An example of this is the HttpService, which will remove all servlets and resources from itself when a client releases the HttpService.

Some special words are needed when using service listeners. A listener will get a ServiceEvent.UNREGISTERING event when a service is in the process of being unregistering from the framework.

While in this UNREGISTERING listener, the getService() call should not be expected to return a valid service object. In fact, the expected value is null, even if the spec is a bit vague on this. The OSGi reference implementation does however return null in this case.

Note: It's fully possible for a client to hold on to a service object via a variable, even after the exporting bundle has been stopped. The client shouldn't expect the service to work in this case and this also blocks any garbage collection. Thus, don't forget to null variables holding services when the service is released.

Best practices for accessing OSGi services

OSGi services must always be considered volatile, and may disappear or become unusable at any time.

Suggested practices:

Consider the bad-styled, but common, code:
Bad code example
01: ServiceReference sr = bc.getServiceReference(HttpService.class.getName()); 02: HttpService http = (HttpService)bc.getService(sr); 03: http.registerServlet(....)
Three things can fail, one for each line!
  1. The ServiceReference can be null if no HttpService is registered, resulting in NullPointerException on line 2.
  2. The http service object cannot be get, due to missing permissions, possible timing issues if the http unregisters between lines 1 and 2, causing NullPointerException on line 3.
  3. The http service may have become unusable, resulting in any RuntimeException subclass, most likely IllegalStateException on line 3.

Additionally, the code above does not handle the case where more than one service is registered and actions should be taken on each of them.

The NPE problems can be naively avoided by adding conditionals:

01: ServiceReference sr  =  bc.getServiceReference(HttpService.class.getName());
02: if(sr != null) {
03:   HttpService http = (HttpService)bc.getService(sr);
04:   if(http != null) {
05:     http.registerServlet(....)
06:   }
07: }

This approach quickly becomes very cumbersome, and also creates an undesirable start order problem, since the HttpService must be available when the code is run.

By using a service listener, the code can avoid the first ServiceReference null problem:

Using a service listener
01: ServiceListener sl = new ServiceListener() { 02: public void ServiceChanged(ServiceEvent ev) { 03: ServiceReference sr = ev.getServiceReference(); 04: switch(ev.getType()) { 05: case ServiceEvent.REGISTERED: 06: { 07: HttpService http = (HttpService)bc.getService(sr); 08: http.registerServlet(...); 09: } 10: break; 11: default: 12: break; 13: } 14: } 15: }; 16: 17: String filter = "(objectclass=" + HttpService.class.getName() + ")"; 18: try { 19: bc.addServiceListener(sl, filter); 20: } catch (InvalidSyntaxException e) { 21: e.printStackTrace(); 22: }

The possible RuntimeException when actually calling the service (line 8) is handled by the framework event delivery code, so if no special handling is needed in the client code, nothing needs to be done.

There's still one problem -- if all HttpServices already had been registered, the listener above would not be called until the HttpServices were restarted. A small trick solves this: Manually construct REGISTERED ServiceEvents and call the listener:

Construct ServiceEvents - contd. from above
18: try { 19: bc.addServiceListener(sl, filter); 20: ServiceReference[] srl = bc.getServiceReferences(null, filter); 21: for(int i = 0; srl != null && i < srl.length; i++) { 22: sl.serviceChanged(new ServiceEvent(ServiceEvent.REGISTRED, 23: srl[i])); 24: } 25: } catch (InvalidSyntaxException e) { 26: e.printStackTrace(); 27: }

Now the number of lines has grown from three to 25+. Which may be OK if this is a one-time operation. But if continuous use of a service is intended, it's easier to use a ServiceTracker:

ServiceTracker example
01: ServiceTracker logTracker = new ServiceTracker(bc, LogService.class.getName(), null); 02: logTracker.open(); 03: ((LogService)logTracker.getService()).doLog(...);

The tracker guarantees to hold all currently available services, but may naturally return null if no services are available. This is often OK, since the code need to be prepared for RuntimeException anyway.

The down-side of this approach is the continuous and annoying usage of casting to get the desired object. Wrapping this in a single utility method can ease usage a bit:

01: ServiceTracker logTracker;
02:
03: void init() {
04:   logTracker = new ServiceTracker(bc, LogService.class.getName(), null);
05: }
06:
07: LogService getLog() {
08:   return (LogService)logTracker.getService();
09: }
10: 
11: void test() {
12:   getLog().doLog(...);
13: }

The white-board model

"Don't call getService(), call registerService() instead!"

Consider the HttpService case. All clients must constantly monitor the framework in some way to add its servlet(s) to the web server. This requires at least the amount of code examplified above, and even this doesn't completely guard against timing problems.

Additionally, the Http service must provide special methods for adding and removing servlets, and maintain an internal list of added servlets. This adds complexity both to the API and to the internal server code.

This is a very common scenario -- some kind of callbacks/listeners are needed and the server needs to keep track of all available listeners.

The OSGi framework already provides exactly this functionality.

Thus, an alternative and better approach is to let the client register its servlets into the framework, and let the http server use this list instead.

Servlet client
01: class MyServlet extends HttpServlet { 02: ... 03: } 04: 05: HttpServlet servlet = new MyServlet(); 06: Hashtable props = new Hashtable(); 07: props.put("alias", "/servlets/foo"); // desired http alias 08: ServiceRegistration reg = 09: bc.registerService(HttpServlet.class.getName(), servlet, props);

The client needn't do anything more. If the servlet should be removed, it is simply unregistered from the framework.

10: reg.unregister();

The new, improved http server would monitor the framework for all services being HttpServlets, and use the "alias" property to set the alias.

Below is a minimalistic wrapper for the existing http service: (a complete class doing this can be found in the Http Console example code.

HttpService wrapper
void open() { httpTracker = new ServiceTracker(bc, HttpService.class.getName(), null); httpTracker.open(); ServiceListener sl = new ServiceListener() { public void serviceChanged(ServiceEvent ev) { ServiceReference sr = ev.getServiceReference(); switch(ev.getType()) { case ServiceEvent.REGISTERED: { registerServlet(sr); } break; case ServiceEvent.UNREGISTERING: { unregisterServlet(sr); } break; } } }; String filter = "(objectclass=" + HttpServlet.class.getName() + ")"; try { bc.addServiceListener(sl, filter); ServiceReference[] srl = bc.getServiceReferences(null, filter); for(int i = 0; srl != null && i < srl.length; i++) { sl.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, srl[i])); } } catch (InvalidSyntaxException e) { e.printStackTrace(); } } void registerServlet(ServiceReference sr) { HttpServlet servlet = (HttpServlet)bc.getService(sr); String alias = (String)sr.getProperty("alias"); Object[] httplist = httpTracker.getServices(); for(int i = 0; httplist != null && i < httplist.length; i++) { HttpService http = (HttpService)httplist[i]; try { Hashtable props = new Hashtable(); http.registerServlet(alias, servlet, props, null); } catch (Exception e) { e.printStackTrace(); } } } void unregisterServlet(ServiceReference sr) { String alias = (String)sr.getProperty("alias"); Object[] httplist = httpTracker.getServices(); for(int i = 0; httplist != null && i < httplist.length; i++) { HttpService http = (HttpService)httplist[i]; try { http.unregister(alias); } catch (Exception e) { e.printStackTrace(); } bc.ungetService(sr); } }

$Rev: 917 $, $Author: wistrand $