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 serviceLong 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.
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 exampleclass 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.
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.
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.
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.
Suggested practices:
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!
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:
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:
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:
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: }
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 client01: 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 wrappervoid 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); } }