3

I have services that need to be created on a per-configuration base, each of which relies on an external resource and thus should manage it's own lifcycle (i.e. (de)register the service). Thus implementing these as DS and let SCR spawn multiple instances does not work.

One can implement a bundle that registers a ManagedServiceFactory to accomplish this task perfectly (see my previous post). But as a consequence, if the factory depends on several other services, you need to start tracking those services and write a lot of glue code to get everything running. Instead I'd like to implement the factory as (singleton) declarative service, for which the SCR registers a ManagedServiceFactory at the Service Registry.

Here's my attempt:

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.component.ComponentContext;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class Factory implements ManagedServiceFactory {

private BundleContext bundleCtxt;
private Map<String, ServiceRegistration> services;

public void activate(ComponentContext context) throws Exception {
    System.out.println("actiavting...");
    this.bundleCtxt = context.getBundleContext();
    services = new HashMap<String, ServiceRegistration>();
}

public void deactivate(ComponentContext context) {
    for(ServiceRegistration reg : services.values()) {
        System.out.println("deregister " + reg);
        reg.unregister();
    }
    services.clear();
}

@Override
public String getName() {
    System.out.println("returning factory name");
    return "my.project.servicefactory";
}


@Override
public void updated(String pid, Dictionary properties)
        throws ConfigurationException {
    System.out.println("retrieved update for pid " + pid);
    ServiceRegistration reg = services.get(pid);
    if (reg == null) {
        services.put(pid, bundleCtxt.registerService(ServiceInterface.class,
                new Service(), properties));
    } else {
        // i should to some update here
    }
}

@Override
public void deleted(String pid) {
    ServiceRegistration reg = services.get(pid);
    if (reg != null) {
        reg.unregister();
        services.remove(pid);
    }
}
}

and the service description:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="ignore" name="my.project.servicefactory">
   <implementation class="my.project.factory.Factory"/>
   <service>
      <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
   </service>
   <property name="service.pid" type="String" value="my.project.servicefactory"/>
</scr:component>

I already found out that the "factory" property in the service description is the wrong path, because this way the component is never registerd as ManagedServiceFactory in the Service Registry, instead it becomes a ComponentFactory.

As a kind of hack, I just added a component property, namely

<property name="service.pid" type="String" value="my.project.servicefactory"/>

and added configuration-policy="ignore". This works: configurations named my.project.servicefactory-foobar.cfg are handed to my service, which registers them as in the Service Registry, everything fine.

But theres two things about it i do not like:

  1. manually setting the property service.pid feels like a dirty hack to me
  2. setting configuration-policy="ignore" prevents me from configuring the ManagedServiceFactory itsself. If I escape this property or set it to require, I would get one ManagedServiceFactory for a configuration named my.project.servicefactory.cfg and then two services for each configuration named with the pattern my.project.servicefactory-foobar.cfg: one ManagedServiceFactory that the SCR spawns and one ServiceInterface that my first ManagedServiceFactory registers when it gets notified about this new configuration. (At least this is not growing exponetially because SCR overwrites the service.pid property for factory configurations)

So how should I set this up properly?

PS: For those wondering about my reference to configurations on their filenames: I use Felix Fileinstall for the configurations, thus foo.cfg is put to the ConfigAdmin for PID foo, and foo-bar.cfg is put there for factory-pid foo.

Community
  • 1
  • 1
benjamin
  • 1,066
  • 11
  • 22

2 Answers2

3

Just use your DS instance headless, the properties, and register the service yourself:

@Component(immedate=true, provide={}, serviceFactory=true, configurationPolicy=require)
public class Mine {
    BundleContext context;
    volatile ServiceRegistration r;

    @Activate
    void activate(BundleContext context, Map<String,Object> map) {
         this.context = context;
         track(map);
    }

    @Deactivate
    void deactivate() {
        if ( r != null)
              r.unregisterService();
    }

    void track(Map<String,Object> map) {
       ... // do your remote stuff
       r = context.registerService(...);
       ...
    }
}
Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
  • This does solve the problem indeed, so I'll accept this although it's not an anwer to the exact question. I suspect on cannot expect to be able to realize lower level interfaces such as the MSF with higher level tools such as DS. Your solution is really nice and simple (I'm wondering how I could not see that), thank you. – benjamin Aug 13 '12 at 12:59
2

Why doesn't the support in DS for this not work for you? See 112.6:

Factory Configuration – If a factory PID exists, with zero or more Configurations, that is equal to the configuration PID, then for each Configuration, a component configuration must be created that will obtain additional component properties from Configuration Admin.

This says that if the configuration pid of your component is the same as the factory pid in CM, then DS will create an instance of your component for each configuration under the factory pid.

BJ Hargrave
  • 9,324
  • 1
  • 19
  • 27
  • This does not work, because then the Service Component Runtime (SCR) would also manage this component's lifecycle, i.e. register it as a service immediately (after having given it the configuration). But I need to control this myself, to be able to (de)register the service depending on the availability of some network device. My services basically monitor external devices and register them as services as soon as they get available, and deregister them as soon as they go away. – benjamin Aug 08 '12 at 17:10
  • 1
    So break it into several components. One monitors each external device and registers a simple service representing the functionality of that device. Another is your higher level component that depends on the device service and also requires configuration (i.e. configuration policy "require"). – Neil Bartlett Aug 08 '12 at 17:36
  • Good idea, although the 'monitoring' in my case is more than just waiting for an IP adress to aswer, it also needs to check whether or not the device is (yet) completely initialized, which in turn needs the configuration that is externalized in your proposal. In other terms, this configuration is already for the lowest level of abstraction, which means it can't be split. – benjamin Aug 09 '12 at 06:52