This section presents a quick overview of the capabilities and usage of the DependencyManager java 5 annotations. In particular, we will recap the DependencyManager annotation architecture, and identify some simple usage scenarios using a SpellChecker sample application with annotated components (the application is available from the felix trunk, in the dependencymanager/samples.annotation maven subproject).
Instead of writing Activators which extends the DependencyActivatorBase class, service components can now be annotated using the annotations provided by the org.apache.felix.dependencymanager.annotation bundle. Annotations are not reflectively parsed at runtime; but we use a BND plugin which scans annotations at compilation phase and generates a compact metadata file in the bundle's META-INF/dependencymanager subdirectory. This has the following benefits:
- JVM startup speed is not affected, and class files are not parsed when the framework is starting
- Moreover, since the annotations are not retained by the VM at runtime, it is not necessary to load the annotation API bundle at runtime.
At runtime, the metadata generated during the compilation phase are processed by a specific DependencyManager Runtime bundle, which is in charge of managing the service component lifecycle and dependencies. This Runtime bundle actually uses the DependencyManager programmatic API in order to manage the annotated components. Annotated components can then be inspected with the DependencyManager Gogo shell, as it is the case with DM components declared through the programmatic DM API.
To register a service, your can annotate your class with a @Component annotation, and an instance of your class will be registered under all directly implemented interfaces into the OSGi registry. You can however take control on the interfaces to be exposed, and in this case, you can use the provides attribute, which takes a list of classes to expose from the registry.
To illustrate this, we are now introducing a SpellChecker application which provides a Felix "spellcheck" Gogo shell command. Gogo is the new shell supported by the Felix Framework. Our "spellcheck" command is implemented by the SpellChecker component which accepts a string as parameter. This string is then checked for proper existence. To do the checking, The SpellChecker class has a required/multiple (1..N) dependency over every available DictionaryService services. Such DictionaryService represents a real dictionary for a given language (it has a lang service property), and is configurable/instantiable from Configuration Admin.
The OSGi Configuration Admin service provides a mechanism for configuring components (using ManagedService interfaces), and WebConsole actually implements this service. ConfigAdmin is also able to instantiate some Services (using ManagedServiceFactory interfaces).
Now we have introduced the background, here is the SpellCheck component:
In the code above, you see that the SpellCheck is annotated with the @Component annotation. Gogo runtime does not required shell commands to implement a specific interface. Commands just have to register some Pojos in the OSGi registry, but the only thing required is to provide the Pojos with two service properties ( COMMAND_SCOPE, and COMMAND_FUNCTION) which will be used by the Gogo runtime when instropecting the Pojo for invoking the proper functions.
So, coming back to the sample code, the SpellChecker class registers itself into the OSGi registry, using the provides attribute, which just refer to our SpellChecker class, and the two mandatory Gogo service properties are also specified using the @Property annotation. It is not shown here, but service properties can also be provided dynamically from a method that can return a Map, and annotated with the @Start lifecycle callback, but we will see this feature in a another section.
Our SpellChecker component can expose itself as a Gogo shell command, but before being registered into the OSGi registry, we also need to be injected with two dependencies: one required dependency (at minimum) on a DictionaryService, and another optional one on a LogService. First, let's look at the DictionaryService, which is a simple interface:
And here is our previous SpellChecker component, augmented with two new ServiceDependency annotations:
There are many things to describe in the code above:
First, we define an optional dependency on the LogService, by defining a @ServiceDependency(required=false) annotation on our m_logService field: This means that our component will be provided into the OSGi registry even if there is no available LogService, and in this case, a NullObject will be injected in our class field; This will avoid to check for nullability, when using the m_logService field. All optional dependencies applied on class fields are injected with a NullObject (when not available). The NullObject can be invoked and will do nothing. For a lot of cases that is good enough to handle optional dependencies. But when you really want to check if an optional service is there or not, then you have to apply the optional dependency on a callback method, which will be called when the optional service is available.
Next comes the dependency on the DictionaryService. Here, we use a ServiceDependency annotation, but this time we apply it on a method (add/removeDictionary). There is no need to specify the "required=true" flag because it is the default value. Notice that this behavior is different from the API, where service dependencies are optional by default. We use a callback method, because we just need to register all available DictionaryService services in our dictionary list, which is used when checking word existence. This list is a copy on write list because the dependency may be injected at any time, possibly from another thread. So, using a copy on write list avoid us to use synchronized methods.
The @Component annotation is not the only one for creating services. Another one is the @FactoryConfigurationAdapterService annotation which allows to instantiate many instances of the same annotated service class from ConfigAdmin (and WebConsole). To illustrate this, let's take a look at our DictionaryImpl class which is part of the SpellChecker sample. This service is required by the SpellChecker component, when checking for proper word existence. And you can instantiate as many DictionaryService as you want, from ConfigAdmin ...
Our DictionaryImpl class implements a DictionaryService, and our class will be registered under that interface (all directly implemented interfaces are used when registering the service, but you can select some others using the provides attribute). The @FactoryConfigurationAdapterService annotation will instantiate our service for each configuration created from web console (and matching our "DictionaryImplFactoryPid" factoryPid).
We also use the updated attribute, which specifies a callback method which will handle properties configured by ConfigAdmin. The updated callback will also be called when our properties are changing. Every properties are propagated to our service properties, unless the properties starting with a dot ("."). Configuration properties starting with a dot (".") are considered private and are not propagated.
Notice that this annotation also supports optional meta type attributes, which allow to customize the ConfigAdmin GUI, with custom messages, like heading/property title, property type, property description, etc ...). So, let's revisit our DisctionaryImpl service, but this time with meta type support:
As we have seen in the previous section, there are many annotations that can be used to specify a service. Another one is the @AspectService annotation. This annotation allows to decorate an existing service in order to add certain "capabilities" to it, like adding a specific caching mechanism to a storage service or implementing logging. Aspects can be plugged to an existing service at runtime, and can also be removed dynamically. This is transparent, and the clients using the existing service are not interrupted, they are just rebound with the aspect service.
As an example, we go back to our SpellChecker application, and we are now looking at the DictionaryAspect class. This class uses the @AspectService annotation in order to add some custom words to an English DictionaryService (with the service property lang=en). The Extra words to add to the English Dictionary will be configured from ConfigAdmin. That's why the class also uses a @ConfigurationDependency annotation:
The annotation does the following: because our class implements the DictionaryService contract, it will instantiate our service each time it finds another existing DictionaryService matching the filter attribute we provide in the annotation (filter="(lang=en)"). And it will inject the existing service in our m_originalDictionary field, by reflection. But we can also specify a field attribute in the annotation, if we want to explicitly inject the existing service in a given class field. So, any client depending on an English DictionaryService will be transparently rebound to our aspect Dictionary.
In the Annotation, also notice the ranking attribute: It is the level used to organize the aspect chain ordering (multiple aspects may be applied on a given service).
The ConfigurationDependency is another dependency that we have not seen before: it is used to configure the extra English words from ConfigAdmin. This annotation normally requires a pid parameter, which is a persistent identifier uniquely identifying our component, but by default, the pid is set to the fully qualified name of our class.
Notice that like the @FactoryConfigurationAdapterService, the @ConfigurationDependency annotation also supports meta type attributes.
- Install the following bundles:
- Start felix
- Go to web console: in the Configuration panel, edit the "Dictionary Services" Configuration. By default, an English dictionary is displayed. Just click on "save", then refresh your web browser (click on refresh): you will see a new dictionary service instance. At this point, a DictionaryService service will be enabled (with the service property "lang=en"), and the SpellCheck component will be injected with it. Then you should see the "spellcheck" command, when typing "help" on the gogo shell.
- Just type "spellcheck hello", and the command should reply a fantastic message, like "word hello is correct".
- You can also click on the "Aspect Dictionary" button, in order to decorate the English dictionary with some custom words. By default, the "aspect" word is pre configured, but you can click on the "+" button in order to add more words. Then click on Save. At this point, the English DictionaryService will be decorated with the aspect service. So, now, if you type "spellcheck aspect", then the message: "word aspect is correct" should be displayed.