Single-Sourcing with declarative services
In my last blog post I introduced the idea of using OSGi services for single sourcing a RAP/RCP application. I think this approach is quite elegant, but it has one major drawback. When you use normal OSGi services in your application you will mix your application code with the OSGi Framework code everytime you reference or register a service. Not only does this look ugly, it’s also hard to test.
Luckily there is a lifesaver called Declarative Services. (For those of you who are familiar with declarative services, skip to the next paragraph.) The OSGi declarative services (ds) are specified in the OSGi compendium so they are standardized. It’s just a component framework on top of OSGi services. It gives you the ability to register and consume services in a declarative way using some xml files and simple accessors in your classes.
I’ll take the same example from in my last blog post and use it to look at ds for single sourcing. In that blog, we single sourced a themed button widget. We can use the same interfaces and service implementations, and the only differences will be how the services will be registered and referenced.
First, let’s take a look at the service registration. In our example we need to register the services in the rap and rcp bundles. It’s common practice to put all service declarations in a folder called OSGI-INF in the root of your project. Once you’ve created this folder you can create a “component definition” using the wizard. After this you need to fill in three things: first, the filename of your choice, second, the component name which needs to be unique in the framework and third, the component itself.
The component is the fully qualified classname of your implementation. In the case of service registration, it’s your service implementation. To add the service registration, press ‘Finish’ to open the component editor. We only want to register a service, so we jump to the service tab. In the bottom section you can add the service interface.
To understand what happens in the background let’s recap what we have declared. We defined a service interface, a service implementation, a component name and a filename. The component framework takes care of instantiating the service implementation and registering it as a service using the service interface. It registers the component using the component name. The filename will be automatically added to your bundle’s manifest. That’s all, the next time we start this bundle the service will be registered. Your component definition should look something like this:
Now we get to the service referencing. As you recall, we need to reference the service in our app bundle. Therefore, we need to create a component in this bundle too. In this example I decided to reference the service in the bundle’s activator and provide it with a static accessor. But you can reference it wherever you want. In this case the component is our Activator. The only difference to the service registration is in the service tab of the component editor. Instead of providing a service, we are now referencing one. You can do this by clicking ‘Add’ in the upper section of this tab.
After this you need to declare the Interface of the service you want to reference, in our case the ISingleSoucingService. The question is now, “how does this service finds its way to the Activator?” The answer is binding methods. In the reference you can specify a bind and an unbind method e.g. setService and unsetService. You need to add these methods to your activator with the service as a parameter. That’s all - we are referencing the service now. Your component should look something like this:
The last thing we need is a little bit of glue to bring these components together. The glue is in the org.eclipse.equinox.ds
bundle. This bundle contains the component framework implementation, and to make everything work together you need to add this bundle to your launch configuration. It’s not included in the RAP target components, but you can download it using the Indigo release site or the Equinox SDK.
I added a branch called “ds” to the repository on github from the last blog. This branch represents an example implementation using ds for single sourcing.
Have fun declaring services ;)