Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Declarative Services vs Objects

2011, osgi

I'm coming to the conclusion there is an opportunity for improvement of the Declarative Services specification. Right now, DS needs to be able to instantiate a class and then mutate it as services come and go, instead of creating and disposing of classes on demand.

This poses somewhat of a problem if you want to publish objects in the OSGi service registry of types which you don't control. According to the Declarative Services specification, you need to have an object which has a default constructor. The object is then fully configured with a number of bind and unbind methods, which means the object goes through a series of states where it is not fully valid.

There's also the category of classes which aren't under your control (e.g. an existing API) or are acquired elsewhere (e.g. a third party library). These often can't be extended or otherwise mutated.

Example

Let's say we want to have a list of URLs stored as services in the registry. We'll be publishing URL objects as the service (and using java.net.URL as the interface type as well; it is a contrived example, after all). Now, the URL doesn't have a default constructor; every URL must have a url. So we can use it for registering a component which needs a service (e.g. BookmarkService) quite happily. But we can't register a URL with DS.

This presents us with a challenge. We might have a component which can register URL services, but given a 1..n BookmarkService and no URLs published, Declarative Services can't help us. In essence, there's no way to have a delayed URL service in DS.

What you can do is provide a URLFactoryService with DS. This can be activated on demand, and a registerURLs() method called which in turn registers a bunch of URL objects in the registry; and once that's done, DS can kick off the BookmarkService.

Unfortunately, there's no way of being able to tell DS that the URLFactoryService is something which is capable of generating URL objects, so DS never knows it needs to start the URLFactoryService to acquire the URLs.

Solution

The solutions are either to not use DS to populate the initial set of URLs (and require an earlyStart or startlevel hack to bring the initial bundle on-line) or to modify DS in a way which allows the implementation class to be a factory for the interface type but without being a subtype of it. The existing factory specification currently requires that the factory class is still an instance of the interface class; all that changes is the cardinality.

Here's what the solution might look like:


<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="URLdemo">
   <implementation class="URLService"/>
   <service>
      <provide interface="java.net.URL"/>
   </service>
</scr:component>

In this case, the URLFactoryService doesn't implement/extend java.net.URL. But this tells DS that when this component is activated, it gets a URL out of it, which is what we want DS to know.

We could use a new API, like ServiceOnDemand, which is a generified type that can provide us with a service when we need it:


public interface ServiceOnDemand<T> {
  public T getService();
}

If the URLFactoryService implemented the ServiceOnDemand<URL> interface, then a call to getService would return us with the URL object that could subsequently be registered on our behalf into the service registry. We can still use the bind and unbind calls as before; they just affect the ServiceOnDemand dataset. After each change of the properties (and providing the component was valid) the getService() could be treated as a factory for these object types, being called only when it is in a fully configured state.

For tear down, it becomes just as easy. Instead of removing the URLFactoryService from the registry, we can just remove its previously generated URL objects. The new DS would have to co-ordinate to know which services were registered and associated with which factory; but this shouldn't be significantly different to the way the factory works at the moment.

We could also use this in conjunction with the DS factory class. The key difference here is that the factory class must implement the interface provided, which isn't necessarily always possible.

Summary

DS, and in particular its lazy activation of components, is a great way of providing systems which evolve into being. Unfortunately, the current system is constrained to allowing only objects to be registered to which the user has full control. Although the above used URL as an example, the same could also be said for network-discovered services such as over LDAP or DNS service records, or even database connections (which typically can't be subclassed since they are binary only drivers). Finally, this could be the missing link between config admin and declarative services, which have never really played well together. Being able to use DS to fire up a factory, which consumes its configuration and then is able to generate multiple services would allow us all to avoid the pain and effort that is otherwise trying to control start ordering in order to provide services dynamically.