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.
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.
6. Links and Literature
6.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting