Abstract

Passing Data between Plug-ins
The Eclipse Platform provides a very rich API for the development and configuration of plug-ins and RCPs. It does this in two ways: by providing the corresponding classes and interfaces or by using the extension point mechanism. During the development the question arises, how to develop own extension points and how those plug-in interfaces look like. This article summarizes some of my experiences with developing plug-ins extension points.

Introduction

The extensive use of extension points is the standard approach during the development of own plug-ins and Eclipse-based applications. Especially, in the 3.x branch, the Eclipse Developers introduced tons of new extension points, primarily for the user interface, moving towards the declarative definition of the UI. Even if the the topic of the definition of extension points is covered in several books and articles, it is a bit challenging to come up with a clean extension point design for a particular scenarios, especially for beginners. This has to do with the specific way, how the Eclipse platform handles extensions.

In order to have a concrete scenario, lets assume that a small RCP application consisting of two plug-ins is being developed. The application prints out the time every ten seconds. One plug-in is responsible for the functionality of the time generation (lets call it the Core plug-in) and another, for the presentation of the results of the first one (lets call it UI plug-in). The separation of code in UI and non-UI plug-ins is a common practice and the standard question is, how to to pass data between the two. In general we assume, that the UI plug-in depends on the Core plug-in.

In the following the different alternatives are discussed.

Call me synchronously

One of the most simple and common cases is the situation, in which the UI plug-in has the control and needs to call a method on some instance of the Core plug-in. This happens particularly in cases, in which a command handler or an action implemented in the UI plug-in is invoked on behalf of user interaction and the functionality of the Core plug-in is called. The only thing to do is to add the classes to the exported by the Core plug-in. Since the UI plug-in depends on Core, the classes will become visible (in terms of OSGi) and can be imported and used directly. If the method is synchronous, the result can be received upon the completion of the method and displayed by the UI plug-in. In this case the plug-in boundary disappears.

Call me asynchronously

The things get more complicated, if the call is not synchronous, but is invoked inside of a IWorkspaceRunnable or a Job. This situation is common in enterprise scenarios, when the invoked functionality is a long running operation, like loading data from a database or accessing a resource over the network. The signatures of the methods for those operations do not provide a possibility to get the invocation result.

public interface IWorkspaceRunnable {
    public void run(IProgressMonitor monitor) throws CoreException;
}
public abstract class Job extends InternalJob implements IAdaptable {
    protected abstract IStatus run(IProgressMonitor monitor);
}

In fact the method invocation of asynchronous calls can be performed from any plug-in (so not only UI) and it does not play any role for the result. Thus, this case can be discussed together with the case, in which the Core plug-in broadcasts the data change.

Don’t call us, we will call you

Another case, which can be seen as a general extension of the asynchronous case is if the state changes, and consequently the UI change originates from the Core plug-in. This use case is possible if there are several UI plug-ins working with one Core plug-in, and the changes inside of the Core plug-in have to be propagated to the views, following the well-known Model-View-Controller Design Pattern. Then, a common approach is the implementation of the Listener-Broadcaster Design Pattern. The UI plug-ins (Views) should register themselves as listeners by the Core plug-ins and will be notified on changes of its (Model) state. In this case the extension point mechanism of Eclipse can be used as discussed in the following. It is also a good idea, to hide the code dealing with Eclipse Extension Registry and extensions, so a simple implementation of the broadcaster is provided, too.

Definition of the extension point (Core plug-in side)

The definition of extension point for the listener/broadcaster case involves four steps:

  • Creation of the listener interface
  • Registration of the extension point
  • Creation of the extension point schema
  • Implementation of the broadcaster

The so defined extension point can be used by plug-ins, which then provide implementations of the listener interface. The broadcaster, located in the Core plug-in is responsible for sending the data to them. Note, that providing the implementation of the listener via extension point is logically equivalent to the creation of a factory which is responsible for the production of listeners. The Core plug-in has the control of how this factory is used, how many listeners (from each extension) are created, and when these are notified.

Creation of the listener interface

The interface for the listener is usually very simple. It provides a method, which is called by the broadcaster, e.G:

