SeamFramework.orgCommunity Documentation
Everything possible in CDI applications is possible in bean bundle. They can take advantage of injection, producers, interceptors, decorators and alternative. But influence boundary of the CDI compliant container stay within the bean bundle for classic CDI usages. So external dependencies cannot be injected and interceptor, decorator or alternative of another bean bundle cannot be used (yet interceptors, decorators and alternatives still need to be declares in the bean bundle bean.xml file).
That is all we will say about classic CDI usages, please report to CDI documentation for more information.
CDI-OSGi provides more functionality using CDI in a OSGi environment.
It mainly focuses on the OSGi service layer. It addresses the difficulties in publishing and consuming services. CDI-OSGi allows developers to publish and consume OSGi services as CDI beans. However, since OSGi services are dynamic there are some differences with classic bean injection. This section presents how OSGi services can be published and consumed using CDI-OSGi.
CDI-OSGi also provides utilities for event notification and communication in and between bundles as well as some general OSGi utilities.
Examples use this very sophisticated service interface:
public interface MyService { void doSomething(); }
First it is important to be clear about what are a service, its implementations, its instances and its registrations.
A service is mostly an interface. This interface defines the contract that describes what the service may do. It might be several way to actually providing the service, thus a service might have multiple implementations.
A service implementation is a class that implements this service. It is what is available to other components that use the service. To use the service the component obtain an instance of the implementation.
A service instance is an instance of one of the service implementations. It is what the user manipulates to perform the service.
A registration is the object that represents a service registered with a particular implementation. Then this implementation can be searched and its instances can be obtained. Every time a service implementation his register a corresponding registration object is created.
There are two ways to obtain a service instances using CDI-OSGi: direct injection and programmatic lookup.
The main way to perform an OSGi injection is to use the @Inject
@OSGiService
annotation combination. It acts like a common injection
except that CDI-OSGi will search for injectable instances in the service
registry.
That is how it looks like:
@Inject @OSGiService MyService service; service.doSomething();
The behavior is similar with classic CDI injection. @OSGiService
is just a special qualifier that allows extension bundle to manage the injection
instead of CDI implementation.
Instead of obtain directly a service instance it is possible to choose between
service implementations and instantiate one at runtime. The interface
Service<T>
works as a service instance producer: it
retrieves all the corresponding (to the service parametrized type) service
implementations and allows to get an instance for each.
Service implementations and a corresponding instance can be obtained like that:
@Inject Service<MyService> services; services.get().doSomething();
All implementations can also be iterated like that:
@Inject Service<MyService> services; for (MyService service : services) { service.get().doSomething(); }
The
get()
method returns an instance of the first available service
implementation.
Service<T>
acts like CDI Instance<T>
except
that the injection process is managed by the extension bundle instead of CDI
implementation. So the available implementations are searched dynamically into
the service registry.
CDI-OSGi allows developers to automatically publish service implementation. There is nothing to do, just put the annotation. OSGi framework is completely hidden. Then the service is accessible through CDI-OSGi service injection and OSGi classic mechanisms.
Automatically publish a new service implementation:
@Publish public class MyServiceImpl implements MyService { @Override public void doSomething() { } }
It
registers a service MyService
with the implementation
MyServiceImpl
.
The behavior is similar with classic CDI bean registration, except that services are not register into the bean manager but into the service registry.
It is possible to specify the contracts interface that this specific
implementation fulfill using the Publish
annotation.
@Publish(contracts = {MyService.class, MyOtherService.class}) public class MyServiceImpl { public void doSomething() { } }
If no contract list are provided, the implementation will match each of its
interface types. Every CDI container of a bean bundle might have a blacklist that
excludes some interface from being register as a service type. See the specific
integration bundle doccumentation to obtain the used blacklist. This blacklist is
only used when no interface array is provided within the Publish
annotation.
There might be multiple implementations of the same service. It is possible to provide properties to an implementation in CDI-OSGi. This qualification is available both during publishing and consuming.
There are two ways for qualifying: a CDI like and a OSGi like, both are presented below.
Qualifiers are used like in classic CDI applications. An implementation can be
qualified by as many qualifiers as needed. An injection point can be also
qualified in order to restraint the potential injected implementations. It is
finally possible to select the instance produced when using the
Service<T>
interface.
Qualified service publishing:
@Publish @AnyQualifier public class MyServiceQualifiedImpl implements MyService { @Override public void doSomething() { } }
Qualified injection point:
@Inject @OSGiService @AnyQualifier MyService qualifiedService; qualifiedService.doSomething();
or
with Service<T>
@Inject @AnyQualifier Service<MyService> service; service.get().doSomething();
The qualifiers should be seen as the service properties. Here another example:
@Publish @Lang(EN) @Country(US) @ApplicationScoped public class MyServiceImpl implements MyService { @Override public void doSomething() { } } @Inject @Lang(EN) @Country(US) Service<MyService> service;
The behavior is similar with classic CDI qualifiers. But there another possibility in order to qualify a service.
Since CDI-OSGi services stay OSGi services they can be filtered through LDAP
filter. Properties might be added at publishing using the Publish
annotation values. Then an LDAP filter can be use at injection point using the
Filter
annotation.
An example is worth a thousand words:
@Publish({ @Property(name="lang", value="EN"), @Property(name="country", value="US") }) @ApplicationScoped public class MyServiceImpl implements MyService { @Override public void doSomething() { } }
As
many @Property
annotation as wanted can be added, they are
registered with the service implementation.
Then it is possible to filter an injection point with the Filter
annotation like a regular LDAP filter:
@Inject @Filter("(&(lang=*)(country=US))") Service<MyService> service;
or
@Inject @Filter("(&(lang=*)(country=US))") MyService service;
A service implementation has properties. These properties can be added by both
Qualifier
annotations and Property
annotations of
a Filter
annotation. Therefore there is a link between
Qualifier
and LDAP filter used in Filter
annotation as Property
.
A property is a couple of String
, the first entry is the name of
the property, the second the value. This scheme is clear in the
Property
annotation. For Qualifier
the property
name is the decapitalized Qualifier
class name and the property
value is the value
of the Qualifier
(empty
String
is assumed if value
does not exist).
@Publish @Lang(EN) @Country(US) @ApplicationScoped public class MyServiceImpl implements MyService { } @Inject @Filter("(&(lang=EN)(country=US))") Service<MyService> service;
@Publish({ @Property(name="lang", value="EN"), @Property(name="country", value="US") }) @ApplicationScoped public class MyServiceImpl implements MyService { } @Inject @Lang(EN) @Country(US) Service<MyService> service;
It is even possible to mix up Qualifier
and
Property
:
@Publish({ @Property(name="lang", value="EN"), }) @Country(US) @ApplicationScoped public class MyServiceImpl implements MyService { } @Inject @Filter("lang=EN") @Country(US) Service<MyService> service;
It is possible to do a programmatic filtering after injection using
Service<T>
like with
Instance<T>
:
@Inject Service<MyService> services; services.select(new AnnotationLiteral<AnyQualifier>() {}).get().deSomething();
or
@Inject Service<MyService> services; services.select("(&(lang=*)(country=US))").get().deSomething();
However OSGi service may not be subtyped, thus it is not possible to use
select()
methods with a specified subclass like with
Instance<T>
Like for bean instances, service instances are contextual. Every implementation is bounded to a particular scope. Provided that an satisfactory implementation is available, a service injection will return a contextual instance of the implementation.
All CDI scopes are available for CDI-OSGi services and their use is the same:
@Publish @ApplicationScoped public class MyServiceImpl implements MyService { @Override public void doSomething() { } }
A instance will be shared by the entire application.
@Publish @RequestScoped public class MyServiceImpl implements MyService { @Override public void doSomething() { } }
A new instance is created for every request.
If no scope is provided Dependent
scope is assumed, a new instance
will be create for every injection. An instance obtained with regular OSGi
mechanisms assumes a ApplicationScoped
scope in order to maintain
regular OSGi comportment.
A registration object represent all the bindings between a service contract class
and its OSGi ServiceRegistration
s. With this object it is possible to
navigate through multiple implementations of the same service, obtain the
corresponding Service<T>
or unregister these implementations.
A registration is obtained like that:
@Inject Registration<MyService> registrations;
The injection point can be filtered using qualifiers:
@Inject @AnyQualifier Registration<MyService> qualifiedRegistrations;
or using LDAP filter:
@Inject @Filter("(&(lang=EN)(country=US))") Registration<MyService> qualifiedRegistrations;
It is possible to iterate through registration:
if(registrations.size() > 0) { for(Registration<T> registration : registrations) { } }
It is also possible to request a subset of the service implementations using qualifiers:
Registration<T> filteredRegistrations = registrations.select(new AnnotationLiteral<AnyQualifier>() {});
or a LDAP filter:
Registration<T> filteredRegistrations = registrations.select("(&(lang=EN)(country=US))");
or both:
Registration<T> filteredRegistrations = registrations.select(new AnnotationLiteral<AnyQualifier>() {}).select("(&(lang=EN)(country=US))");
Link between qualifier and LDAP filter as well as subtyped service selection are explain above in service injection section.
CDI-OSGi offers another way to deal with services: the service registry. It can be obtained in any bean bundle as a injected bean. The service registry allows developers to dynamically register service implementation, to obtain services and registrations.
First get the service registry:
@Inject ServiceRegistry registry;
Register a service implementation:
registry.registerService(MyService.class,MyServiceImpl.class);
or
MyServiceImpl implementation = new MyServiceImpl(); registry.registerService(MyService.class,implementation);
It is possible to collect the corresponding registration:
Registration<MyService> registeredService = registry.registerService(MyService.class,MyServiceImpl.class);
Obtain a service implementations:
Service<MyService> services = registry.getServiceReference(MyService.class); for (MyService service : services) { service.doSomething(); }
Obtain all registrations of a filtered or not filtered specified service:
Registration<?> registrations = registry.getRegistrations(); Registration<MyService> myRegistrations = registry.getRegistrations(MyService.class); Registration<MyService> myFrenchRegistrations = registry.getRegistrations(MyService.class,"(lang=FR)");
Because OSGi service are dynamic they might be unavailable at the time they should be used. On a service call if the targeted service isn't available a specific runtime exception is raised:
public class OSGiServiceUnavailableException extends RuntimeException {}
CDI-OSGi makes heavy usage of CDI events. These events allow CDI-OSGi to do its work. Events are important for the running of CDI-OSGi itself but they can also be used by client bean bundles to perform some operations or override some normal CDI-OSGi behavior.
This section shows the numerous events provided by CDI-OSGi and the ways to use them.
Two events inform about the state of the CDI container of every bean bundle:
BundleContainerInitialized
and BundleContainerShutdown
events.
The BundleContext
from where the event comes is sent with these
events so developers can retrieve useful information (such as the bundle owning the
CDI container).
The BundleContainerInitialized
event is fired every time a CDI
container is initialized in a bean bundle. It point out that the CDI container
is ready to manage the bean bundle with the CDI-OSGi features.
Here the way to listen this event:
public void onStartup(@Observes BundleContainerInitialized event) { BundleContext bundleContext = event.getBundleContext(); Bundle firingBundle = bundleContext.getBundle(); }
The BundleContainerShutdown
event is fired every time a CDI
container is stopped in a bean bundle. It point out that the CDI container wil
not manage the bean bundle with CDI-OSGi anymore.
Here the way to listen this event:
public void onShutdown(@Observes BundleContainerShutdown event) { BundleContext bundleContext = event.getBundleContext(); Bundle firingBundle = bundleContext.getBundle(); }
Ten events might be fired during a bundle lifecycle. They represent the possible
states of a bundle and monitor the changes occurred in bundle lifecycles. These
events carry the Bundle
object from where the event comes so developers
can retrieve useful information (such as the bundle id).
CDI-OSGi provides a way to listen all bundle events in a single method: the
AbstractBundleEvent
abstract class. Every bundle lifecycle
event is an AbstractBundleEvent
and a method allows to retrieve the
actual fired bundle event. All bundle states are listed in the
BundleEventType
enumeration. Then this event can be used as the
corresponding bundle event.
Here the way to listen all bundle events and to retrieve the corresponding bundle state:
public void bundleChanged(@Observes AbstractBundleEvent event) { BundleEventType event.getType(); }
Rather than listen all bundle events and then filter the intended, it is possible to choose the listened bundle event.
Here the way to listen a specific bundle event:
public void bindBundle(@Observes BundleInstalled event) { Bundle bundle = event.getBundle(); long id = event.getBundleId(); String symbolicName = event.getSymbolicName(); Version version = event.getVersion(); }
All other bundle events could be listened the same way.
It is possible to filter the listened events depending on the bundles that are concerned. This filter might cover the name or the version of the bundles.
Here the way to filter bundle events on the bundle names:
public void bindBundle(@Observes @BundleName("com.sample.gui") BundleInstalled event) { }
or on the bundle versions:
public void bindBundle(@Observes @BundleVersion("4.2.1") BundleInstalled event) { }
or on both bundle names and versions:
public void bindBundle(@Observes @BundleName("com.sample.gui") @BundleVersion("4.2.1") BundleInstalled event) { }
Three events might be fired during a service lifecycle. They represent the
possible states of a service and monitor the changes occurred in service lifecycles.
These events carry the BundleContext
object from where the event comes
and the ServiceReference
object corresponding to the service so
developers can retrieve useful information (such as the registering bundle) and
acces useful actions (such as register or unregister services).
CDI-OSGi provides a way to listen all service events in a single method: the
AbstractServiceEvent
abstract class. Every service lifecycle
event is an AbstractServiceEvent
and a method allows to retrieve
the actual fired service event. All service states are listed in the
ServiceEventType
enumeration. Then this event can be used as
the corresponding service event.
Here the way to listen all service events and to retrieve the corresponding service state:
public void serviceEvent(@Observes AbstractServiceEvent event) { ServiceEventType event.getType(); }
Rather than listen all service events and then filter the intended, it is possible to choose the listened service event.
Here the way to listen a specific service event:
public void bindService(@Observes ServiceArrival event) { ServiceReference serviceReference = event.getRef(); Bundle registeringBundle = event.getRegisteringBundle(); }
All other bundle events could be listened the same way.
It is possible to filter the listened events depending on the services that are concerned. This filter might cover the service specification class or it might be a LDAP filter.
Here the way to filter service events on the specification class:
public void bindService(@Observes @Specification(MyService.class) ServiceArrival event) { }
or using a LDAP filter:
public void bindService(@Observes @Specification(MyService.class) ServiceArrival event) { }
or on both specification class and LDAP filter:
public void bindService(@Observes @Specification(MyService.class) @Filter("(&(lang=EN)(country=US))") ServiceArrival event) { }
Some bean bundle might declare required dependencies on services (using the
Required
annotation). CDI-OSGi fired events when these required
dependencies are all validated or when at least one is invalidated. It allows
application to be automatically started or stopped.
An event is fired every time all the required dependencies are validated.
Here the way to listen this event:
public void validate(@Observes Valid event) { }
CDI-OSGi provides a way to communicate within and between bean bundles. This communication occurs in a totally decoupled manner using CDI events.
An InterBundleEvent
is a message containing a object and
transmitted using CDI event.
Here the way to fire such an event:
@Inject Event<InterBundleEvent> event; MyMessage myMessage = new MyMessage(); event.fire(new InterBundleEvent(myMessage));
When an InterBundleEvent
is fired it might be catch either within
the bundle or in other bundles or both.
Here the way to receive a communication event from within the bundle:
public void listenAllEvents(@Observes InterBundleEvent event) { }
Here the way to receive a communication event from another bundles:
public void listenAllEventsFromOtherBundles(@Observes @Sent InterBundleEvent event) { }
It is possible to filter the listened events depending on the message type. This filter may specify the type of message that is listened.
Here the way to filter communication events specifying a message type:
public void listenStringEventsFromOtherBundles(@Observes @Sent @Specification(MyMessage.class) InterBundleEventevent) { }
Only
the message with type MyMessage
will be received.
CDI-OSGi provide some facilities for OSGi usage. It allows to obtain, by injection, some of the useful objects of the OSGi environment.
Here a way to obtain the current bundle:
@Inject Bundle bundle;
Here a way to obtain the current bundle context:
@Inject BundleContext bundleContext;
Here a way to obtain all the current bundle headers:
@Inject @BundleHeaders Map<String,String>metadata;
or a particular header
@Inject @BundleHeader("Bundle-SymbolicName") String symbolicName;
Here a way to obtain a resource file from the current bundle:
@Injec @BundleDataFile("test.txt") File file;
Here a way to obtain a specified bundle from its name and version:
@Inject @BundleName("com.sample.gui") @BundleVersion("4.2.1") bundle;
or just from its name:
@Inject @BundleName("com.sample.gui") bundle;
Here a way to obtain all the specified bundle headers:
@Inject @BundleName("com.sample.gui") @BundleVersion("4.2.1") @BundleHeaders Map<String,String>metadata;
or a particular header:
@Inject @BundleName("com.sample.gui") @BundleVersion("4.2.1") @BundleHeader("Bundle-SymbolicName") String symbolicName;
Here there are some examples that concretely show what CDI-OSGi is avoiding to the developer:
How to obtain a service available implementations list ?
In CDI-OSGI:
@Inject Service<MyService> references;
versus in classic OSGi:
ServiceReference references = bundleContext.getServiceReferences(MyService.class.getName());
//Current BundleContext must be known
How to obtain a service instance ?
In CDI-OSGi:
@Inject @OSGiService MyService service;
versus in classic OSGi:
ServiceReference reference = bundleContext.getServiceReference(MyService.class.getName()); MyService service = (MyService) bundleContext.getService(reference); //Current BundleContext must be known
How to publish a new service implementation ?
In CDI-OSGi:
@Publish
public class MyServiceImpl implements MyService {
@Override
void doSomething() {...}
}
versus in classic OSGi:
public class MyServiceImpl implements MyService {
@Override
void doSomething() {...}
}
{ //in another class
ServiceRegistration registration = bundleContext.registerService(MyService.class, new MyServiceImpl(), null);
}
//Current BundleContext must be known
How to obtain the current Bundle
?
In CDI-OSGi:
@Inject Bundle currentBundle;
versus in classic OSGi:
Bundle currentBundle = FrameworkUtil.getBundle(this.getClass());
How to obtain the current BundleContext
?
In CDI-OSGi:
@Inject BundleContext bundleContext;
versus in classic OSGi:
BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
How to obtain a specified Bundle
and BundleContext
?
In CDI-OSGi:
@Inject @BundleNamer("symbolicName") @BundleVersion("version") Bundle specifiedBundle; @Inject @BundleNamer("symbolicName") @BundleVersion("version") BundleContext specifiedBundleContext;
versus in classic OSGi:
ServiceReference reference = bundleContext.getServiceReference(PackageAdmin.class.getName()); PackageAdmin packageAdmin = bundleContext.getService(reference); Bundle specifiedBundle = packageAdmin.getBundles("symbolicName","version")[0]; BundleContext specifiedBundleContext = specifiedBundle.getBundleContext(); //Current BundleContext must be known