4

I have an OSGi component MyComponent .

This component has reference to a service MyService. Now MyService has a couple of implementations MyServiceImpl1 and MyServiceImpl2. MyComponent also has property MyProperty.

Now what I want is that whenever MyProperty is 1, MyComponent.MyService binds to MyServiceImpl1. And if I change MyProperty to 2, MyComponent.MyService dynamically updates the MyService binding to `MyServiceImpl2.

How do I acheive this? For reference, I am using Apache Felix container and would prefer to avoid lower level OSGi apis.

sdm
  • 1,236
  • 11
  • 9

3 Answers3

8

The easiest way to configure dependencies is with the '.target' property. This requires that the implementations register with an identifying property, lets say impl=1. and impl=2.

@Component(property="impl=1")
public class MyServiceImpl1 implements MyService {
}
@Component(property="impl=2")
public class MyServiceImpl2 implements MyService {
}

The component then could look like:

@Component
public class MyComponent {
    @Reference(target="(impl=1)")
    volatile MyService myService;
}

In this case you won't be able to use 1 or 2 as flag but you would have to modify the configuration property for MyComponent with the name myService.target with another filter. (This is shown with OSGi standardized annotations.)

If you insist on a property that is 1 or 2 (let's call it select) for MyComponent then it is more elaborate. First we have the problem of being satisfied. Should MyComponent be satisfied when it needs according to the select property impl 1 but only 2 is available? If that is ok then the following much more complicated solution should work

@Designate( ocd=Config.class )
@Component( property = "select=1" )
public class MyComponent {
      static Class<?> types [] = {
          MyServiceImpl1.class,
          MyServiceImpl2.class,
      };
      @interface Config {
          int select() default 1;
      }

      @Reference(target="(|(impl=1)(impl=2))")
      volatile List<MyService> candidates;

      volatile MyService selected;

      @Activate
      @Modify
      void changed( Config config ) {
          Class<?> type = types[config.select()];
          Optional<MyService> service = candidates.
              stream().
              filter( type::isInstance ).
              findFirst();
          this.service = service.isPresent() ? service.get() : null;
      }
}

As you can see it is generally a bad idea for a component to start to handle its own dependencies. I am therefore curious to your real world scenario.

I find it always very awkward and hard to maintain designs where components are picky about who they reference. It is sometimes inevitable but in general solutions where MyServiceImpl2 and MyServiceImpl1 decide to register or not based on some condition reflect reality better.

So the big question I have what does the 1 or 2 property reflect in the real world? Can this not be modeled as a service dependency?

(disclaimer: code not tested and no error handling in it)

Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
  • Thanks Peter Kriens for a very insightful answer as always! Let me explain my use case. `MyComponent` basically is a token fetcher which talks to an external REST service to fetch a token. Now there are several variants of this REST service. The `MyServiceImpl`s are basically request mappers corresponding to different variants of the REST service. I need to provide a mechanism to have `MyComponent` choose the appropriate service impl. – sdm Jun 24 '16 at 07:42
  • The question is what the criterium is? Who sets the 0 or 1? – Peter Kriens Jun 25 '16 at 08:41
  • This is typically set by the system administrator who knows the REST service variant being used and configures the system appropriately. Additionally, `MyComponent` can potentially have multiple instances because of being a configuration factory. In such a scenario, each `MyComponent` instance will use the appropriate service implementation depending on its individual configuration. – sdm Jun 29 '16 at 11:11
3

I'm assuming implementation of MyService can be queried to report its type (e.g. below):

public interface MyService {
    public static final String TYPE = "myservice.type";
}

If so, for a Declarative Service OSGi component on Apache Felix, here's an approach:

  • keep MyService reference in MyComponent with
    • Dynamic Reference Policy (policy = ReferencePolicy.DYNAMIC)
    • 1..n cardinality (cardinality = ReferenceCardinality.MANDATORY_MULTIPLE)
  • install bind/unbind methods for MyService reference in MyComponent
  • install a Modified method in MyComponent

bind/unbind methods of MyComponent will called as and when MyService implementations are instantiated by Felix SCR. You would want to maintain a map of available implementations.

Modified method will be called whenever there's a configuration-update-event for MyComponent. In this method, based on the updated Component Configuration Property, appropriate method can be selected for further processing.

This is how the component would look like when using Felix SCR annotations.

@Component (metatype = true, immediate = true)
@Service (value = MyComponent.class)
public class MyComponent {
    @Property(name = "impl.selector", value = "impl_1")
    private String implSelector = "impl_1";

    @Reference(
        referenceInterface = MyService.class,
        policy = ReferencePolicy.DYNAMIC,
        cardinality = ReferenceCardinality.MANDATORY_MULTIPLE,
        strategy = ReferenceStrategy.EVENT,
        bind = "bindService",
        unbind = "unbindService"
    )

    private Map<String, MyService> availableMyServiceImpls = new HashMap<String, MyService>();
    private MyService service = null;

    @Activate
    public void activate(ComponentContext componentContext) {
        service = availableMyServiceImpls.get(implSelector);
    }

    @Deactivate
    public void deactivate(ComponentContext componentContext) {
        availableMyServiceImpls.clear();
    }

    public void bindService(MyService serviceRef, Map<?,?> refProperties) {
        String serviceImplName = (String) refProperties.get(MyService.NAME_PROPERTY);
        availableMyServiceImpls.put(serviceImplName, serviceRef);
    }

    public void unbindService(MyService serviceRef, Map<?,?> refProperties) {
        String serviceImplName = (String) refProperties.get(MyService.NAME_PROPERTY);
        availableMyServiceImpls.remove(serviceImplName);
    }

    @Modified
    public void modified(ComponentContext componentContext) {
        Dictionary<String, Object> componentProps = componentContext.getProperties();
        implSelector = PropertiesUtil.toString(componentProps.get("impl.selector"), "");
        service = availableMyServiceImpls.get(implSelector);
    }
}
  • This is a good approach if the property is changed through configuration, but if it is a method calling a ``setProperty`` method ? You can always call ``modified`` in the ``setProperty`` method, seems a little bit odd but it should work. – Alexandre Cartapanis Jun 21 '16 at 14:56
  • Not sure I follow entirely, @AlexandreCartapanis. Upon updating the `property` of `MyComponent` above via [Felix's `ConfigurationAdmin` interface](http://felix.apache.org/apidocs/configadmin/1.2.4/org/osgi/service/cm/ConfigurationAdmin.html), viz. `Configuration config = configAdmin.getConfiguration(COMPONENT_PID)` `config.update(new Hashtable() {{put("impl.selector", "impl_4");}});` the method annotated as `@Modified` was called. Can you please help me understand the `setProperty` scenario you allude to? – Ashish Chopra Jun 21 '16 at 18:45
  • If you don't update the ``property`` via Felix ConfigurationAdmin but directly via code (a ``property = XXX`` or a ``setProperty(XXX)`` the method annotated with ``@Modified`` will not be called. – Alexandre Cartapanis Jun 22 '16 at 07:01
  • `public void bindService(MyService serviceRef, Map,?> refProperties) ` The 2nd parameter is not documented anywhere, but does work! – Bageshwar Pratap Narain Aug 13 '20 at 11:40
1

First, you must publish your implementations with different filter, to be able to get one or other through filtering. Then you can change the ServiceReference whenever the property change using bundleContext.getServiceReferences().

The technical detail depends on the framework you are using (DS, iPojo, none, ...).

Alexandre Cartapanis
  • 1,513
  • 3
  • 15
  • 19