CDI + OSGi and whiteboard pattern
One fundamental pattern in OSGi land is what is called "whiteboard pattern". Long story short, it is based on the fact the OSGi registry is a singleton for all bundles and the only single instance any (dynamic) bundle can rely on to have a state independent of deployments. Associated with a ServiceListener, it enables to use the registry to lookup all services matching a condition (type, filter, ...). Then the whiteboard runtime can use these instance to do what it needs. Common examples of whiteboards are servers: HTTP (you don't want to bind a port per bundle and potentially rebind the same port), JAX-RS server, ...
There are several abstraction to simplify the write of whiteboard - felix has one, karaf has some utilities about it too, but it is interesting to see that some newly API introduced by OSGi-CDI specification also help in such a task.
Let's take the simple example you want to write a registry holding all the service instances matching the filter mywhiteboard=true.
The CDI implementation will be based on the BindService API. It is more or less an OSGi-CDI facade to a ServiceTracker and can be injected with a filter passed to @Reference to filter the listened services.
Here is what it an look like:
public void onStart(
@Observes @Initialized(ApplicationScoped.class) Object init, // 1
@Reference(target = "(mywhiteboard=true)") // 2
final BindService<Object> myWhiteboardServicesListener) { // 3
myWhiteboardServicesListener // 4
.adding(this::registerService)
.removed(this::unregisterService)
.bind(); // 5
}
- We initialize the binder when the application starts, the BindService injection could be done in a constructor or field used in a @PostConstruct method, but this enables to activate the registry with the application, so if the registry is injected, it is already initialized and you don't add latency at the first calls,
- Using @Reference we filter the services we want to listen using a custom filter, type stays Object here but it can be specified in the annotation as well,
- We inject the BindService instance which is provided by the OSGi-CDI implementation,
- We set up our add/remove methods, it will typically enable to "parse" the service instance (introspect it if needed),
- Finally we bind() the BindService which means we start to listen for services (we open the ServiceTracker).
The last piece to make this work is to add the mywhiteboard=true marker, this can be done in a lot of ways but the simplest is likely to create a custom annotation and mark it with @BeanPropertyType from OSGi-CDI to enforce the (OSGi-CDI) service registration to have this property:
import org.osgi.service.cdi.annotations.BeanPropertyType;
@BeanPropertyType
@Target(TYPE)
@Retention(RUNTIME)
public @interface Mywhiteboard {
boolean value() default true;
}
By convention, this annotation will be converted to mywhiteboard=true property when used with OSGi-CDI @Service to register a service.
Now, as an user of this whiteboard, you can just mark your bean with this annotation and it will be registered in the whiteboard:
@Mywhiteboard // 1
@Service(Object.class) // 2
public class MyService {
}
- Mark the service as being using the whiteboard
- Request OSGi-CDI to register the bean as a service
In practise, if you think about doing a CLI like whiteboard using Tomitribe Crest library, you could end up with something looking like:
@CrestWhiteboard
@Service(Object.class)
public class MyService {
@Inject
private Dao dao;
@Command
public void listEntities(@Out PrintStream out) {
out.println(
dao.list().stream()
.map(Dto::toString)
.collect(joining("\n")));
}
}
Nothing else to do, @Service will take care to register the bean in OSGi registry, @CrestWhiteboard will take care to mark the command bean as being to be parsed and registered in the crest whiteboard so then calling crest you can execute this command without having to do anything else, and this is the strength of the whiteboard pattern, you just declare your business logic and then it is available thanks the runtime automatically.
Last note: don't forget to have a beans.xml (or to use @Bean(s)) and bnd/maven-bundle-plugin set up to ensure your module is registered as a OSGi-CDI module in your jar MANIFEST.MF.
Happy OSGi/CDI hacking!
From the same author:
In the same category: