OSGi Event Admin – Publish & Subscribe

In this blog post I want to write about the publish & subscribe mechanism in OSGi, provided via the OSGi Event Admin Service. Of course I will show this in combination with OSGi Declarative Services, because this is the technology I currently like very much, as you probably know from my previous blog posts.

I will start with some basics and then show an example as usual. At last I will give some information about how to use the event mechanism in Eclipse RCP development, especially related to the combination between OSGi services and the GUI.

If you want to read further details on the Event Admin Service Specification have a look at the OSGi Spec. In Release 6 it is covered in the Compendium Specification Chapter 113.

Let’s start with the basics. The Event Admin Service is based on the Publish-Subscribe pattern. There is an event publisher and an event consumer. Both do not know each other in any way, which provides a high decoupling. Simplified you could say, the event publisher sends an event to a channel, not knowing if anybody will receive that event. On the other side there is an event consumer ready to receive events, not knowing if there is anybody available for sending events. This simplified view is shown in the following picture:

simple_event

Technically both sides are using the Event Admin Service in some way. The event publisher uses it directly to send an event to the channel. The event consumer uses it indirectly by registering an event handler to the EventAdmin to receive events. This can be done programmatically. But with OSGi DS it is very easy to register an event handler by using the whiteboard pattern.

Event

An Event object has a topic and some event properties. It is an immutable object to ensure that every handler gets the same object with the same state.

The topic defines the type of the event and is intended to serve as first-level filter for determining which handlers should receive the event. It is a String arranged in a hierarchical namespace. And the recommendation is to use a convention similar to the Java package name scheme by using reverse domain names (fully/qualified/package/ClassName/ACTION). Doing this ensures uniqueness of events. This is of course only a recommendation and you are free to use pseudo class names to make the topic better readable.

Event properties are used to provide additional information about the event. The key is a String and the value can be technically any object. But it is recommended to only use String objects and primitive type wrappers. There are two reasons for this:

  1. Other types cannot be passed to handlers that reside external from the Java VM.
  2. Other classes might be mutable, which means any handler that receives the event could change values. This break the immutability rule for events.

Common Bundle

It is some kind of best practice to place common stuff in a common bundle to which the event publisher bundle and the event consumer bundle can have a dependency to. In our case this will only be the definition of the supported topics and property keys in a constants class, to ensure that both implementations share the same definition, without the need to be dependent on each other.

  • Create a new project org.fipro.mafia.common
  • Create a new package org.fipro.mafia.common
  • Create a new class MafiaBossConstants
public final class MafiaBossConstants {

    private MafiaBossConstants() {
        // private default constructor for constants class
        // to avoid someone extends the class
    }

    public static final String TOPIC_BASE = "org/fipro/mafia/Boss/";
    public static final String TOPIC_CONVINCE = TOPIC_BASE + "CONVINCE";
    public static final String TOPIC_ENCASH = TOPIC_BASE + "ENCASH";
    public static final String TOPIC_SOLVE = TOPIC_BASE + "SOLVE";
    public static final String TOPIC_ALL = TOPIC_BASE + "*";

    public static final String PROPERTY_KEY_TARGET = "target";

}
  • PDE
    • Open the MANIFEST.MF file and on the Overview tab set the Version to 1.0.0 (remove the qualifier).
    • Switch to the Runtime tab and export the org.fipro.mafia.common package.
    • Specify the version 1.0.0 on the package via Properties…
  • Bndtools
    • Open the bnd.bnd file
    • Add the package org.fipro.mafia.common to the Export Packages

In MafiaBossConstants we specify the topic base with a pseudo class org.fipro.mafia.Boss, which results in the topic base org/fipro/mafia/Boss. We specify action topics that start with the topic base and end with the actions CONVINCE, ENCASH and SOLVE. And additionally we specify a topic that starts with the base and ends with the wildcard ‘*’.

These constants will be used by the event publisher and the event consumer soon.

Event Publisher

The Event Publisher uses the Event Admin Service to send events synchronously or asynchronously. Using DS this is pretty easy.

We will create an Event Publisher based on the idea of a mafia boss. The boss simply commands a job execution and does not care who is doing it. Also it is not of interest if there are many people doing the same job. The job has to be done!

  • Create a new project org.fipro.mafia.boss
  • PDE
    • Open the MANIFEST.MF file of the org.fipro.mafia.boss project and switch to the Dependencies tab
    • Add the following dependencies on the Imported Packages side:
      • org.fipro.mafia.common (1.0.0)
      • org.osgi.service.component.annotations (1.3.0)
      • org.osgi.service.event (1.3.0)
    • Mark org.osgi.service.component.annotations as Optional via Properties…
    • Add the upper version boundaries to the Import-Package statements.
  • Bndtools
    • Open the bnd.bnd file of the org.fipro.mafia.boss project and switch to the Build tab
    • Add the following bundles to the Build Path
      • org.apache.felix.eventadmin
      • org.fipro.mafia.common

Note:
Adding org.osgi.service.event to the Imported Packages with PDE on a current Equinox target will provide a package version 1.3.1. You need to change this to 1.3.0 if you intend to run the same bundle with a different Event Admin Service implementation. In general it is a bad practice to rely on a bugfix version. Especially when thinking about interfaces, as any change to an interface typically is a breaking change.
To clarify the statement above. As the package org.osgi.service.event contains more than just the EventAdmin interface, the bugfix version increase is surely correct in Equinox, as there was probably a bugfix in some code inside the package. The only bad thing is to restrict the package wiring on the consumer side to a bugfix version, as this would restrict your code to only run with the Equinox implementation of the Event Admin Service.

  • Create a new package org.fipro.mafia.boss
  • Create a new class BossCommand
@Component(
    property = {
        "osgi.command.scope=fipro",
        "osgi.command.function=boss" },
    service = BossCommand.class)
public class BossCommand {

    @Reference
    EventAdmin eventAdmin;

    @Descriptor("As a mafia boss you want something to be done")
    public void boss(
        @Descriptor("the command that should be executed. "
            + "possible values are: convince, encash, solve")
        String command,
        @Descriptor("who should be 'convinced', "
            + "'asked for protection money' or 'finally solved'")
        String target) {

        // create the event properties object
        Map<String, Object> properties = new HashMap<>();
        properties.put(MafiaBossConstants.PROPERTY_KEY_TARGET, target);
        Event event = null;

        switch (command) {
            case "convince":
                event = new Event(MafiaBossConstants.TOPIC_CONVINCE, properties);
                break;
            case "encash":
                event = new Event(MafiaBossConstants.TOPIC_ENCASH, properties);
                break;
            case "solve":
                event = new Event(MafiaBossConstants.TOPIC_SOLVE, properties);
                break;
            default:
                System.out.println("Such a command is not known!");
        }

        if (event != null) {
            eventAdmin.postEvent(event);
        }
    }
}

Note:
The code snippet above uses the annotation @Descriptor to specify additional information for the command. This information will be shown when executing help boss in the OSGi console. To make this work with PDE you need to import the package org.apache.felix.service.command with status=provisional. Because the PDE editor does not support adding additional information to package imports, you need to do this manually in the MANIFEST.MF tab of the Plugin Manifest Editor. The Import-Package header would look like this:

Import-Package: org.apache.felix.service.command;status=provisional;version="0.10.0",
 org.fipro.mafia.common;version="[1.0.0,2.0.0)",
 org.osgi.service.component.annotations;version="[1.3.0,2.0.0)";resolution:=optional,
 org.osgi.service.event;version="[1.3.0,2.0.0)"

With Bndtools you need to add org.apache.felix.gogo.runtime to the Build Path in the bnd.bnd file so the @Descriptor annotation can be resolved.

There are three things to notice in the BossCommand implementation:

  • There is a mandatory reference to EventAdmin which is required for sending events.
  • The Event objects are created using a specific topic and a Map<String, Object> that contains the additional event properties.
  • The event is sent asynchronously via EventAdmin#postEvent(Event)

The BossCommand will create an event using the topic that corresponds to the given command parameter. The target parameter will be added to a map that is used as event properties. This event will then be send to a channel via the EventAdmin. In the example we use EventAdmin#postEvent(Event) which sends the event asynchronously. That means, we send the event but do not wait until available handlers have finished the processing. If it is required to wait until the processing is done, you need to use EventAdmin#sendEvent(Event), which sends the event synchronously. But sending events synchronously is significantly more expensive, as the Event Admin Service implementation needs to ensure that every handler has finished processing before it returns. It is therefore recommended to prefer the usage of asynchronous event processing.

Note:
The code snippet uses the Field Strategy for referencing the EventAdmin. If you are using PDE this will work with Eclipse Oxygen. With Eclipse Neon you need to use the Event Strategy. In short, you need to write the bind-event-method for referencing EventAdmin because Equinox DS supports only DS 1.2 and the annotation processing in Eclipse Neon also only supports the DS 1.2 style annotations.

Event Consumer

In our example the boss does not have to tell someone explicitly to do the job. He just mentions that the job has to be done. Let’s assume we have a small organization without hierarchies. So we skip the captains etc. and simply implement some soldiers. They have specialized, so we have three soldiers, each listening to one special topic.

  • Create a new project org.fipro.mafia.soldier
  • PDE
    • Open the MANIFEST.MF file of the org.fipro.mafia.soldier project and switch to the Dependencies tab
    • Add the following dependencies on the Imported Packages side:
      • org.fipro.mafia.common (1.0.0)
      • org.osgi.service.component.annotations (1.3.0)
      • org.osgi.service.event (1.3.0)
    • Mark org.osgi.service.component.annotations as Optional via Properties…
    • Add the upper version boundaries to the Import-Package statements.
  • Bndtools
    • Open the bnd.bnd file of the org.fipro.mafia.boss project and switch to the Build tab
    • Add the following bundles to the Build Path
      • org.apache.felix.eventadmin
      • org.fipro.mafia.common
  • Create a new package org.fipro.mafia.soldier
  • Create the following three soldiers Luigi, Mario and Giovanni
@Component(
    property = EventConstants.EVENT_TOPIC
        + "=" + MafiaBossConstants.TOPIC_CONVINCE)
public class Luigi implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        System.out.println("Luigi: "
        + event.getProperty(MafiaBossConstants.PROPERTY_KEY_TARGET)
        + " was 'convinced' to support our family");
    }

}
@Component(
    property = EventConstants.EVENT_TOPIC
        + "=" + MafiaBossConstants.TOPIC_ENCASH)
public class Mario implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        System.out.println("Mario: "
        + event.getProperty(MafiaBossConstants.PROPERTY_KEY_TARGET)
        + " payed for protection");
    }

}
@Component(
    property = EventConstants.EVENT_TOPIC
        + "=" + MafiaBossConstants.TOPIC_SOLVE)
public class Giovanni implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        System.out.println("Giovanni: We 'solved' the issue with "
        + event.getProperty(MafiaBossConstants.PROPERTY_KEY_TARGET));
    }

}

Technically we have created special EventHandler for different topics. You should notice the following facts:

  • We are using OSGi DS to register the event handler using the whiteboard pattern. On the consumer side we don’t need to know the EventAdmin itself.
  • We need to implement org.osgi.service.event.EventHandler
  • We need to register for a topic via service property event.topics, otherwise the handler will not listen for any event.
  • Via Event#getProperty(String) we are able to access event property values.

The following service properties are supported by event handlers:

Service Registration Property Description
event.topics Specify the topics of interest to an EventHandler service. This property is mandatory.
event.filter Specify a filter to further select events of interest to an EventHandler service. This property is optional.
event.delivery Specifying the delivery qualities requested by an EventHandler service. This property is optional.

The property keys and some default keys for event properties are specified in org.osgi.service.event.EventConstants.

Launch the example

Before moving on and explaining further, let’s start the example and verify that each command from the boss is only handled by one soldier.

With PDE perform the following steps:

  • Select the menu entry Run -> Run Configurations…
  • In the tree view, right click on the OSGi Framework node and select New from the context menu
  • Specify a name, e.g. OSGi Event Mafia
  • Deselect All
  • Select the following bundles
    (note that we are using Eclipse Oxygen, in previous Eclipse versions org.apache.felix.scr and org.eclipse.osgi.util are not required)

    • Application bundles
      • org.fipro.mafia.boss
      • org.fipro.mafia.common
      • org.fipro.mafia.soldier
    • Console bundles
      • org.apache.felix.gogo.command
      • org.apache.felix.gogo.runtime
      • org.apache.felix.gogo.shell
      • org.eclipse.equinox.console
    • OSGi framework and DS bundles
      • org.apache.felix.scr
      • org.eclipse.equinox.ds
      • org.eclipse.osgi
      • org.eclipse.osgi.services
      • org.eclipse.osgi.util
    • Equinox Event Admin
      • org.eclipse.equinox.event
  • Ensure that Default Auto-Start is set to true
  • Click Run

With Bndtools perform the following steps:

  • Open the launch.bndrun file in the org.fipro.mafia.boss project
  • On the Run tab add the following bundles to the Run Requirements
    • org.fipro.mafia.boss
    • org.fipro.mafia.common
    • org.fipro.mafia.soldier
  • Click Resolve to ensure all required bundles are added to the Run Bundles via auto-resolve
  • Click Run OSGi

Execute the boss command to see the different results. This can look similar to the following:

osgi> boss convince Angelo
osgi> Luigi: Angelo was 'convinced' to support our family
boss encash Wong
osgi> Mario: Wong payed for protection
boss solve Tattaglia
osgi> Giovanni: We 'solved' the issue with Tattaglia

Handle multiple event topics

It is also possible to register for multiple event topics. Say Pete is a tough guy who is good in ENCASH and SOLVE issues. So he registers for those topics.

@Component(
    property = {
        EventConstants.EVENT_TOPIC + "=" + MafiaBossConstants.TOPIC_CONVINCE,
        EventConstants.EVENT_TOPIC + "=" + MafiaBossConstants.TOPIC_SOLVE })
public class Pete implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        System.out.println("Pete: I took care of "
        + event.getProperty(MafiaBossConstants.PROPERTY_KEY_TARGET));
    }

}

As you can see the service property event.topics is declared multiple times via the @Component annotation type element property. This way an array of Strings is configured for the service property, so the handler reacts on both topics.

If you execute the example now and call boss convince xxx or boss solve xxx you will notice that Pete is also responding.

It is also possible to use the asterisk wildcard as last token of a topic. This way the handler will receive all events for topics that start with the left side of the wildcard.

