-
Notifications
You must be signed in to change notification settings - Fork 10
Design Spec
- This bundle provide a full Weld stack with all it's dependencies
- This bundle can be started
- This bundle implements the extender pattern that target CDI beans (with a beans.xml inside)
- For each bundle declaring a beans.xml file, a corresponding Weld container is launched
- This bundle is responsible for OSGi event (bundles and services) forwarding to Weld containers
- This bundle is responsible of the Weld containers lifecyle management. Each time a managed bundle is started/stopped, the corresponding Weld container is started/stopped.
- This bundle is responsible for specific events triggering related to CDI OSGi extension
- This bundle is responsible for inter bundle communication between two Weld container
- This bundle is responsible for automated OSGi services publications
- This bundle is responsible for OSGi services unregistration and Weld containers shutdown
- This bundle must be separated in two parts :
- A CDI OSGi standard extension providing utilities for OSGi environment
- An integration part for bootstrapping CDI environment such as Weld. This part should provide a standard API allowing any CDI implementation to be bootstrapped no matter who's providing it.
The integration part of the Weld/OSGi bundle should be usable on it's own (as a single bundle) and provide the minimum integration to start a Weld container in a pure OSGi environment. No other utilities should be provided here.
The integration part of the Weld/OSGi bundle should provide a minimal API to start a Weld container. This API should used as following :
WeldContainer weld = new WeldOSGi(bundleContext).initialize(); // based on Weld SE
...
MyService service = weld.instance().select(MyService.class).get();
...
weld.stop();
EmbeddedCDIContainer cdi = new EmbeddedContainer(bundleContext).initialize();
...
MyService service = cdi.instance().select(MyService.class).get();
...
cdi.stop();
Can we somehow retrofit this to the Weld API introduced in Weld-SE? -- pmuir
This API must work with the Weld/OSGi bundle started or just installed. To prevent multiple CDI container started for the same bundle, it's possible to add information in the OSGi headers of the bundle to prevent automated container start from the infrastructure bundle :
Embedded-CDIContainer true;
The API must also provide a default WeldActivation that extends OSGi BundleActivator. This activator start a Weld container when it starts and stop it when the bundle stop. This activator should be an abstract class ans should support dependency injection. It should also check the embedded mode in the bundle manifest.
Usage of embedded API
public class MyApp implements BundleActivator {
private WeldContainer weld;
@Override
public void start(BundleContext bc) throws Exception {
// start a Weld container
weld = new WeldOSGi(bundleContext).initialize();
...
MyService service = weld.instance().select(MyService.class).get();
}
@Override
public void stop(BundleContext bc) throws Exception {
// stop the container
weld.shutdown();
}
}
Lets assume :
public interface MyService {
void doSomething();
}
There are several way to consume OSGi services :
@Inject @OSGiService MyService service;
service.doSomething();
...
@Inject Service<MyService> service;
if (service.isAvailable()) {
service.get().doSomething();
}
...
if (service.size() > 0) {
for (MyService service : service) {
service.doSomething();
}
}
...
@Inject ServiceProviders<MyService> services;
if (service.size() > 0) {
for (Service<MyService> service : services) {
if (service.isAvailable()) {
service.get().doSomething();
}
}
}
I assume that ServiceProviders, Services and Service are OSGi interfaces, not interfaces we are introducing? -- pmuir
These interfaces are not OSGi interfaces because the OSGi service layer API isn't typesafe and user friendly at all. To get a service, it's something like
java MyService myService = (MyService) bundleContext.getService(bundleContext.getServiceReference(MyService.class.getName()));
-- mathieuancelin
On services calls, if the targeted service isn't available, it should throw a specific Exception such as
public class OSGiServiceUnavailableException extends RuntimeException {}
These OSGi service injection types can be qualified with the @Filter
qualifier such as :
@Inject @OSGiService @Filter("(&(lang=EN)(country=US))") MyService service;
...
@Inject @Filter("(&(lang=EN)(country=US))") Service<MyService> service;
The value of the filter annotation is a standard OSGi/LDAP filter. If the value of @Filter
isn't specified, the service lookup is filtered with the current qualifiers on the injection point.
We should additionally support @Filter as a meta-annotation, IOW you can create a filter such @MyFilter which can then be applied at injection points. This should probably be a stereotype -- pmuir
to register automatically a new OSGi service at container startup, the registerable classes should be marked
with a specific annotation like (maybe we should change the name. What about @Register
?)
I like @Publish -- pmuir
Me too :) -- mathieuancelin
:
// it's not a qualifier
@Target({ TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Publish {
public Class[] contracts() default {};
public Property[] properties() default {};
}
@Publish
@ApplicationScoped
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
}
}
These classes should be discovered right after Weld container startup and automatically registered in the current bundle context. The registered objects should be CDI already managed beans to handle context management.
To use OSGi service registration with properties, there are two possible ways :
- using qualifiers : just use the
@Publish
annotation without a properties() value and the service will be registered with its qualifiers as properties.
@Publish
@Lang(Languages.EN)
@Country(Countries.US)
@ApplicationScoped
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
}
}
...
@Inject Service<MyService> @Filter @Lang(Languages.EN) @Country(Countries.US) service;
- using explicit properties : use the
@Publish
annotation with values in value() field
@Publish({
@Property(name="lang", value="EN"),
@Property(name="country", value="US")
})
@ApplicationScoped
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
}
}
...
@Inject Service<MyService> @Filter("(&(lang=*)(country=US))") service;
I think supporting the latter is useful, but the former seems much more CDI ish -- pmuir
The OSGi service registry should be available programmatically inside OSGi/CDI bundles to allow dynamic registration/unregistration of services. The proposed API for the ServiceRegistry is :
public interface ServiceRegistry {
// register a new service based on a known CDI bean
<T> Registration<T> registerService(Class<T> contract, Class<? extends T> implementation);
// register a new service based on any object instance
<T, U extends T> Registration<T> registerService(Class<T> contract, U implementation);
// get all services based on the contract class
<T> Service<T> getServiceReferences(Class<T> contract);
// get the first service based on the contract class
<T> Service<T> getServiceReference(Class<T> contract);
// get all services based on the contract class with an OSGi filter
<T> Services<T> getServiceReferences(Class<T> contract, String filter);
// get the first service based on the contract class with an OSGi filter
<T> Service<T> getServiceReference(Class<T> contract, String filter);
// get all service registrations managed by the current Weld container
<T> Registrations<T> getRegistrations();
// get all service registrations for class contract managed by the current Weld container
<T> Registrations<T> getRegistrations(lass<T> contract);
// get all service registrations for class contract managed by
// the current Weld container with an OSGI filter
<T> Registrations<T> getRegistrations(lass<T> contract, String filter);
}
this API should used like :
@Inject Instance<Object> instance;
@Inject ServiceRegistry registry;
...
// get a bean instance
MyService mySuperServiceInstance = instance.select(MySuperServiceImpl.class).get();
...
// register dynamically a new service
Registration<MyService> registeredService = registry.registerService(MyService.class, mySuperServiceInstance);
...
// get an existing service
Service<MyService> service = registry.getServiceReference(MyService.class);
service.get().doSomething();
...
// get all existing services
logger.log(services.size());
...
for (MyService service : service) {
service.doSomething();
}
...
// unregister dynamically previously registered service
registeredService.unregister();
It should be also possible to inject existing services registrations (all services registrations handled by the current Weld container, including manuel or automated one) directly inside beans :
@Inject Registrations<Object> allRegistrations;
@Inject Registrations<MyService> allMyServiceRegistrations;
@Inject @Filter("(lang=FR)") Registrations<MyService> allMyServiceFrenchRegistrations;
...
for (Registration registredService : allRegistrations) {
registredService.unregister();
}
CDI/OSGI makes heavy usage of CDI events to work, so many specific events can be used :
- Listen to current bundle CDI container start
public void onStartup(@Observes BundleContainerInitialized event) {}
- Listen to current bundle CDI container stop
public void onShutdown(@Observes BundleContainerShutdown event) {}
- Listen to bundles lifecycle events
public void bindBundle(@Observes BundleInstalled event) {}
public void unbindBundle(@Observes BundleUninstalled event) {}
public void lazyInitBundle(@Observes BundleLazyActivation event) {}
public void resolveBundle(@Observes BundleResolved event) {}
public void startBundle(@Observes BundleStarted event) {}
public void startingBundle(@Observes BundleStarting event) {}
public void stopBundle(@Observes BundleStopped event) {}
public void stoppingBundle(@Observes BundleStopping event) {}
public void unresolveBundle(@Observes BundleUnresolved event) {}
public void updateBundle(@Observes BundleUpdated event) {}
It should be also possible to filter event for only certain bundles with :
public void bindBundle(@Observes @BundleName("com.sample.gui") BundleInstalled event) {}
public void bindBundle(@Observes @BundleVersion("4.2.1") BundleInstalled event) {}
// listen events from bundle "com.sample.gui" in its "4.2.1" version
public void bindBundle(@Observes @BundleName("com.sample.gui")
@BundleVersion("4.2.1") BundleInstalled event) {}
- Listen to services lifecycle events
public void bindService(@Observes ServiceArrival event) {}
public void unbindService(@Observes ServiceDeparture event) {}
public void changeService(@Observes ServiceChange event) {}
It should be also possible to filter services events with :
public void bindService(@Observes @Specification(MyService.class) ServiceArrival event) {}
// listen ServiceArrival event for type MyService.class with properties lang=EN and country=US
public void bindService(@Observes @Specification(MyService.class)
@Filter("(&(lang=EN)(country=US))") ServiceArrival event) {}
- Use event to validate OSGi service dependencies : if an CDI/OSGi bundle absolutely need certain OSGi services to run application, it can mark them as Required. Then when the required services will be registered in OSGi service registry, the Valid event will be raised so you can catch it and launch your app. When the required services will be unregistered from OSGi service registry, the Invalid event will be raised.
@Inject @OSGiService @Required MyService service;
...
@Inject @Required Service<MyService> myService;
...
@Inject @Required Services<MyService> myServices;
...
public void validate(@Observes Valid event) {
// start application here because all dependencies are availables
}
...
public void invalidate(@Observes Invalid event) {
// stop application here because one or more dependencies are unavailables
}
I think the naming here needs some work, I understand what is trying to be done, I think, but I'm not sure the names reflect it --pmuir
I agree. Maybe you've got some ideas ? --mathieuancelin
- Use events to communicate between OSGi bundles It should also be possible to communicate between CDI/OSGi bundle with InterBundleEvent
@Inject Event<InterBundleEvent> event;
...
event.fire(new InterBundleEvent("Hello bundles"));
then in another CDI/OSGi bundle :
// listen to all InterBundleEvent even from your own bundle
public void listenAllEvents(@Observes InterBundleEvent event) {}
...
// listen to all InterBundleEvent from other bundles
public void listenAllEventsFromOtherBundles(@Observes @Sent InterBundleEvent event) {}
...
// listen to all InterBundleEvent containing String object from other bundles
public void listenStringEventsFromOtherBundles(
@Observes @Sent @Specification(String.class) InterBundleEvent event) {}
Some bean types should be provided to easily interact with OSGi environment :
// injection of the current bundle
@Inject Bundle bundle;
...
// injection of the current bundle context
@Inject BundleContext context;
...
// injection of the current bundle headers
@Inject @BundleHeaders Map<String, String> metadata;
...
// injection of a specific bundle header of the current bundle
@Inject @BundleHeader("Bundle-SymbolicName") String symbolicName;
...
// injection of a data file for the current bundle
@Inject @BundleDataFile("test.txt") File file;
The SPI possibilities of CDI are fully supported in OSGi environment. To enable an extension inside a bundle, the META-INF/services/javax.enterprise.inject.spi.Extension file is required in that particular bundle. Then the sources of the extension can be bundled inside the bundle or in another visible bundle.
To enable automatically extensions on all OSGi bundles, it's possible to deploy CDI extension bundle with a special OSGi header such as :
CDI-Extension com.sample.foo.MyExtension;
Then, that particular extension will be used for each new OSGi/CDI bundle that will start.
A basic Maven archetype to create new Weld/OSGi projet with test environment.
A SeamForge plugin allowing user to declare OSGi services, services injection, inter bundle event broadcasting, etc ... to easily develop OSGi/CDI enabled applications.
We need to provide a subset of CDI/OSGi for hybrid application server so Java EE apps will be able to use service injection, etc ...
As Weld always inject generated proxy, client have to use API for which they don't declare import. So when injection is performed, ClassNotFound exceptions are thrown. The trick to fix it is to enable dynamic import in client bundle :
DynamicImport-Package *
A good solution to this issue is to provide a Bridge classloader to delegate loading of proxy classes to the Weld bundle classloader.
Update : this issue is fixed with commit https://github.com/mathieuancelin/weld-osgi/commit/d3b72fff4681bffac88427d280e1561446a4a3ad. The Weld SPI ProxyServices is used to pass BridgeClassLoaders that delegate unknown classes to infra bundle ClassLoader.
Currently, Weld makes heavy usage of a Singleton based API to locate the current Weld container through static methods. Unfortunately, the SingletonProvider interface can't locate current Container in an OSGi environment.
- What about making OSGi Imports as CDI beans ?
- What about OSGi services injection thread safety ?