|
PeaberryActivationGuide
Automatic bootstrapping of Peaberry bundles
Featured IntroductionEvery Guice application has to be bootstrapped by a small amount of code where the Injector is created and the first few objects are wired together. The goal of Peaberry Activation is to remove the need to write this bit of bootstrap code. In this way Guice users can work in a completely declarative way only having to code their Modules. Currently Peaberry Activation works only under OSGi. SetupJust install and start the Peaberry Activation bundle in your OSGi framework. The bundle follows the extender model and will pick up your Peaberry bundles and process them on sight. If you plan to use the Dynamic Configuration feature you must also install an implementation of the OSGi ConfigurationAdmin service. OverviewUnder OSGi the Peaberry bootstrap code is contained in a BundleActivator. This bundle activator is coded in a repeatable pattern: public Activator implements BundleActivator {
/*
* Bundle roots
*/
@Inject
private Export<Service1> export1;
...
@Inject
private Export<ServiceN> exportN;
@Inject
private Singleton1 singleton1;
...
@Inject
private SingletonN singletonN;
public void start(BundleContext bc) {
/* Setup Guice */
Injector inj = Guice.createInjector(Peaberry.osgiModule(bc), new Module1(), ..., new ModuleN());
/* Create bundle content */
inj.injectMembers(this);
/* Activate content. Call the relevant start()/open() methods */
singletonM.start();
...
singletonK.start();
}
public void stop(BundleActivator bc) {
/* Deactivate content. Call the relevant stop()/close()/dispose() methods */
export1.unput();
...
exportN.unput();
singletonM.stop();
...
singletonK.stop();
/* Detach dead content */
export1 = null;
...
exportN = null;
singleton1 = null;
...
singletonN = null;
}
}The activator is specified in the bundle manifest: ... Bundle-Activator: com.acme.Activator ... The OSGi framework requires that the activator class has a default constructor. It instantiates the activator and calls the start()/stop() methods to activate and deactivate the bundle. Peaberry Activation captures this pattern and implements it for you. All you need to do is to supply the Guice definitions of your singletons, exports and dynamic configuration values as a regular Guice Module. UsageBootstrap ConfigurationPeaberry Activation replaces the default OSGi bootstrap hook with a Guice bootstrap module. Rather than implement a BundleActivator you need to implement a single Module and declare it inside the bundle manifest like this: ... Bundle-Module: com.acme.Config ... As with the activator the bootstrap module has to have a default constructor. public class Config extends AbstractModule {
@Override
protected void configure() {
bind(export(HelloImpl.class)).toProvider(service(HelloImpl.class).export());
bind(HelloImpl.class).in(Singleton.class);
}
}When you need many Guice modules you can install them from the single bootstrap module like this: public class Config extends AbstractModule {
@Override
protected void configure() {
install(new Module1());
install(new ModuleN());
}
}The Peaberry Activation extender will instantiate the bootstrap module and pass it to a Guice Injector. It will than use the Guice introspection SPI to discover all Export<T> bindings, all Singleton scoped bindings, and all bindings to a dynamic configuration Provider. These three sets form the bundle roots. The roots are instantiated and started upon activation and stopped and dropped upon deactivation. I.e. we follow the standard startup and shutdown sequences outlined in the BundleActivator template shown above. Currently there are two minor restrictions we must follow:
Lifecycle for the SingletonsIf some of the bundle singletons need additional activation or deactivation we can annotate any parameter-less method of the respective singleton class as follows: public HelloImpl implements Hello {
public void hello(String who) {
System.out.println("Hello " + who + "!");
}
@Start
public void start() {
System.out.println("Hello service online");
}
@Stop
public void stop() {
System.out.println("Hello service offline");
}
}The @Start and @Stop annotations are located in the org.ops4j.peaberry.activation package. Notice that if a method is annotated but has parameters it will be ignored. In a future version an error will be emitted for such methods. We can annotate as many methods as we like. All @Start methods will be called in the order in which they are defined in the bundle class. The same goes for the @Stop methods. ConfigurationAdmin SupportPeaberryActivation can track dynamic configurations supplied by the OSGi ConfigurationAdmin service and inject their elements to @Named injection points. The API to do this is similar to the API used to setup dynamic service proxies. The org.ops4j.peaberry.activation.Configurables facade must be used to create a Provider for each dynamic value: public class Config extends AbstractModule {
@Override
protected void configure() {
...
bind(Integer.class).annotatedWith(named("test")).toProvider(
Configurables.configurable(Integer.class).from("test.pid").named("test.key"));
}
}Here we map an Integer named test to the dynamic value named test.key from the configuration with PID test.pid. It is expected that the object supplied from the ConfiguratinAdmin service will be of the same type as the type of the binding. In this case PeaberryActivation expects that under test.key it will discover an object of type Integer. PeaberryActivation does not yet support automatic conversions. Also notice that you can not use the Binder.bindConstant() method for dynamic values because it does not allow the use of Providers. Shorthand Peaberry SyntaxThe Peaberry philosophy to extending Guice is to implement different kinds of Provider. This design gives maximum flexibility and allows us to combine Peaberry with any other Guice extension in a nice orthogonal way. However this design also leads to lots of repetition in the Guice configuration code: class Config extends AbstractModule {
@Override
protected void configure() {
/* Import */
bind(Service.class).toProvider(service(Service.class).single());
/* Import Multiple */
bind(iterable(Service.class)).toProvider(service(Service.class).multiple());
/* Export */
bind(export(ServiceImpl.class)).toProvider(service(ServiceImpl.class).export());
/* Configure */
bind(Integer.class).annotatedWith(named("test")).toProvider(
configurable(Integer.class).from("test.pid").named("test.key"));
}
}To ease the burden PeaberryActivation provides a set of simple wrappers of the regular Guice Binder and AbstractModule that allow us to re-write the definitions above like this: class Config extends PeaberryActivationModule {
@Override
protected void configure() {
/* Import */
bindService(Service.class).single();
/* Import Multiple */
bindService(Service.class).multiple();
/* Export */
bindService(ServiceImpl.class).export();
/* Configure */
bindConfigurable(Integer.class).annotatedWith(named("test")).from("test.pid").named("test.key");
}
}Notice that rather than AbstractModule now we extend PeaberryActivationModule. These definitions are exactly equivalent to the long versions above:
For configurables there is also a default syntax that will bind the value under the same name as the corresponding key found in the dynamic configuration: bindConfigurable(Integer.class).from("test.pid").named("test.key");This will bind an Integer under the name @Named("test.key") found in configuration with PID test.pid under key test.key. The initial method of each short-hand definition returns a DSL builder that implements some of the Provider-building interfaces found in org.ops4j.peaberry.Peaberry or org.ops4j.peaberry.activation.Configurables. The difference is that instead of returning the built Provider instance from the final method call this builder will directly bind that instance into Guice and return null. You can freely mix the long and short forms in the same configure() method since PeaberryActivationModule extends AbstractModule. The shorthand Peaberry DSL can be found in the org.ops4j.peaberry.activation.util package. OperationThe PeaberryActivation extender takes each bundle it processes through the following phases. PreparationWhen
Sequence
ActivationWhen
Sequence
This sequence guarantees that your bundle's internals will be fully set up before they are exposed to control flow either from OSGi clients or from your own @Start methods. DeactivationWhen
Sequence
ReactivationWhen
Sequence
This sequence is roughly equivalent to a bundle restart - we only skip the Preparation phase. This means the entire bundle structure and all runtime state will be scrapped and a new singletons and exports will be instantiated and injected with the new dynamic configuration. In this way we can inject the dynamic configuration values as if they were the traditional set of static constants. I.e. we work with normal @Named injection points, and program without any special constructs and additional thread safety issues. |
Should we consider the 'Require-Bundle' manifest header during Activation?
e.g. the bundles listed in 'Require-Bundle' should be activated before this bundle.
Couple of remarks here:
1) Do you mean peaberry activation should track the dependencies between the guice bundles it processes and start/stop them in order? Or it should act on non-guice bundles as well?
2) Generally I think an OSGi application should not depend on the startup order of it's bundles. Each bundle is started by an external admin app, hooks to whatever dependencies it can find, and starts it's work. If a dependency is missing the bundle will just handle the error when it tries to call the service. Because of dynamics you need good ServiceUnavailableExeption? handling anyway. When enough bundles come together the useful activities will gradually start to "come through". A dependency-driven startup order is in most cases a performance optimization that simply reduces the number of errors until everyone join the game.
This is all so much complicated... I was expecting to declare OSGI dynamic services in a bundle without making any reference to DI code or framework. And then in the client bundle just to depend on a DI bundle, some peaberry-eclipse and on Guice bundle and directly use the @inject code to get instance of the service. All the registry and the life cycle of the services are handled by OSGI, why redoing it ?????? why not depend on it ? And once again I do not want my dynamic services plugin to depend on DI, I just want to use DI to ease the client usage. May be I do not understand the goals of this project. I afraid this complexity will make adopt of this project difficul. Or there is something I have not understood!!!
You need to configure the DI framework somehow. In spring you have an xml file. In blueprint you have a similar xml file. In Guice you do the same with java code. The Guice modules are configuration data separate from the application code of your bundle. This is declarative Java code - it does not do anything. It just describes which classes should be instantiated and than exported as services or which services should be looked up from the OSGi registry. As you see we are not reinventing OSGi - we just take instructions on how to talk to OSGi in the form of Java code rather than XML.
I can imagine a much more magical approach: use some kind of bytecode engineering library to scan every class in the bundle for annotations and than construct the Guice configuration out of that. However this is just too magical. If another person picks up the code they would have to read through every class to understand how it all fits together. It is much more preferable to have a top-level description. Also we would loose all the flexibility of Java when building the configuration - we'll need to add ever more annotations to cover ever more exotic use cases.
What I do not understand is why the Guice configuration is not done by inspecting the OSGI service registry. This should be good enought to automatically configure the Guice DI framework, no ? your are mentioning the spring xml file, but could'nt we consider dynamic services definition via component.xml to be some configuration file for the Guice framework ?
My problem is the setup phase :s I have to integrate my plugins in an Eclipse-based environment. I want these plugins be activated by PeaberryActivation in order to use guice DI & GuiceExtensionFactory. (ie export a service from one plugin, and inject it in a ViewPart? constructor in an other plugin). Is it possible ? Can I "re-bundle" the PeaberryActivation bundle to deploy it with my plugins ? Am I completely off track ?
As I didn't succeed in installing and running peaberry activation bundle, I used the old version (all declarations in bundle activator). To activate my bundle, I decide to use the Startup extension point. It works... but I've read that using such extension point is not recommended.