This tutorial explains how to use the event admin service in OSGi applications and the IEventBroker in Eclipse RCP and plug-ins.

1. Communication within your Eclipse application

It a common requirement that certain application components should update other components. If possible this communication should be decoupled, e.g. the components should not be tightly coupled.

This can be archived by the subscriber/ publisher model implemented as an event system. Software components can register for events, other components can send events. The event system ensures that all registered components receive the event they registered for.

1.1. The event bus of Eclipse

For this purpose the Eclipse platform and the OSGI runtime provide a global event based communication system, called the event bus.

Any software component which has access to the event system can send out arbitrary events as depicted in the following graphic.

Sending out events with an event broker

The Eclipse platform makes sure that registered components receive the messages. The Eclipse platform uses this event system for the internal communication.

1.2. Event service

The Eclipse framework provides the event service for event communication. This service can be accessed via dependency injection based on the IEventBroker interface. This communication service can also be used to communicate between your own application components.

The Eclipse event service is based on the OSGi EventAdmin service.

1.3. Required plug-ins to use the event service

The following plug-ins are required to use the event service functionality:

  • org.eclipse.e4.core.services

  • org.eclipse.osgi.services

1.4. Sending out events

The event service can be injected via dependency injection.

@Inject
private IEventBroker eventBroker;

The following code examples assume that you have a class named TaskEventConstants. This class contains a static final field (constant) to define a string.

The event service allows to notify the registered components. This can be done asynchronously or synchronously.

@Inject IEventBroker broker;

...
// asynchronously
// sending code is not blocked until the event has been delivered
broker.post(TaskEventConstants.TOPIC_TODO_NEW, todo);

//synchronously sending a todo
//the calling code is blocked until delivery

broker.send(TaskEventConstants.TOPIC_TODO_NEW, newTodo);

You can now send arbitrary Java objects or primitives through the event bus.

1.5. Registration for events for receiving events

You can use dependency injection to register and respond to events. In this case the Eclipse framework automatically removes all event subscriptions when the model class is disposed.

The @EventTopic and @UIEventTopic annotations tag methods and fields that should be notified on event changes. The @UIEventTopic ensures the event notification is performed in the user interface thread.

import java.util.Map;

// TaskEventConstants.TOPIC_TODO_UPDATE is
// a String constant

@Inject
@Optional
private void subscribeTopicTodoUpdated
    (@UIEventTopic(TaskEventConstants.TOPIC_TODO_UPDATE)
        Todo todo) {
    if (viewer!=null) {
        // this example assumes that you do not use data binding
        todoService.getTodos(viewer::setInput);
    }
}

An object can also register directly an instance of the org.osgi.service.event.EventHandler via the IEventBroker.subscribe() method. To unsubscribe use the unsubscribe() method.

Using dependency injection for subscribing should be preferred compared to the direct subscription. This way the framework handles the listener registration and de-registration automatically for you. But sometimes it is useful to control when to listening to events, for, example when an event should only received once.

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;

public class LazyLoadingAddon implements EventHandler {

    @Inject
    private IEventBroker broker;

    @PostConstruct
    public void lazyLoadInContributorPerspective() {
        broker.subscribe(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT, this);
    }

    @Override
    public void handleEvent(Event event) {
        Object property = event.getProperty(UIEvents.EventTags.NEW_VALUE);

        if (!(property instanceof MPerspective)) {
            return;
        }

        MPerspective perspective = (MPerspective) property;

        // only load the data, when this particular perspective is selected
        if ("com.example.speciallazyloaded.perspective".equals(perspective.getElementId())) {

            // loading data...

            // unsubscribe afterwards, so that the loading is only done once
            broker.unsubscribe(this);
        }
    }

}

1.6. Which objects should be send out?

The event system allows sending and receiving objects of an arbitrary type. Often it is sufficient to send the desired object through an event. But it is good practice to use a map like structure for sending events, i.e., to have common interface for events.

1.7. Subscribing to sub-topics

