OSGi service binding without throwing in the kitchen sink.

by kengilmer

 

In the BUG 2.0 software release we’ve added an API to allow for easy service binding with minimal code.  The API is in com.buglabs.application.ServiceTrackerHelper and the primary actor is the ManagedRunnable.  To illustrate the usage of the API consider this example:

We will create two bundles, one as an API bundle and another as the implementation bundle.  For brevity, the API bundle will also create some services so that we can bind them in our implementation bundle.  I will start with a Kitchen bundle that will define 3 services: Tap, Sink, and Drain.  These services are exported in the bundle manifest.


public interface Tap {
public void turnOn(Sink sink);
public void turnOff(Sink sink);
public boolean isHot();
}


public interface Sink {
public void waterIn(int amount, int duration);
}


public interface Drain {
public void out(int amount, int duration);
}

Next I will define an Activator that will create instances of Tap and Drain.  Again, in a “pure” example these would be handled by separate bundles, as it’s best to keep the API isolated from the implementations.


public class Activator implements BundleActivator {

private ServiceRegistration tapReg;
private ServiceRegistration drainReg;

public void start(BundleContext context) throws Exception {
tapReg = context.registerService(Tap.class.getName(), new ColdTap(), null);
drainReg = context.registerService(Drain.class.getName(), new CloggedUpDrain(), null);
}

public void stop(BundleContext context) throws Exception {
drainReg.unregister();
tapReg.unregister();
}
}

The implementations of Tap and Drain are not important, but all the source can be accessed at http://www.buglabs.net/applications/Kitchen and http://www.buglabs.net/applications/Sink.

Now we will create our dependent bundle.  Here is where the service binding come into play.  In the past we relied on the AbstractServiceTracker API to get to our depended services.  While this is a fine approach, it tends to add a lot of code to your application.  First we start with an empty activator, define a String array that will define the services we depend on.  Then, we simply call ServiceTrackerHelper.openServiceTracker() with our services and an instance of ManagedRunnable, which is just like java.lang.Runnable except that a map is passed into run that contains all of our service dependencies.


public class Activator implements BundleActivator {

private final static String [] services = {
Tap.class.getName(),
Drain.class.getName()
};
private ServiceTracker st;

public void start(BundleContext context) throws Exception {
st = ServiceTrackerHelper.openServiceTracker(context, services, new SinkImpl()); }

public void stop(BundleContext context) throws Exception {
st.close();
}
}

If you’ve ever used the AbstractServiceTracker you will probably notice how much less code this is.  From here, the ManagedRunnable is essentially our application.   In my example I turn the faucet on for a second then turn it back off:


public class SinkImpl implements ManagedRunnable, Sink {

private Tap tap;
private Drain drain;

@Override
public void run(Map services) {
tap = (Tap) services.get(Tap.class.getName());
drain = (Drain) services.get(Drain.class.getName());

tap.turnOn(this);
try {
Thread.sleep(1000);

tap.turnOff(this);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void shutdown() {
tap.turnOff(this);
}

@Override
public void waterIn(int amount, int duration) {
if (amount > 0) {
System.out.println("Water is coming from the tap.");
} else {
System.out.println("Water has stopped.");
}
drain.out(amount, duration);
}
}

There are only two lines of code in our implementation class that deal with service binding, which are the lines that extract the services from the map in the run() method.  The rest of the code is application specific. 

This example shows an alternative to the AbstractServiceTracker for service binding in BUG applications.  In this example the dependencies are all-or-nothing.  The ManagedRunnable thread does not get created until/when the specified services are available, and is shutdown as soon as one service becomes unavailable.  The API has some other features and benefits as well, which I hope to cover soon!

Advertisements