To let other components in the system know about displayed notifications we use the White-Board-Pattern to consume listener services in our NotificationServiceImpl class.
A listener for notifications has a very easy interface:
public interface NotificationListener extends EventListener {
void onEvent(NotificationEvent event);
}
It extends the default Java (empty) EventListener base interface and contains a single method. This single method and its associated event object allows us to extend the kind of events we can publish without changing the interface for each new event like "Show" or "Hide".
For now we will only publish "Show" events. But if in the future we decide to also notify listeners about the close/hide of a notification popup we would have to add another method to the interface "onHide" and breaking the API somehow.
Using an event object we can put the type of the event inside the event object and the client would then have to decide what to do for each event type. It would still only implement a single method "onEvent" instead of having to implement a new method for each new event type such as "onShow" or "onHide".
The event object itself looks like this:
public class NotificationEvent extends EventObject {
private static final long serialVersionUID = -8633958140848611658L;
private Notification notification;
private NotificationEventType type;
public NotificationEvent(NotificationService source, Notification notification, NotificationEventType type) {
super(source);
this.notification = notification;
this.type = type;
}
public Notification getNotification() {
return notification;
}
public NotificationEventType getType() {
return type;
}
}
The event type is an enum:
public enum NotificationEventType {
Show
}
The NotificationServiceImpl is only slightly changed:
Right before we display the notification we get all the NotificationListener registered in the OSGi system and (safely) call their onEvent method. There are several ways to get the list of currently registered NotificationListener services. One way is to use the BundleContext.getServiceReferences(String, String) method. But this would involve getting the service object ourself and not forget to "unget" it also. Another way is to use a ServiceTracker. But since our implementation already is a Component with bindings we can just add a new binding to our service component definition XML:
We then use the ComponentContext object to get an array of services that could be bound to "NotificationListener" (as named in the component XML).
This context we get from OSGi in a new protected method:
That's all for notifying listeners about notification display.
We can now create a simple new bundle that will register a NotificationListener into the system and simply System.out.println the notifications data.
public class SysoutNotificationListener implements NotificationListener {
public void onEvent(NotificationEvent event) {
if (event.getType() == NotificationEventType.Show) {
System.out.println(event.getNotification().getTitle() + ": " //$NON-NLS-1$
+ event.getNotification().getMessage());
}
}
}
Pretty simple. Here is what the result looks like:
Listeners could also be used to log a history of notifications in various ways. In the next chapter of this series we will develop a View for Eclipse RCP apps that logs a history of notifications in a table.
In this series of tutorials I will describe a small, extensible notification framework for JFace/Eclipse applications. I will make heavy use of OSGi services and Eclipse Extensions. So before you continue to read, I suggest you make yourself familiar with those concepts to be able to follow the series.
The inspiration for using notifications in my own programs came from Mylyn that shows neat little notifications upon changes in the watched buglists. I was further inspired by the great blog post over at Hexapixel: Creating a Notification Popup Widget. We will use this widget as the base of this tutorial series.
Creating the service
First we create a simple service interface for our NotificationService. Its intentionally simple and does not contain any query methods to enumerate notifications or such. It simply provides a method to show a single notification. But as you will see in the course of this series that's enough to drive a powerful notification system.
So the interface will look like this:
For the Notification interface itself we will use JFace's IMessageProvider as a base as it provides already a message text and type for our notification. All that is left is a title. So our Notification interface looks pretty simple too:
interface Notification extends IMessageProvider {
/**
* @return the title of this notification or null if none.
*/
String getTitle();
}
Thats basically all we need for a simple notification. As the base for the implementation of our Notification Service we will use Hexapixel's code for now. That makes the implementation of the NotificationService really simple:
public void show(final Notification notification) {
if (notification != null) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
NotificationType type;
switch (notification.getMessageType()) {
case IMessageProvider.WARNING:
type = NotificationType.WARN;
break;
case IMessageProvider.ERROR:
type = NotificationType.ERROR;
break;
default:
type = NotificationType.INFO;
break;
}
NotifierDialog.notify(notification.getTitle(), notification.getMessage(), type);
}
});
}
}
I only modified the original NotifierDialog class so that it no longer requires an active shell for operating. There is one more little thing we also need to do, to actually see the notification on screen. We need a thread that polls the SWT message loop. This is usually done for us when we run inside the Eclipse workbench, but since we are going to test this notification framework in an OSGi environment (for now) we need a thread that polls the SWT message loop for us. The code is also inside the NotificationService component implementation:
Runnable runnable = new Runnable() {
public void run() {
while (!Display.getDefault().isDisposed()) {
if (!Display.getDefault().readAndDispatch()) {
Display.getDefault().sleep();
}
}
}
};
new Thread(runnable, "SWT").start();
Equinox Console Command to show notifications
Basically we have everything in place now to show notifications programmatically. We can have other components use our notification service to display messages. So we will create a console command for the Equinox OSGi console and implement a simple CommandProvider. It will expose the command "notify" to the Equinox console if you start the OSGi configuration with the "-console" command.
The notify command will take at least one parameter, which will be the notifications message. If you specify another parameter, then this will become the notifications title. Otherwise a default title is choosen.
public class NotifyCommand implements CommandProvider {
NotificationService service;
protected void activate(ComponentContext context) {
service = (NotificationService) context.locateService("NotificationService");
}
public void _notify(CommandInterpreter ci) {
final String message = ci.nextArgument();
if (null == message) {
ci.println("You need to specify a message");
return;
}
String title = ci.nextArgument();
if (title == null) {
title = "Notification";
}
final String finalTitle = title;
service.show(new Notification() {
public String getTitle() {
return finalTitle;
}
public String getMessage() {
return message;
}
public int getMessageType() {
return IMessageProvider.INFORMATION;
}
});
}
public String getHelp() {
return "--- Notification ---\n\tnotify message [title]";
}
}
Create the component definitions
All that is left to make those 2 services run as Declerative Services is to create the required component XML files in /OSGI-INF/.
For the NotificationService it looks like this:
To run this we create an OSGi run configuration and add the following bundles to it:
ui.notification
ui.notification.command
org.eclipse.osgi
org.eclipse.osgi.services
org.eclipse.equinox.ds
org.eclipse.equinox.util
org.eclipse.equinox.common
org.eclipse.core.commands
org.eclipse.swt
org.eclipse.swt.win32.win32.x86
org.eclipse.jface
Make sure the org.eclipse.equinox.ds bundle is started, as well as our two ui.notification* bundles.
If we start the configuration now, we will be able to type:
notify "Hello World"
and see a notification coming up at the right bottom of our primary screen as seen in this video:
That's all for now. Stay tuned for the next part where we will notify listener services about notifications using the White-Board pattern. Feedback is always welcome, especially about how to poll the SWT message loop in OSGi apps.
We all know the standard JFace icons very well. They appear in Dialogs, Wizards and Forms.
And in a form they look like this:
Now, there is a quick and easy way to replace them with your own icons if you like.
You just have to create a Fragment bundle and set org.eclipse.jface as its Bundle-Host. In your fragment bundle you include the icons you want to replace in the following folder structure:
Don't forget to add the icons folder and all of its subfolder and content into your build.properties file:
bin.includes = META-INF/,\
.,\
OSGI-INF/,\
icons/
Interesting fact: You can replace the JFace icons with PNGs too. You can just rename them to have the .GIF extensions. The image file loader of SWT does not care about the extension but analyses the content of the file.