Saturday, January 23, 2010

Adding actions to Eclipse console commands

This article assumes you know what Declarative Services (DS) are and have a basic understanding of OSGi.

Introduction

There is one big problem with the Eclipse console commands. You can have only one command with a specific name. That's of course the case in all consoles known to me. However in most consoles the commands express by their name what they actually do. Now in Eclipse we have good commands like "ss" for a quick overview of the currently known bundles in the system. Then there are the framework commands like "launch", "shutdown" and "exit" which fairly behave like you would expect them by their name. But do you know what the "list", "ls" or "enable", "en" commands do? Well, they come from the "Service Component Runtime" (SCR) bundle.
list/ls [-c] [bundle id] - Lists all components; add -c to display the complete info for each component;
enable/en  - Enables the specified component;
So when you load the SCR bundle the command "list" will be taken. And it will list components. A better name would have been "listcomponents". An even better approach would have been, if the Eclipse command framework was initially planned to have commands that can be extended by actions. Because then we would have a "list" command that could be extended by actions. We would have "list components", "list bundles" and "list services". There would be much less namespace clutter and a tighter set of commands. But since we cannot create a new "list" command, as SCR occupies it already, we have to go a slightly different way.

What are command actions?

Command actions are a way to group commands and redirect the concrete execution to an action. So for the SCR I would like to suggest the following command: component This command would have several actions: list,enable,disable,info and for the sake of completeness the short versions of the original SCR commands as actions: ls,en,dis,i A command action is an OSGi services, that can dynamically extend an existing command. Its service interface is really simple:
public interface CommandAction {
 void execute(CommandActionContext context) throws Exception;
}
The action gets in a context that allows it to query execution parameters that the user typed in the command line. It also allows the action to print text on the console.
public interface CommandActionContext {
 String getArgument(int index);

 boolean hasOption(String name);

 boolean isVerbose();

 void print(String text);

 void println(String text);
}
A sample action would look like this:
public class FileCopyAction implements CommandAction {
 void execute(CommandActionContext context) throws Exception {
   String from = context.getArgument(0);
   String to = context.getArgument(1);
   if (from == null || to == null) {
     throw new IllegalArgumentException("You must specify from and to arguments");
   }
   // Copy code here...
 }
}
Its OSGi DS definition would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.example.commands.file.CopyAction">
  <implementation class="org.example.commands.file.CopyAction"/>
  <service>
     <provide interface="org.eclipse.osgi.framework.console.action.CommandAction"/>
  </service>
  <property name="command" type="String" value="file"/>
  <property name="action" type="String" value="copy"/>
</scr:component>
Now we need a way to bring a command and its actions together. This is implemented in an abstract class that implements the standard Equinox CommandProvider. It also parses the command line and prints help. It takes the first argument from the command line and searches an action that matches the argument. It then calls the found actions execute method with the rest of the command line neatly seperated into arguments and options (that start with "-" or "--"). It also catches any exception that the action may throw during its execution. If the action throws an IllegalArgumentException, our command implementation will print help for the command. If we want to introduce a new command "file" all we have to do is to create a small subclass from the abstract base class "ActionCommand".
public class FileCommand extends ActionCommand {
 public void _file(CommandInterpreter ci) {
   execute("file", ci);
 }
}
Per definition, to create a command in Eclipse Equinox we have to have a public method beginning with an underscore. So there is a small duplication of names here, since our ActionCommand baseclass does not know the name of the method its called from, we have to hand it in as first parameter to the execute method. Somewhere we would also have to define the file command itself. Its published as a CommandProvider.
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.example.commands.file">
  <implementation class="org.example.commands.file.FileCommand"/>
  <service>
     <provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
  </service>
  <reference cardinality="1..n" interface="org.eclipse.osgi.framework.console.action.CommandAction" name="CommandAction" policy="static" target="(command=file)"/>
</scr:component>
Please note, that this command component will only be instantiated if there is at least one CommandAction service registered that has its "command" property set to "file" and is therefor providing an action for our command. See the example file copy action in "action" (It does not really copy a file):
That's all. Happy extending your own commands. And maybe this will find its way back into the Equinox code and we can finally have one global "list" command with lots of actions.

The code is available at git.

Tuesday, January 12, 2010

Notification Listeners

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:
Object listeners[] = context.locateServices("NotificationListener"); //$NON-NLS-1$
if (listeners != null) {
 NotificationEvent event = new NotificationEvent(this, notification, NotificationEventType.Show);
 for (Object listener : listeners) {
   try {
     ((NotificationListener)listener).onEvent(event);
   } catch (Throwable e) {          
   }
 }
}
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:
<reference cardinality="0..n" interface="ui.notification.NotificationListener" name="NotificationListener" policy="dynamic"/>
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:
protected void activate(ComponentContext context) {
 this.context = context;
}
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());
    }
  }
}
Its component definition would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ui.notification.listener.sysout">
 <implementation class="ui.notification.listener.sysout.internal.SysoutNotificationListener"/>
 <service>
    <provide interface="ui.notification.NotificationListener"/>
 </service>
</scr:component>
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.