OSGi Configuration Admin and Plugin start ordering
The current ConfigurationAdmin specification can be found here:
https://osgi.org/specification/osgi.cmpn/7.0.0/service.cm.html
One feature of this specification is the Configuration Plugin. An implementation can be used to participate in the configuration process. You can e.g. add new properties or modify existing properties for a configuration.
A use case could be to substitute credential information. Where the default configuration just contains placeholder values, the plugin can substitute these values by the real ones, that can come from a system property or environment variable. The following example will show how to do this.
Configurable Component
We use the Configurator to make the example more handy. So here is an example configuration:
{
":configurator:resource-version": 1,
"ExampleConfig":
{
'your.prop.here': 'ENV.TEST'
}
}
An component that consumes this configuration can look like this:
package demo.cm;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
@Component(configurationPid = "ExampleConfig", configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true)
public class ExampleComponent {
@interface Config {
String your_prop_here();
}
@Activate
public void activate(Config config) {
System.out.println("Activate ExampleConfig: " + config.your_prop_here());
}
}
We put all in one bundle named demo.cm. When running this example we end up with a print-out like this:
____________________________
Welcome to Apache Felix Gogo
g! Activate ExampleConfig: ENV.TEST
Configuration Plugin
Because we are not happy the value ENV.TEST, we want to replace that. The Configuration Admin specification has the Configuration Plugin for that. This is just an interface to be implemented and registered as a service.
To outline the behavior, we want to have that implementation in a different bundle that is named e.g. demo.cm.plugin
package demo.cm.plugin;
import java.util.Dictionary;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationPlugin;
import org.osgi.service.component.annotations.*;
@Component(property = "cm.target=ExampleConfig")
public class ExamplePlugin implements ConfigurationPlugin {
@Override
public void modifyConfiguration(ServiceReference<> reference, Dictionary<String, Object> properties) {
String prop = (String) properties.get("your.prop.here");
System.out.println("Config plugin current value: " + prop);
if (prop != null && prop.startsWith("ENV")) {
prop = "my.value.there";
System.out.println("Config plugin new value: " + prop);
properties.put("your.prop.here", prop);
}
}
}
When we now starting our bundles demo.cm and the demo.cm.plugin we may end up with the final result:
____________________________
Welcome to Apache Felix Gogo
g! Activate ExampleConfig: my.value.there
WHAT? We expected something like:
____________________________
Welcome to Apache Felix Gogo
Config plugin current value: ENV.TEST
Config plugin new value: my.value.there
Activate ExampleConfig: my.value.there
Whats the problem?
YES, it is possible that the substitution doesn’t work. To outline the problem I show two different run configuration in bndtools:
This configuration may not work:
...
-runbundles: \
demo.cm;version=snapshot,\
org.apache.felix.configadmin;version='[1.9.16,1.9.17)',\
org.apache.felix.configurator;version='[1.0.8,1.0.9)',\
org.apache.felix.scr;version='[2.1.16,2.1.17)',\
org.osgi.util.function;version='[1.1.0,1.1.1)',\
org.osgi.util.promise;version='[1.1.0,1.1.1)',\
demo.cm.plugin;version=snapshot
whereas this configuration works:
...
-runbundles: \
org.apache.felix.configadmin;version='[1.9.16,1.9.17)',\
org.apache.felix.configurator;version='[1.0.8,1.0.9)',\
org.apache.felix.scr;version='[2.1.16,2.1.17)',\
org.osgi.util.function;version='[1.1.0,1.1.1)',\
org.osgi.util.promise;version='[1.1.0,1.1.1)',\
demo.cm.plugin;version=snapshot,\
demo.cm;version=snapshot
The difference is the start ordering of the bundles. This is related to the dynamics in OSGi. The demo.cm as well as Configurator and ConfigAdmin bundle starting very early, just before the demo.cm.plugin bundle. Means that no plugin service is available when we consume the configuration in our ExampleComponent in the demo.cm bundle.
Apache Felix Configuration Admin
As of version 1.9.16 the Apache Felix Configuration Admin implementation contains a feature to deal with this situation. The corresponding entry in the Apache bug tracker is FELIX-6059.
What to do? At first the plugin needs a additional service property config.plugin.id:
@Component(property = {"cm.target=ExampleConfig", "config.plugin.id=MyValuePlugin"})
public class ExamplePlugin implements ConfigurationPlugin {
...
}
The seconds thing is to add the framework property felix.cm.config.plugins with a comma separated list of plugins (config.plugin.id - values) to wait for, before activating the Configuration Admin instance.
We have to add this property to the previous non-working configuration:
...
-runbundles: \
demo.cm;version=snapshot,\
org.apache.felix.configadmin;version='[1.9.16,1.9.17)',\
org.apache.felix.configurator;version='[1.0.8,1.0.9)',\
org.apache.felix.scr;version='[2.1.16,2.1.17)',\
org.osgi.util.function;version='[1.1.0,1.1.1)',\
org.osgi.util.promise;version='[1.1.0,1.1.1)',\
demo.cm.plugin;version=snapshot
-runproperties: felix.cm.config.plugins=MyValuePlugin
Now we get the expected result:
____________________________
Welcome to Apache Felix Gogo
Config plugin current value: ENV.TEST
Config plugin new value: my.value.there
Activate ExampleConfig: my.value.there
Thanks to Carsten Ziegeler, who implemented that!
The forth-coming Condition Service will also be useful in such situation. Because start ordering should play a role in OSGi you will nevertheless need some conditions to activate a service or whatever. If you are interested, please read this blog post:
https://blog.osgi.org/2019/08/new-in-osgi-r8-condition-service.html
by Ilenia Salvadori, Mark Hoffmann, Jürgen Albert