0

I am working on an application that is meant to be extensible by the customer. It is based on OSGi (Equinox) and makes heavy use of Declarative Services (DS). Customer-installed bundles provide their own service implementations which my application then makes use of. There is no limit on the number of service implementations that customer-specific bundles may provide.

Is there a way to ensure that, when the application's main function is executed, all customer-provided service implementations have been registered?

To clarify, suppose my application consists of a single DS component RunnableRunner:

public class RunnableRunner
{
    private final List<Runnable> runnables = new ArrayList<Runnable>();

    public void bindRunnable(Runnable runnable)
    {
        runnables.add(runnable);
    }

    public void activate()
    {
        System.out.println("Running runnables:");
        for (Runnable runnable : runnables) {
            runnable.run();
        }
        System.out.println("Done running runnables.");
    }
}

This component is registered using a DS component.xml such as the following:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="RunnableRunner" activate="activate">
   <implementation class="RunnableRunner"/>
   <reference bind="bindRunnable" interface="java.lang.Runnable" name="Runnable"
              cardinality="0..n" policy="dynamic"/>
</scr:component>

I understand that there is no guarantee that, at the time activate() is called, all Runnables have been bound. In fact, experiments I made with Eclipse/Equinox indicate that the DS runtime won't be able to bind Runnables contributed by another bundle if that bundle happens to start after the main bundle (which is a 50/50 chance unless explicit start levels are used).

So, what alternatives are there for me? How can I make sure the OSGi containers tries as hard as it can to resolve all dependencies before activating the RunnableRunner?

Alternatives I already thought about:

  • Bundle start levels: too coarse (they work on bundle level, not on component level) and also unreliable (they're only taken as a hint by OSGi)
  • Resorting to Eclipse's Extension Points: too Eclipse-specific, hard to combine with Declarative Services.
  • Making the RunnableRunner dynamically reconfigure whenever a new Runnable is registered: not possible, at some point I have to execute all the Runnables in sequence.

Any advice on how to make sure some extensible service is "ready" before it is used?

2 Answers2

2

By far the best way is not to care and design your system that it flows correctly. There are many reasons a service appears and disappears so any mirage of stability is just that. Not handling the actual conditions creates fragile systems.

In your example, why can't the RunnableRunner not execute the stuff for each Runnable service as it comes available? The following code is fully OSGi dynamic aware:

@Component
public class RunnableRunner {

  @Reference Executor executor;

  @Reference(policy=ReferencePolicy.DYNAMIC)
  void addRunnable( Runnable r) {
     executor.execute(r);
  }
}

I expect you find this wrong for a reason you did not specify. This reason is what you should try to express as a service registration.

If you have a (rare) use cases where you absolutely need to know that 'all' (whatever that means) services are available then you could count the number of instances, or use some other condition. In OSGi with DS the approach is then to turn this condition into a service so that then others can depend on it and you get all the guarantees that services provide.

In that case just create a component that counts the number of instances. Using the configuration, you register a Ready service once you reach a certain count.

public interface Ready {}

@Component
@Designate( Config.class )
public class RunnableGuard {
   @ObjectClass
   @interface Config {
      int count();
   }
   int count = Integer.MAX_VALUE;
   int current;
   ServiceRegistration<Ready> registration;

   @Activate 
   void activate(Config c, BundleContext context) {
      this.context = context;
      this.count = c.count();
      count();   
   }
   @Deactivate void deactivate() {
      if ( registration != null ) 
        registration.unregister();
   }

   @Reference 
   void addRunnable( Runnable r ) { 
     count(1); 
   }
   void removeRunnable( Runnable r ) { 
      count(-1); 
   }

   synchronized void count(int n) {
      this.current += n;
      if ( this.current >= count && registration==null) 
        registration = context.registerService( 
           Ready.class, new Ready() {}, null 
        );
      if ( this.current < count && registration != null) {
        registration.unregister();
        registration = null;
      }
   }
}

Your RunnableRunner would then look like:

@Component
public class RunnableRunner {

  @Reference volatile List<Runnable> runnables;

  @Reference Ready ready;

  @Activate void activate() {
    System.out.println("Running runnables:");
    runnables.forEach( Runnable::run );
    System.out.println("Done running runnables.");
  }
}

Pretty fragile code but sometimes that is the only option.

I did not know there were still people writing XML ... my heart is bleeding for you :-)

Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
  • Thanks for the advice, Peter! You're right, I didn't clearly specify why I can't make the RunnableRunner dynamic like you suggested. The reason is that, in the actual software, a process with (real-world) side effects is started. It's a one-off, and it's important to get right.The counting mechanism is nice, but unfortunately I don't know the actual number of dependencies beforehand. – Jonas Eschenburg Aug 10 '16 at 15:34
  • The count was just an example of a condition ... You have to exactly define what your actual condition is and then turn that condition into a service. That was what I tried show. This is a common pattern in OSGi: find your condition, turn it into a service, and then depend on it. Makes things explicit. – Peter Kriens Aug 10 '16 at 16:14
  • why have you synchronized `deactivate` and `count`? deactivation and injection of references can happen concurrently ? – Jérémie B Aug 10 '16 at 20:41
  • The synchronized on the deactivate is not necessary, when deactivate is called all services have been uninjected. I've removed it – Peter Kriens Aug 11 '16 at 07:21
  • My mistake was to assume that there is some natural definition of "all" (purposefully underspecified by me) extensions available to the DS runtime. Apparently this is not the case, although static analysis of component XMLs seems to be an option. However, as (like @PeterKriens pointed out) the trend goes towards using annotations, such static analysis becomes less and less feasible. – Jonas Eschenburg Aug 11 '16 at 07:59
  • all is in the eye if the beholder and in a dynamic environment a moving target – Peter Kriens Aug 11 '16 at 12:38
0

If you do not know which extensions you need to start then you can only make you component dynamic. You then react to each extension as it is added.

If you need to make sure that your extensions have been collected before some further step may happen then you can use names for your required extensions and name them in a config.

So for example you could have a config property "extensions" that lists all extension names spearated by spaces. Each extension then must have a service property like "name". In your component you then compare the extensions you found with the required extensions by name. You then do your "activation" only when all required extensions are present.

This is for example used in CXF DOSGi to apply intents on a service like specified in remote service admin spec.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
  • Thanks for the suggestion of listing required extensions in a configuration property. A problem I see with this approach is that the customer not only needs to install a new bundle, but also edit a central configuration file. I'd prefer if the required information were provided by the individual extension bundles in a decentralized way. – Jonas Eschenburg Aug 10 '16 at 15:25
  • Like Peter also suggests if you want to guarantee that all your extensions are present you have to specify some condition for it. This can not be solved in a completely generic way. – Christian Schneider Aug 10 '16 at 22:58