Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically update references in an OSGi component?

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.

like image 810
sdm Avatar asked Dec 15 '22 05:12

sdm


2 Answers

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)

like image 125
Peter Kriens Avatar answered Dec 28 '22 08:12

Peter Kriens


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);
    }
}
like image 40
Ashish Chopra Avatar answered Dec 28 '22 09:12

Ashish Chopra