public interface ISimpleListener
{
    public void eventOccured();
}

Sometimes the method takes parameters, like public void eventOccured(String message) or public void eventOccured(ISimpleEvent event) and sometimes it returns a value. In general, the signature of the notification method is application-specific and will not be discussed further. Since the implementers of the listener does not have control over the way, how and when this method is used, it is advised to add two life-cycle methods to the interface initialize() and terminate():

package de.techjava.rcp.plugins.core.listener;

/**
 * A simple listener
 * @author Simon Zambrovski, TechJava Group
 */
public interface ISimpleListener
{
    /**
     * Initialization life-cycle method
     */
    public void initialize(Object source);

    /**
     * Signals the event occurrence
     * @param event
     */
    public void eventOccured(SimpleEvent event);

    /**
     * Termination life-cycle method
     */
    public void terminate();
}

The life-cycle of the listener is then defined as follows: first, its parameter-less constructor is called, then the initialize method is invoked, providing the ability to hook into some infrastructure inside of the implementing plug-in. After the initialization, the eventOccured method is called multiple times. Finally, the listener is informed about the fact that the source will not send any information by the invocation of the terminate method. The source parameter of the ISimpleListener#initialize(Object) method again is application specific and should be chosen that way, that the listener is able to determine the source of events. Alternatively, the method ISimpleListener#eventOccured(SimpleEvent) can carry this information on every invocation, either in the SimpleEvent object or as additional parameter.

Registration of the extension point

After the Java interface of the listener has been created, the extension point can be defined. The easiest way to this, is to use the Plug-In Manifest Editor > Extension Points, but you can edit the plugin.xml directly. Basically, three values are required: id, name and schema location.
extension-point-wizard

The corresponding plugin.xml code looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.5"?>
<plugin>
   <extension-point
      id="de.techjava.rcp.plugins.core.listener"
   	  name="Simple Core Listener"
   	  schema="schema/org.techjava.rcp.plugins.core.listener.exsd"/>
</plugin>

Since the dependent plug-ins should implement the ISimpleListener interface, the containing package has to be exposed. This can be done either by adding the package to the list of exported packages using the Plug-In Manifest Editor > Runtime > Exported Packages or by simple editing of the MANIFEST.MF file and adding the following line to it:

Export-Package: de.techjava.rcp.plugins.core.listener

Creation of the extension point schema

The definition of the extension point is performed by creation of the XML-Schema document, describing the structure and the data of the extension point. The Eclipse IDE provides a convenient Extension Point Schema Editor for this task, implemented as multi-page editor with master-detail page for schema definition. The creation wizard already creates the extension element. What has to be done is this:

  • Create New Element
  • Provide an adequate Name for it (listener in our case)
  • Add an Attribute to this element
  • Provide an adequate Name for it (class in our case)
  • Change the Use to required
  • Change the Type to java
  • Fill the Implements with the full-qualified name of the listener interface (de.techjava.rcp.plugins.core.listener.ISimpleListener)
  • Optionally, fill the description
  • Add a Sequence to the extension
  • Add a reference to the listener to the Sequence
  • Optionally, adjust the multiplicity of the listener element, to allow multiple listeners to be registered using one extension point

In the Extension Point Schema Editor the corresponding changes should look like this: extension-point-editor

Implementation of the broadcaster

After the interface is created and the corresponding extension point is defined, a broadcaster can be implemented. Its purpose is to hide the code related to the use of the Extension Point Registry. For convenience, the broadcaster can implement the same interface as the listeners do (ISimpleListener).

/**
 * Simple broadcaster implementing the listener interface
 * @author Simon Zambrovski, TechJava Group
 */
public class SimpleBroadcaster implements ISimpleListener
{

    private static final String POINT_ID = "de.techjava.rcp.plugins.core.listener";
    private static final String CLASS_ATTRIBUTE = "class";

    private List<ISimpleListener> listeners = null;

    /**
     * Construct the broadcaster and initializes the listeners registered
     * using {@link de.techjava.rcp.plugins.core.listener} extension
     * point
     */
    public SimpleBroadcaster(Object source)
    {
        // initialize on creation
        initialize(source);
    }