You can subscribe to specific topics or use wildcards to subscribe to all sub-events. Sub-events are separated by /. The following example code defines string constants including the TOPIC_TODO_ALLTOPICS constant. This constant can be used to register for all sub-events.

package com.vogella.tasks.events;
/**
 *
 * @noimplement This interface is not intended to be implemented by clients.
 *
 * Only used for constant definition
 */

public interface TaskEventConstants {

    // topic identifier for all topics
    String TOPIC_TASKS = "TOPIC_TASKS";

    // this key can only be used for event registration, you cannot
    // send out generic events
    String TOPIC_TASKS_ALLTOPICS = "TOPIC_TASKS/*";

    String TOPIC_TASKS_CHANGED = "TOPIC_TASKS/CHANGED";

    String TOPIC_TASKS_NEW = "TOPIC_TASKS/TASKS/NEW";

    String TOPIC_TASKS_DELETE = "TOPIC_TASKS/TASKS/DELETED";

    String TOPIC_TASKS_UPDATE = "TOPIC_TASKS/TASKS/UPDATED";
}

1.8. Sending events to the Eclipse 4 IEventBroker

The IEventBroker used in Eclipse 4 RCP application is based on the OSGi event admin service. To send event via dependency injection and the IEventBroker, you also need to add the "org.eclipse.e4.data" in your event send from event admin.

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

The above code would be believed

1.9. Event system compared to Eclipse context

The IEventBroker is a global event bus and is unaware of the IEclipseContext hierarchy. The IEventBroker service supports sending event information without knowing who is receiving it. Interested classes can register for events without knowing who is going to provide them. This is known as the whiteboard pattern and this pattern supports the creation of very loosely coupled application components.

The disadvantage is that it is a global bus, i.e. there is no scoping of the events. Publishers have to ensure they provide enough information in the topic and the send object to allow subscribers to discriminate and decide that the particular event is applicable to a subscriber.

1.10. Asynchronous processing and the event bus

Your threads can use the IEventBroker to send event data. The event listeners are called by the framework. If the method is annotated with the UIEventTopic annotation, it is called in the main thread.

private static final String UPDATE ="update";

// get the IEventBroker injected
@Inject
IEventBroker broker;

// somewhere in you code you do something
// performance intensive

button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                Runnable runnable = new Runnable() {
                    public void run() {
                        for (int i = 0; i < 10; i++) {
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            // send out an event to update
                            // the UI
                            broker.send(UPDATE, i);
                        }
                    }
                };
                new Thread(runnable).start();
            }
        });



// more code
// ....


// get notified and sync automatically
// with the UI thread

@Inject @Optional
public void  getEvent(@UIEventTopic(UPDATE) int i) {
    // text1 is a SWT Text field
    text1.setText(String.valueOf(i));
    System.out.println(i);
}

1.11. Evaluation of @CanExecute for Eclipse 4 handlers

A method annotated with @CanExecute is called by the framework, if a change in the Eclipse context happens. For example, if you select a new part. If the method returns false, the framework disables any menu and tool items that point to that command.

You can request the re-evaluation of the @CanExecute methods by sending out an event via the event broker.

// evaluate all @CanExecute methods
eventBroker.post(UIEvents.REQUEST_ENABLEMENT_UPDATE_TOPIC, UIEvents.ALL_ELEMENT_ID);

// evaluate a context via a selector
Selector s = (a selector that an MApplicationElement or an ID);
eventBroker.post(UIEvents.REQUEST_ENABLEMENT_UPDATE_TOPIC, s);


//See  https://bugs.eclipse.org/bugs/show_bug.cgi?id=427465 for details

2. Exercise: Using the event admin service in OSGi

2.1. Create plug-in for events

Create a new plug-in called com.vogella.events.

Create a new class VogellaEventConstants.

package com.vogella.events;


public final class VogellaEventConstants {

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

