0
votes

I have written a BundleActivator which should update certain configurations before its bundle starts. I need the ConfigurationAdmin service, but I get a null ServiceReference from the BundleContext in the start method of the BundleActivator.

The BundleActivator extends following abstract class and only implements the specific update logic:

public abstract class AbstractConfigUpdater implements BundleActivator {

    private ServiceReference<ConfigurationAdmin> configurationAdminServiceReference;

    @Override
    public void start(final BundleContext context) throws Exception {
        configurationAdminServiceReference = context.getServiceReference(ConfigurationAdmin.class);
        final ConfigurationAdmin configurationAdmin = context.getService(configurationAdminServiceReference);
        final Configuration[] configurations =
                                         configurationAdmin.listConfigurations(getFilter());
        if (configurations != null) {
            for (final Configuration configuration : configurations) {
                final Dictionary<String, Object> properties = configuration.getProperties();
                    if (updateProperties(properties)) {
                    configuration.update(properties);
                }
            }
        }
    }

    protected abstract String getFilter();

    /**
     * Updates the properties if needed.
     *
     * @param properties
     *            the configuration properties
     * @return if any modifications to the Dictionary were made
     */
    protected abstract boolean updateProperties(final Dictionary<String, Object> properties);

    @Override
    public void stop(final BundleContext context) throws Exception {
        context.ungetService(configurationAdminServiceReference);
    }

}

I have added an annotation to the concrete BundleActivator to generate a manifest header to require the ConfigurationAdmin service to be available to the bundle:

@RequireCapability(filter = "(objectClass=org.osgi.service.cm.ConfigurationAdmin)",
               ns = "osgi.service",
               resolution = Resolution.mandatory)

The manifest header is generated, but I still get a null ServiceReference. How should I fix this? Or is there an alternative approach I could take to update configurations before their components are started?

2
Is there any reason at all not use Declarative Services (DS)? The problem you're trying to solve is really hard in an Activator but trivial in DS. - Peter Kriens
You also confuse static dependencies (Require-Capability) with dynamic dependencies (this bundle should be active). They are two different things and unrelated. The Require Capability ensures there is a bundle installed but there is no guarantee that the service is registered since this can depend on other things. - Peter Kriens
Thanks for the clarification on Require-Capability. The reason I chose a BundleActivator instead of a DS component, is that I want to update configurations on bundle startup, as the ObjectClassDefinition of the configurations has changed and I want to run the configuration update before activation of the corresponding DS components. - Auke te Winkel
Did you look at the OSGi Configurer in R7? (Or its predecessor in v2archive.osgi.enroute/osgi.enroute.configurator.simple.provider). That might solve your problems in an easier way. The way you're going now is a rabbit hole. - Peter Kriens

2 Answers

0
votes

I don't know if this could help, but you can develop a org.osgi.service.cm.ConfigurationPlugin to intercept all the properties that are injected at runtime and modify them:

public class MyConfigurationPlugin implements BundleActivator, ConfigurationPlugin {
    ServiceRegistration<ConfigurationPlugin> configPluginRef;

    @Override
    public void start(BundleContext context) throws Exception {
        //... init the config plugin
        Map<String,String> properties = new HashMap<>();            
        configPluginRef = context.registerService(
            ConfigurationPlugin.class, 
            this, 
            new Hashtable<>(properties));
    }

    @Override
    public void modifyConfiguration(ServiceReference<?> reference,
            Dictionary<String, Object> properties) {
        /*
         * View and possibly modify a set of configuration properties 
         * before they are sent to the Managed Service or the Managed Service Factory.  
         */
    }

}

Of course the Declarative Service approach is a way far simpler option:

@Component ( 
    service= {}, 
    configurationPid={
        configPid1,
        configPid2,
        ...
    })
public class MyComponent {

    @Activate
    public void activate(BundleContext context, Map<String, String> properties) {

    }

    @Modified
    public void updated(BundleContext context, Map<String, String> properties) {
        // Called when properties change
    }
}

but in this case you cannot alter properties values: you can only react to properties changes.

0
votes

You can use OSGi ServiceTracker to wait and retrieve the service from the service registry.

For example,

import org.osgi.framework.Constants
import org.osgi.framework.Filter;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.service.cm.ConfigurationAdmin;

...

private static final long TIMEOUT_MILLIS = 10000;

@Override
public void start(final BundleContext context) throws Exception {
    Filter filter = context.createFilter("(" + Constants.OBJECTCLASS + "=org.osgi.service.cm.ConfigurationAdmin)");
    ServiceTracker<?, ?> configurationAdminTracker = new ServiceTracker<>(context, filter, null);

    configurationAdminTracker.open();
    ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) configurationAdminTracker.waitForService(TIMEOUT_MILLIS);
    configurationAdminTracker.close();

    if (configurationAdmin == null) {
        // Not found
    }
    ...
}