Let’s say we have a very motivated young guy called Ray who wants to prove himself to the boss. So he takes every command from the boss. For this we set the service property event.topics=org/fipro/mafia/Boss/*

@Component(
    property = EventConstants.EVENT_TOPIC
        + "=" + MafiaBossConstants.TOPIC_ALL)
public class Ray implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        String topic = event.getTopic();
        Object target = event.getProperty(MafiaBossConstants.PROPERTY_KEY_TARGET);

        switch (topic) {
            case MafiaBossConstants.TOPIC_CONVINCE:
                System.out.println("Ray: I helped in punching the shit out of" + target);
                break;
            case MafiaBossConstants.TOPIC_ENCASH:
                System.out.println("Ray: I helped getting the money from " + target);
                break;
            case MafiaBossConstants.TOPIC_SOLVE:
                System.out.println("Ray: I helped killing " + target);
                break;
            default: System.out.println("Ray: I helped with whatever was requested!");
        }
    }

}

Executing the example again will show that Ray is responding on every boss command.

It is also possible to filter events based on event properties by setting the service property event.filter. The value needs to be an LDAP filter. For example, although Ray is a motivated and loyal soldier, he refuses to handle events that target his friend Sonny.

The following snippet shows how to specify a filter that excludes event processing if the target is Sonny.

@Component(
    property = {
        EventConstants.EVENT_TOPIC + "=" + MafiaBossConstants.TOPIC_ALL,
        EventConstants.EVENT_FILTER + "=" + "(!(target=Sonny))"})
public class Ray implements EventHandler {

Execute the example and call two commands:

  • boss solve Angelo
  • boss solve Sonny

You will notice that Ray will respond on the first call, but he will not show up on the second call.

Note:
The filter expression can only be applied on event properties. It is not possible to use that filter on service properties.

At last it is possible to configure in which order the event handler wants the events to be delivered. This can either be ordered in the same way they are posted, or unordered. The service property event.delivery can be used to change the default behavior, which is to receive the events from a single thread in the same order as they were posted.

If an event handler does not need to receive events in the order as they were posted, you need to specify the service property event.delivery=async.unordered.

@Component(
    property = {
        EventConstants.EVENT_TOPIC + "="
            + MafiaBossConstants.TOPIC_ALL,
        EventConstants.EVENT_FILTER + "="
            + "(!(target=Sonny))",
        EventConstants.EVENT_DELIVERY + "="
            + EventConstants.DELIVERY_ASYNC_UNORDERED})

The value for ordered delivery is async.ordered which is the default. The values are also defined in the EventConstants.

Capabilities

By using the event mechanism the code is highly decoupled. In general this is a good thing, but it also makes it hard to identify issues. One common issue in Eclipse RCP for example is to forget to automatically start the bundle org.eclipse.equinox.event. Things will simply not work in such a case, without any errors or warnings shown on startup.

The reason for this is that the related interfaces like EventAdmin and EventHandler are located in the bundle org.eclipse.osgi.services. The bundle wiring therefore shows that everything is ok on startup, because all interfaces and classes are available. But we require a bundle that contains an implementation of EventAdmin. If you remember my Getting Started Tutorial, such a requirement can be specified by using capabilities.

To show the implications, let’s play with the Run Configuration:

  • Uncheck org.eclipse.equinox.event from the list of bundles
  • Launch the configuration
  • execute lb on the command line (or ss on Equinox if you are more familiar with that) and check the bundle states
    • Notice that all bundles are in ACTIVE state
  • execute scr:list (or list on Equinox < Oxygen) to check the state of the DS components
    • Notice that org.fipro.mafia.boss.BossCommand has an unsatisfied reference
    • Notice that all other EventHandler services are satisfied

That is of course a the correct behavior. The BossCommand service has a mandatory reference to EventAdmin and there is no such service available. So it has an unsatisfied reference. The EventHandler implementations do not have such a dependency, so they are satisfied. And that is even fine when thinking in the publish & subscribe pattern. They can be active and waiting for events to process, even if there is nobody available to send an event. But it makes it hard to find the issue. And when using Tycho and the Surefire Plugin to execute tests, it will even never work because nobody tells the test runtime that org.eclipse.equinox.event needs to be available and started in advance.

This can be solved by adding the Require-Capability header to require an osgi.service for objectClass=org.osgi.service.event.EventAdmin.

Require-Capability: osgi.service;
 filter:="(objectClass=org.osgi.service.event.EventAdmin)"

By specifying the Require-Capability header like above, the capability will be checked when the bundles are resolved. So starting the example after the Require-Capability header was added will show an error and the bundle org.fipro.mafia.boss will not be activated.

If you add the bundle org.eclipse.equinox.event again to the Run Configuration and launch it again, there are no issues.

As p2 does still not support OSGi capabilities, the p2.inf file needs to be created in the META-INF folder with the following content:

requires.1.namespace = osgi.service
requires.1.name = org.osgi.service.event.EventAdmin

Typically you would specify the Require-Capability to the EventAdmin service with the directive effective:=active. This implies that the OSGi framework will resolve the bundle without checking if another bundle provides the capability. It can then be more seen as a documentation which services are required from looking into the MANIFEST.MF.

Important Note:
Specifying the Require-Capability header and the p2 capabilities for org.osgi.service.event.EventAdmin will only work with Eclipse Oxygen. I contributed the necessary changes to Equinox for Oxygen M1 with Bug 416047. With a org.eclipse.equinox.event bundle in a version >= 1.4.0 you should be able to specify the capabilities. In previous versions the necessary Provide-Capability and p2 capability configuration in that bundle are missing.

Handling events in Eclipse RCP UI

When looking at the architecture of an Eclipse RCP application, you will notice that the UI layer is not created via OSGi DS (actually that is not a surprise!). And we can not simply say that our view parts are created via DS, because the lifecycle of a part is controlled by other mechanics. But as an Eclipse RCP application is typcially an application based on OSGi, all the OSGi mechanisms can be used. Of course not that convenient as with using OSGi DS directly.

The direction from the UI layer to the OSGi service layer is pretty easy. You simply need to retrieve the service you want to uw3. With Eclipse 4 you simply get the desired service injected using @Inject or @Inject in combination with @Service since Eclipse Oxygen (see OSGi Declarative Services news in Eclipse Oxygen). With Eclipse 3.x you needed to retrieve the service programmatically via the BundleContext.

The other way to communicate from a service to the UI layer is something different. There are two ways to consider from my point of view:

This blog post is about the event mechanism in OSGi, so I don’t want to go in detail with the observer pattern approach. It simply means that you extend the service interface to accept listeners to perform callbacks. Which in return means you need to retrieve the service in the view part for example, and register a callback function from there.

With the Publish & Subscribe pattern we register an EventHandler that reacts on events. It is a similar approach to the Observer pattern, with some slight differences. But this is not a design pattern blog post, we are talking about the event mechanism. And we already registered an EventHandler using OSGi DS. The difference to the scenario using DS is that we need to register the EventHandler programmatically. For OSGi experts that used the event mechanism before DS came up, this is nothing new. For all others that learn about it, it could be interesting.

The following snippet shows how to retrieve a BundleContext instance and register a service programmatically. In earlier days this was done in an Activator, as there you have access to the BundleContext. Nowadays it is recommended to use the FrameworkUtil class to retrieve the BundleContext when needed, and to avoid Activators to reduce startup time.

private ServiceRegistration<?> eventHandler;

...

// retrieve the bundle of the calling class
Bundle bundle = FrameworkUtil.getBundle(getClass());
BundleContext bc = (bundle != null) ? bundle.getBundleContext() : null;
if (bc != null) {
    // create the service properties instance
    Dictionary<String, Object> properties = new Hashtable<>();
    properties.put(EventConstants.EVENT_TOPIC, MafiaBossConstants.TOPIC_ALL);
    // register the EventHandler service
    eventHandler = bc.registerService(
        EventHandler.class.getName(),
        new EventHandler() {

            @Override
            public void handleEvent(Event event) {
                // ensure to update the UI in the UI thread
                Display.getDefault().asyncExec(() -> handlerLabel.setText(
                        "Received boss command "
                            + event.getTopic()
                            + " for target "
                            + event.getProperty(MafiaBossConstants.PROPERTY_KEY_TARGET)));
            }
        },
        properties);
}

This code can be technically added anywhere in the UI code, e.g. in a view, an editor or a handler. But of course you should be aware that the event handler also should be unregistered once the connected UI class is destroyed. For example, you implement a view part that registers a listener similar to the above to update the UI everytime an event is received. That means the handler has a reference to a UI element that should be updated. If the part is destroyed, also the UI element is destroyed. If you don’t unregister the EventHandler when the part is destroyed, it will still be alive and react on events and probably cause exceptions without proper disposal checks. It is also a cause for memory leaks, as the EventHandler references a UI element instance that is already disposed but can not be cleaned up by the GC as it is still referenced.

Note:
The event handling is executed in its own event thread. Updates to the UI can only be performed in the main or UI thread, otherwise you will get a SWTException for Invalid thread access. Therefore it is necessary to ensure that UI updates performed in an event handler are executed in the UI thread. For further information have a look at Eclipse Jobs and Background Processing.
For the UI synchronization you should also consider using asynchronous execution via Display#asyncExec() or UISynchronize#asyncExec(). Using synchronous execution via syncExec() will block the event handler thread until the UI update is done.

If you stored the ServiceRegistration object returned by BundleContext#registerService() as shown in the example above, the following snippet can be used to unregister the handler if the part is destroyed:

if (eventHandler != null) {
    eventHandler.unregister();
}

In Eclipse 3.x this needs to be done in the overriden dispose() method. In Eclipse 4 it can be done in the method annotated with @PreDestroy.

Note:
Ensure that the bundle that contains the code is in ACTIVE state so there is a BundleContext. This can be achieved by setting Bundle-ActivationPolicy: lazy in the MANIFEST.MF.

Handling events in Eclipse RCP UI with Eclipse 4

In Eclipse 4 the event handling mechanism is provided to the RCP development via the EventBroker. The EventBroker is a service that uses the EventAdmin and additionally provides injection support. To learn more about the EventBroker and the event mechanism provided by Eclipse 4 you should read the related tutorials, like

We are focusing on the event consumer here. Additionally to registering the EventHandler programmatically, it is possible in Eclipse 4 to specify a method for method injection that is called on event handling by additionally providing support for injection.

Such an event handler method looks similar to the following snippet:

@Inject
@Optional
void handleConvinceEvent(
        @UIEventTopic(MafiaBossConstants.TOPIC_CONVINCE) String target) {
    e4HandlerLabel.setText("Received boss CONVINCE command for " + target);
}

By using @UIEventTopic you ensure that the code is executed in the UI thread. If you don’t care about the UI thread, you can use @EventTopic instead. The handler that is registered in the back will also be automatically unregistered if the containing instance is destroyed.

While the method gets directly invoked as event handler, the injection does not work without modifications on the event producer side. For this the data that should be used for injection needs to be added to the event properties for the key org.eclipse.e4.data. This key is specified as a constant in IEventBroker. But using the constant would also introduce a dependency to org.eclipse.e4.core.services, which is not always intended for event producer bundles. Therefore modifying the generation of the event properties map in BossCommand will make the E4 event handling injection work:

// create the event properties object
Map<String, Object> properties = new HashMap<>();
properties.put(MafiaBossConstants.PROPERTY_KEY_TARGET, target);
properties.put("org.eclipse.e4.data", target);

Note:
The EventBroker additionally adds the topic to the event properties for the key event.topics. In Oxygen it does not seem to be necessary anymore.

The sources for this tutorial are hosted on GitHub in the already existing projects:

The PDE version also includes a sample project org.fipro.mafia.ui which is a very simple RCP application that shows the usage of the event handler in a view part.

Posted in Dirk Fauth, Eclipse, Java, OSGi | 2 Comments

Access OSGi Services via web interface

In this blog post I want to share a simple approach to make OSGi services available via web interface. I will show a simple approach that includes the following:

  • Embedding a Jetty  Webserver in an OSGi application
  • Registering a Servlet via OSGi DS using the HTTP Whiteboard specification

I will only cover this simple scenario here and will not cover accessing OSGi services via REST interface. If you are interested in that you might want to look at the OSGi – JAX-RS Connector, which looks also very nice. Maybe I will look at this in another blog post. For now I will focus on embedding a Jetty Server and deploy some resources.

I will skip the introduction on OSGi DS and extend the examples from my Getting Started with OSGi Declarative Services blog. It is easier to follow this post when done the other tutorial first, but it is not required if you adapt the contents here to your environment.

As a first step create a new project org.fipro.inverter.http. In this project we will add the resources created in this tutorial. If you use PDE you should create a new Plug-in Project, with Bndtools create a new Bnd OSGi Project using the Component Development template.

PDE – Target Platform

In PDE it is best practice to create a Target Definition so the work is based on a specific set of bundles and we don’t need to install bundles in our IDE. Follow these steps to create a Target Definition for this tutorial:

  • Create a new target definition
    • Right click on project org.fipro.inverter.http → New → Other… → Plug-in Development → Target Definition
    • Set the filename to org.fipro.inverter.http.target
    • Initialize the target definition with: Nothing: Start with an empty target definition
  • Add a new Software Site in the opened Target Definition Editor by clicking Add… in the Locations section
    • Select Software Site
    • Software Site http://download.eclipse.org/releases/oxygen
    • Disable Group by Category
    • Select the following entries
      • Equinox Core SDK
      • Equinox Compendium SDK
      • Jetty Http Server Feature
    • Click Finish
  • Optional: Add a new Software Site to include JUnit to the Target Definition (only needed in case you followed all previous tutorials on OSGi DS or want to integrate JUnit tests for your services)
    • Software Site http://download.eclipse.org/tools/orbit/R-builds/R20170307180635/repository
    • Select JUnit Testing Framework
    • Click Finish
  • Save your work and activate the target platform by clicking Set as Target Platform in the upper right corner of the Target Definition Editor

Bndtools – Repository

Using Bndtools is different as you already know if you followed my previous blog posts. To be also able to follow this blog post by using Bndtools, I will describe the necessary steps here.

We will use Apache Felix in combination with Bndtools instead of Equinox. This way we don’t need to modify the predefined repository and can start without further actions. The needed Apache Felix bundles are already available.

PDE – Prepare project dependencies

We will prepare the project dependencies in advance so it is easier to copy and paste the code samples to the project. Within the Eclipse IDE the Quick Fixes would also support adding the dependencies afterwards of course.

  • Open the MANIFEST.MF file of the org.fipro.inverter.http project and switch to the Dependencies tab
  • Add the following two dependencies on the Imported Packages side:
    • javax.servlet (3.1.0)
    • javax.servlet.http (3.1.0)
    • org.fipro.inverter (1.0.0)
    • org.osgi.service.component.annotations (1.3.0)
  • Mark org.osgi.service.component.annotations as Optional via Properties…
  • Add the upper version boundaries to the Import-Package statements.

Bndtools – Prepare project dependencies

  • Open the bnd.bnd file of the org.fipro.inverter.http project and switch to the Build tab
  • Add the following bundles to the Build Path
    • org.apache.http.felix.jetty
    • org.apache.http.felix.servlet-api
    • org.fipro.inverter.api

Create a Servlet implementation

  • Create a new package org.fipro.inverter.http
  • Create a new class InverterServlet
@Component(
    service=Servlet.class,
    property= "osgi.http.whiteboard.servlet.pattern=/invert",
    scope=ServiceScope.PROTOTYPE)
public class InverterServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Reference
    private StringInverter inverter;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String input = req.getParameter("value");
        if (input == null) {
            throw new IllegalArgumentException("input can not be null");
        }
        String output = inverter.invert(input);

        resp.setContentType("text/html");
        resp.getWriter().write(
            "<html><body>Result is " + output + "</body></html>");
        }

}

Let’s look at the implementation:

  1. It is a typical Servlet implementation that extends javax.servlet.http.HttpServlet
  2. It is also an OSGi Declarative Service that is registered as service of type javax.servlet.Servlet
  3. The service has PROTOTYPE scope
  4. A special property osgi.http.whiteboard.servlet.pattern is set. This configures the context path of the Servlet.
  5. It references the StringInverter OSGi service from the previous tutorial via field reference. And yes since Eclipse Oxygen this is also supported in Equinox (I wrote about this here).

PDE – Launch the example

Before explaining the details further, launch the example to see if our servlet is available via standard web browser. For this we create a launch configuration, so we can start directly from the IDE.

  • Select the menu entry Run -> Run Configurations…
  • In the tree view, right click on the OSGi Framework node and select New from the context menu
  • Specify a name, e.g. OSGi Inverter Http
  • Deselect All
  • Select the following bundles
    (note that we are using Eclipse Oxygen, in previous Eclipse versions org.apache.felix.scr and org.eclipse.osgi.util are not required)

    • Application bundles
      • org.fipro.inverter.api
      • org.fipro.inverter.http
      • org.fipro.inverter.provider
    • Console bundles
      • org.apache.felix.gogo.command
      • org.apache.felix.gogo.runtime
      • org.apache.felix.gogo.shell
      • org.eclipse.equinox.console
    • OSGi framework and DS bundles
      • org.apache.felix.scr
      • org.eclipse.equinox.ds
      • org.eclipse.osgi
      • org.eclipse.osgi.services
      • org.eclipse.osgi.util
    • Equinox Http Service and Http Whiteboard
      • org.eclipse.equinox.http.jetty
      • org.eclipse.equinox.http.servlet
    • Jetty
      • javax.servlet
      • org.eclipse.jetty.continuation
      • org.eclipse.jetty.http
      • org.eclipse.jetty.io
      • org.eclipse.jetty.security
      • org.eclipse.jetty.server
      • org.eclipse.jetty.servlet
      • org.eclipse.jetty.util
  • Ensure that Default Auto-Start is set to true
  • Switch to the Arguments tab
    • Add -Dorg.osgi.service.http.port=8080 to the VM arguments
  • Click Run

Note:
If you include the above bundles in an Eclipse RCP application, ensure that you auto-start the org.eclipse.equinox.http.jetty bundle to automatically start the Jetty server. This can be done on the Configuration tab of the Product Configuration Editor.

If you now open a browser and go to the URL http://localhost:8080/invert?value=Eclipse you should get a response with the inverted output.

Bndtools – Launch the example

  • Open the launch.bndrun file in the org.fipro.inverter.http project
  • On the Run tab add the following bundles to the Run Requirements
    • org.fipro.inverter.http
    • org.fipro.inverter.provider
    • org.apache.felix.http.jetty
  • Click Resolve to ensure all required bundles are added to the Run Bundles via auto-resolve
  • Add -Dorg.osgi.service.http.port=8080 to the JVM Arguments
  • Click Run OSGi

Http Service & Http Whiteboard

Now why is this simply working? We only implemented a servlet and provided it as OSGi DS. And it is “magically” available via web interface. The answer to this is the OSGi Http Service Specification and the Http Whiteboard Specification. The OSGi Compendium Specification R6 contains the Http Service Specification Version 1.2 (Chapter 102 – Page 45) and the Http Whiteboard Specification Version 1.0 (Chapter 140 – Page 1067).

The purpose of the Http Service is to provide access to services on the internet or other networks for example by using a standard web browser. This can be done by registering servlets or resources to the Http Service. Without going too much into detail, the implementation is similar to an embedded web server, which is the reason why the default implementations in Equinox and Felix are based on Jetty.

To register servlets and resources to the Http Service you know the Http Service API very well and you need to retrieve the Http Service and directly operate on it. As this is not every convenient, the Http Whiteboard Specification was introduced. This allows to register servlets and resources via the Whiteboard Pattern, without the need to know the Http Service API in detail. I always think about the whiteboard pattern as a “don’t call us, we will call you” pattern. That means you don’t need to register servlets on the Http Service directly, you will provide it as a service to the service registry, and the Http Whiteboard implementation will take it and register it to the Http Service.

Via Http Whiteboard it is possible to register:

  • Servlets
  • Servlet Filters
  • Resources
  • Servlet Listeners

I will show some examples to be able to play around with the Http Whiteboard service.

Register Servlets

An example on how to register a servlet via Http Whiteboard is shown above. The main points are:

  • The servlet needs to be registered as OSGi service of type javax.servlet.Servlet.
  • The component property osgi.http.whiteboard.servlet.pattern needs to be set to specify the request mappings.
  • The service scope should be PROTOTYPE.

For registering servlets the following component properties are supported. (see OSGi Compendium Specification Release 6 – Table 140.4):

Component Property Description
osgi.http.whiteboard.servlet.asyncSupported Declares whether the servlet supports the asynchronous operation mode. Allowed values are true and false independent of case. Defaults to false.
osgi.http.whiteboard.servlet.errorPage Register the servlet as an error page for the error code and/or exception specified; the value may be a fully qualified exception type name or a three-digit HTTP status code in the range 400-599. Special values 4xx and 5xx can be used to match value ranges. Any value not being a three-digit number is assumed to be a fully qualified exception class name.
osgi.http.whiteboard.servlet.name The name of the servlet. This name is used as the value of the javax.servlet.ServletConfig.getServletName()
method and defaults to the fully qualified class name of the service object.
osgi.http.whiteboard.servlet.pattern Registration pattern(s) for the servlet.
servlet.init.* Properties starting with this prefix are provided as init parameters to the javax.servlet.Servlet.init(ServletConfig) method. The servlet.init. prefix is removed from the parameter name.

The Http Whiteboard service needs to call javax.servlet.Servlet.init(ServletConfig) to initialize the servlet before it starts to serve requests, and when it is not needed anymore javax.servlet.Servlet.destroy() to shut down the servlet. If more than one Http Whiteboard implementation is available in a runtime, the init() and destroy() calls would be executed multiple times, which violates the Servlet specification. It is therefore recommended to use the PROTOTYPE scope for servlets to ensure that every Http Whiteboard implementation gets its own service instance.

Note:
In a controlled runtime, like an RCP application that is delivered with one Http Whiteboard implementation and that does not support installing bundles at runtime, the usage of the PROTOTYPE scope is not required. Actually such a runtime ensures that the servlet is only instantiated and initialized once. But if possible it is recommended that the PROTOTYPE scope is used.

To register a servlet as an error page, the service property osgi.http.whiteboard.servlet.errorPage needs to be set. The value can be either a three-digit  HTTP error code, the special codes 4xx or 5xx to specify a range or error codes, or a fully qualified exception class name. The service property osgi.http.whiteboard.servlet.pattern is not required for servlets that provide error pages.

The following snippet shows an error page servlet that deals with IllegalArgumentExceptions and the HTTP error code 500. It can be tested by calling the inverter servlet without a query parameter.

@Component(
    service=Servlet.class,
    property= {
        "osgi.http.whiteboard.servlet.errorPage=java.lang.IllegalArgumentException",
        "osgi.http.whiteboard.servlet.errorPage=500"
    },
    scope=ServiceScope.PROTOTYPE)
public class ErrorServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        resp.setContentType("text/html");
        resp.getWriter().write(
        "<html><body>You need to provide an input!</body></html>");
    }
}

Register Filters

Via servlet filters it is possible to intercept servlet invocations. They are used to modify the ServletRequest and ServletResponse to perform common tasks before and after the servlet invocation.

The example below shows a servlet filter that adds a simple header and footer on each request to the servlet with the /invert pattern:

@Component(
    property = "osgi.http.whiteboard.filter.pattern=/invert",
    scope=ServiceScope.PROTOTYPE)
public class SimpleServletFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig)
            throws ServletException { }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        response.setContentType("text/html");
        response.getWriter().write("<b>Inverter Servlet</b><p>");
        chain.doFilter(request, response);
        response.getWriter().write("</p><i>Powered by fipro</i>");
    }

    @Override
    public void destroy() { }

}

To register a servlet filter the following criteria must match:

  • It needs to be registered as OSGi service of type javax.servlet.Filter.
  • One of the given component properties needs to be set:
    • osgi.http.whiteboard.filter.pattern
    • osgi.http.whiteboard.filter.regex
    • osgi.http.whiteboard.filter.servlet
  • The service scope should be PROTOTYPE.

For registering servlet filters the following service properties are supported. (see OSGi Compendium Specification Release 6 – Table 140.5):

Service Property Description
osgi.http.whiteboard.filter.asyncSupported Declares whether the servlet filter supports asynchronous operation mode. Allowed values are true and false independent of case. Defaults to false.
osgi.http.whiteboard.filter.dispatcher Select the dispatcher configuration when the
servlet filter should be called. Allowed string values are REQUEST, ASYNC, ERROR, INCLUDE, and FORWARD. The default for a filter is REQUEST.
osgi.http.whiteboard.filter.name The name of a servlet filter. This name is used as the value of the FilterConfig.getFilterName() method and defaults to the fully qualified class name of the service object.
osgi.http.whiteboard.filter.pattern Apply this servlet filter to the specified URL path patterns. The format of the patterns is specified in the servlet specification.
osgi.http.whiteboard.filter.regex Apply this servlet filter to the specified URL paths. The paths are specified as regular expressions following the syntax defined in the java.util.regex.Pattern class.
osgi.http.whiteboard.filter.servlet Apply this servlet filter to the referenced servlet(s) by name.
filter.init.* Properties starting with this prefix are passed as init parameters to the Filter.init() method. The filter.init. prefix is removed from the parameter name.

Register Resources

It is also possible to register a service that informs the Http Whiteboard service about static resources like HTML files, images, CSS- or Javascript-files. For this a simple service can be registered that only needs to have the following two mandatory service properties set:

Service Property Description
osgi.http.whiteboard.resource.pattern The pattern(s) to be used to serve resources. As defined by the [4] Java Servlet 3.1 Specification in section 12.2, Specification of Mappings.This property marks the service as a resource service.
osgi.http.whiteboard.resource.prefix The prefix used to map a requested resource to the bundle’s entries. If the request’s path info is not null, it is appended to this prefix. The resulting
string is passed to the getResource(String) method of the associated Servlet Context Helper.

The service does not need to implement any specific interface or function. All required information is provided via the component properties.

To create a resource service follow these steps:

  • Create a folder resources in the project org.fipro.inverter.http
  • Add an image in that folder, e.g. eclipse_logo.png
  • PDE – Add the resources folder in the build.properties
  • Bndtools – Add the following line to the bnd.bnd file on the Source tab
    -includeresource: resources=resources
  • Create resource service
@Component(
    service = ResourceService.class,
    property = {
        "osgi.http.whiteboard.resource.pattern=/files/*",
        "osgi.http.whiteboard.resource.prefix=/resources"})
public class ResourceService { }

After starting the application the static resources located in the resources folder are available via the /files path in the URL, e.g. http://localhost:8080/files/eclipse_logo.png

Note:
While writing this blog post I came across a very nasty issue. Because I initially registered the servlet filter for the /* pattern, the simple header and footer where always added. This also caused setting the content type, that didn’t match the content type of the image of course. And so the static content was never shown correctly. So if you want to use servlet filters to add common headers and footers, you need to take care of the pattern so the servlet filter is not applied to static resources.

Register Servlet Listeners

It is also possible to register different servlet listeners as whiteboard services. The following listeners are supported according to the servlet specification:

  • ServletContextListener – Receive notifications when Servlet Contexts are initialized and destroyed.
  • ServletContextAttributeListener – Receive notifications for Servlet Context attribute changes.
  • ServletRequestListener – Receive notifications for servlet requests coming in and being destroyed.
  • ServletRequestAttributeListener – Receive notifications when servlet Request attributes change.
  • HttpSessionListener – Receive notifications when Http Sessions are created or destroyed.
  • HttpSessionAttributeListener – Receive notifications when Http Session attributes change.
  • HttpSessionIdListener – Receive notifications when Http Session ID changes.

There is only one component property needed to be set so the Http Whiteboard implementation is handling the listener.

Service Property Description
osgi.http.whiteboard.listener When set to true this listener service is handled by the Http Whiteboard implementation. When not set or set to false the service is ignored. Any other value is invalid.

The following example shows a simple ServletRequestListener that prints out the client address on the console for each request (borrowed from the OSGi Compendium Specification):

@Component(property = "osgi.http.whiteboard.listener=true")
public class SimpleServletRequestListener
    implements ServletRequestListener {

    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("Request initialized for client: "
            + sre.getServletRequest().getRemoteAddr());
    }

    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("Request destroyed for client: "
            + sre.getServletRequest().getRemoteAddr());
    }

}

Servlet Context and Common Whiteboard Properties

The ServletContext is specified in the servlet specification and provided to the servlets at runtime by the container. By default there is one ServletContext and without additional information the servlets are registered to that default ServletContext via the Http Whiteboard implementation. This could lead to scenarios where different bundles provide servlets for the same request mapping. In that case the service.ranking will be inspected to decide which servlet should be delivered. If the servlets belong to different applications, it is possible to specify different contexts. This can be done by registering a custom ServletContextHelper as whiteboard service and associate the servlets to the corresponding context. The ServletContextHelper can be used to customize the behavior of the ServletContext (e.g. handle security, provide resources, …) and to support multiple web-applications via different context paths.

A custom ServletContextHelper it needs to be registered as service of type ServletContextHelper and needs to have the following two service properties set:

  • osgi.http.whiteboard.context.name
  • osgi.http.whiteboard.context.path
Service Property Description
osgi.http.whiteboard.context.name Name of the Servlet Context Helper. This name can be referred to by Whiteboard services via the osgi.http.whiteboard.context.select property. The syntax of the name is the same as the syntax for a Bundle Symbolic Name. The default Servlet Context Helper is named default. To override the
default, register a custom ServletContextHelper service with the name default. If multiple Servlet Context Helper services are registered with the same name, the one with the highest Service Ranking is used. In case of a tie, the service with the lowest service ID wins. In other words, the normal OSGi service ranking applies.
osgi.http.whiteboard.context.path Additional prefix to the context path for servlets. This property is mandatory. Valid characters are specified in IETF RFC 3986, section 3.3. The context path of the default Servlet Context Helper is /. A custom default Servlet Context Helper may use an alternative path.
context.init.* Properties starting with this prefix are provided as init parameters through the ServletContext.getInitParameter() and ServletContext.getInitParameterNames() methods. The context.init. prefix is removed from the parameter name.

The following example will register a ServletContextHelper for the context path /eclipse and will retrieve resources from http://www.eclipse.org. It is registered with BUNDLE service scope to ensure that every bundle gets its own instance, which is for example important to resolve resources from the correct bundle.

Note:
Create it in a new package org.fipro.inverter.http.eclipse within the org.fipro.inverter.http project, as we will need to create some additional resources to show how this example actually works.

@Component(
    service = ServletContextHelper.class,
    scope = ServiceScope.BUNDLE,
    property = {
        "osgi.http.whiteboard.context.name=eclipse",
        "osgi.http.whiteboard.context.path=/eclipse" })
public class EclipseServletContextHelper extends ServletContextHelper {

    public URL getResource(String name) {
        // remove the path from the name
        name = name.replace("/eclipse", "");
        try {
            return new URL("http://www.eclipse.org/" + name);
        } catch (MalformedURLException e) {
            return null;
        }
    }
}

Note:
With PDE remember to add org.osgi.service.http.context to the Imported Packages. With Bndtools remember to add the new package to the Private Packages in the bnd.bnd file on the Contents tab.

To associate servlets, servlet filter, resources and listeners to a ServletContextHelper, they share common service properties (see OSGi Compendium Specification Release 6 – Table 140.3) additional to the service specific properties:

Service Property Description
osgi.http.whiteboard.context.select An LDAP-style filter to select the associated ServletContextHelper service to use. Any service property of the Servlet Context Helper can be filtered on. If this property is missing the default Servlet Context Helper is used. For example, to select a Servlet Context Helper with name myCTX provide the following value:
(osgi.http.whiteboard.context.name=myCTX)To select all Servlet Context Helpers provide the following value:
(osgi.http.whiteboard.context.name=*)
osgi.http.whiteboard.target The value of this service property is an LDAP style filter expression to select the Http Whiteboard implementation(s) to handle this Whiteboard service. The LDAP filter is used to match HttpServiceRuntime services. Each Http Whiteboard implementation exposes exactly one HttpServiceRuntime service. This property is used to associate the Whiteboard service with the Http Whiteboard implementation that registered the HttpServiceRuntime service. If this property is not specified, all Http Whiteboard implementations can handle the service.

The following example will register a servlet only for the introduced /eclipse context:

@Component(
    service=Servlet.class,
    property= {
        "osgi.http.whiteboard.servlet.pattern=/image",
        "osgi.http.whiteboard.context.select=(osgi.http.whiteboard.context.name=eclipse)"
    },
    scope=ServiceScope.PROTOTYPE)
public class ImageServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        resp.setContentType("text/html");
        resp.getWriter().write("Show an image from www.eclipse.org");
        resp.getWriter().write(
            "<p><img src='img/nattable/images/FeatureScreenShot.png'/></p>");
    }

}

And to make this work in combination with the introduced ServletContextHelper we need to additionally register the resources for the /img context, which is also only assigned to the /eclipse context:

@Component(
    service = EclipseImageResourceService.class,
    property = {
        "osgi.http.whiteboard.resource.pattern=/img/*",
        "osgi.http.whiteboard.resource.prefix=/eclipse",
        "osgi.http.whiteboard.context.select=(osgi.http.whiteboard.context.name=eclipse)"})
public class EclipseImageResourceService { }

If you start the application and browse to http://localhost:8080/eclipse/image you will see an output from the servlet together with an image that is loaded from http://www.eclipse.org.

Note:
The component properties and predefined values are available via org.osgi.service.http.whiteboard.HttpWhiteboardConstants. So you don’t need to remember them all and can also retrieve some additional information about the properties via the corresponding Javadoc.

The sources for this tutorial are hosted on GitHub in the already existing projects:

 

Posted in Dirk Fauth, Eclipse, Java, OSGi | 5 Comments

OSGi Declarative Services news in Eclipse Oxygen

With this blog post I want to share my excitement about the OSGi DS related news that are coming with Eclipse Oxygen. I want to use this blog post to inform about the new features and also the changes you will face with these. With Oxygen M6 you can already have a look at those features and also provide feedback if you find any issues.

Note:
You don’t have to be a committer or contribute code to be part of an Open Source Community. Also testing new features and providing feedback is a very welcome contribution to a project. So feel free to participate in making the Eclipse Oxygen release even better than the previous releases!

DS 1.3 with Felix SCR

The Equinox team decided to drop Equinox DS (stuck with DS 1.2) and replace it with Felix SCR (Bug 501950). This brings DS 1.3 to Eclipse which was the last missing piece in the OSGi R6 compendium in Equinox.

It was already possible to exchange Equinox DS with Felix SCR with Neon, but now you don’t need to replace it yourself, it is directly part of Equinox. There are some important things to notice though, which I will list here:

Felix SCR bundle from Orbit

The Felix SCR bundle included in Equinox/Eclipse is not equal to the Felix SCR bundle from Apache. The Apache bundle imports and exports the org.osgi packages it requires, e.g. the component related interfaces like ComponentContext, ComponentFactory or ComponentServiceObjects. It also contains the Promises API used by Felix SCR, which was not available in Equinox before. The Felix SCR bundle in Orbit does not contain these packages. They are provided by other Equinox bundles, which are now required to use DS with Equinox.

Note:
If you are interested in some more information about the reasons for the changes to the Orbit Felix SCR bundle, have a look at Bug 496559 where Thomas Watson explained the reasons very nicely.

The bundles needed for DS in Equinox are now as follows:

  • org.apache.felix.scr
    The Declarative Services implementation.
  • org.eclipse.osgi.services
    Contains the required OSGi service interfaces.
  • org.eclipse.osgi.util
    Contains the Promises API and implementation required by Felix SCR.
  • org.eclipse.equinox.ds (optional)
    Wrapper bundle to start Felix SCR and provide backwards compatibility.

Adding the Promises API (see OSGi R6 Compendium Specification chapter 705) in Equinox is also a very nice, but worth its own blog post. So I will not go into more details here. The more interesting thing is that org.eclipse.equinox.ds is still available and in some scenarios required. It does not contain a DS implementation anymore. It is used as a wrapper bundle to start Felix SCR and provide backwards compatibility. The main reasons are:

  1. Auto-starting DS
    The Equinox startup policy is to start bundles only if a class is accessed from them, or if it is configured for auto-starting. As the SCR needs to be automatically started but actually no one really accesses a class from it, every Eclipse application that makes use of Declarative Services configured the auto-start of org.eclipse.equinox.ds in the Product Configuration. If that bundle would be simply replaced, every Eclipse based product would need to modify the Product Configuration.
  2. Behavioral Compatibility
    Equinox DS and Felix SCR behave differently in some cases. For example Felix SCR deactivates and destroys a component once the last consumer, that references the component instance, is done with it. Equinox DS on the other hand keeps the instance (I explained that in my Control OSGi DS Component Instances blog post). As p2 and probably also other implementations rely on the Equinox behavior that components are not deactivated and destroyed immediately, the property

    ds.delayed.keepInstances=true

    is set automatically by org.eclipse.equinox.ds.

Considering these changes it is also possible to remove org.eclipse.equinox.ds from an Eclipse Product Configuration and solely rely on org.apache.felix.scr. You just need to ensure org.apache.felix.scr is automatically started and ds.delayed.keepInstances is set to true (e.g. required when using p2 as described in Bug 510673.

DS Console Commands

If you want to inspect services via console, you need to know the new commands, as the old commands are not available anymore:

Equinox DS Felix SCR Description
list/ls
[bundle-id]
scr:list
[bundle-id]
List all components.
component|comp
<comp-id>
scr:info
<comp-id>
Print all component information.
enable|en
<comp-id>
scr:enable
<comp-name>
Enable a component.
disable|dis
<comp-id>
scr:disable
<comp-name>
Disable a component.
enableAll|enAll
[bundle-id]
- Enable all components.
disableAll|disAll
[bundle-id]
- Disable all components.

Despite some different command names and the fact that the short versions are not supported, you should notice the following:

  • The scope (scr:) is probably not needed in Equinox because there are by default no multiple commands with the same name. So only the command names after the colon can be used.
  • There are no equivalent commands to enable or disable all components at once.
  • To enable or disable a component you need to specify the name of the component, not the id that is shown by calling list before.

DS 1.3 Annotations Support in PDE

With Eclipse Neon the DS Annotations Support was added to PDE. Now Peter Nehrer (Twitter: @pnehrer) has contributed the support for DS 1.3 annotations. In the Preferences you will notice that you can specify which DS specification version you want to use. By default it is set to 1.3. The main idea is that it is possible to configure that only DS 1.2 annotations should be used in case you still need to develop on that specification level (e.g. for applications that run on Eclipse Neon).

PDE_DS_annotations_1.3

The Preferences page also has another new setting “Add DS Annotations to classpath”, which is enabled by default. That setting will automatically add the necessary library to the classpath. While this is nice if you only implement a plain OSGi application, this will cause issues in case of Eclipse RCP applications that are build using Tycho. The JAR that is added to the classpath is located in the IDE, so the headless Tycho build is not aware of it! For Eclipse RCP development I therefore suggest to disable that setting and add org.osgi.service.component.annotations as an optional dependency to the Import-Package header as described in my Getting Started tutorial. At least if the bundles should be build with Tycho.

As a quick overview, with DS 1.3 the following modifications to the annotations are available:

  • Life cycle methods accept Component Property Types as parameter
  • Introduction of the Field Strategy which means @Reference can be used for field injection
  • Event methods can get the ComponentServiceObjects parameter type for PROTOTYPE scoped references, and there are multiple parameter type options for these methods
  • @Component#configurationPid
    multiple configuration PID values can be set and the value “$” can be used as placeholder for the name of the component
  • @Component#servicefactory
    deprecated and replaced by scope
  • @Component#reference
    specify Lookup Strategy references
  • @Component#scope
    specify the service scope of the component
  • @Reference#bind
    specify the name of the bind event method of a reference
  • @Reference#field
    name of the field, typically not specified manually
  • @Reference#fieldOption
    specify how field values should be managed
  • @Reference#scope
    specify the reference scope

Note:
For further information have a look at my previous blog posts where I explained these options in comparison to DS 1.2.

Although already in a very good shape, the DS 1.3 annotations are not finished 100% as of now. I already uncovered the following missing pieces:

  • Missing Require-Capability header in MANIFEST.MF (Bug 513216)
  • Missing Provide-Capability header in MANIFEST.MF (Bug 490063)
  • False error when using bind/updated/unbind parameter on field references (Bug 513462)

IMHO it would be also nice if the necessary p2.inf files are automatically created/updated to support p2 Capability Advice configurations, which is necessary because p2 still does not support OSGi capabilities.

As stated at the beginning, you could help with the implementation by testing and giving feedback on this implementation. It would be very helpful to have more people testing this, to have a stable implementation in the Oxygen release.

Thanks to Peter for adding that long waiting feature to PDE!

@Service Annotation for Eclipse RCP

Also for RCP development there are some news with regards to OSGi services. The @Service annotation, created by Tom Schindl for the e(fx)clipse project, has been ported to the Eclipse Platform (introduced here).

When using the default Eclipse 4 injection mechanisms, the injection of OSGi services is limited to a unary cardinality. Given an OSGi service of type StringInverter (see my previous tutorials) the injection can be done like this:

public class SamplePart {

    @Inject
    StringInverter inverter;

    @PostConstruct
    public void postConstruct(Composite parent) {
        ...
    }
}
public class SamplePart {

    @Inject
    @Optional
    StringInverter inverter;

    @PostConstruct
    public void postConstruct(Composite parent) {
        ...
    }
}

This means:

  • Only a single service instance can get injected.
  • If the cardinality is MANDATORY (no @Optional), a service instance needs to be available, otherwise the injection fails with an exception.
  • If the cardinality is OPTIONAL (@Inject AND @Optional) and no service is available at creation time, a new service will get injected when it becomes available.

This behavior is similar to the DYNAMIC GREEDY policy for OSGi DS service references. But the default injection mechanism for OSGi services has several issues that are reported in Bug 413287.

  • If a service is injected and a new service becomes available, the new service will be injected, regardless of his service ranking. So even if the new service has a lower ranking it will be injected. Compared with the OSGi service specification this is incorrect as the service with the highest ranking should be used, or, if the ranking is equal, the service that was registered first .
  • If a service is injected and it becomes unavailable, there is no injection of a service with a lower service ranking. Instead null will be injected, even if a valid service is still available.
  • If a service implements multiple service interfaces, only the first service key is reset.
  • If a service instance should be created per bundle or per requestor by using either a service factory or scope, there will be only one instance for every request, because the service is always requested via BundleContext of one of the platform bundles.

Note:
I was able to provide a fix for the first three points. The last issue in the list regarding scoped services can not be solved for the default injection mechanism.

The @Service annotation was introduced to solve all these issues and additionally support the multiple cardinality (only MULTIPLE, not AT_LEAST_ONE).

To use it simply add @Service additionally to @Inject:

public class SamplePart {

    @Inject
    @Service
    StringInverter inverter;

    @PostConstruct
    public void postConstruct(Composite parent) {
        ...
    }
}

The above snippet is similar to the Field Strategy in OSGi DS. To get something similar to the Event Strategy you would use method injection like in the following snippet:

public class SamplePart {

    StringInverter inverter;

    @Inject
    public void setInverter(@Service StringInverter inverter) {
        this.inverter = inverter;
    }
    @PostConstruct
    public void postConstruct(Composite parent) {
        ...
    }
}

With using the @Service annotation on a unary reference, you get a behavior similar to the DYNAMIC GREEDY policy for OSGi DS service references, which is actually the same as with the default injection mechanism after my fix is applied. Additionally the usage of a service factory or scoped services is supported by using the @Service annotation, as the BundleContext of the requestor is used to retrieve the service.

Note:
While writing this blog post there is an issue with the OPTIONAL cardinality in case no service is available at creation time. If a new service becomes available, it is not injected automatically. I created Bug 513563 for this and provided a fix for both, the Eclipse Platform and e(fx)clipse.

One interesting feature of the @Service annotation is the support of the MULTIPLE cardinality. This way it is possible to get all OSGi services of a specific type injected, in the same order as in the OSGi service registry. For this simply use the injection on a list of the desired service type.

public class SamplePart {

    @Inject
    @Service
    List<StringInverter> inverter;

    @PostConstruct
    public void postConstruct(Composite parent) {
        ...
    }
}

Another nice feature (and also pretty new for e(fx)clipse) is the filter support. Tom introduced this here. e(fx)clipse supports static as well as dynamic filters that can change at runtime. Because of dependency issues only the support for static filters was ported to the Eclipse Platform. Via filterExpression type element it is possible to specify an LDAP filter to constrain the set of services that should be injected. This is similar to the target type element of OSGi DS service references.

public class SamplePart {

    // only get services injected that have specified the
    // value "online" for the component property "connection"
    @Inject
    @Service(filterExpression="(connection=online)")
    List<StringInverter> inverter;

    @PostConstruct
    public void postConstruct(Composite parent) {
        ...
    }
}

With the @Service annotation the Eclipse injection for OSGi services aligns better with OSGi DS. And with the introduction of DS 1.3 to Equinox the usage of OSGi services for Eclipse RCP applications should become even more a common pattern than it was before with using the Equinox only Extension Points.

For me the news on OSGi DS in the Eclipse Platform are the most interesting ones in the Oxygen release. But of course not the only ones. So I encourage everyone to try out the newest Oxygen milestone releases to get the best out of it for everyone!

Posted in Dirk Fauth, Eclipse, Java, OSGi | 1 Comment

Control OSGi DS Component Instances via Configuration Admin

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.

In my last blog post I showed how to manage service instances with Declarative Services. In that scope I mainly showed the following scenarios:

  • one service instance per runtime
  • one service instance per bundle
  • one service instance per component/requestor
  • one service instance per request

For cases like the one in the RAP scenario, these four categories doesn’t match very well. We actually need something additionally like one service per session. But a session is nothing natural in the OSGi world. At least not as natural as it is in the context of a web application.

First I tried to find a solution using PROTOTYPE scoped services introduced with DS 1.3. But IMHO that approach doesn’t fit very well, as by default the services have bundle scope, unless the consumer specifies that a new instance is needed. Also the approach of creating service instances on demand by using a Factory Component or the DS 1.3 ComponentServiceObjects interface does not seem to be a good option in this case. The consumer is in charge of creating and destroying the instances, and he needs to be aware of that fact.

A session is mainly used to associate a set of states to someone (e.g. a user) over time. The localization setting of a user is a configuration value. And configurations for OSGi services are managed by the Configuration Admin. Having these things in mind and searching the web and digging through the OSGi Compendium Specification, I came across the Managed Service Factory and this blog post by Neil Bartlett (already quiet some years old).

To summarize the information in short, the idea is to create a new service instance per Component Configuration. So for every session a new Component Configuration needs to be created, which leads to the creation of a new Component Instance. Typically some unique identifier like the session ID needs to be added to the component properties, so it is possible to use filters based on that.

The Managed Service Factory description in the specification is hard to understand (at least for me), the tutorials that exist mainly focus on the usage without Declarative Services by implementing the corresponding interfaces, and the blog post by Neil unfortunately only covers half of the topic. Therefore I will try to explain how to create service instances for different configurations with a small example that is based on the previous tutorial.

The sources for this blog post can be found in my DS projects on GitHub:

Note:
I will try to bring in some Configuration Admin details at the corresponding places, but for more information in advance please have a look at my Configuring OSGi Declarative Services blog post.

Service Implementation

Let’s start by creating the service implementation. Implement the OneShot service interface and put it in the org.fipro.oneshot.provider bundle from the previous blog post.

@Component(
    configurationPid="org.fipro.oneshot.Borg",
    configurationPolicy=ConfigurationPolicy.REQUIRE)
public class Borg implements OneShot {

    @interface BorgConfig {
        String name() default "";
    }

    private static AtomicInteger instanceCounter =
            new AtomicInteger();

    private final int instanceNo;
    private String name;

    public Borg() {
        instanceNo = instanceCounter.incrementAndGet();
    }

    @Activate
    void activate(BorgConfig config) {
        this.name = config.name();
    }

    @Modified
    void modified(BorgConfig config) {
        this.name = config.name();
    }

    @Override
    public void shoot(String target) {
        System.out.println("Borg " + name
            + " #" + instanceNo + " of "+ instanceCounter.get()
            + " took orders and executed " + target);
    }

}

You should notice the following with that implementation:

  • We specify a configuration PID so it is not necessary to use the fully qualified class name later.
    Remember: the configuration PID defaults to the configured name, which defaults to the fully qualified class name of the component class.
  • We set the configuration policy REQUIRE, so the component will only be satisfied and therefore activated once a matching configuration object is set by the Configuration Admin.
  • We create the Component Property Type BorgConfig for type safe access to the Configuration Properties (DS 1.3).
  • We add life cycle methods for activate to initially consume and modified to be able to change the configuration at runtime.

Configuration Creation

The next thing is to create a configuration. For this we need to have a look at the ConfigurationAdmin API. In my Configuring OSGi Declarative Services blog post I only talked about ConfigurationAdmin#getConfiguration(String, String). This is used to get or create the configuration of  a singleton service. For the configuration policy REQUIRE this means that a single Managed Service is created once the Configuration object is used by a requesting bundle. In such a case the Configuration Properties will contain the property service.pid with the value of the configuration PID.

To create and handle multiple service instances via Component Configuration, a different API needs to be used. For creating new Configuration objects there is ConfigurationAdmin#createFactoryConfiguration(String, String). This way a Managed Service Factory will be registered by the requesting bundle, which allows to create multiple Component Instances with different configurations. In this case the Configuration Properties will contain the property service.factoryPid with the value of the configuration PID and the service.pid with a unique value.

As it is not possible to mix Managed Services and Managed Service Factories with the same PID, another method needs to be used to access existing configurations. For this ConfigurationAdmin#listConfigurations(String) can be used. The parameter can be a filter and the result will be an array of Configuration objects that match the filter. The filter needs to be an LDAP filter that can test any Configuration Properties, including service.pid and service.factoryPid. The following snippet for example will only return existing Configuration objects for the Borg service when it was created via Managed Service Factory.

this.configAdmin.listConfigurations(
    "(service.factoryPid=org.fipro.oneshot.Borg)")

The parameters of ConfigurationAdmin#getConfiguration(String, String) and ConfigurationAdmin#createFactoryConfiguration(String, String) are actually the same. The first parameter is the PID that needs to match the configuration PID of the component, the second is the location binding. It is best practice to use “?” as value for the location parameter.

Create the following console command in the org.fipro.oneshot.command bundle:

@Component(
    property= {
        "osgi.command.scope=fipro",
        "osgi.command.function=assimilate"},
    service=AssimilateCommand.class
)
public class AssimilateCommand {

    @Reference
    ConfigurationAdmin configAdmin;

    public void assimilate(String soldier) {
        assimilate(soldier, null);
    }

    public void assimilate(String soldier, String newName) {
        try {
            // filter to find the Borg created by the
            // Managed Service Factory with the given name
            String filter = "(&(name=" + soldier + ")"
                + "(service.factoryPid=org.fipro.oneshot.Borg))";
            Configuration[] configurations =
                this.configAdmin.listConfigurations(filter);

            if (configurations == null
                    || configurations.length == 0) {
                //create a new configuration
                Configuration config =
                    this.configAdmin.createFactoryConfiguration(
                        "org.fipro.oneshot.Borg", "?");
                Hashtable<String, Object> map = new Hashtable<>();
                if (newName == null) {
                    map.put("name", soldier);
                    System.out.println("Assimilated " + soldier);
                } else {
                    map.put("name", newName);
                    System.out.println("Assimilated " + soldier
                        + " and named it " + newName);
                }
                config.update(map);
            } else if (newName != null) {
                // update the existing configuration
                Configuration config = configurations[0];
                // it is guaranteed by listConfigurations() that
                // only Configuration objects are returned with
                // non-null properties
                Dictionary<String, Object> map =
                    config.getProperties();
                map.put("name", newName);
                config.update(map);
                System.out.println(soldier
                    + " already assimilated and renamed to "
                    + newName);
            }
        } catch (IOException | InvalidSyntaxException e1) {
            e1.printStackTrace();
        }
    }
}

In the above snippet name is used as the unique identifier for a created Component Instance. So the first thing is to check if there is already a Configuration object in the database for that name. This is done by using ConfigurationAdmin#listConfigurations(String) with an LDAP filter for the name and the Managed Service Factory with service.factoryPid=org.fipro.oneshot.Borg which is the value of the configuration PID we used for the Borg service component. If there is no configuration available for a Borg with the given name, a new Configuration object will be created, otherwise the existing one is updated.

Note:
To verify the Configuration Properties you could extend the activate method of the Borg implementation to show them on the console like in the following snippet:

@Activate
void activate(BorgConfig config, Map<String, Object> properties) {
    this.name = config.name();
    properties.forEach((k, v) -> {
        System.out.println(k+"="+v);
    });
}

Once a service instance is activated it should output all Configuration Properties, including the service.pid and service.factoryPid for the instance.

Note:
Some more information on that can be found in the enRoute documentation and of course in the specification.

Service Consumer

Finally we add the following execute command in the org.fipro.oneshot.command bundle to verify the instance creation:

@Component(
    property= {
        "osgi.command.scope=fipro",
        "osgi.command.function=execute"},
    service=ExecuteCommand.class
)
public class ExecuteCommand {

    @Reference(target="(service.factoryPid=org.fipro.oneshot.Borg)")
    private volatile List<OneShot> borgs;

    public void execute(String target) {
        for (ListIterator<OneShot> it =
            borgs.listIterator(borgs.size());
                it.hasPrevious(); ) {
                it.previous().shoot(target);
        }
    }
}

For simplicity we have a dynamic reference to all available OneShot service instances that have the service.factoryPid=org.fipro.oneshot.Borg. As a short reminder on the DS 1.3 field strategy: if the type is a Collection the cardinality is 0..n, and marking it volatile specifies it to be a dynamic reluctant reference.

Starting the application and executing some assimilate and execute commands will show something similar to the following on the console:

g! assimilate Lars
Assimilated Lars
g! assimilate Simon
Assimilated Simon
g! execute Dirk
Borg Lars #1 of 2 took orders and executed Dirk
Borg Simon #2 of 2 took orders and executed Dirk
g! assimilate Lars Locutus
Lars already assimilated and renamed to Locutus
g! execute Dirk
Borg Locutus #1 of 2 took orders and executed Dirk
Borg Simon #2 of 2 took orders and executed Dirk

The first two assimilate calls create new Borg service instances. This is verified by the execute command. The following assimilate call renames an existing Borg, so no new service instance is created.

Now that I have learned about Managed Service Factories and how to use them with DS, I hope I am able to adapt that in the Eclipse Platform. So stay tuned for further DS news!

Posted in Dirk Fauth, Eclipse, Java, OSGi | 1 Comment

Control OSGi DS Component Instances

I recently came across some use cases where a more fine grained control is needed for component instance creation. I spent some time in investigating how this is done with OSGi Declarative Services in detail. It turned out that it is easier as it seems, mainly because of missing or misleading tutorials. Therefore I decided to write a new blog post about that topic as part of my OSGi Declarative Service blog post series.

For the start you need to know that by default there is only one component configuration created and activated in the OSGi runtime at the same time. This means that every bundle is sharing the same component instance. So you have a singleton instance for every service. Note: singleton instance in terms of “one single instance” not “Singleton Pattern”!

If you think about multi-threading or context dependent services, you may need multiple instances of a service. In an OSGi environment there are basically the following categories:

  • one service instance per runtime
  • one service instance per bundle
  • one service instance per component/requestor
  • one service instance per request

Instance creation control can only be done for service components. So ensure to specify the service annotation type element in @Component if the implementation does not implement an interface.

To control the instance creation you can use the following mechanisms:

  • DS 1.2 – servicefactory annotation type element of @Component
  • DS 1.3 – scope annotation type element of @Component to specify the service scope
  • DS 1.2 / DS 1.3 – Create a factory component by using the factory annotation type element of @Component

Preparation

For some hands on this topic, we first create some bundles to play with.

Note:
I don’t want to explain every step for creating services in detail in this blog post. If you don’t know how to perform the necessary steps, please refer to my Getting Started with OSGi Declarative Services blog post.

  1. Create an API bundle org.fipro.oneshot.api with a service interface OneShot
    public interface OneShot {
    
        void shoot(String target);
    
    }
  2. Create a provider bundle org.fipro.oneshot.provider with a service implementation Hitman
    @Component
    public class Hitman implements OneShot {
    
        private static AtomicInteger instanceCounter =
                new AtomicInteger(); 
    
        private final int instanceNo;
    
        public Hitman() {
            instanceNo = instanceCounter.incrementAndGet();
        }
    
        @Override
        public void shoot(String target) {
            System.out.println("BAM! I am hitman #"
                + instanceNo + ". And I killed " + target);
        }
    
    }

    This implementation will count the number of instances in a static field and remembers it in a member variable, so we can identify the created instance when the service is called.

  3. Create a command bundle org.fipro.oneshot.command with a console command to call the service
    @Component(
        property= {
            "osgi.command.scope=fipro",
            "osgi.command.function=kill"},
        service=KillCommand.class
    )
    public class KillCommand {
    
        private OneShot killer;
    
        @Reference
        void setOneShot(OneShot oneShot) {
            this.killer = oneShot;
        }
    
        public void kill(String target) {
            killer.shoot(target);
        }
    }
  4. Create a command bundle org.fipro.oneshot.assassinate with two different console commands that call the service
    @Component(
        property= {
            "osgi.command.scope=fipro",
            "osgi.command.function=assassinate"},
        service=AssassinateCommand.class
    )
    public class AssassinateCommand {
    
        private OneShot hitman;
    
        @Reference
        void setOneShot(OneShot oneShot) {
            this.hitman = oneShot;
        }
    
        public void assassinate(String target) {
            hitman.shoot(target);
        }
    }
    @Component(
        property= {
            "osgi.command.scope=fipro",
            "osgi.command.function=eliminate"},
        service=EliminateCommand.class
    )
    public class EliminateCommand {
    
        private ComponentContext context;
        private ServiceReference<OneShot> sr;
    
        @Activate
        void activate(ComponentContext context) {
            this.context = context;
        }
    
        @Reference(name="hitman")
        void setOneShotReference(ServiceReference<OneShot> sr) {
            this.sr = sr;
        }
    
        public void eliminate(String target) {
            OneShot hitman =
                (OneShot) this.context.locateService("hitman", sr);
            hitman.shoot(target);
        }
    }

The EliminateCommand uses the Lookup Strategy to lazily activate the referenced component. In this example probably quite useless, but I wanted to show that this also works fine.

Note:
I am using the DS 1.2 notation here to make it easier to follow the example in both worlds. In the DS 1.3 only examples later in this blog post, you will see the modified version of the components using DS 1.3 annotations.

The sources for this blog post can be found on GitHub:

One instance per runtime

There is not much to say about this. This is the default behavior if you do not specify something else. There is only one component configuration created and activated. Therefore only one component instance is created and shared between all bundles.

In DS 1.2 a singleton instance can be explicitly configured on the component like this:

@Component(servicefactory=false)
public class Hitman implements OneShot {

In DS 1.3 a singleton instance can be explicitly configured on the component like this:

@Component(scope=ServiceScope.SINGLETON)
public class Hitman implements OneShot {

Note:
For Immediate Components and Factory Components it is not allowed to use other values for servicefactory or scope!

If you launch an OSGi application with the necessary bundles (org.apache.felix.scr, org.apache.felix.gogo.*, org.fipro.oneshot.*) and call the commands one after the other, you should get an output similar to this (on a Felix console):

g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #1. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #1. And I killed Dirk

Every command has a reference to the same Hitman instance, as can be seen by the instance counter in the output.

One instance per bundle

There are use cases where it is useful to have one component configuration created and activated per bundle. For example if the component configuration contains special bundle related configurations.

In DS 1.2 a bundle scope service can be configured on the component like this:

@Component(servicefactory=true)
public class Hitman implements OneShot {

In DS 1.3 a bundle scope service can be configured on the component like this:

@Component(scope=ServiceScope.BUNDLE)
public class Hitman implements OneShot {

When launching the OSGi application and calling the commands one after the other, you should get an output similar to this (on a Felix console):

g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #2. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #2. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk

You can see that the kill command has a reference to the Hitman instance #1, while the assassinate and the eliminate command both have a reference to the Hitman instance #2, as both reside in the same bundle.

One instance per requestor

There are some use cases where every consumer needs its own instance of a service. With DS 1.2 you could achieve this by creating a Factory Component. As this is basically the same as getting a service instance per request, I will explain the Factory Component in the following chapter. For now I will focus on the DS 1.3 variant to create and use a service instance per requestor.

In DS 1.3 the PROTOTYPE scope was introduced for this scenario.

@Component(scope=ServiceScope.PROTOTYPE)
public class Hitman implements OneShot {

Setting the scope of the service component to PROTOTYPE does not mean that every consumer gets a distinct service instance automatically. By default the result will be the same as with using the BUNDLE scope. So if you start the application with the updated Hitman service, you will get the same result as before.

The reason for this is the reference scope that was also introduced with DS 1.3. It is configured on the consumer side via @Reference and specifies how the service reference should be resolved. There are three possible values:

  • BUNDLE
    All component instances in a bundle will use the same service object. (default)
  • PROTOTYPE
    Every component instance in a bundle may use a distinct service object.
  • PROTOTYPE_REQUIRED
    Every component instance in a bundle must use a distinct service object.

As the default of the reference scope is BUNDLE, we see the same behavior for service scope PROTOTYPE as we saw for service scope BUNDLE. That means the consumer components need to be modified to achieve that every one gets its own service instance.

@Component(
    property= {
        "osgi.command.scope=fipro",
        "osgi.command.function=assassinate"},
    service=AssassinateCommand.class
)
public class AssassinateCommand {

    @Reference(scope=ReferenceScope.PROTOTYPE_REQUIRED)
    private OneShot hitman;

    public void assassinate(String target) {
        hitman.shoot(target);
    }
}
@Component(
    property= {
        "osgi.command.scope=fipro",
        "osgi.command.function=eliminate"},
    service=EliminateCommand.class,
    reference=@Reference(
            name="hitman",
            service=OneShot.class,
            scope=ReferenceScope.PROTOTYPE_REQUIRED
    )
)
public class EliminateCommand {

    private ComponentContext context;

    @Activate
    void activate(ComponentContext context) {
        this.context = context;
    }

    public void eliminate(String target) {
        OneShot hitman =
            (OneShot) this.context.locateService("hitman");
        hitman.shoot(target);
    }
}

Note:
The above examples are showing the DS 1.3 version of the command services. You should recognize the usage of the field strategy and the DS 1.3 lookup strategy, which makes the code more compact.

Note:
In the example I have chosen to use the reference scope PROTOTYPE_REQUIRED. In the given scenario also PROTOTYPE would be sufficient, as the concrete service implementation uses the PROTOTYPE service scope. But IMHO it is better to specify directly which reference scope to use, instead of having a weak rule.

When launching the OSGi application and calling the commands one after the other, you should get an output similar to this (on a Felix console):

g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #2. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #3. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk

You can see that every command gets its own service instance.

One instance per request

In some use cases it is required to have a distinct service instance per request. This is for example needed for web requests, where it is required that services are created and destroyed in every request, or for multi-threading where services can be executed in parallel (hopefully without side-effects).

With DS 1.2 a Factory Component needs to be used. With DS 1.3 again the PROTOTYPE scope helps in solving that requirement. In both cases some OSGi DS API needs to be used to create (and destroy) the service instances.

First lets have a look at the DS 1.3 approach using PROTOTYPE scoped services and the newly introduced ComponentServiceObjects interface. The implementation of the ComponentServiceObjects is a factory that allows to create and destroy service instances on demand. The following example shows the usage. Create it in the org.fipro.oneshot.command bundle.

@Component(
    property= {
        "osgi.command.scope=fipro",
        "osgi.command.function=terminate"},
    service=TerminateCommand.class
)
public class TerminateCommand {

    // get a factory for creating prototype scoped service instances
    @Reference(scope=ReferenceScope.PROTOTYPE_REQUIRED)
    private ComponentServiceObjects<OneShot> oneShotFactory;

    public void terminate(String target) {
        // create a new service instance
        OneShot oneShot = oneShotFactory.getService();
        try {
            oneShot.shoot(target);
        } finally {
            // destroy the service instance
            oneShotFactory.ungetService(oneShot);
        }
    }
}

Note:
There is no special modification needed in the component configuration of the provider. It simply needs to be configured with a PROTOTYPE service scope as shown before. The consumer needs to decide what instance should be referenced, the same as every service in the bundle, a new one for every component or a new one for each request.

Executing the terminate command multiple times will show that for each call a new Hitman instance is created. Mixing it with the previous commands will show that the other services keep a fixed instance, while terminate constantly will create and use a new instance per execution.

g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #2. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #3. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #4. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk

Factory Component

With DS 1.2 you need to create a Factory Component to create a service instance per consumer or per request. The Factory Component is the third type of components specified by the OSGi Compendium Specification, next to the Immediate Component and the Delayed Component. It therefore also has its own lifecycle, which can be seen in the following diagram.

factory_lifecycle

When the component configuration is satisfied, a ComponentFactory is registered. This can be used to activate a new component instance, which is destroyed once it is disposed or the component configuration is not satisfied anymore.

While this looks quite complicated on first sight, it is a lot easier when using DS annotations. You only need to specify the factory annotation type element on @Component. The following snippet shows this for a new OneShot implementation. For the exercise add it to the org.fipro.oneshot.provider bundle.

@Component(factory="fipro.oneshot.factory")
public class Shooter implements OneShot {

    private static AtomicInteger instanceCounter =
        new AtomicInteger(); 

    private final int instanceNo;

    public Shooter() {
        instanceNo = instanceCounter.incrementAndGet();
    }

    @Override
    public void shoot(String target) {
        System.out.println("PEW PEW! I am shooter #"
            + instanceNo + ". And I hit " + target);
    }

}

As explained above, the SCR will register a ComponentFactory that can be used to create and activate new component configurations on demand. On the consumer side this means it is not possible to get a Shooter service instance via @Reference, as it is not registered as a Delayed Component. You need to reference a ComponentFactory instance by specifying the correct target property. The target property needs to be specified for the key component.factory and the value of the factory annotation type element on the @Component annotation of the Factory Component.

The following snippet shows the consumer of a Factory Component. Create it in the org.fipro.oneshot.command bundle.

@Component(
    property= {
        "osgi.command.scope=fipro",
        "osgi.command.function=shoot"},
    service=ShootCommand.class
)
public class ShootCommand {

    @Reference(target = "(component.factory=fipro.oneshot.factory)")
    private ComponentFactory factory;

    public void shoot(String target) {
        // create a new service instance
        ComponentInstance instance = this.factory.newInstance(null);
        OneShot shooter = (OneShot) instance.getInstance();
        try {
            shooter.shoot(target);
        } finally {
            // destroy the service instance
            instance.dispose();
        }
    }
}

Comparing the Factory Component with the PROTOTYPE scoped service, the following differences can be seen:

  • A PROTOTYPE scoped service is a Delayed Component, while the Factory Component is a different component type with its own lifecycle.
  • A Factory Component can only be consumed by getting the ComponentFactory injected, while a PROTOTYPE scoped service can be created and consumed in different ways.
  • component configuration needs to be provided when creating the component instance via ComponentFactory. A PROTOTYPE scoped service can simply use the configuration mechanisms provided in combination with the Configuration Admin.
  • ComponentServiceObjects is type-safe. The result of ComponentInstance#getInstance() needs to be cast to the desired type.

Compared to creating the service instance by using the constructor, the nice thing on using a Factory Component or a PROTOTPYE scoped service is that the configured service references are resolved by the SCR. You could verify this for example by adding a reference to the StringInverter service from my previous blog post.

Note:
To create an instance per requestor by using a Factory Component, you would simply create the instance in the @Activate method, and dispose it on @Deactivate.

Component Instance cleanup

When Peter Kirschner (Twitter: @peterkir) and I prepared our tutorial for the EclipseCon Europe 2016, we noticed a runtime difference between Equinox DS and Felix SCR. In the Console Exercise we also talked about the lifecycle methods and wanted to show them. So we added the @Activate and the @Deactivate method to the StringInverterImpl and the StringInverterCommand. Running the example on Equinox and executing the console command showed a console output for activating the service and the command. But both never were deactivated. Running the example with Felix SCR the StringInverterCommand was activated, executed and deactivated right after the execution. We wondered about that different behavior, but were busy with other topics, so we didn’t search further for the cause.

Note:
The tutorial sources and slides can be found on GitHub.

I recently learned what causes this different behavior and how it can be adjusted.

For Delayed Components the OSGi Compendium Specification says:
If the service registered by a component configuration becomes unused because there are no more bundles using it, then SCR should deactivate that component configuration.

Should is a quite weak statement, so it is easy to have a different understanding of this part of the specification. Apache Felix SCR is taking that statement very serious and deactivates and destroys the component once the last consumer that references the component instance is done with it. Equinox DS on the other hand keeps the instance. At least this is the default behavior in those SCR implementations. But both can be configured via system properties to behave differently.

To configure Equinox DS to dispose component instances that are no longer used (like the Felix SCR default behavior), use the following JVM parameter (see Equinox DS Runtime Options):

-Dequinox.scr.dontDisposeInstances=false

To configure Felix SCR to keep component instances and not dispose them once they are no longer used (like the Equinox DS default behavior), use the following Framework property, e.g. by setting it as JVM parameter (see Felix SCR Configuration):

-Dds.delayed.keepInstances=true

To get some more insight on this you might also want to look at the ticket in the Felix bug tracker where this was discussed.

To experiment with that you can modify for example Hitman and KillCommand and add methods for @Activate and @Deactivate.

@Activate
void activate() {
    System.out.println(
        getClass().getSimpleName() + " activated");
}

@Deactivate
void deactivate() {
    System.out.println(
        getClass().getSimpleName() + " deactivated");
}

Add the JVM arguments for the runtime you are experimenting with and check the results.

Posted in Dirk Fauth, Eclipse, Java, OSGi | 2 Comments

New Java index now used in Eclipse 4.7 for caching JAR information

Stefan Xenos from Google reports that the JDT index is being used as a cache for the majority of .jar file access. This should significantly speed up the usage of the Eclipse Java tooling.

Please download the latest I-Build from http://download.eclipse.org/eclipse/downloads/ and follow Stefan instructions to report remaining UI freezes in the Java tooling. Good test candidates are large Eclipse workspaces with ton of projects and dependencies.

Posted in Eclipse, Lars Vogel | Comments Off on New Java index now used in Eclipse 4.7 for caching JAR information

New year resolution for using Eclipse – Hiding the toolbar

Happy 2017.

For this year I plan to use Eclipse without toolbar. I think this will enforce me to use more shortcuts, e.g. for perspective switching, for starting the last run porgram and the like. Also it gives me more “real estate” in the IDE for the code.

If you want to do the same, select Windows -> Appearance -> Hide Toolbar from the menu.

Posted in Eclipse, Lars Vogel | 2 Comments

Dependency injection for Eclipse 3.x views available for Eclipse 4.7 M3

You can now use dependency injection (di) in your Eclipse 3.x. views.You can enable this for a view via the new inject flag in the org.eclipse.ui.views extension.

e3-views-enable-di


public class SampleView extends ViewPart {

@Inject IWorkbench workbench;

private TableViewer viewer;

@Override
public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);

viewer.setContentProvider(ArrayContentProvider.getInstance());
viewer.setInput(new String[] { "One", "Two", "Three" });
viewer.setLabelProvider(new ViewLabelProvider());

// Create the help context id for the viewer's control
workbench.getHelpSystem().setHelp(viewer.getControl(), "test7.viewer");
}
// more stuff
}

I hope that this will help Eclipse IDE extenders to move towards the Eclipse 4.x programming approach.

Posted in Eclipse, Lars Vogel | Comments Off on Dependency injection for Eclipse 3.x views available for Eclipse 4.7 M3

Welcome Sergey Prigogin from Google in the PMC of the Top-Level Eclipse Project

I would like to welcome Sergey in the Eclipse Project Management Committee (PMC) for the Eclipse Top Level Project.

I think of Sergey as a friend and I’m very happy that he joins our team. Based on what I have seen of his work in Platform UI, he focuses on clean and easy to use API, code cleanup and process simplifications. Sergey contributed the UI freeze monitor and together with his team (most notable Stefan Xenos) he works on improving the interactive performance of the Eclipse IDE.

Welcome Sergey!

Posted in Eclipse, Lars Vogel, Other | Comments Off on Welcome Sergey Prigogin from Google in the PMC of the Top-Level Eclipse Project

Configuring OSGi Declarative Services

In my blog post about Getting Started with OSGi Declarative Services I provided an introduction to OSGi declarative services. How to create them, how they behave at runtime, how to reference other services, and so on. But I left out an important topic there: configuring OSGi components. Well to be precise I mentioned it, and one sort of configuration was also used in the examples, but it was not explained in detail. As there are multiple aspects with regards to component configuration I wanted to write a blog post that is dedicated to that topic, and here it is.

After reading this blog post you should have a deeper understanding of how OSGi components can be configured.

Basics

A component can be configured via Component Properties. Properties are key-value-pairs that can be accessed via Map<String, Object>. With DS 1.3 the Component Property Types are introduced for type safe access to Component Properties.

Component Properties can be defined in different ways:

  • inline
  • via Java properties file
  • via OSGi Configuration Admin
  • via argument of the ComponentFactory.newInstance method
    (only for factory components, and as I didn’t cover them in the previous blog post, I won’t cover that topic here aswell)

Component Properties that are defined inline or via properties file can be overridden by using the OSGi Configuration Admin or the ComponentFactory.newInstance argument. Basically the property propagation is executed sequentially. Therefore it is even possible to override inline properties with properties from a properties file, if the properties file is specified after the inline properties.

The SCR (Service Component Runtime) always adds the following Component Properties that can’t be overridden:

  • component.name – The component name.
  • component.id – A unique value (Long) that is larger than all previously assigned values. These values are not persistent across restarts.

In a life cycle method (activate/modified/deactivate) you can get the Component Properties via method parameter. The properties that are retrieved in event methods for referencing other services (bind/updated/unbind) are called Service Properties. The SCR performs a property propagation in that case, which means that all non-private Component Properties are propagated as Service Properties. To mark a property as private, the property name needs to be prefixed with a full stop (‘.’).

First I will explain how to specify Component Properties in different ways. I will use a simple example that inspects the properties in a life cycle method. After that I will show some examples on the usage of properties of service references.

Let’s start to create a new project for the configurable components:

  • Create a new Plug-in Project via File -> New -> Plug-in Project. (Plug-in Perspective needs to be active)
    • Set the Plug-in name to org.fipro.ds.configurable
    • Press Next
    • Ensure that no Activator is generated, no UI contributions will be added and that no Rich Client Application is created
    • Press Finish
  • Open the MANIFEST.MF file and switch to the Dependencies tab
  • Add the following dependency on the Imported Packages side:
    • org.osgi.service.component.annotations (1.2.0)
  • Mark org.osgi.service.component.annotations as Optional via Properties… to ensure there are no runtime dependencies. We only need this dependency at build time.
  • Create the package org.fipro.ds.configurable

Inline Component Properties

You can add Component Properties to a declarative service component via the @Component annotation property type element. The value of that annotation type element is an array of Strings, which need to be given as key-value pairs in the format
<name>(:<type>)?=<value>
where the type information is optional and defaults to String.

The following types are supported:

  • String (default)
  • Boolean
  • Byte
  • Short
  • Integer
  • Long
  • Float
  • Double
  • Character

There are typically two use cases for specifying Component Properties inline:

  • Define default values for Component Properties
  • Specify some sort of meta-data that is examined by referencing components

Of course the same applies for Component Properties that are applied via Properties file, as they have an equal ranking.

  • Create a new class StaticConfiguredComponent like shown below.
    It is a simple Immediate Component with the Component Properties message and iteration, where message is a String and iteration is an Integer value. In the @Activate method the Component Properties will be inspected and the message will be printed out to the console as often as specified in iteration.
    Remember that it is an Immediate Component, as it doesn’t implement an interface and it doesn’t specify the service type element.
package org.fipro.ds.configurable;

import java.util.Map;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component(
    property = {
        "message=Welcome to the inline configured service",
        "iteration:Integer=3"
    }
)
public class StaticConfiguredComponent {

    @Activate
    void activate(Map<String, Object> properties) {
        String msg = (String) properties.get("message");
        Integer iter = (Integer) properties.get("iteration");

        for (int i = 1; i <= iter; i++) {
            System.out.println(i + ": " + msg);
        }
        System.out.println();
    }
}

Now execute the example as a new OSGi Framework run configuration (please have a look at Getting Started with OSGi Declarative Services – 6. Run to see how to setup such a configuration). If you used the same property values as specified in the above example, you should see the welcome message printed out 3 times to the console.

It is for sure not a typical use case to inspect the inline specified properties at activation time. But it should give an idea on how to specify Component Properties statically inline via @Component.

Component Properties from resource files

Another way specify Component Properties statically is to use a Java Properties File that is located inside the bundle. It can be specified via the @Component annotation properties type element, where the value needs to be an entry path relative to the root of the bundle.

  • Create a simple properties file named config.properties inside the OSGI-INF folder of the org.fipro.ds.configurable bundle.
message=Welcome to the file configured service
iteration=4
  • Create a new class FileConfiguredComponent like shown below.
    It is a simple Immediate Component like the one before, getting the Component Properties message and iteration from the properties file.
package org.fipro.ds.configurable;

import java.util.Map;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component(
    properties="OSGI-INF/config.properties"
)
public class FileConfiguredComponent {

    @Activate
    void activate(Map<String, String> properties) {
        String msg = (String) properties.get("message");
        String iter = (String) properties.get("iteration");

        if (msg != null && iter != null) {
            Integer count = Integer.valueOf(iter);
            for (int i = 1; i <= count; i++) {
                System.out.println(i + ": " + msg);
            }
            System.out.println();
        }
    }
}
  • Add the OSGI-INF/config.properties file to the build.properties to include it in the resulting bundle jar file. This is of course only necessary in case you haven’t added the whole directory to the build.properties.

On executing the example you should now see the console outputs for both components.

I’ve noticed two things when playing around with the Java Properties File approach:

  • Compared with the inline properties it is not possible to specify a type. You can only get Strings, which leads to manual conversions (at least before DS 1.3 – see below).
  • The properties file needs to be located in the same bundle as the component. It can not be added via fragment.

Having these two facts in mind, there are not many use cases for this approach. IMHO this approach was intended to support client specific properties that are for example placed inside the bundle in the build process.

Bndtools vs. PDE

  • Create the config.properties file in the project root
  • Add the -includeresource instruction to the bnd.bnd file
    This is necessary to include the config.properties file to the resulting bundle jar file. The instruction should look similar to the following snippet to specify the destination and the source.

    -includeresource: OSGI-INF/config.properties=config.properties

    Note:
    The destination is on the left side of the assignment and the source is on the right.
    If only the source is specified (that means no assignment), the file is added to the bundle root without the folder where it is included in the sources.

Component Properties via OSGi Configuration Admin

Now let’s have a look at the dynamic configuration by using the OSGi Configuration Admin. For this we create a new component, although it would not be necessary, as we could also use one of the examples before (remember that we could override the statically defined Component Properties dynamically via the Configuration Admin). But I wanted to start with creating a new component, to have a class that can be directly compared with the previous ones.

To specify properties via Configuration Admin it is not required to use any additional type element. You only need to know the configuration PID of the component to be able to provide a configuration object for it. The configuration PID (Persistent IDentity) is used as a key for objects that need a configuration dictionary. With regards to the Component Configuration this means, we need the configuration PID to be able to provide the configuration object for the component.

The PID can be specified via the configurationPid type element of the @Component annotation. If not specified explicitly it is the same as the component name, which is the fully qualified class name, if not explicitly set to another value.

Via the configurationPolicy type element it is possible to configure the relationship between component and component configuration, e.g. whether there needs to be a configuration object provided via Configuration Admin to satisfy the component. The following values are available:

  • ConfigurationPolicy.OPTIONAL
    Use the corresponding configuration object if present, but allow the component to be satisfied even if the corresponding configuration object is not present. This is the default value.
  • ConfigurationPolicy.REQUIRE
    There must be a corresponding configuration object for the component
    configuration to become satisfied. This means that there needs to be a configuration object that is set via Configuration Admin before the component is satisfied and therefore can be activated. With this policy it is for example possible to control the startup order or component activation based on configurations.
  • ConfigurationPolicy.IGNORE
    Always allow the component configuration to be satisfied and do
    not use the corresponding configuration object even if it is present. This basically means that the Component Properties can not be changed dynamically using the Configuration Admin.

If a configuration change happens at runtime, the SCR needs to take actions based on the configuration policy. Configuration changes can be creating, modifying or deleting configuration objects. Corresponding actions can be for example that a Component Configuration becomes unsatisfied and therefore Component Instances are deactivated, or to call the modified life cycle method, so the component is able to react on a change.

To be able to react on a configuration change at runtime, a method to handle the modified life cycle can be implemented. Using the DS annotations this can be done by using the @Modified annotation, where the method parameters can be the same as for the other life cycle methods (see the Getting Started Tutorial for further information on that).

Note:
If you do not specify a modified life cycle method, the Component Configuration is deactivated and afterwards activated again with the new configuration object. This is true for the configuration policy require as well as for the configuration policy optional.

Now create a component similar to the previous ones, that should only be satisfied if a configuration object is provided via the Configuration Admin. It should also be prepared to react on configuration changes at runtime. Specify an alternative configuration PID so it is not necessary to use the full qualified class name of the component.

  • Create a new class AdminConfiguredComponent like shown below.
    It is an Immediate Component that prints out a message for a specified number of iterations.

    • Specify the configuration PID AdminConfiguredComponent so it is not necessary to use the full qualified class name of the component when trying to configure it.
    • Set the configuration policy REQUIRE, so the component will only be activated once a configuration object is set by the Configuration Admin.
    • Add life cycle methods for modified and deactivate to be able to play around with different scenarios.
package org.fipro.ds.configurable;

import java.util.Map;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;

@Component(
    configurationPid = "AdminConfiguredComponent",
    configurationPolicy = ConfigurationPolicy.REQUIRE
)
public class AdminConfiguredComponent {

    @Activate
    void activate(Map<String, Object> properties) {
        System.out.println();
        System.out.println("AdminConfiguredComponent activated");
        printMessage(properties);
    }

    @Modified
    void modified(Map<String, Object> properties) {
        System.out.println();
        System.out.println("AdminConfiguredComponent modified");
        printMessage(properties);
    }

    @Deactivate
        void deactivate() {
        System.out.println("AdminConfiguredComponent deactivated");
        System.out.println();
    }

    private void printMessage(Map<String, Object> properties) {
        String msg = (String) properties.get("message");
        Integer iter = (Integer) properties.get("iteration");

        if (msg != null && iter != null) {
            for (int i = 1; i <= iter; i++) {
                System.out.println(i + ": " + msg);
            }
        }
    }
}

If we now execute our example, we will see nothing new. The reason is of course that there is no configuration object yet provided by the Configuration Admin.

Before we are able to do this we need to prepare our environment. That means that we need to install the Configuration Admin Service to the Eclipse IDE or the used Target Platform, as it is not part of the default installation.

To install the Configuration Admin to the Eclipse IDE you need to perform the following steps:

  • Select Help -> Install New Software… from the main menu
  • Select the Neon – http://download.eclipse.org/releases/neon repository
    (assuming you are following the tutorial with Eclipse Neon, otherwise use the matching update site)
  • Disable Group items by category
  • Filter for Equinox
  • Select the Equinox Compendium SDKinstall_equinox_compendium
  • Click Next
  • Click Next
  • Accept the license agreement and Finish
  • Restart the Eclipse IDE to safely apply the changes

Now we can create a Gogo Shell command that will be used to change a configuration object at runtime.

  • Open MANIFEST.MF of org.fipro.ds.configurable
    • Add org.osgi.service.cm to the Imported Packages
  • Create a new package org.fipro.ds.configurable.command
  • Create a new class ConfigureServiceCommand in that package that looks similar to the following snippet.
    It is a Delayed Component that will be registered as a service for the ConfigureCommand class. It has a reference to the ConfigurationAdmin service, which is used to create/get the Configuration object for the PID AdminConfiguredComponent and updates the configuration with the given values.
package org.fipro.ds.configurable.command;

import java.io.IOException;
import java.util.Hashtable;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(
    property = {
        "osgi.command.scope=fipro",
        "osgi.command.function=configure"
    },
    service=ConfigureCommand.class
)
public class ConfigureCommand {

    ConfigurationAdmin cm;

    @Reference
    void setConfigurationAdmin(ConfigurationAdmin cm) {
        this.cm = cm;
    }

    public void configure(String msg, int count) throws IOException {
        Configuration config =
            cm.getConfiguration("AdminConfiguredComponent");
        Hashtable<String, Object> props = new Hashtable<>();
        props.put("message", msg);
        props.put("iteration", count);
        config.update(props);
    }
}

Note:
The ConfigurationAdmin reference is a static reference. Therefore it doesn’t need an unbind method. If you follow the example with Eclipse Neon you will probably see an error mentioning the missing unbind method. Either implement the unbind method for now or disable the error via Preferences. This is fixed with Eclipse Oxygen M2.

Note:
The two Component Properties osgi.command.scope and osgi.command.function are specified inline. These are necessary so the Apache Gogo Shell recognizes the component as a service that can be triggered by entering the corresponding values as a command to the console. This shows the usage of Component Properties as additional meta-data that is examined by other components. Also note that we need to set the service type element, as only services can be referenced by other components.

To execute the example you need to include the org.eclipse.equinox.cm bundle to the Run configuration.

On executing the example you should notice that the AdminConfiguredComponent is not activated on startup, although it is an Immediate Component. Now execute the following command on the console: configure foo 2

As a result you should get an output like this:

AdminConfiguredComponent activated
1: foo
2: foo

If you execute the command a second time with different parameters (e.g. configure bar 3), the output should change to this:

AdminConfiguredComponent modified
1: bar
2: bar
3: bar

The component gets activated after we created a configuration object via the Configuration Admin. The reason for this is ConfigurationPolicy.REQUIRED which means that there needs to be a configuration object for the component configuration in order to be satisfied. Subsequent executions change the configuration object, so the modified method is called then. Now you can play around with the implementation to get a better feeling. For example, remove the modified method and see how the component life cycle handling changes on configuration changes.

Note:
To start from a clean state again you need to check the option Clear the configuration area before launching in the Settings tab of the Run configuration.

Using the modified life cycle event enables to react on configuration changes inside the component itself. To be able to react to configuration changes inside components that reference the service, the updated event method can be used.

  • Create a simple component that references the AdminConfiguredComponent to test this:
package org.fipro.ds.configurable;

import java.util.Map;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;

@Component
public class AdminReferencingComponent {

    AdminConfiguredComponent component;

    @Activate
    void activate() {
        System.out.println("AdminReferencingComponent activated");
    }

    @Modified
    void modified() {
        System.out.println("AdminReferencingComponent modified");
    }

    @Deactivate
    void deactivate() {
        System.out.println("AdminReferencingComponent deactivated");
    }

    @Reference
    void setAdminConfiguredComponent(
        AdminConfiguredComponent comp, Map<String, Object> properties) {
        System.out.println("AdminReferencingComponent: set service");
        printMessage(properties);
    }

    void updatedAdminConfiguredComponent(
        AdminConfiguredComponent comp, Map<String, Object> properties) {
        System.out.println("AdminReferencingComponent: update service");
        printMessage(properties);
    }

    void unsetAdminConfiguredComponent(
        AdminConfiguredComponent comp) {
        System.out.println("AdminReferencingComponent: unset service");
    }

    private void printMessage(Map<String, Object> properties) {
        String msg = (String) properties.get("message");
        Integer iter = (Integer) properties.get("iteration");
        System.out.println("[" + msg + "|" + iter + "]");
    }
}
  • Configure the AdminConfiguredComponent to be a service component by adding the attribute service=AdminConfiguredComponent.class to the @Component annotation. Otherwise it can not be referenced.
@Component(
    configurationPid = "AdminConfiguredComponent",
    configurationPolicy = ConfigurationPolicy.REQUIRE,
    service=AdminConfiguredComponent.class
)
public class AdminConfiguredComponent {

Now execute the example and call the configure command two times. The result should look similar to this:

osgi> configure blubb 2
AdminConfiguredComponent activated
1: blubb
2: blubb
AdminReferencingComponent: set service
[blubb|2]
AdminReferencingComponent activated
osgi> configure dingens 3
AdminConfiguredComponent modified
1: dingens
2: dingens
3: dingens
AdminReferencingComponent: update service
[dingens|3]

Calling the configure command the first time triggers the activation of the AdminConfiguredComponent, which then can be bound to the AdminReferencingComponent, which is satisfied and therefore can be activated afterwards. The second execution of the configure command triggers the modified life cycle event of the AdminConfiguredComponent and the updated event method of the AdminReferencingComponent.

If you ask yourself why the AdminConfiguredComponent is still immediately activated, although we made it a service now, the answer is, because it is referenced by an Immediate Component. Therefore the target services need to be bound, which means the referenced services need to be activated too.

This example is also helpful in getting a better understanding of the component life cycle. For example, if you remove the modified life cycle method from the AdminConfiguredComponent and call the configure command subsequently, both components get deactivated and activated, which results in new instances. Modifying the @Reference attributes will also lead to different results then. Change the cardinality, the policy and the policyOption to see the different behavior. Making the service reference OPTIONAL|DYNAMIC|GREEDY results in only re-activating the AdminConfiguredComponent but keeping the AdminReferencingComponent in active state. Changing it to OPTIONAL|STATIC|GREEDY will lead to re-activation of both components, while setting it OPTIONAL|STATIC|RELUCTANT any changes will be ignored, and actually nothing happens as the AdminReferencingComponent never gets satisfied, and therefore the AdminConfiguredComponent never gets activated.

The correlation between cardinality, reference policy and reference policy option is explained in detail in the OSGi Compendium Specification (table 112.1 in chapter 112.3.7 Reference Policy Option in Specification Version 6).

Location Binding

Some words about location binding here. The example above created a configuration object using the single parameter version of ConfigurationAdmin#getConfiguration(String). The parameter specifies the PID for which a configuration object is requested or should be created. This means that the configuration is bound to the location of the calling bundle. It then can not be consumed by other bundles. So the method is used to ensure that only the components inside the same bundle are affected.

A so-called bound configuration object is sufficient for the example above, as all created components are located in the same bundle. But there are also other cases where for example a configuration service in another bundle should be used to configure the components in all bundles of the application. This can be done by creating an unbound configuration object using the two argument version of ConfigurationAdmin#getConfiguration(String, String). The first parameter is the PID and the second parameter specifies the bundle location string.

Note:
The location parameter only becomes important if a configuration object will be created. If a configuration for the given PID already exists in the ConfigurationAdmin service, the location parameter will be ignored and the existing object will be returned.

You can use different values for the location argument:

  • Exact bundle location identifier
    In this case you explicitly specify the location identifier of the bundle to which the configuration object should be bound. The location identifier is set when a bundle is installed and typically it is a file URL that points to the bundle jar. It is impossible to have that hard coded and work across multiple installations. But you could retrieve it via a snippet similar to this:

    Bundle adminBundle =
        FrameworkUtil.getBundle(AdminConfiguredComponent.class);
    adminBundle.getLocation()

    But doing this introduces a dependency to the bundle that should be configured, which is typically not a good practice.

  • null
    The location value for the binding will be set when a service with the corresponding PID is registered the first time. Note that this could lead to issues if you have multiple services with the same PID in different bundles. In that case only the services in the first bundle that requests a configuration object would be able to get it because of the binding.
  • Multi-locations
    By using a multi-location binding, the configurations are dispatched to any target that has visibility to the configuration. A multi-location is specified with a leading question mark. It is possible to use only the question mark or adding a multi-location name behind the question mark, e.g.

    Configuration config =
        cm.getConfiguration("AdminConfiguredComponent", "?");
    Configuration config =
        cm.getConfiguration("AdminConfiguredComponent", "?org.fipro");

    Note:
    The multi-location name only has importance in case security is turned on and a ConfigurationPermission is specified. Otherwise it doesn’t has an effect. That means, it can not be used to restrict the targets based on the bundle symbolic name without security turned on.

Note:
The Equinox DS implementation has some bugs with regards to location binding. Basically the location binding is ignored. I had a discussion on Stackoverflow (thanks again to Neil Bartlett) and created the ticket Bug 493637 to address that issue. I also created Bug 501898 to report that multi-location binding doesn’t work.

To get familiar with the location binding basics create two additional bundles:

  • Create the bundle org.fipro.ds.configurator
    • Open the MANIFEST.MF file and switch to the Dependencies tab
    • Add the following dependencies on the Imported Packages side:
      • org.osgi.service.cm
      • org.osgi.service.component.annotations (1.2.0)
      • Mark org.osgi.service.component.annotations as Optional
    • Create the package org.fipro.ds.configurator
    • Create the class ConfCommand
      • Copy the ConfigureCommand implementation
      • Change the property value for osgi.command.function to conf
      • Change the method name from configure to conf to match the osgi.command.function property
  • Create the bundle org.fipro.ds.other
    • Open the MANIFEST.MF file and switch to the Dependencies tab
    • Add the following dependency on the Imported Packages side:
      • org.osgi.service.component.annotations (1.2.0)
      • Mark org.osgi.service.component.annotations as Optional
    • Create the package org.fipro.ds.other
    • Create the class OtherConfiguredComponent
      • Copy the AdminConfiguredComponent implementation
      • Change the console outputs to show the new class name
      • Ensure that it is an Immediate Component (i.e. remove the service property or add the immediate property)
      • Ensure that configurationPID and configurationPolicy are the same as in AdminConfiguredComponent

Use three different scenarios:

  1. Use the single parameter getConfiguration(String)
    Calling the conf command on the console will result in nothing. As the configuration object is bound to the bundle of the command, the other bundles don’t see it and the contained components don’t get activated.
  2. Use the double parameter getConfiguration(String, String) where location == null
    Only the component(s) of one bundle will receive the configuration object, as it will be bound to the bundle that first registers a service for the corresponding PID.
  3. Use the double parameter getConfiguration(String, String) where location == “?”
    The components of both bundles will receive the configuration object, as it is dispatched to all bundles that have visibility to the configuration. And as we didn’t mention and configure permissions, all our bundles receive it.

Note:
Because of the location binding issues in Equinox DS (see above), the examples doesn’t work using it. For testing I replaced Equinox DS with Apache Felix SCR in the Run Configuration, which worked well. To make this work just download SCR (Declarative Services) from the Apache Felix Download page and put it in the dropins folder of your Eclipse installation. After restarting the IDE you are able to select org.apache.felix.scr as bundle in the Run Configuration. Remember to remove org.eclipse.equinox.ds to ensure that only one SCR implementation is running.

Bndtools vs. PDE

For the org.fipro.ds.configurable bundle you need to add the package org.fipro.ds.configurable.command to the Private Packages in the bnd.bnd file. Otherwise it will not be part of the resulting bundle.

While we needed to add the Import-Package statement for org.osgi.service.cm manually in PDE, that import is automatically calculated by Bndtools. So at that point there is no action necessary. Only the launch configuration needs to be updated manually to include the Configuration Admin bundle.

  • Open the launch.bndrun file
  • On the Run tab click on Resolve
  • Verify the values values shown in the opened dialog in the Required Resources section
  • Click Finish

If you change a component class while the example is running, you will notice that the OSGi framework automatically restarts and the values set before via Configuration Admin are gone. This is because the Bndtools OSGi Framework launch configuration has two options enabled by default on the OSGi tab:

  • Framework: Update bundles during runtime.
  • Framework: Clean storage area before launch.

To test the behavior of components in case of persisted configuration values, you need to disable these settings.

DS 1.3

A new feature added to the DS 1.3 specification are the Component Property Types. They can be used as alternative to the component property Map<String, Object> parameter for retrieving the Configuration Properties in a life cycle method. The Component Property Type is specified as a custom annotation type, that contains property names, property types and default values. The following snippet shows the definition of such an annotation for the above examples:

package org.fipro.ds.configurable;

public @interface MessageConfig {
    String message() default "";
    int iteration() default 0;
}

Most of the examples found in the web show the definition of the annotation inside the component class. But of course it is also possible to create a public annotation in a separate file so it is reusable in multiple components.

The following snippet shows one of the examples above, modified to use a Component Property Type instead of the property Map<String, Object>.

package org.fipro.ds.configurable;

import java.util.Map;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component(
    property = {
        "message=Welcome to the inline configured service",
        "iteration:Integer=3"
    }
)
public class StaticConfiguredComponent {

    @Activate
    void activate(MessageConfig config) {
        String msg = config.message();
        int iter = config.iteration();

        for (int i = 1; i <= iter; i++) {
            System.out.println(i + ": " + msg);
        }
    }
}

Note:
If properties are needed that are not specified in the Component Property Type, you can have both as method arguments. Since DS 1.3 there are different method signatures supported, including the combination of Component Property Type and the component property Map<String, Object>.

Although the Component Property Type is defined as an annotation type, it is not used as an annotation. The reasons for choosing annotation types are:

  • Limitations on annotation type definitions match component property types (no-argument methods and limited return types supported)
  • Support of default values

As Component Property Types are intended to be type safe, an automatic conversion happens. This is also true for Component Properties that are specified via Java Properties files.

To set configuration values via ConfigurationAdmin service you still need to operate on a Dictionary, which means you need to know the parameter names. But of course on setting the values you are type safe.

Another new feature in DS 1.3 is that you can specify multiple configuration PIDs for a component. This way it is for example possible to specify configuration objects for multiple components that share a common PID, while at the same time having a specific configuration object for a single component. To specify multiple configuration PIDs and still keep the default (that is the component name), the placeholder “$” can be used. By adding the following property to the StaticConfiguredComponent and the FileConfiguredComponent created before, the execution of the configure command will update all three components at once.

@Component(
    configurationPid = {"$", "AdminConfiguredComponent"},
    ...
)

Note that we don’t update the configurationPid value of AdminConfiguredComponent. The reason for this is that we use the configuration policy REQUIRE, which means that the component only gets satisfied if there are configuration objects available for BOTH configuration PIDs. And our example does not create a configuration object for the default PID of the AdminConfiguredComponent.

The order of the configuration PIDs matters with regards to property propagation. The configuration object for a PID at the end overrides values that were applied by another configuration object for a PID before. This is similar to the propagation of inline properties or property files. The processing is sequential and therefore later processed instructions override previous ones.

Service Properties

As initially explained there is a slight difference between Component Properties and Service Properties. Component Properties are all properties specified for a component that can be accessed in life cycle methods via method parameter. Service Properties can be retrieved via Event Methods (bind/updated/unbind) or since DS 1.3 via field strategy. They contain all public Component Properties, which means all excluding those whose property names start with a full stop. Additionally some service properties are added that are intended to give additional information about the service. These properties are prefixed with service, set by the framework and specified in the OSGi Core Specification (service.id, service.scope and service.bundeid).

To play around with Service Properties we set up another playground. For this create the following bundles to simulate a data provider service:

  • API bundle
    • Create the bundle org.fipro.ds.data.api
    • Add the following service interface
      package org.fipro.ds.data;
      
      public interface DataService {
      
          /**
           * @param id
           * The id of the requested data value.
           * @return The data value for the given id.
           */
          String getData(int id);
      }
    • Modify the MANIFEST.MF to export the package
  • Online data service provider bundle
    • Create the bundle org.fipro.ds.data.online
    • Add the necessary package import statements to the MANIFEST.MF
    • Create the following simple service implementation, that specifies the property fipro.connectivity=online for further use
      package org.fipro.ds.data.online;
      
      import org.fipro.ds.data.DataService;
      import org.osgi.service.component.annotations.Component;
      
      @Component(property="fipro.connectivity=online")
      public class OnlineDataService implements DataService {
      
          @Override
          public String getData(int id) {
              return "ONLINE data for id " + id;
          }
      }
  • Offline data service provider bundle
    • Create the bundle org.fipro.ds.data.offline
    • Add the necessary package import statements to the MANIFEST.MF
    • Create the following simple service implementation, that specifies the property fipro.connectivity=offline for further use
      package org.fipro.ds.data.offline;
      
      import org.fipro.ds.data.DataService;
      import org.osgi.service.component.annotations.Component;
      
      @Component(property="fipro.connectivity=offline")
      public class OfflineDataService implements DataService {
      
          @Override
          public String getData(int id) {
              return "OFFLINE data for id " + id;
          }
      }