    public static final String TOPIC_BASE = "com/vogella/events/";
    public static final String TOPIC_HELP = TOPIC_BASE + "HELP";
    public static final String TOPIC_UPDATE= TOPIC_BASE + "UPDATE";
    public static final String TOPIC_SWITCH= TOPIC_BASE + "SWITCH";
    public static final String TOPIC_ALL = TOPIC_BASE + "*";

    public static final String PROPERTY_KEY_TARGET = "target";

}

Export the package of this class as API via the Runtime tab of manifest editor.

2.2. Create plug-in for consumer

Add org.osgi.service.event as package dependency to the manifest of com.vogella.osgi.console.

Add a package dependency to com.vogella.events in com.vogella.osgi.console via the Dependency tab of the its manifest editor.

Create a new class named EventCommandSender in your plug-in com.vogella.osgi.console.

package com.vogella.osgi.console;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;

import com.vogella.events.VogellaEventConstants;


@Component
public class EventCommandSender implements CommandProvider {

    @Reference
    EventAdmin eventAdmin;

    public void _sendEvent(CommandInterpreter ci) {
        String command = ci.nextArgument();
        if (command == null)
        {
            command = "";
        }

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

        switch (command) {
            case "help":
                event = new Event(VogellaEventConstants.TOPIC_HELP, properties);
                break;
            case "update":
                event = new Event(VogellaEventConstants.TOPIC_UPDATE, properties);
                break;
            case "switch":
                event = new Event(VogellaEventConstants.TOPIC_SWITCH, properties);
                break;
            default:
                System.out.println(command + " is not known as event! See help sendEvent");
        }

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

    @Override
    public String getHelp() {
        return "Allows to tigger events via the command line, options: help, update, switch ";
    }
}

2.3. Create plug-in for receiver

Create a new plug-in named com.vogella.osgi.eventreceiver.

Add the following dependencies on the Imported Packages side:

  • com.vogella.events

  • org.osgi.service.component.annotations

  • org.osgi.service.event

Implement the following class which reacts to the events.

package com.vogella.osgi.eventreceiver;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

import com.vogella.events.VogellaEventConstants;

@Component(
    property = EventConstants.EVENT_TOPIC
        + "=" + VogellaEventConstants.TOPIC_UPDATE)

public class EventReaction implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        System.out.println("I will now update: "
        + event.getProperty(VogellaEventConstants.PROPERTY_KEY_TARGET));
    }
}

2.4. Add required plug-ins to your runtime

Add the following plug-ins to your product (via the feature).

  • org.eclipse.equinox.event

  • com.vogella.osgi.eventreceiver

2.5. Handle multiple event topics

You event receiver can also handle for multiple events. Change your event handler to receive multiple events.

package com.vogella.osgi.eventreceiver;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

import com.vogella.events.VogellaEventConstants;

@Component(
    (1)
    property = {
            EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_UPDATE,
            EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_SWITCH
    })

public class EventReaction implements EventHandler {

    @Override
    public void handleEvent(Event event) {
        System.out.println("I will now update: "
        + event.getProperty(VogellaEventConstants.PROPERTY_KEY_TARGET));
    }
}
1 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.

2.6. Register for wildcard events

It is also possible to register for the all events of a certain subtype. Create a new event handler can register it for the VogellaEventConstants.TOPIC_ALL event. Ensure that it is called if you send any event of your known event types.

2.7. Filter

You can also use LDAP type filter to filter on event properties. Change your above implementation to be triggered for all events, except "update".

@Component(
    property = {
            EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_ALL,
            EventConstants.EVENT_FILTER + "=" + "=" + "(!(target=update))"})

Validate that your WildcardEventReceiver is not triggered if the "update" event is send.

3. Learn more and get support

This tutorial continues on Eclipse RCP online training or Eclipse IDE extensions with lots of video material, additional exercises and much more content.

5. vogella training and consulting support

Copyright © 2012-2019 vogella GmbH. Free use of the software examples is granted under the terms of the Eclipse Public License 2.0. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.