    public void eventOccured(SimpleEvent event)
    {
        // broadcast the events
        for (ISimpleListener listener : this.listeners)
        {
            if (listener != null)
            {
                listener.eventOccured(event);
            }
        }
    }

    public void initialize(Object source)
    {
        // retrieve the registered listeners
        this.listeners = getRegisteredListeners();

        // initialize
        for (ISimpleListener listener : this.listeners)
        {
            if (listener != null)
            {
                listener.initialize(source);
            }
        }
    }

    public void terminate()
    {
        // terminate
        for (ISimpleListener listener : this.listeners)
        {
            if (listener != null)
            {
                listener.terminate();
            }
        }
        this.listeners = null;
    }
...
}

The broadcaster holds the list of registered ISimpleListener instances. Its own implementation of the ISimpleListener is trivial and just delegates the calls to the elements of this list. The static method SimpleBroadcaster#getRegisteredListeners() is responsible for querying the registry for the registered extension and looks as follows:

    /**
     * Retrieves all registered listeners
     */
    public static List<ISimpleListener> getRegisteredListeners()
    {
        IConfigurationElement[] decls = Platform.getExtensionRegistry().getConfigurationElementsFor(POINT_ID);

        Vector<ISimpleListener> validExtensions = new Vector<ISimpleListener>();
        for (int i = 0; i < decls.length; i++)
        {
            try
            {
                ISimpleListener extension = (ISimpleListener) decls[i].createExecutableExtension(CLASS_ATTRIBUTE);
                validExtensions.add(extension);
            } catch (CoreException e)
            {
                Activator.logError("Error instatiating the " + POINT_ID + " extension", e);
            }
        }
        return validExtensions;
    }

Use of the broadcaster

The use of the broadcaster from the Core plug-in is straight-forward. Here is an example of a job, which periodically sends message containing the time to the listeners.

/**
 * Broadcasts the current time
 * @author Simon Zambrovski
 * @version $Id$
 */
public class SimpleTimeBroadcastJob extends Job
{
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    private SimpleBroadcaster broadcaster = null;

    public SimpleTimeBroadcastJob()
    {
        super("Time Broadcast Job");
    }

    protected IStatus run(IProgressMonitor monitor)
    {
        try
        {
            broadcaster = new SimpleBroadcaster(this);
            broadcaster.eventOccured(new SimpleEvent("Curent time in Core is " + sdf.format(new Date())));
            broadcaster.terminate();
            return Status.OK_STATUS;
        } finally
        {
            // restart again in a 10 seconds
            schedule(10 * 1000);
        }
    }
}

Now, after the Core part is implemented, we focus on the implementation of the UI part, which receives the events.

Use of extension point (UI plug-in side)

The usage of the extension point is simpler than its definition and basically consists of the implementation of the listener interface and its registration. Here is a sample implementation writing to System.out:

public class SimpleListener implements ISimpleListener
{
    private Object source;

    public SimpleListener()
    {
    }

    public void eventOccured(SimpleEvent event)
    {
        String message = "Event from " + source.toString() + ": " + event.getMessage();
        System.out.println(message);
    }

    public void initialize(Object source)
    {
        this.source = source;
        System.out.println("New " + SimpleListener.class.getName() + " listens to " + source.toString());
    }

    public void terminate()
    {
        this.source = null;
        this.consoleStream = null;
        System.out.println("Listners destroyed");
    }
}

In order to tell the Eclipse Platform about the existence of this implementation, the extension point defined in the Core plug-in and has to be used in the UI plug-in. For this purpose, the UI plug-in must list the Core plug-in in its Dependencies and define the use of the extension point in the plugin.xml:

   <extension
         point="de.techjava.rcp.plugins.core.listener">
      <listener
            class="de.techjava.rcp.plugins.ui.SimpleListener">
      </listener>
   </extension>

That’s it.

Example

The example source for this article is available here. It is a little bit more elaborate and provides a simple application, which prints the time into the Eclipse Console. I used Eclipse Galileo (3.5.0) – just copy the two projects into your workspace and run the product.
time-rcp

References