OSGi service binding II: muddy waters

by kengilmer

In the first post regarding binding to OSGI services on BUG, we covered the simplest (and most common) case: binding to a set of services and running an application when all services are available.  Now we will look at a less common but nonetheless important scenario: binding to multiple instances of the same service.  The code shown in this tutorial can also be modified to apply to many cases where programmatic control of service binding is required.

First, we need to make our Kitchen bundle emit two instances of the Tap service.  The bundle is renamed to Kitchen2 to keep it from clobbering Part I.  The main interest in the modifications to Kitchen are in the start() method:


public void start(BundleContext context) throws Exception {
BigTap bigTap = new BigTap();
Hashtable dict = new Hashtable();
dict.put(LITRES_PER_SECOND, bigTap.TAP_AMOUNT);
coldTapReg = context.registerService(Tap.class.getName(), bigTap, dict);

TinyTap tinyTap = new TinyTap();
dict = new Hashtable();
dict.put(LITRES_PER_SECOND, tinyTap.TAP_AMOUNT);
coldTapReg = context.registerService(Tap.class.getName(), tinyTap, dict);

drainReg = context.registerService(Drain.class.getName(), new CloggedUpDrain(), null);
}

Note that if you’re simply consuming services (rather than creating them), this code won’t need to be used.  Two instances of the Tap service (a big and a small one) are registered in the OSGi service registry.  You can see this service registration in the OSGi shell by typing ‘services’ and seeing the two instances.  You’ll also note the service properties.  These properties are how a consumer can choose which service implementation to consume in the case that multiples exist.  Or, when looking for a service with specific properties.  In our example, the property litresPerSecond is registered for each service.

Now let’s take a look at the service consumer.  First, we’ll start with the Activator:


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, ServiceFilterGenerator.generateServiceFilter(context, services), new HotAndColdSinkImpl(context));
}

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

If Part I of this tutorial is fresh in your mind you’ll see how little has changed in the Activator; essentially just a different static method call to ServiceTrackerHelper.  This is by design, as often applications grow in complexity and simple service binding logic needs to change.  

Now let’s look at the code that binds and consumes the services:


public class HotAndColdSinkImpl extends Thread implements ServiceTrackerCustomizer, Sink {

private Tap tinyTap;
private Tap bigTap;
private Drain drain;
private final BundleContext context;

public HotAndColdSinkImpl(BundleContext context) {
this.context = context;
}

@Override
public void run() {
tinyTap.turnOn(this);
tinyTap.turnOn(this);

try {
Thread.sleep(10000);

tinyTap.turnOff(this);
tinyTap.turnOff(this);
} catch (InterruptedException e) {
//Shutdown
}
}

public void shutdown() {
interrupt();
tinyTap.turnOff(this);
bigTap.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);
}

@Override
public Object addingService(ServiceReference reference) {
Object svc = context.getService(reference);

if (svc instanceof Tap) {
//We know we have a Tap service, now check the service properties to determine it's characteristics.
if (Integer.parseInt(reference.getProperty("litresPerSecond").toString()) < 5) {
tinyTap = (Tap) svc;
} else {
bigTap = (Tap) svc;
}
}

if (svc instanceof Drain) {
drain = (Drain) svc;
}

if (tinyTap != null && bigTap != null && drain != null) {
//We have all of our services, we can start!
this.start();
}

return svc;
}

@Override
public void modifiedService(ServiceReference reference, Object service) {
//We will ignore this case.
}

@Override
public void removedService(ServiceReference reference, Object service) {
shutdown();
}
}

First, note that the class now implements ServiceTrackerCustomizer instead of ManagedRunnable.  ServiceTrackerCustomizer is an interface defined by OSGi, whereas ManagedRunnable is in the BUG API.  ServiceTrackerCustomizer is very flexible and can handle the most complex service binding scenarios.  When all else fails in the OSGi service binding world, ServiceTrackerCustomizer has got your back.  

The second (and last) notable part of this class is in the addingService() method.  This callback is called when the OSGi service registry receives service registrations.  In our implementation, we extract the service properties of Tap services, and use those properties to determine how they should be bound.  This code could be changed to reject services based on properties, or to start/stop threads based on availabiltiy, etc..  It is in addingService() that you have control as to how your bundle interacts at the service level.

In running the Kitchen2 and HotAndColdSink bundles, you will see that the two services are bound, and that the two taps are turned on for a period of time, and that the water is drained away by the partially stopped up drain.   

 

Advertisements