Note:
For Java best practices you would of course specify the property name and the possible values as constants in the API bundle to prevent typing errors.

To be able to interact with the data provider services, we create an additional console command in the bundle  that references the services and shows the retrieved data on the console on execution. Add it to the bundle org.fipro.ds.configurator or create a new bundle if you skipped the location binding example.

package org.fipro.ds.configurator;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.fipro.ds.data.DataService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;

@Component(
    property= {
        "osgi.command.scope:String=fipro",
        "osgi.command.function:String=retrieve"},
    service=DataRetriever.class
)
public class DataRetriever {

    private List<DataService> dataServices = new ArrayList<>();

    @Reference(
        cardinality=ReferenceCardinality.MULTIPLE,
        policy=ReferencePolicy.DYNAMIC
    )
    void addDataService(
            DataService service, Map<String, Object> properties) {
        this.dataServices.add(service);
        System.out.println(
            "Added " + service.getClass().getName());
    }

    void removeDataService(DataService service) {
        this.dataServices.remove(service);
        System.out.println(
            "Removed " + service.getClass().getName());
    }

    public void retrieve(int id) {
        for (DataService service : this.dataServices) {
            System.out.println(service.getData(id));
        }
    }
}

Add the new bundles to an existing Run Configuration and execute it. By calling the retrieve command on the console you should get an output similar to this:

