The situation
Sometimes you cannot use the beauty of DS (ie. in Eclipse Workbench classes, that are instantiated by the Workbench) and have to revert back to the next best thing in the OSGi world when working with services. The
ServiceTracker.
If you want to use a certain service in your command handlers
execute method you would have to create a service tracker, query it for the service, and call the services method like this:
// Somewhere in your code
private static final ServiceTracker tracker = new ServiceTracker(
FramworkUtil.getBundle(MyClass.class,
EventAdmin.class.getName(),
null) {
{ open(); }
};
Object execute(ExecutionEvent event) {
EventAdmin eventAdmin = (EventAdmin)tracker.getService();
if (eventAdmin != null) {
eventAdmin.postEvent(new Event("EVENT/TOPIC", (Map)null));
}
return null;
}
Or lets say you have a service that returns a collection of items:
Collection listAllCars() {
CarService carService = (CarService)carServiceTracker.getService();
if (carService != null) {
return carService.findAll();
}
return Collections.emptyList();
}
As you can see, there is always boyer-plate code involved (and I am already using a shortcut to open the tracker) including null checking and we have not even looked on fall-back code, that should be executed if the service is not available. Let's say we want to log something with the OSGi
LogService but if its not available we will just print to the standard output stream.
public static void log(int level, Throwable t, String message, Object... args) {
final String text = String.format(message, args);
LogService logService = (LogService)tracker.getService();
if (logService != null) {
logService.log(level, text, t);
} else {
System.out.println(text);
}
}
Again we would have to create the the service tracker and check if the service is available. What, if we could do that all in one call?
Introducing Trackers
We could have a small helper class "
Trackers" that creates service trackers for use automatically and executes a piece of code depending on the availability of the requested service. From the JDK we all know the Runnable interface. We will just take the idea a step further and give its run method a parameter, much like Eclipse's
ParameterizedRunnable. But instead of doing it the Java 1.4 way we use generics like this:
public interface ServiceRunnable<T> {
void run(T service);
}
The code in the
run() method is only executed if there is a Service that publishes itself with the type "
T". Our helpers class static method would look like this:
public final class Trackers {
public static <T> void run(Class<T> serviceClass, ServiceRunnable<T> runnable) {
T service = getService(serviceClass);
if (service != null) {
runnable.run(service);
}
}
}
And we would call it like this:
Trackers.run(EventAdmin.class, new ServiceRunnable<EventAdmin>() {
void run(EventAdmin service) {
service.postEvent(new Event("EVENT/TOPIC", (Map)null));
}
});
Who tracks the services?
Above you saw the not further explained call to "
getService(serviceClass)". That's where
Trackers fetches the service. And in case there is no tracker for it yet, it will create one for this service.
It keeps a map of ServiceTracker, on for each service class you request.
private static <T> T getService(final Class<T> serviceClass) {
ServiceTracker tracker = trackers.get(serviceClass);
if (null == tracker) {
tracker = createTracker(serviceClass);
trackers.put(serviceClass, tracker);
}
// Safe to cast here, since OSGi already checks the returned service.
return (T)tracker.getService();
}
Creating the tracker is pretty standard:
private static <T> ServiceTracker createTracker(final Class<T> serviceClass) {
ServiceTracker tracker = new ServiceTracker(
FrameworkUtil.getBundle(Trackers.class).getBundleContext(),
serviceClass.getName(),
null);
tracker.open();
return tracker;
}
Some Sugar
Remember our sample about the car listings? Rewriting it using the new
Trackers class it would like this:
Collection<Car> listAllCars() {
Collection<Car> cars = Collections.emptyList();
Trackers.run(CarService.class, new ServiceRunnable<CarService> {
void run(CarService service) {
cars = service.findAll();
}
});
return cars;
}
Looks ok, but can be improved. It would be nice if we could return a value from
Trackers.run() so that our code would look like this:
Collection<car> listAllCars() {
return Trackers.run(CarService.class, new ServiceRunnable<CarService, Collection<Car>> {
Collection<Car> run(CarService service) {
return service.findAll();
}
});
}
This can be done by changing the run method as follows:
public static <T, R> R run(Class<T> serviceClass, ServiceRunnable<T, R> runnable) {
final T service = getService(serviceClass);
if (service != null) {
return runnable.run(service);
}
return null;
}
It would take into account a user specified return value. If no service was found the
run method would now return null. So to fulfill our API contract of before (returning an empty list and not null) the final method would be:
Collection<Car> listAllCars() {
Collection<Car> cars = Trackers.run(CarService.class, new ServiceRunnable<CarService, Collection<Car>> {
Collection<Car> run(CarService service) {
return service.findAll();
}
});
return cars != null ? cars : Collections.emptyList();
}
This could further be improved if we would have a fall-back facility.
public interface ServiceRunnableFallback<T, R> extends ServiceRunnable<T, R> {
R run();
}
This simply introduces a new method that has no parameter but the same return value as the base interface. The Trackers class will use this interface if the service is not available:
if (service != null) {
return runnable.run(service);
} else if (runnable instanceof ServiceRunnableFallback<?,?>) {
return ((ServiceRunnableFallback<T, R>) runnable).run();
}
The car listing sample would then change to:
Collection<car> listAllCars() {
return Trackers.run(CarService.class, new ServiceRunnableFallback<CarService, Collection<Car> > {
Collection<Car> run(CarService service) {
return service.findAll();
}
Collection<Car> run() {
return Collections.emptyList();
}
});
}
Of course this is such a common use case, that we can create an abstract class for that, that will take the default return value in case the service is not available as a constructor argument:
public abstract class DefaultServiceRunnable<T, R> implements ServiceRunnableFallback<T, R> {
private final R defaultReturn;
public DefaultServiceRunnable(final R defaultReturn) {
this.defaultReturn = defaultReturn;
}
public final R run() {
return defaultReturn;
}
}
And voilá, the final car listing example:
Collection<Car> listAllCars() {
return Trackers.run(CarService.class, new DefaultServiceRunnable<CarService, Collection<Car>>(Collections.EMPTY_LIST) {
Collection<Car> run(CarService service) {
return service.findAll();
}
});
}
Now, the logging example from above does not return anything, so it would be nice if the code could be further simplified.
We create another abstract class that will implement the ServiceRunnableFallback methods for us and just return null:
public abstract class SimpleServiceRunnable<T> implements ServiceRunnableFallback<T, Object> {
public final Object run(final T service) {
doRun(service);
return null;
}
public final Object run() {
doRun();
return null;
}
protected void doRun() {
}
protected abstract void doRun(T service);
}
Note that this class marks the run(T) and run() as final so no subclass can override them but must doRun(T) instead. The abstract class has an empty body for doRun() in the case that the service is not available.
With that in place the logging example could be re-written like this:
public static void log(int level, Throwable t, String message, Object... args) {
final String text = String.format(message, args);
Trackers.run(LogService.class, new SimpleServiceRunnable<LogService>() {
protected void doRun(final LogService service) {
service.log(level, text, t);
}
protected void doRun() {
System.out.println(text);
}
});
}
I will upload the full source code to github soon, but to get you started here is full code of the Trackers class:
public final class Trackers {
private static final Map<Class<?>, ServiceTracker> trackers = new HashMap<Class<?>, ServiceTracker>();
public static <T, R> R run(Class<T> serviceClass, ServiceRunnable<T, R> runnable) {
final T service = getService(serviceClass);
if (service != null) {
return runnable.run(service);
} else if (runnable instanceof ServiceRunnableFallback<T, R>) {
return ((ServiceRunnableFallback<T, R>) runnable).run();
}
return null;
}
private static <T> T getService(Class<T> serviceClass) {
ServiceTracker tracker = trackers.get(serviceClass);
if (null == tracker) {
tracker = createTracker(serviceClass);
trackers.put(serviceClass, tracker);
}
return serviceClass.cast(tracker.getService());
}
private static <T> ServiceTracker createTracker(Class<T> serviceClass) {
return new ServiceTracker(FrameworkUtil.getBundle(Trackers.class)
.getBundleContext(), serviceClass.getName(), null) {
{
open();
}
};
}
private Trackers() {
}
}
The interfaces are all fully mentioned here, it'ss just a matter of copy&paste until everything is up on github.
As always the code mentioned on this blog is licensed under EPL 1.0, so feel free to use and enhance it.
Very nice (and much better than directly using ServiceTracker in any and every class around).
ReplyDeleteStill a lot of boilerplate for my tastes, but I don't think we can do much better with Java. I would like to see the Scala-yfied equivalent in the ScalaModules library. I might do it for fun if I have some time.
Thanks for your comment, Simon. Granted, in Ruby it would look a lot nicer too :) But I am afraid that's as good as it gets in Java.
ReplyDeleteGood example. Is EventAdmin another Workbench class that you cannot use DS for? I've tried several different ways in eclipse 3.5 to get it to register via DS to no avail.
ReplyDeleteEventAdmin is a standard OSGi service. What kind of problem do you have with it? It is registered via DS and all you need to do to receive events is to register an EventHandler yourself. If you want to use EventAdmin to publish events yourself you can use there helpers shown in this blog post.
ReplyDelete