Changing OSGi References

Using OSGi Config Admin to bind to something else...

So we've all seen those @Reference annotations scattered throughout the Liferay code, and it can almost seem like those references are not changeable.

In fact, this is not really true at all.

The OSGi Configuration Admin service can be used to change the reference binding without touching the code.

Let's take a look at a contrived example from Liferay's com.liferay.blogs.demo.internal.BlogsDemo class.

This class has a number of @Reference injections for different types of demo content generators. One of those is declared as:

@Reference(target = "(source=lorem-ipsum)", unbind = "-")
protected void setLoremIpsumBlogsEntryDemoDataCreator(
    BlogsEntryDemoDataCreator blogsEntryDemoDataCreator) {

  _blogsEntryDemoDataCreators.add(blogsEntryDemoDataCreator);
}

So in this example, the com.liferay.blogs.demo.data.creator.internal.LoremIpsumBlogsEntryDemoDataCreatorImpl is registered with a property, "source=lorem-ipsum", and it can generate content for a blogs entry demo.

Let's say that we have our own demo data creator, com.example.KlingonBlogsEntryDemoDataCreatorImpl that generates blog entries in Klingon (it has "source=klingon" defined for its property), and we want the blogs demo class to not use the lorem-ipsum version, but instead use our klingon variety.

How can we do this?

Well, BlogsDemo is a component, so we could create a copy of it and change the relevant code to @Reference ours, but this seems kind of like overkill.

A much easier way would be to get OSGi to just bind to our instance rather than the original. This is actually quite easy to do.

First we will need to create a configuration admin override file in osgi/config named after the full class name but with a .config extension.

So we need to create an osgi/config/com.liferay.blogs.demo.internal.BlogsDemo.config file. This file will have our override for the reference to bind to, but we need to get some more details for that.

We need to know the name for the field that we're going to be setting, that will be part of the configuration change. This will actually come from what the @Reference decorates. If @Reference is on a field, the field name will be the name you need; if it is on a setter, the name will be the setter method name without the leading "set" prefix.

So, from above, since we have setLoremIpsumBlogsEntryDemoDataCreator(), our field name will be "LoremIpsumBlogsEntryDemoDataCreator".

To change the target, we'll need to add a line to our config file with the following:

LoremIpsumBlogsEntryDemoDataCreator.target="(source\=klingon)"

This will effectively change the target string from the old (source=lorem-ipsum) to the new (source=klingon).

So this is how we can basically change up the wiring w/o really overriding a line of code.

You can even take this further. With a simple @Reference annotation w/o a target filter, you can add a target filter to bind a different reference. This could be an alternative to relying on a higher service ranking for binding.

For those cases where a service tracker is being used to track a list of entities, you can use this technique to exclude one or more references that you don't want to have the service tracker capture.

So actually I didn't come up with all of this myself.  It's actually an adaptation of https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/overriding-service-references#configure-the-component-to-use-the-custom-service to demonstrate just how that can be used to change the wiring.

Blogs

While trying to clean up the OSGi services in the Eclipse Platform Runtime I came across the fact that singleton service instances are not always feasible. For example the fact that the localization is done on application level does not work in the context of RAP, where every user can have a different localization.