Introduction to the Apache Felix Dependency Manager

The Apache Felix Dependency Manager is a great tool that can mean a lot for ease of development of OSGi applications. Unfortunately, its documentation is not complete and probably generally a bit to concise for the OSGi beginner. This is really a shame, as it might keep people from using it, whilst they might benifit a lot from it. So i decided to write a series of articles about it, in an attempt to make it more accessible for the not-yet-so-experienced OSGi developer. Of course, at the moment of writing, this is still only a plan 😉 – but you can help me to stick to my plan by sending supporting comments and feedback.

In this first article, we’ll start with a basic example. It might not be the most simple example, but it will show the “business case” for the DependendyManager. As the name suggests, it is all about dependencies. In a dynamic service architecture like OSGi, one should take into account that services can come and go dynamically. Of course, this holds for any Service Oriented Architecture, but it is often ignored. Handling such dynamics can be quite cumbersome, and this is exactly where the DependendyManager helps out.

An example

For the example, we’ll create a service that simulates a hardware device and second service that depends on it, in order to deliver some useful data. Don’t conclude that OSGi is only useful for controlling hardware, it’s just that it is a sample that is easy to understand.

For the hardware simulating service, we’ll take the example of a temperature sensor. It provides a numerical value that represents the temperature of the sensor’s environment. To obtain the temparature in Celcius, the value has to be scaled according to a lineair function; this will be the responsiblity of a second service.

The dynamic nature of this example lies in the fact that the hardware might not always be present. The sensor might be unplugged, maybe the hardware device needs external power supply that is (temporarily) switched off, or maybe the hardware is just broken. The point is: you cannot predict when the hardware is there, and if it’s there, you cannot rely on the fact that if will be there forever. So if you write a service that provides the temperature in Celcius, you must handle the case that the hardware is gone, or not “delivering”, and the best way to express this fact is to retract (unregister) the service. (If you’re not sure that this is the best way: what else could you do? Return a default or bogus value? That would be fooling the clients. Throw an exception? At least that would send the message that something is wrong, but what (usefull thing) can the client do with this exception? By withdrawing the service, you send (in time!) a very clear message (“i am not available anymore”) and the client can look for another service providing a similar service.)

Let’s switch to code now. If you are a familiar with OSGi, the implementation of the hardware simulating service will not contain any surprises. The essence is outlined below and you can find the complete source code of the sensor bundle bitbucket.

It publishes a service called SensorService, which contains just one method: getMeasurement(), that simulates retrieving a sensor value from the hardware. You can find the complete code on bitbucket, as well as a pre-build bundle (directions for how to deploy the bundle in the Felix OSGi container at the end of the article).

Dependency injection

The second bundle will provide the temperature-in-degrees-celcius service. Its service interface contains one method too:

The interesing part is of course how this second bundle manages its dependencies, i.e. keep track of the presence/absence of the SensorService that is needed to compute the actual temperature in degrees Celcius. This is all done in the Activator’s start() method. We start with creating a DependencyManager component, the building block the DependencyManager can manage dependencies for:

You’ll notice two things in the code fragment above: we define a method called init instead of start, and the activator class is derived from DependencyActivatorBase. Actually, that class is the one implementing the Activator.start() and it calls the init() in the derived class (the well-known template pattern). The base class will create a DependencyManager object for us, and hands it over to the init method.

Next, we’ll declare the dependency on the SensorService:

By setting the required flag to true, we express that we can’t do without and instruct the DependencyManager not to start our component, when that service is not yet there. And of course, to stop our component when the service goes away.

Now, let’s add a real implementation to the component and add the component to the DependencyManager to get things started:

This is all we need, to have our component’s lifecycle tied to the availability of the SensorService. Once it appears the DependencyManager will instantiate an object of the class TemperatureServiceImpl and try to inject the required service into it. We say “try”, because the implementation must have a member variable to inject it in of course, e.g.:

Notice that the member must be declared as volatile (we’ll elaborate on that in a later article).

Temperature service

We’re not completely finished yet, as we need more behaviour than just have our dependency injected: we want our service to be registered too. This turns out to be a really simple and minimal addition we have to make to the activator: just add

Note that this works because we’ve set the implementation class before, and that our implementation class implements the TemperatureService interface. So when the required dependency becomes available, the DependencyManager will start our component and register the service it implements. Similarly, when the required dependency disappears, our service will immediately be unregistered by the DependencyManager.

You might wonder what ‘start the component’ exactly means. Just add a start and stop method to the TemperatureServiceImpl class that prints something to System.out, e.g.

and you’ll see what’s going on. If you dislike the names of the start and stop method, or if the names clash with existing methods, you can name them differently – i’ll explain the details later. Note that the DependencyManager calls these methods by reflection, thereby enabling the flexibility to name them differently.

One more thing: most people that use the DependencyManager will write it a bit differently:

Don’t be offended by this fragment. It’s exactly the same as the more verbose lines explained above; it’s just smart use of the fact that most Dependencymanager methods return something usefull instead of void (the builder pattern).

Sample deployment in Felix

To run the samples in an OSGi container, follow the steps below. Of course, the samples will run in any OSGi compliant framework, but we’ll use Apache Felix for the demo.

  • download Felix from http://felix.apache.org/downloads.cgi#framework
  • start it with java -jar bin/felix.jar (in the felix-framework home dir)
  • download the DependencyManager from http://felix.apache.org/downloads.cgi#subprojects (you’ll only need the “Dependency Manager” jar)
  • install it by issueing the following command on to gogo shell ("g! "): install file:/path/to/org.apache.felix.dependencymanager-3.1.0.jar
  • download the OSGi compendium jar (containing all standard OSGi service interfaces) from http://mvnrepository.com/artifact/org.osgi/org.osgi.core/4.3.0
  • install that too
  • download the sample bundles from bitbucket
  • install each of the sample bundles by issueing the following command on to gogo shell ("g! "): install file:/path/to/bundle.jar
  • type lb at the gogo shell to see all installed bundles:
  • let Felix resolve (wire) all bundles by typing resolve 5 6 7 8 9 (assuming your bundles have the same bundle id as displayed above; if not, change command accordingly)
  • type inspect c service 7 8 9 and you’ll see that the bundles haven’t published any service yet (of course, as the bundles aren’t even started)
  • start the temperature bundle (8) and inspect again: still no services, which is correct, as the temperature depends on the sensor
  • now start the sensor bundle (7) and once you inspect, you’ll see that now both bundles have their service published
  • play with starting and stopping the bundles in different orders and see how the dependency manager will take care of publishing / retracting the temperature service
  • you can start the display bundle also to have a tiny user-interface for calling the temperature service

Conclusion

By now, you have seen the basic use case for the DependencyManager: by declaring that our TemperatureService depends on the SensorService we get several things for free:

  1. the DependencyManager injects the SensorService into our implementation (TemperatureServiceImpl),
  2. the DependencyManager publishes our service (the TemperatureService) when the SensorService is available, and unregisters when it becomes unavailable,
  3. the DependencyManager notifies our implementation that its service is published / unregistered, by calling start versus stop,
  4. our implementation (the TemperatureServiceImpl) does not contain any OSGi related code, so it’s framework agnostic (which is usually consider a good thing)

And all of this only for the cost of a few lines of (declarative style) code in our Activator. Meet the power of the Apache Felix DependencyManager!

That’s it for today. Please respond if anything is unclear, and I’ll try to clarify. In the next article, we’ll talk about optional dependencies and callbacks, stay tuned!

Tweet about this on TwitterShare on LinkedIn

Reacties

Het e-mailadres wordt niet gepubliceerd.

*