Configuration Admin is amongst the most used services from the OSGi specification. But on the other hand, the usage is barely noticed. Component frameworks like Declarative Services (DS) or CDI Integration take care of the heavy lifting. Configuration Admin allows to create, update and delete configurations. It is up to the implementation where these configurations are stored. A configuration has a unique persistent identifier (PID) and a dictionary of properties. This guide explains how to best interact with Configuration Admin when writing OSGi code. It is based on over a decade of experience working on large enterprise applications using OSGi. It might not apply to every OSGi application.
Do not talk to Configuration Admin
The basic rule for dealing with OSGi configurations is very simply: do not write code that talks to Configuration Admin directly. But of course you should use OSGi configurations to configure your application. So how does that work? The answer is very easy: let someone else talk to Configuration Admin and do the work for you.
A very nice way to develop OSGi components and services is to use Declarative Services (DS). By using Java annotations, the component code tells DS which configuration(s) it wants to consume. DS does all the hard work behind the scenes. Defining a Component Property Type simplifies the usage further. A component property type provides type conversion and default values.
But if it is that easy why am I writing this? It is good to know some of the details about configuration handling – especially in cases where you cannot use DS.
Be Aware of Configuration Plugins
OSGi configurations are used to configure the whole system. In many cases that includes configurations for connections to other systems or services. Such configurations usually include endpoints and credentials. The values for these properties might depend on your environment: a different endpoint is used in development than in production. In addition, you don’t want to store credentials or any other secrets directly in your code or configuration.
A good mechanism for managing such configurations is to use “late binding” of the values. Instead of storing the values directly in the OSGi configuration you use placeholders. The placeholders get replaced at runtime with the real values. Support for such placeholders usually comes in the form of Configuration Plugins. The Apache Felix Interpolation Plugin is a very nice plugin supporting environment variables, system properties and secrets. For example, a configuration for a connection could look like this (using the Configurator JSON notation) :
{
"de.osoco.business.server" : {
"url" : "$[env:BUSINESS_SERVER_URL]",
"user": "$[env:BUSINESS_SERVER_USER]",
"password" : "$[secret:business.server.pw]"
}
}
It uses environment variables for the url and the username and a secret for the password. With such an approach, the configuration stored in Configuration Admin only contains the above visible placeholders. However, when the configurations is provided to the code using the configuration, a plugin like the Apache Felix Interpolation Plugin needs to be invoked, replacing the placeholders with real values and then handing out the configuration.
The good news is, if you are using Declarative Services this happens automatically and you don’t have to worry about it.
ManagedService(Factory)
However, sometimes there are use cases where you either can’t or want to use Declarative Services (or a similar component framework). The second best way to deal with configurations is to register either a ManagedService or ManagedServiceFactory. These are basically callbacks which are called by Configuration Admin with the configurations, the registered service is interested in. And the good part is, Configuration Admin calls the configuration plugins before handing out the configuration.
Threefore it is advisable to either use Declarative Services or register a managed service for consuming OSGi configurations. But there might be some very rare use cases where both is not possible and you need to talk to Configuration Admin directly.
How to Talk to Configuration Admin
First, as explained, try to avoid this situation. If you can’t, be aware of the following points. If you want to get a configuration from Configuration Admin do not use one of the getter methods like getConfiguration or getFactoryConfiguration. These methods have side effects and will create such a configuration if it does not exist! Use listConfigurations instead:
ConfigurationAdmin ca = ...;
Configuration[] configs =
ca.listConfigurations("service.pid=de.osoco.business.server");
if ( configs != null && configs.length > 0 ) {
// use configuration (we just assume we got only one configuration back)
Configuration cfg = configs[0];
...
}
Once you have the configuration, the next thing is to get its properties – there is a method called getProperties on a Configuration object – but do you remember the placeholders and configuration plugins? If you call getProperties these are not invoked, and the values will be the placeholders itself! Therefore don’t use that method for consuming configurations – use getProcessedProperties instead. That method invokes all plugins before handing out the properties.
To identify the caller getProcessedProperties takes a required ServiceReference as a parameter. If you already have a reference at hand, use it – if not, you need to register a service in the service registry.
Avoid Configuration Binding
Finally, if you want to create configurations, the previously mentioned getter methods (getConfiguration and getFactoryConfiguration) can be used to do so. Make sure that you use the variant which takes the additional location parameter and pass in null for the location. If you don’t do this, then the configuration gets bound to your bundle and will not be delivered to other consumers anymore. Configuration binding and targeting is a complicated topic – which is best to avoid. Of course, as always, there are good use cases for these features. Only use it if you really need it. This article contains some information about configuration binding and DS.
I hope with these tips, handling OSGi configurations becomes very easy. Just as a recap:
- Use component frameworks like Declarative Services – all hard work is done for you
- If that’s not possible, use ManagedService or ManagedServiceFactory – again most of the hard work is done for you
- If that’s also not possible, make sure to not create configurations as a side effect and use the right method to get properties
- Avoid configuration binding and targeting if possible