Search code examples
javaosgiapache-felixequinoxdeclarative-services

OSGi modify event method not called


I'm playing around with OSGi DS components and the ConfigurationAdmin.

I created a simple configurable component

@Component(service=ConfigurableService.class)
public class ConfigurableService {

  private String message;

  @Activate
  public void activate(Map<String, Object> params) {
    System.out.println("Activate configurable");
    message = (String) params.get("msg");
  }

  @Modified
  public void modified(Map<String, Object> params) {
    System.out.println("Modify configurable");
    message = (String) params.get("msg");
  }

  @Deactivate
  public void deactivate(Map<String, Object> params) {
    System.out.println("Deactivate configurable");
    message = (String) params.get("msg");
  }

  public void execute() {
    System.out.println("Service says: " + message);
  }
}

Then I created a Felix Gogo shell command component to trigger the configuration via ConfigurationAdmin

@Component(property =   {
    CommandProcessor.COMMAND_SCOPE + "=fipro",
    CommandProcessor.COMMAND_FUNCTION + "=configure"
},
service = ConfigurationCommand.class
)
public class ConfigurationCommand {

  private ConfigurationAdmin cm;

  @Reference(unbind="-")
  public void setConfigAdmin(ConfigurationAdmin cm) {
    this.cm = cm;
  }

  public void configure(String input) throws IOException {
    Configuration config = cm.getConfiguration("org.fipro.osgi.config.ConfigurableService");
    Hashtable<String, Object> props = new Hashtable<>();
    props.put("msg", input);
    config.update(props);
  }
}

And at last I created another Felix Gogo shell command component that makes use of the ConfigurableService

@Component(property =   {
    CommandProcessor.COMMAND_SCOPE + "=fipro",
    CommandProcessor.COMMAND_FUNCTION + "=welcome"
},
service = WelcomeCommand.class
)
public class WelcomeCommand {

  private ConfigurableService service;

  @Reference(unbind="-")
  public void setConfigurable(ConfigurableService service) {
    this.service = service;
  }

  public void updatedConfigurable(ConfigurableService service, Map<String, Object> properties) {
    System.out.println("ConfigurableService updated");
  }

  public void welcome() {
    service.execute();
  }
}

If I start an OSGi application with the bundles that contain these components, I expect that on executing welcome initially, I will see that the component is activated and the service output is null because no configuration is applied yet (sure this changes on consecutive calls). If I afterwards execute configure Dirk I expect that the method annotated with @Modified is executed to indicate that the service configuration has been updated. I also expect that the updatedConfigurable method in the WelcomeCommand is executed. At least that is my understanding from reading the spec.

Now I observe different behavior in Equinox and Felix.

Equinox:

The modified method is called as expected and the ConfigurableService is configured correctly. But the updatedConfigurable(<Service>, <Map>) is not called. Only if I change the method signature to take a ServiceReference the updated method gets called.

The specification says that all reference event methods support the following method signatures

void <method-name>(ServiceReference);
void <method-name>(<parameter-type>);
void <method-name>(<parameter-type>, Map);

Is there an exception for the updated method I haven't seen in the spec or is this an issue in Equinox where I should raise a ticket for?

Felix:

If I run the same example on Felix in Bndtools, neither the modified nor the update methods get called. I checked the ConfigurationCommand and there is a ConfigurationAdmin available so there is no exception on updating the configuration. But it gets never applied somehow.

Am I missing something on running the example on Felix?

Update:

Adding console outputs to every lifecycle event method creates the following output:

____________________________
Welcome to Apache Felix Gogo

g! ConfigurationCommand: Activate
ConfigurableService: Activate
WelcomeCommand: Activate
welcome
Service says: null
g! configure Dirk
g! welcome
Service says: null
g! exit 0
WelcomeCommand: Deactivate
ConfigurableService: Deactivate
ConfigurationCommand: Deactivate

As you can see, the modify and updated events are never called.


Solution

  • I think the problem is the lifecycle for Gogo commands.

    Gogo does not hold on to service objects while a command is not running. It tracks the ServiceReference but does not call getService until you actually invoke the welcome command. Therefore when you invoke welcome, the WelcomeCommand component will be instantiated, which forces the instantiation of the ConfigurableService at that time.

    Later when the welcome command completes, the WelcomeCommand is released, therefore both WelcomeCommand and ConfigurableService will be GC'd. Therefore no instance of ConfigurableService ever lives long enough to receive the Modified event.

    To address this, try making WelcomeCommand immediate:

    @Component(immediate = true, ...)
    

    UPDATE

    On further discussion with Dirk over email, it turns out that the issue is location binding. In Config Admin, configurations are by default "bound" to the bundle which creates them, in this case the bundle that contains ConfigurationCommand. Once bound they cannot be consumed by another bundle, so the ConfigurableService never sees the config.

    To create an unbound configuration that can be used by any bundle, call the two-arg version of ConfigAdmin.getConfiguration() and pass null for the second arg.