Tuesday, April 14, 2009

Creating, Loading and Using Eclipse RCP Extension Points (Eclipse 3.4 - Ganymede)

As you know, eclipse platform by itself is very small and most of the functionality is provided by surrounding plugins. These plugins interact via extension points. Extension points allow the user to extend/customize the functionality of other plugins. Everything in the platform, for example views, editors, perspectives, preferences etc., use extension points.

It is easy to use the extension points defined by other plugins. It is slightly more complex to define our own extension points so that it is consumed by other plugins. This article primarily focuses on creating new points.

Creating New Extension Points:

The assumption is you have a plugin project working and you need to create a new extension point. Open plugin.xml and click on the "Extension Points" tab. Extensions tab is where we use extension points defined by other plugins. Extension Points tab is where we create our own extension points.

Click on "Add" to create a new extension point. Fill out the extension point id and name. The schema is auto-created in location schema\id. You can change it if you want. Once you click ok, the extension point schema editor will be open. You can also open it at a later time by double clicking on the schema file (.exsd) in explorer.

Overview tab:

This tab defines the basics - plugin id, point id, name and documentation. The plugin id is concatenated with the extension id by Eclipse to create a unique identifier. Documentation is very helpful for consumers using the extension points. They are used by the Extensions editor while client plugins use the extension points.

Extension Point Schema Definition:

This is where we actually specify the schema of the extension point.
  1. Create a new element by clicking on the New Element.
  2. Create new attributes for that element using New Attribute.
  3. The attributes can be of any type - boolean, string, java etc., java type is used to define the java super class or interface, which clients have to extend or implement. You can also use both. The best practice is to use both so that if the interface changes (addition of a new method etc.,) we can add a default implementation in the abstract super class without breaking the client code.
  4. Once the elements and it's attributes are defined, the next step is to add it to the extension. Add a new sequence or choice to extension (by right clicking on extension node).
  5. You can add the element(s) underneath the sequence/choice and specify the min/max occurences.

Source tab:

You can view the actual XML schema for the extension point in this tab.

Exporting the necessary classes:

Once the schema is defined, make sure to export the abstract class/interface to client plugins if your extension point defines one. You can do this by going to plugin.xml->Runtime tab and under exported packages make sure to add the package which has this class/interface defined.

The next step is to load all the extension point implementations from the plugins available in the target platform. Before that, let's see how to use this extension point from a client plugin.

Using the Extension Point:

  1. Open the client plugin's plugin.xml
  2. Go to "Dependencies" tab. Add the main/base plugin (which defines the extension point) as it's dependency.
  3. Go to "Extensions" tab and click on "Add" to add the extension point.
  4. Now, right click on the extension point to add it's children/attributes and fill in the details in "Extension details".
  5. The extensions editor gives the options based on the extension point schema. You can also view the extension point schema and description from here.
  6. If the extension point needs a class implementation, you need to create the class and put the class name in Extension Details". You can click on the class attribute name and that will also open the "New class dialog" with relevant details filled in.
Loading the extension points:

Now, let's see how load all the extension points:

try {
IConfigurationElement[] config =
Platform.getExtensionRegistry().getConfigurationElementsFor(
IGREETER_ID); // 1
for (IConfigurationElement e : config) { // 2
Object o = e.createExecutableExtension("class"); // 3
if (o instanceof IGreeter) {
((IGreeter) o).greet(); // 4
}
}
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}

Eclipse platform maintains a registry of all extension points available in the target platform. Point 1 loads all the extension points named IGREETER_ID from the platform registry. Point 2 iterates through those configs. Point 3 is the main thing. It looks for "class" attribute in the config element and loads that class. In this case, it is the implementation of interface (which this extension point requires clients to implement). You can also use getChildren and getAttribute methods to query the configuration element. Once thing to note here is that, we could not just do Class.forName here. We need to use this method to instantiate the objects. Once the object is created, we can call methods in the interface to do the operation.

Lazy Initialization:

One main thing to note here is, in the above code, I've instantiated the class immediately. I've done that only to show how to do it. But, it is a BAD practice to load the implementations until it is really needed since it loads the corresponding client plugin. Imagine, if all plugins did it this way at start-up, then it would take forever to start the application. Often we need info about the extension points, but we don't want to really load the plugin for that.

For example, a typical use case is "Show views" or "Show perspectives" or in our custom case maybe "Show available implementors". If we have to show this to a user, we don't need to process all of the plugins. Instead we could just query the registry and get all the configuration elements. From the configuration elements, we could get all the information defined by the client in their plugin.xml. A good way is to use the proxy pattern. Store the config element in a proxy object and query the attributes/elements to decorate the UI. Then, when user clicks on a specific implementation and when it is time to do the action call the execute method on the proxy object which will do this:

this.configElement_.createExecutableExtension("class")

So, we actually load the client lazily only when it is needed.

Conclusion:

We saw how to create an extension point, how to use it from a client plugin's perspective, loading available extension points and how to lazily load them. I hope this article helps you in working with extension points.

References:
  1. Building Commercial-Quality Plug-ins by Eric Clayberg and Dan Rubel.
  2. All the articles on extension points in my delicious bookmarks: http://delicious.com/search?context=userposts&p=extension&lc=1&u=rajak27





No comments: