Overview

OSGi declarative services and the event bus – Eclipse RCP Cookbook

5 Comments

OSGi services are used to provide functionality via separate bundles. They are also used to decouple functionality, so it is possible to exchange the implementation at runtime. With the introduction of OSGi declarative services and Eclipse 4 it became more popular to use OSGi services in Eclipse RCP applications.

The communication system in Eclipse 4 is the global event bus. It supports communication between application components and is also used for decoupling, since bundles only need to know the event bus for communication, not the bundles they need to communicate with.

This recipe shows how to use the event bus in an Eclipse 4 based application, how to create a simple service using OSGi declarative services, and how to communicate via event bus in an OSGi service. For this, a part will be added to the application that shows log messages which are sent via event bus.

Ingredients

This recipe is based on the Eclipse RCP Cookbook – Basic Recipe. To get started fast with this recipe, we have prepared the basic recipe for you on GitHub.

To use the prepared basic recipe, import the project by cloning the Git repository:

  • File → Import → Git → Projects from Git
  • Click Next
  • Select Clone URI
  • Enter URI https://github.com/fipro78/e4-cookbook-basic-recipe.git
  • Click Next
  • Select the master branch
  • Click Next
  • Choose a directory where you want to store the checked out sources
  • Click Next
  • Select Import existing projects
  • Click Next
  • Click Finish

Note: With exception to the part implementation, this recipe does not depend on the used UI toolkit. So you can also use the JavaFX version of the basic recipe. If you want to follow this recipe in the JavaFX version, use the following URI to clone the prepared basic recipe: https://github.com/fipro78/e4-cookbook-basic-recipe-fx.git.

Preparation

Step 1: Sending events to the event bus

The Eclipse event service can be used to send events to the event bus. It is implemented via the IEventBroker interface and can get injected. In this step the application is modified to send log events on specific actions.

  • Update the bundle dependencies
    • Open the file MANIFEST.MF in the project de.codecentric.eclipse.tutorial.inverter
    • Switch to the Dependencies tab
    • Add the following packages to the Required Plug-ins
      • org.eclipse.e4.core.services
  • Open the InverterPart in the project de.codecentric.eclipse.tutorial.inverter
    • Get the IEventBroker injected
    • Modify the listeners on the button and the input field to post an event that contains a String with the log message for the topic “TOPIC_LOGGING”.
      @Inject
      IEventBroker broker;
       
      @PostConstruct
      public void postConstruct(Composite parent) {
      	...
      	button.addSelectionListener(new SelectionAdapter() {
      		@Override
      		public void widgetSelected(SelectionEvent e) {
      			...
      			broker.post("TOPIC_LOGGING", "triggered via button");
      		}
      	});
      }
    • For the JavaFX version this means to add the posting of the event to the onAction EventHandler.
      @Inject
      IEventBroker broker;
       
      @PostConstruct
      public void postConstruct(Composite parent) {
      	...
      	button.setOnAction(event -> {
      		...
      		broker.post("TOPIC_LOGGING", "triggered via button");
      	});
      }

Note: via IEventBroker#post() the event is sent asynchronously. If you need to send the event synchronously, use IEventBroker#send().

Step 2: Receiving events from the event bus

The recommended way of receiving events from the event bus is to use dependency injection. Using the annotations @EventTopic and @UIEventTopic for method parameters will cause method execution if an event for the specified topic is fired on the event bus. The difference between the two annotations is that using @UIEventTopic will execute the method in the UI thread.

In this step a log view is added to the application to show the log messages that were sent to the event bus.

  • Create the package de.codecentric.eclipse.tutorial.app.part in the project de.codecentric.eclipse.tutorial.app
  • Create a part that shows the log messages
    • Open the file Application.e4xmi in the project de.codecentric.eclipse.tutorial.app
    • Add a container for the part to the window
      • Application → Windows and Dialogs → Trimmed Window → Controls → Part Sash Container → Add Part Stack
    • Add a part to the container
      • Application → Windows and Dialogs → Trimmed Window → Controls → Part Sash Container → Part Stack → Add Part
      • Set the Label to Log View
    • Create the part implementation
      • Click the Class URI link in the part detail view
      • Set the following values in the opened dialog
        • Package: de.codecentric.eclipse.tutorial.app.part
        • Name: LogViewPart
      • Create a viewer that is used to show the log messages
      • Create a method that is executed/notified when an event for the topic “TOPIC_LOGGING” is send

