Home Tutorials Training Consulting Books Company Contact us


Get more...

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’s common that part of your application components need to update other components. Where possible, this communication should be decoupled, meaning components shouldn’t be tightly linked to one another.

This can be achieved using a subscriber/publisher model, implemented as an event system. Software components can register for events, and other components can send events. The event system ensures all registered components receive the events they subscribed to.

1.1. The event bus in Eclipse and OSGi

The Eclipse platform and OSGi runtime provide a global, event-based communication system known as the event bus.

Any software component with access to the event system can send out arbitrary events, as shown in the following graphic.

Sending out events with an event broker

The Eclipse platform ensures registered components receive the messages. It also uses this event system for internal communication.

1.2. What types of objects should be sent?

The event system allows sending and receiving objects of any type. Often, it’s enough to send the desired object through an event. However, it is good practice to use a map-like structure for sending events, providing a common interface for events.

2. Event service in Eclipse

The Eclipse framework provides and uses the IEventBroker event service for communication. You can use this communication service to allow components within your application to communicate.

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

2.1. Required plug-ins for using the event service

The following plug-ins are required to use the event service functionality in Eclipse components.

  • org.eclipse.e4.core.services

  • org.eclipse.osgi.services

2.2. Sending events in Eclipse

The event service can be injected using dependency injection.

@Inject
private IEventBroker eventBroker;

In the following examples, assume a class named TaskEventConstants contains a static final field (constant) that defines a string.

The event service allows notifications to be sent to 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.

2.3. Registering to receive events

You can use dependency injection to register and respond to events. In this setup, 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 of event changes. The @UIEventTopic ensures event notifications occur 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);
    }
}

You can also register an instance of org.osgi.service.event.EventHandler directly using the IEventBroker.subscribe() method. To unsubscribe, use the unsubscribe() method.

Using dependency injection for subscribing is preferred over direct subscription. This way, the framework manages listener registration and de-registration automatically. However, sometimes it’s useful to control when to listen to events, for example, when an event should only be received once.

import jakarta.annotation.PostConstruct;
import jakarta.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 data when this particular perspective is selected
        if ("com.example.speciallazyloaded.perspective".equals(perspective.getElementId())) {

            // loading data...

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

2.4. 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 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";
}

Assuming your application starts within 5 seconds and a part containing the following code is visible, you would receive this event via the following code:

    @Inject
    public void getFromOSGi(@Optional @EventTopic("YOURKEY") String value) {
        System.out.println(value);
    }

2.5. Event system compared to Eclipse context

The IEventBroker is a global event bus and is independent of the IEclipseContext hierarchy. The IEventBroker service supports sending event information without requiring knowledge of the recipient. Interested classes can register for events without needing to know which classes will provide them. This is known as the whiteboard pattern, which promotes very loosely coupled application components.

The disadvantage is that it is a global bus with no scoping of events. Publishers must ensure they provide enough information in the topic and sent object to enable subscribers to determine the relevance of each event.

2.6. Asynchronous processing and the event bus

Your threads can use the IEventBroker to send event data. Event listeners are called by the framework. If a 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);
}

2.7. Evaluation of @CanExecute for Eclipse 4 handlers

A method annotated with @CanExecute is called by the framework if a change occurs in the Eclipse context, such as when selecting a new part. If the method returns false, the framework disables any menu and tool items associated with that command.

You can request re-evaluation of the @CanExecute methods by sending 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 from the application model 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.8. The event bus in OSGi

OSGi provides the EventAdmin for handling event based communication.

2.9. Required packages for using the event admin

The following packages are required to use the OSGI event service functionality:

  • org.eclipse.osgi.services

You can also register an instance of org.osgi.service.event.EventHandler directly using the IEventBroker.subscribe() method. To unsubscribe, use the unsubscribe() method.

2.10. 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 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";
}

2.11. Sending events to the IEventBroker via OSGi services

The IEventBroker used in Eclipse applications is based on the OSGi Event Admin service. Thus, you can also use OSGi Event Admin to send events, for example, to send events to your RCP application from another OSGi service.

To make an event relevant for your Eclipse application, add "org.eclipse.e4.data" in the event data you send out. The following is an example of a simple OSGi service that uses the OSGi Event Admin and sends out an event after 5 seconds.

package com.vogella.osgi.eventadminuser;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.osgi.service.component.annotations.Activate;
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.tasks.model.TaskService;

@Component
public class EventAdminUser {

    @Reference
    EventAdmin admin;

    @Activate
    public void activate() {

        String payload = "OBJECT to BE SEND";

        // When your program starts up
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

        // then, when you want to schedule a task
        Runnable task = new Runnable() {

            @Override
            public void run() {

                Map<String, Object> properties = new HashMap<>();

                properties.put("org.eclipse.e4.data", payload);
                Event event = new Event("YOURKEY", properties);
                admin.sendEvent(event);

            }
        };
        executor.schedule(task, 5, TimeUnit.SECONDS);

        // and finally, when your program wants to exit
        executor.shutdown();

    }
}

Assuming your application starts within 5 seconds and a part containing the following code is visible, you would receive this event via the following code:

    @Inject
    public void getFromOSGi(@Optional @EventTopic("YOURKEY") String value) {
        System.out.println(value);
    }

2.12. Event system compared to Eclipse context

The IEventBroker is a global event bus and is independent of the IEclipseContext hierarchy. The IEventBroker service supports sending event information without requiring knowledge of the recipient. Interested classes can register for events without needing to know which classes will provide them. This is known as the whiteboard pattern, which promotes very loosely coupled application components.

The disadvantage is that it is a global bus with no scoping of events. Publishers must ensure they provide enough information in the topic and sent object to enable subscribers to determine the relevance of each event.

2.13. Asynchronous processing and the event bus

Your threads can use the IEventBroker to send event data. Event listeners are called by the framework. If a 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);
}

2.14. Wrapper for event processing

As the OSGi event admin API is not very easy to use, you could create a wrapper around it.

import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;

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

public class EventPublisher {
    private final EventAdmin eventAdmin;
    private final Map<String, Object> properties = new HashMap<>();
    private String topic;

    // Constructor
    public EventPublisher(EventAdmin eventAdmin) {
        this.eventAdmin = eventAdmin;
    }

    // Static method to initialize
    public static EventPublisher with(EventAdmin eventAdmin) {
        return new EventPublisher(eventAdmin);
    }

    // Set the topic for the event
    public EventPublisher topic(String topic) {
        this.topic = topic;
        return this;
    }

    // Add a property to the event
    public EventPublisher property(String key, Object value) {
        properties.put(key, value);
        return this;
    }

    // Send the event
    public void send() {
        if (topic == null || topic.isEmpty()) {
            throw new IllegalStateException("Event topic cannot be null or empty");
        }
        Event event = new Event(topic, properties);
        eventAdmin.postEvent(event);
    }
}

This would simplify the sending of events.

import org.osgi.service.event.EventAdmin;

public class EventSenderExample {
    private final EventAdmin eventAdmin;

    // Constructor where EventAdmin is injected
    public EventSenderExample(EventAdmin eventAdmin) {
        this.eventAdmin = eventAdmin;
    }

    public void sendSwitchEvent(String command) {
        EventPublisher.with(eventAdmin)
                .topic(VogellaEventConstants.TOPIC_SWITCH)
                .property(VogellaEventConstants.PROPERTY_KEY_TARGET, command)
                .send();
    }
}

And you could also add a static loopUp() method for retrieving the admin service.

    public static EventAdmin lookup() {
        BundleContext context = FrameworkUtil.getBundle(EventFluentSender.class).getBundleContext();
        ServiceReference<EventAdmin> ref = context.getServiceReference(EventAdmin.class);
        EventAdmin eventAdmin = context.getService(ref);

        if (eventAdmin == null) {
            throw new IllegalStateException("EventAdmin service not available");
        }

        return eventAdmin;
    }

3. Exercise: Using the event admin service in OSGi

In OSGi you can also easily consume and send out events. Event registration is done via registering a service, while the event admin can be use to send out events to them.

Assume you have the following constants.

public interface VogellaEventConstants {

    public static final String TOPIC_BASE = "com/vogella/events/";
    public static final String TOPIC_ALL = TOPIC_BASE + "*";

    public static final String TOPIC_HELP = TOPIC_BASE + "HELP";
    public static final String TOPIC_UPDATE = TOPIC_BASE + "UPDATE";

     public static final String PROPERTY_KEY_TARGET = "target";

}

This OSGi service will produce an event.

package app.services;

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

import org.osgi.service.component.annotations.Activate;
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;

@Component
public class EventProducer {

    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";

    @Activate
    public EventProducer(@Reference EventAdmin admin) {
        System.out.println(admin);
        Map<String, Object> properties = new HashMap<>();
        properties.put(VogellaEventConstants.PROPERTY_KEY_TARGET, "Hello World");
        Event event = new Event(VogellaEventConstants.TOPIC_UPDATE, properties);
        admin.postEvent(event);
    }
}

The following OSGi service registers for this event and receives it.

package app.services;

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;

@Component(property = EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_UPDATE)
public class EventConsumer implements EventHandler {

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

It is also possible to register for all events.

package app.services;

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;

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

    @Override
    public void handleEvent(Event event) {
        String[] propertyNames = event.getPropertyNames();
        for (String name : propertyNames) {
            System.out.println("Property name: " + name+  " with value: " + event.getProperty(name));
        }
    }
}

And you can also register event handlers dynamically as a new service.

package app.services;

import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
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.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

@Component
public class DynamicEventSubscriber {

    @Reference
    private EventAdmin eventAdmin;
    private ServiceRegistration<EventHandler> registration;
    private BundleContext bundleContext;

    @Activate
    public void startListening(@Reference BundleContext bundleContext) {
        this.bundleContext = bundleContext;
        startListening(VogellaEventConstants.TOPIC_UPDATE);
    }


    public void startListening(String topic) {
        Dictionary<String, Object> properties = new Hashtable<>();
        properties.put(EventConstants.EVENT_TOPIC, topic);

        // Register the EventHandler dynamically
        registration = bundleContext.registerService(EventHandler.class, new EventHandler() {
            @Override
            public void handleEvent(Event event) {
                System.out.println("DynamicEventHandler: Received event: " + event.getTopic() + ", target: "
                        + event.getProperty("target"));
            }
        }, properties);

        System.out.println("Listening to topic: " + topic);
    }

    @Deactivate
    public void stopListening() {
        if (registration != null) {
            registration.unregister();
            registration = null;
            System.out.println("Stopped listening to events.");
        }
    }

}

4. Exercise: Using the event admin service in OSGi

4.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.

4.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 ";
    }
}

4.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));
    }
}

4.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

4.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.

4.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.

4.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.

5. 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.