osgi> retrieve 3
OFFLINE data for id 3
ONLINE data for id 3

Nothing special so far. Now let’s modify the example to verify the Service Properties.

  • Modify DataRetriever#addDataService() to print the given properties to the console
    @Reference(
        cardinality=ReferenceCardinality.MULTIPLE,
        policy=ReferencePolicy.DYNAMIC
    )
    void addDataService(
            DataService service, Map<String, Object> properties) {
        this.dataServices.add(service);
    
        System.out.println("Added " + service.getClass().getName());
        properties.forEach((k, v) -> {
            System.out.println(k+"="+v);
        });
        System.out.println();
    }
  • Start the example and execute the retrieve command. The result should now look like this:
    osgi> retrieve 3
    org.fipro.ds.data.offline.OfflineDataService
    fipro.connectivity=offline
    component.id=3
    component.name=org.fipro.ds.data.offline.OfflineDataService
    service.id=51
    objectClass=[Ljava.lang.String;@1403f0fa
    service.scope=bundle
    service.bundleid=5
    
    org.fipro.ds.data.online.OnlineDataService
    fipro.connectivity=online
    component.id=4
    component.name=org.fipro.ds.data.online.OnlineDataService
    service.id=52
    objectClass=[Ljava.lang.String;@c63166
    service.scope=bundle
    service.bundleid=6
    
    OFFLINE data for id 3
    ONLINE data for id 3

    The Service Properties contain the fipro.connectivity property specified by us, aswell as several properties that are set by the SCR.

    Note:
     The DataRetriever is not in Immediate Component and therefore gets activated when the retrieve command is executed the first time. The target services are bound at activation time, therefore the setter is called at that time and not before.

  • Modify the OfflineDataService
    • Add an Activate life cycle method
    • Add a property with a property name that starts with a full stop
    package org.fipro.ds.data.offline;
    
    import java.util.Map;
    
    import org.fipro.data.Constants;
    import org.fipro.data.DataService;
    import org.osgi.service.component.annotations.Activate;
    import org.osgi.service.component.annotations.Component;
    
    @Component(
        property= {
            "fipro.connectivity=offline",
            ".private=private configuration"
        }
    )
    public class OfflineDataService implements DataService {
    
        @Activate
        void activate(Map<String, Object> properties) {
            System.out.println("OfflineDataService activated");
            properties.forEach((k, v) -> {
                System.out.println(k+"="+v);
            });
            System.out.println();
        }
    
        @Override
        public String getData(int id) {
            return "OFFLINE data for id " + id;
        }
    }

    Execute the retrieve command again and verify the console output. You will notice that the output from the Activate life cycle method contains the .private property but no properties with a service prefix. The output from the bind event method on the other hand does not contain the .private property, as the leading full stop marks it as a private property.

    osgi> retrieve 3
    OfflineDataService activated
    objectClass=[Ljava.lang.String;@c60d42
    component.name=org.fipro.ds.data.offline.OfflineDataService
    component.id=3
    .private=private configuration
    fipro.connectivity=offline
    
    org.fipro.ds.data.offline.OfflineDataService
    fipro.connectivity=offline
    component.id=3
    component.name=org.fipro.ds.data.offline.OfflineDataService
    service.id=51
    objectClass=[Ljava.lang.String;@2b5d77a6
    service.scope=bundle
    service.bundleid=5
    
    ...

Service Ranking

In case multiple services of the same type are available, the service ranking is taken into account to determine which service will get bound. In case of multiple bindings the service ranking effects in which order the services are bound. The ranking order is defined as follows:

  • Sorted on descending ranking order (highest first)
  • If the ranking numbers are equal, sorted on ascending service.id property (oldest first)

As service ids are never reused and handed out in order of their registration time, the ordering is always complete.

The property service.ranking can be used to specify the ranking order and in case of OSGi components it can be specified as a Component Property via @Component where the value needs to be of type Integer. The default ranking value is zero if the property is not specified explicitly.

Modify the two DataService implementations to specify the initial service.ranking property.

@Component(
    property = {
        "fipro.connectivity=online",
        "service.ranking:Integer=7"
    }
)
public class OnlineDataService implements DataService {
...
@Component(
    property = {
        "fipro.connectivity=offline",
        "service.ranking:Integer=5",
        ".private=private configuration
    }
)
public class OfflineDataService implements DataService {
...

If you start the application and execute the retrieve command now, you will notice that the OnlineDataService is called first. Change the service.ranking of the OnlineDataService to 3 and restart the application. Now executing the retrieve command will first call the OfflineDataService.

To make this more obvious and show that the service ranking can also be changed dynamically, create a new DataGetter command in the org.fipro.ds.configurator bundle:

package org.fipro.ds.configurator;

import java.util.Map;

import org.fipro.ds.data.DataService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;

@Component(
    property= {
        "osgi.command.scope:String=fipro",
        "osgi.command.function:String=get"
    },
    service=DataGetter.class
)
public class DataGetter {

    private DataService dataService;

    @Reference(
        policy=ReferencePolicy.DYNAMIC,
        policyOption=ReferencePolicyOption.GREEDY
    )
    void setDataService(DataService service,
            Map<String, Object> properties) {
        this.dataService = service;
    }

    void unsetDataService(DataService service) {
        if (service == this.dataService) {
            this.dataService = null;
        }
    }

    public void get(int id) {
        System.out.println(this.dataService.getData(id));
    }
}

This command has a MANDATORY reference to a DataService. The policy option is set to GREEDY which is necessary to bind to a higher ranked service if available. The policy is set to DYNAMIC to avoid re-activation of the DataGetter component if a service changes. If you change the policy to STATIC, the binding to the higher ranked service is done by re-activating the component.

Note:
For dynamic references the unbind event method is mandatory. This is necessary because the component is not re-activated if the bound services change, which means there will be no new Component Instance. Therefore the Component Instance state needs to be secured in the unbind method. In our case we check if the current service reference is the same that should be unbound. In that case we set the reference to null, otherwise there is already another service bound.

Finally create a toggle command, which dynamically toggles the service.ranking property of OnlineDataService.

package org.fipro.ds.configurator;

import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(
    property= {
        "osgi.command.scope:String=fipro",
        "osgi.command.function:String=ranking"
    },
    service=ToggleRankingCommand.class
)
public class ToggleRankingCommand {

    ConfigurationAdmin admin;

    @Reference
    void setConfigurationAdmin(ConfigurationAdmin admin) {
        this.admin = admin;
    }

    public void ranking() throws IOException {
        Configuration configOnline =
            this.admin.getConfiguration(
                "org.fipro.ds.data.online.OnlineDataService",
                null);
        Dictionary<String, Object> propsOnline = null;
        if (configOnline != null
                && configOnline.getProperties() != null) {
            propsOnline = configOnline.getProperties();
        } else {
            propsOnline = new Hashtable<>();
        }

        int onlineRanking = 7;
        if (configOnline != null
                && configOnline.getProperties() != null) {
            Object rank =
                configOnline.getProperties().get("service.ranking");
            if (rank != null) {
                onlineRanking = (Integer)rank;
            }
        }

        // toggle between 3 and 7
        onlineRanking = (onlineRanking == 7) ? 3 : 7;

        propsOnline.put("service.ranking", onlineRanking);
        configOnline.update(propsOnline);
    }
}

Starting the example application the first time and executing the get command will return the ONLINE data. After executing the ranking command, the get command will return the OFFLINE data (or vice versa dependent on the initial state).

Note:
Equinox DS will log an error or warning to the console every second time. Probably an issue with processing the service reference update in Equinox DS. The example will still work, and if you replace Equinox DS with Felix SCR the message does not come up. So it looks like another Equinox DS issue.

Reference Properties

Reference Properties are special Component Properties that are associated with specific component references. They are used to configure component references more specifically. With DS 1.2 the target property is the only supported Reference Property. The reference property name needs to follow the pattern <reference_name>.<reference_property> so it can be accessed dynamically. The target property can be specified via the @Reference annotation on the bind event method via the target annotation type element. The value needs to be an LDAP filter expression and is used to select target services for the reference. The following example specifies a target property for the DataService reference of the DataRetriever command to only select target services which specify the Service Property fipro.connectivity with value online.

@Reference(
    cardinality=ReferenceCardinality.MULTIPLE,
    policy=ReferencePolicy.DYNAMIC,
    target="(fipro.connectivity=online)"
)

If you change that in the example and execute the retrieve command in the console again, you will notice that only the OnlineDataService will be selected by the DataRetriever.

Specifying the target property directly on the reference is a static way of defining the filter. The registering of custom commands to the Apache Gogo Shell seems to work that way, as you can register any service to become a console command when the necessary properties are specified.

In a dynamic environment it needs to be possible to change the target property at runtime aswell. This way it is possible to react on changes to the environment for example, like whether there is an active internet connection or not. To change the target property dynamically you can use the ConfigurationAdmin service. For this the reference property name needs to be known. Following the pattern
    <reference_name>.<reference_property>
this means for our example where
    reference_name = DataService
    reference_property = target
the reference property name is
    DataService.target

To test this we implement a new command component in org.fipro.ds.configurator that allows us to toggle the connectivity state filter on the DataService reference target property.

package org.fipro.ds.configurator;

import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(
    property= {
        "osgi.command.scope:String=fipro",
        "osgi.command.function:String=toggle"
    },
    service=ToggleConnectivityCommand.class
)
public class ToggleConnectivityCommand {

    ConfigurationAdmin admin;

    @Reference
    void setConfigurationAdmin(ConfigurationAdmin admin) {
        this.admin = admin;
    }

    public void toggle() throws IOException {
        Configuration config =
            this.admin.getConfiguration(
                "org.fipro.ds.configurator.DataRetriever");

        Dictionary<String, Object> props = null;
        Object target = null;
        if (config != null
                && config.getProperties() != null) {
        	props = config.getProperties();
        	target = props.get("DataService.target");
        } else {
            props = new Hashtable<String, Object>();
        }

        boolean isOnline = (target == null
            || target.toString().contains("online"));

        // toggle the state
        StringBuilder filter =
            new StringBuilder("(fipro.connectivity=");
        filter.append(isOnline ? "offline" : "online").append(")");

        props.put("DataService.target", filter.toString());
        config.update(props);
    }
}

Some things to notice here:

  1. We use the default PID org.fipro.ds.data.configurator.DataRetriever to get a configuration object.
  2. We check if there is already an existing configuration. If there is an existing configuration we operate on the existing Dictionary. Otherwise we create a new one.
  3. We try to get the current state from the Dictionary.
  4. We create an LDAP filter String based on the retrieved information (or default if the configuration is created) and set it as reference target property.
  5. We update the configuration with the new values.

From my observation the reference policy and reference policy option doesn’t matter in that case. On changing the reference target property dynamically, the component gets re-activated to ensure a consistent state.

DS 1.3

With DS 1.3 the Minimum Cardinality Reference Property was introduced. Via this reference property it is possible to modify the minimum cardinality value at runtime. While it is only possible to specify the optionality via the @Reference cardinality attribute (this means 0 or 1), you can specify any positive number for MULTIPLE or AT_LEAST_ONE references. So it can be used for example to specify that at least 2 services of a special type needs to be available in order to satisfy the Component Configuration.

The name of the minimum cardinality property is the name of the reference appended with .cardinality.minimum. In our example this would be
DataService.cardinality.minimum

Note:
The minimum cardinality can only be specified via the cardinality attribute of the reference element. So it is only possible to specify the optionality to be 0 or 1. To specify the minimum cardinality in an extended way, the minimum cardinality reference property needs to be applied via Configuration Admin.

Create a command component in org.fipro.ds.configurator to modify the minimum cardinality property dynamically. It should look like the following example:

package org.fipro.ds.configurator;

import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(
    property = {
        "osgi.command.scope=fipro",
        "osgi.command.function=cardinality"
    },
    service=ToggleMinimumCardinalityCommand.class
)
public class ToggleMinimumCardinalityCommand {

    @Reference
    ConfigurationAdmin admin;

    public void cardinality(int count) throws IOException {
        Configuration config =
            this.admin.getConfiguration(
                "org.fipro.ds.configurator.DataRetriever");

        Dictionary<String, Object> props = null;
        if (config != null
                && config.getProperties() != null) {
            props = config.getProperties();
        } else {
            props = new Hashtable<String, Object>();
        }

        props.put("DataService.cardinality.minimum", count);
        config.update(props);
    }
}

Launch the example and execute retrieve 3. You should get a valid response like before from a single service (online or offline dependent on the target property that is set). Now if you execute cardinality 2 and afterwards retrieve 3 you should get a CommandNotFoundException. Checking the components on the console via scr:list will show that org.fipro.ds.configurator.DataRetriever now has a unsatisfied reference. Calling cardinality 1 afterwards will resolve that again.

Now you can play around and create additional services to test if this is also working for values > 1.

While I was writing on this blog post, finding and reporting some issues in Equinox DS, the following ticket was created Bug 501950. If everything works out, Equinox DS will be replaced with Felix SCR. This would solve several issues and finally bring DS 1.3 also to Eclipse. So I cross my fingers that this ticket will be fixed for Oxygen. (which on the other hand means some work for the DS Annotations @pnehrer)

That’s if for this blog post. It again got much longer than I intended. But on the way writing the blog post I again learned a lot that wasn’t clear to me before. I hope you also could take something out of it to use declarative services even more in your projects.

Of course you can find the sources of this tutorial in my GitHub account:

Posted in Dirk Fauth, Eclipse, OSGi, Other | 9 Comments