4

After reading an article on Reference target (and corresponding properties), I still don't understand how to retrieve a service, when target is set at runtime (typically target and properties are set at compile time and evaluated by SCR at runtime).

Let's say there are three service implementations defining@Property(name="type", value="csv"), @Property(name="type", value="xls") and @Property(name="type", value="pdf"), respectively.

And one consumer with:

//@Reference(target="(type=%runtime_variable%)")
Service service;

NOTE %runtime_variable% is automatically evaluated at runtime (read from a settings file).


Should I just call getServiceReferences(Class<S> clazz, String filter) in the @Activate/@Modified annotated method in order to get the right service at runtime?

How is the component.xml created, if I don't explicitly use @Reference and dynamically set the target in the @Activate/@Modified annotated method?

And can I use the@Designate metatype annotation to make my life simpler here?

mike
  • 4,929
  • 4
  • 40
  • 80

3 Answers3

3

That article, you have read, is 7 year old and it is not clear to me which annotations it uses (yes, there are several). I'd suggest to ignore it. Today you better use Declarative Services (DS) and the standard OSGi annotations.

In short there are 2 important pieces:

  • XML files in /OSGI-INF folder inside the bundles providing / consuming services
  • Service Component Runtime (SCR) - a bundle that inspects other bundles at runtime and if it finds the above XML files, takes care of registering and wiring the services.

While you can write the XML files by hand, they are typically generated by Bnd or other build tools using Bnd (such us bnd-maven-plugin). This is done at build time when Bnd inspects your classes for annotations and uses the information provided to generate the XML files. Thus the annotations are not used at all at runtime.

As for the wiring, when you have

   @Reference(target="(type=pdf)")
   Service service;

The field service will be automatically wired to one of the instances (yes there can be more than one) of Service service registered in OSGi's service registry that matches the target filter. This is done at runtime by SCR. You can change the target at runtime by reconfiguring your component using its PID. You can do that programmatically or via properties files using Configuration Admin.

The @Designate annotation you mentioned relates to another OSGi specification called Metatype. It allows you to better define the types of the configuration fields. Here you can read more about how to use Metatype together with Declarative Services 1.3.

Another good source of information regarding OSGi annotations is here (ignore the Liferay specific ones)


To reflect your edited question, you have some options. One is to get all instances:

@Reference(
 cardinality = ReferenceCardinality.MULTIPLE,
 policy = ReferencePolicy.DYNAMIC,
 policyOption = ReferencePolicyOption.GREEDY
 )
protected void setService(Service service, Map<String, Object> properties) {
   String type = MapUtil.getString(properties, "type");
   _services.put(type, service);
}

Then you can get your service from _services map by type. Another is reconfigure your component. For example if you define it like this

@Component(
 configurationPid = "my.component"
)
public class MyComponent implements ... {
   @Reference(target="(type=pdf)")
   Service myService;
}    

you can configure it via my.component.cfg in which you specify

myService.target=(type=somethingElse)

You can do the same programmatically using the Configuration Admin API.

Milen Dyankov
  • 2,972
  • 14
  • 25
  • I clarified the question in order for it to reflect more precise what my problem is. I know how to use SCR + annotations + maven. I want to set reference.target at runtime or sth. that is logically equivalent (in order to address specific customer needs by configuration of reference.target **without** recompiling). – mike Nov 20 '17 at 16:48
  • Still, thanks for your answer and the interesting resources you did provide. I'll read through the links! – mike Nov 20 '17 at 16:51
  • As I mentioned earlier annotations are processed at build time. Therefore `@Reference(target="(type=%runtime_variable%)")` will never work. What you may use instead is get a list of all registered services and their metadata, keep them locally in some kind of map and use the one you need based on the runtime parameter. – Milen Dyankov Nov 20 '17 at 18:06
  • What do I need for that? The `@Reference` in order for bnd to create the `component.xml` and a custom `@Activate` method which evaluates the services? – mike Nov 20 '17 at 18:21
  • I updated my answer to reflect your updated question – Milen Dyankov Nov 20 '17 at 18:33
  • Thanks! Where should I use the Configuration Admin API? In one big config bundle that does all the work and is started immediately? – mike Nov 20 '17 at 19:10
  • That depends on the application and your business logic. Keep in mind in OSGi there is no guaranteed start ordering of bundles nor services. Therefore I'd recommend to use configuration file instead to not have to manually manage the whole lifecycle. Moreover from what you wrote "%runtime_variable% is automatically evaluated at runtime (read from a settings file)." this seams to be what you are already doing anyway. Why would you want to manually read a file then manually reconfigure when DS / ConfigAdmin can do that for you? – Milen Dyankov Nov 20 '17 at 19:19
  • I did not depict the current state, rather the system-to-be. The product already has a settings infrastructure using files and a certain settings format (which the customer is already familiar with). The plan is to, at least try to, utilize that in order to simulate setting reference.target. I guess forcing a "configuration bundle" to load early seems like the easiest solution. – mike Nov 20 '17 at 19:34
  • Try to get away from the idea that the configuration bundle (which could be an off-the-shelf solution like Felix File Install) needs to start really early. There is no need to insist on start ordering for OSGi bundles. Components will receive their configuration when it is ready. – Neil Bartlett Nov 27 '17 at 11:25
  • DS link is dead, updated link: [https://enroute.osgi.org/FAQ/300-declarative-services.html](https://enroute.osgi.org/FAQ/300-declarative-services.html) – ocramot Nov 21 '19 at 10:34
2

Simply use

@Reference
Service myService;

At runtime you then create a configuration for your component and set a filter like this:

myService.target=(mykey=1)
Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
  • But how and where do I create a configuration at runtime? In the `@Activate`method? – mike Nov 20 '17 at 17:07
  • 1
    Well a component does not configure itself. Normally the deployer will configure the system via Configuration Admin. So you would need to ensure a configuration under the PID of the component contains the desired target property and value. – BJ Hargrave Nov 20 '17 at 17:11
  • The developed product is client software. Which is (re)started multiples times per day. This kind of configuration does not seam feasible. Would it be possible to write a component that starts immediately and performs the configuration? – mike Nov 20 '17 at 17:24
  • 1
    There are several ways to configure the config admin. With felix fileinstall you can load configs from files. There is also the new felix configurator. It can load configs from json on startup. So you can make sure the config is present. – Christian Schneider Nov 21 '17 at 05:38
  • 1
    Configurations are persisted, so if the client is restarted then they will be automatically injected with the same configuration that they had before. It would be unusual to have a component which puts configuration for other components into configuration admin. Normally configurations are managed using a Configuration Management agent, which may use flat files (e.g. file install) or some more sophisticated mechanism (e.g. pushing data from a UI or database). – Tim Ward Nov 22 '17 at 15:28
0

You could do something like this:

@Property(name = "myService.target", label = "My Service", description = "The target reference for the MyService, e.g. use target=(type=html) to bind to services by type.")
@Reference(name = "myService")
private Service myService;

you can then create configuration file for you component com.example.impl.MyComponent.config :

myService.target="(type\=pdf)"

you could also change this value on runtime using Apache Felix Web Console (http://localhost:8888/system/console/configMgr).

Tomasz Szymulewski
  • 2,003
  • 23
  • 23