The following is an example of a part using SWT:

public class LogViewPart {
 
	ListViewer viewer;
 
	@PostConstruct
	public void postConstruct(Composite parent) {
		viewer = new ListViewer(parent);
	}
 
	@Inject
	@Optional
	void logging(@UIEventTopic("TOPIC_LOGGING") String message) {
		viewer.add(message);
	}
 
}

The following is an example of a part using JavaFX:

public class LogViewPart {
 
	ListView viewer;
 
	@PostConstruct
	public void postConstruct(BorderPane parent) {
		viewer = new ListView();
		parent.setCenter(viewer);
	}
 
	@Inject
	@Optional
	void logging(@UIEventTopic("TOPIC_LOGGING") String message) {
		viewer.getItems().add(message);
	}
 
}

You can also subscribe for events by registering an org.osgi.service.event.EventHandler for a topic to the IEventBroker. In such a case you also need to take care of unregistering the handler again.

Step 3: Create an OSGi declarative service

An OSGi service is a java object instance, registered into an OSGi framework. Any java object can be registered as a service, but typically it implements a well-known interface. Via OSGi declarative services it is possible to define and implement an OSGi service without implementing or extending OSGi framework classes.

The basic recipe uses a static helper class to implement the functionality of inverting a String. In this step a new plug-in is created that contains an OSGi declarative service for that purpose. This way it will be possible to exchange the implementation at runtime or mock the implementation for testing.

  • Create a new plug-in project
    • Main Menu → File → New → Plug-in Project
    • Set name to de.codecentric.eclipse.tutorial.service.inverter
    • Click Next
    • Select Execution Environment JavaSE-1.8
    • Ensure that Generate an Activator and This plug-in will make contributions to the UI are disabled
    • Click Finish
  • Create an interface for the service definition
    • Main Menu → File → New → Interface
      • Source Folder: de.codecentric.eclipse.tutorial.service.inverter/src
      • Package: de.codecentric.eclipse.tutorial.service.inverter
      • Name: InverterService
    • Add the method definition String invert(String value);
  • Create the service implementation
    • Main Menu → File → New → Class
      • Source Folder: de.codecentric.eclipse.tutorial.service.inverter/src
      • Package: de.codecentric.eclipse.tutorial.service.inverter.impl
      • Name: InverterServiceImpl
      • Interfaces: de.codecentric.eclipse.tutorial.service.inverter.InverterService
    • Implement the method String invert(String);
  • Configure the bundle via MANIFEST.MF
    • Open the file META-INF/MANIFEST.MF in the project de.codecentric.eclipse.tutorial.service.inverter
    • Switch to the Overview tab
      • Activate Activate this plug-in when one of its classes is loaded
    • Switch to the Dependencies tab
      • Add the plug-in org.eclipse.osgi.services to the Required Plug-ins
    • Switch to the Runtime tab
      • Add de.codecentric.eclipse.tutorial.service.inverter to the list of Exported Packages
  • Configure the OSGi declarative service
    • Create the folder OSGI-INF in the project de.codecentric.eclipse.tutorial.service.inverter
    • Create a Component Definition
      • File → New → Component Definition
        • Parent folder: de.codecentric.eclipse.tutorial.service.inverter/OSGI-INF
        • File name: inverter.xml
        • Component Definition Name: de.codecentric.eclipse.tutorial.service.inverter
        • Component Definition Class: de.codecentric.eclipse.tutorial.service.inverter.impl.InverterServiceImpl
    • Switch to the Services tab
      • Add de.codecentric.eclipse.tutorial.service.inverter.InverterService to the Provided Services
    • Ensure the Service-Component entry pointing to OSGI-INF/inverter.xml is added to the MANIFEST.MF file
    • Open the build.properties file in the project de.codecentric.eclipse.tutorial.service.inverter
      • Add the folder OSGI-INF to the Binary Build
  • Use the created InverterService in the InverterPart
    • Open the file META-INF/MANIFEST.MF in the project de.codecentric.eclipse.tutorial.inverter
    • Switch to the Dependencies tab
      • Add the plug-in de.codecentric.eclipse.tutorial.service.inverter to the Required Plug-ins
    • Open the InverterPart
      • Inject the InverterService as instance field
      • Replace the usage of the StringInverter helper class with using the InverterService
  • Update the feature
    • Open the file feature.xml in the project de.codecentric.eclipse.tutorial.feature
    • Switch to the Plug-ins tab
      • Add the plug-in de.codecentric.eclipse.tutorial.service.inverter to the list of Plug-ins and Fragments

