OSGi Declarative Services: Configuring multiple instances of a component

Declarative Services are a nice way to create services in the OSGi environment, but there are some subtleties that are not always easy to get under control. Imagine you have a component that observes a directory and acts on the presence of new files (a “hotfolder”, much like the Apache Felix FileInstall bundle). You may have multiple such directories which you want to observe, which you want to configure. You could create a ManagedServiceFactory but there’s a simpler way with Declarative Services once you’ve figured out the subtleties.
Let’s first define a very simple service interface:

public interface Greeter {
    void sayHello();
}

Then let’s implement this interface:

public class GreeterImpl implements Greeter {

    private ComponentContext context;

    protected void activate(ComponentContext context) {
        this.context = context;
        System.out.println("Creating new greeter for " + getName()
                + ": " + context.getComponentInstance().toString());
    }

    protected void deactivate(ComponentContext context) {
        System.out.println("Deactivating greeter for " + getName()
                + ": " + context.getComponentInstance().toString());
        this.context = null;
    }

    public void sayHello() {
        System.out.println("Hello, I'm " + getName());
    }

    private String getName() {
        return (String)this.context.getProperties().get("name");
    }

}

Through the activate(ComponentContext) method, we’ll get the component’s configuration once an instance is activated.
Now, we need the component descriptor for our Greeter component (OSGI-INF/component.xml):

<?xml version="1.0" encoding="UTF-8"?>
<component name="demo.scr.componentfactory.greeter">
  <!-- No factory attribute here! -->
  <implementation class="demo.scr.componentfactory.impl.GreeterImpl"/>
  <service>
    <provide interface="demo.scr.componentfactory.Greeter"/>
  </service>
  <property name="service.description"
      value="A nice component that can say hello"/>
</component>

If you know a little bit about Declarative Services, you might at first expect a factory attribute on the component, since we’re going to create multiple instances, but it would really just wrong with the approach I’m showing here. The final missing clue to make this work with multiple configurations lies with the MetaType descriptor we need to build (OSGI-INF/metatype/metatype.xml):

<?xml version="1.0" encoding="UTF-8"?>
<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0"
    localization="OSGI-INF/metatype/metatype">
  <metatype:OCD id="demo.scr.componentfactory.greeter"
      name="SCR Component Factory Demo">
    <metatype:AD id="name" type="String"
        name="%name.name" description="%name.desc"/>
  </metatype:OCD>
  <metatype:Designate pid="demo.scr.componentfactory.greeter"
      factoryPid="demo.scr.componentfactory.greeter">
    <metatype:Object ocdref="demo.scr.componentfactory.greeter"/>
  </metatype:Designate>
</metatype:MetaData>

Note especially the “Designate” Element which has both a “pid” and a “factoryPid” attribute. The important part is the “factoryPid” attribute which will enable the desired “factory” functionality.
If you want, a properties file with the translations (OSGI-INF/metatype/metatype.properties):

name.name=Name
name.desc=The name. D'oh!

Just for completeness and illustration, here’s the effective bundle manifest:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: DemoScrComponentFactory
Bundle-SymbolicName: demo.scr.componentfactory
Bundle-Version: 1.0.0
Export-Package: demo.scr.componentfactory
Import-Package: org.osgi.framework,
 org.osgi.service.cm,
 org.osgi.service.component,
Service-Component: OSGI-INF/component.xml

If you deploy the resulting bundle in Felix, for example, you should see our new component in the dropdown list of factory configurations on the configuration page of Felix’s WebConsole.
Besides the UI in the WebConsole, you can also use Apache Felix FileInstall’s configuration feature. Just create a properties file (ex. “demo.scr.componentfactory.greeter-C3PO.cfg”) in the directory observed by FileInstall:

name=C3PO, human cyborg relations

Just use the component’s PID followed by a dash and a name of your choosing. You can create as many configuration files as you like. For each one, SCR will create a service for you.

Update, 2010-05-19:
There may be a little problem with the above. If there are no configurations in ConfigurationAdmin, one component instance will still always be activated which may have unwanted side-effects if your component depends on certain values in the configuration. If you experience that, you can switch to the version 1.1 of Declarative Services and use the configuration policy to control this. The following example will require at least one configuration in the ConfigurationAdmin before the component is activated:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
    name="demo.scr.componentfactory.greeter"
    configuration-policy="require">
[..]
</scr:component>
Getagged mit: , , ,
Veröffentlicht unter Java, OSGi
2 Kommentar auf “OSGi Declarative Services: Configuring multiple instances of a component
  1. Johny Tex sagt:

    I’m doing something similar, but I’m having problems with creation of new component instances with exactly the same configuration.

    Using your example, say I’m using the web console to save the configuration name=”C3PO, human cyborg relations”. How can I avoid creating a “duplicate”?
    I could probably look for a service with this name in the component activation method, and then throw an exception.

    Is there a better solution?

    My components are instantiated with “immediate = true” (using annotations rather than xml), maybe this is related to my issue

    • Jeremias Märki sagt:

      I don’t think this is directly related to declarative services or using annotations. Basically, every factory-based configuration gets its own unique ID and there’s nothing to stop you from creating two equivalent (and in your case possibly conflicting) configurations. There are no built-in precautions against that because in other cases it might be totally OK.
      In the Configuration Admin Spec, there is a ConfigurationPlugin that allows to modify configurations on the fly but there is not “veto” mechanism to reject a configuration. Also, the ConfigurationListener interface is called asynchronously and therefore without consequence from a thrown exception, so no help from there, either. The only thing you could do is delete the duplicate configuration in the ConfigurationListener but that is likely to still instantiate a new instance even if it is short-lived. And that could already be problematic.
      So, in the end, I don’t see any other way than to verify the validity of a configuration prior to activating an instance. Find out if you can find a duplicate service just by doing a service lookup with the right filter on the service properties. In the worst case, you’ll have to acquire the service and query it. Maybe there is a better solution, but I don’t see it.
      In one of my applications, I have a central “system health” component (with a GUI addition on the side) where I list all potentially conflicting configurations/services. The administrator is then responsible to resolve the configuration problems. HTH and good luck!