# OSGi Service Tutorial

## 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:

- Export functionality from a bundle to other bundles
- Import functionality from other bundles
- Register listeners for events from other bundles
- Expose external devices, such as UPnP devices or even hardware, to other OSGi bundles. See the Device and UPnP APIs
- Expose java code running in OSGI to an external network, e.g. via the UPnP or SOAP protocols.
- Bundle configuration, using the Configuration Manager

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 retrieved 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 fulfill. 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:

- Always expect RuntimeException when calling a service
  A service may fail at any time, similar to RMI based code. The recommended exception an unusable service should throw is `IllegalStateException` This does not mean that a try/catch block should be made on each service call, rather that the code must be prepared to handle such exceptions on a higher level.
- Use the white-board model
  If possible, construct your code so users of your bundle functionality can participate by registering services, instead of getting services. It's much easier to register a service, than to get it. Thus, if you want to make life easier for clients, go for the white-board model.
- Use listeners
  ServiceListener and ServiceTracker provide various levels of automation for getting services.
- Use the declarative ServiceBinder
  The ServiceBinder by Cervantes/Hall provide a framework for cases where it's possible to declare service dependencies in XML.

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 exemplified 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$, $Author$