Step 4: Send events via OSGi declarative service

The IEventBroker is not available in the OSGi context, which allows us, for example, to have multiple instances in one application. This also means that it cannot be referenced in an OSGi declarative service. But as the IEventBroker makes use of the OSGi EventAdmin service, it is possible to send events to the event bus from an OSGi declarative service by directly using the EventAdmin.

  • Open the file META-INF/MANIFEST.MF in the project de.codecentric.eclipse.tutorial.service.inverter
    • Switch to the Dependencies tab
    • Add the plug-in org.eclipse.e4.core.services to the Required Plug-ins
  • Open the file OSGI-INF/inverter.xml in the project de.codecentric.eclipse.tutorial.service.inverter
    • Switch to the Services tab
    • Add the org.osgi.service.event.EventAdmin to the Referenced Services
    • Edit the Referenced Service
      • Specify the methods for binding and unbinding the EventAdmin servicereference_eventadmin
  • Open the InverterServiceImpl
    • Add an instance field of type EventAdmin
    • Add the methods for binding and unbinding the EventAdmin
    • Use the EventAdmin in invertString(String)
      • Create an instance of java.util.Dictionary<String, Object>
      • Put the event topic value to the Dictionary for the key EventConstants.EVENT_TOPIC
      • Put the event value to the Dictionary for the key IEventBroker.DATA
      • Create an instance of type org.osgi.service.event.Event using the topic and the Dictionary
      • Post the event via the EventAdmin

The finished InverterServiceImpl should look similar to the following snippet:

package de.codecentric.eclipse.tutorial.service.inverter.impl;
 
import java.util.Dictionary;
import java.util.Hashtable;
 
import org.eclipse.e4.core.services.events.IEventBroker;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
 
import de.codecentric.eclipse.tutorial.service.inverter.InverterService;
 
public class InverterServiceImpl implements InverterService {
 
	EventAdmin eventAdmin;
 
	@Override
	public String invert(String value) {
		String result = new StringBuilder(value).reverse().toString();
 
		String topic = "TOPIC_LOGGING";
		Dictionary&lt;String, Object&gt; data = new Hashtable&lt;String, Object&gt;(2);
		data.put(EventConstants.EVENT_TOPIC, topic);
		data.put(IEventBroker.DATA, "Inverted " + value + " to " + result);
		Event event = new Event(topic, data);
 
		eventAdmin.postEvent(event);
 
		return result;
	}
 
	 void registerEventAdmin(EventAdmin admin) {
		 this.eventAdmin = admin;
	 }
 
	 void unregisterEventAdmin(EventAdmin admin) {
		 this.eventAdmin = null;
	 }
}

Step 5: Taste

  • Start the application from within the IDE
    • Open the Product Configuration in the de.codecentric.eclipse.tutorial.product project
    • Select the Overview tab
    • Click Launch an Eclipse Application in the Testing section

The started application should look similar to one of the following screenshots.

osgi_events_result

 

osgi_events_result_fx

Further information:

 

 

Kommentare

Comment

Your email address will not be published. Required fields are marked *