The JSF plugin API

One of the main JSF features is its component-centric design. It allows developers to create component packages as JAR files that can be deployed in existing applications with little or no additional configuration. Prominent examples in the free software world are MyFaces Tomahawk, ICEfaces, or JBoss RichFaces.

[fleXive] encourages the use of all JSF and Facelets features for writing reusable components, and adds its own plugin API to the mix for modifying existing [fleXive] applications, most notably the backend administration application. For example, a plugin developer may extend main menu entries, add new buttons or create whole new administration areas inside the backend administration application.

Currently the plugin API is only available in the JSF layer, although it is not specifically tied to JSF. It is a pure Java solution that may be used for adding plugins to any Java application, it's just that inside [fleXive] most sensible extension points for plugins are in the JSF layer.

The plugin API chapter covers only a small set of features needed for extending [fleXive], for information on writing reusable components see the section called “Writing reusable [fleXive] components ”.

The plugin API is based on three core interfaces and a JSF managed bean:

  • An extension point defines a "code point" where existing Java code can be extended.

  • Extension points are parameterized with a concrete sub-interface of a plugin executor . A plugin executor interface exposes the methods which plugins may call to modify application behaviour. For example, a plugin executor for a tree may specify methods for adding or removing tree nodes.

  • A plugin implements an interface which is parameterized with the type of an extension point, which in turn is parameterized with a plugin executor interface which will be used by the plugin.

  • The fxPluginRegistryBean acts as a global plugin registry for the application. Plugins can be registered at application startup using the PluginFactory interface.

Extension points and plugin executors are provided by the application developer, the plugin developer only needs to implement a properly parameterized interface.

Example 7.18. Making a [fleXive] application extensible

The intent of the next two examples is to create an extension point which can handle arbitrary plugins attached to it, and then write a plugin for it. The first part is the responsibility of the original application developer, the second one applies to any plugin developer for that application.

The first step for the application developer is to create a PluginExecutor interface that defines the methods available to plugins. In this example, we want to accumulate numbers generated by plugins. We define the following interface:

public interface TestExecutor extends PluginExecutor {
    void pushResult(int result);
}

The executor takes a result (generated by the plugin), and stores it in an internal data structure. Why do we specify an interface instead of a concrete implementation class? The actual plugin executor implementation depends on the code being extended, and may contain private classes that are not (or should not be) visible to the plugin developer. Also, sometimes it is convenient to implement the executor as an anonymous inner class that has access to the surrounding method's variables, without publishing the actual implementation to somebody else.

Next we declare an extension point, for example in a public shared class:

public static final ExtensionPoint<TestExecutor> EXTENSIONPOINT =
        new ExtensionPoint<TestExecutor>("ourapp.extensionpoint") { };

We bind the extension point to our executor interface, and specify a unique identifier (ourapp.extensionpoint). When a plugin developer does not want to or cannot reference the EXTENSIONPOINT constant in his code, he can create a new extension point with the same identifier (and the same executor interface).

Finally we create the executor implementation:

public class TestExecutorImpl implements TestExecutor {
    private final List<Integer> results = new ArrayList<Integer>();

    public void pushResult(int result) {
        results.add(result);
    }

    public List<Integer> getResults() {
        return results;
    }
}

The class implements the TestExecutor interface, and adds an implementation-specific method to obtain the results. The executor implementation does not have to be visible to the client, but its interface has to.

Extending our application is the last step. We instantiate a new executor, and ask the fxPluginRegistryBean to execute all registered plugins. Then we collect the result and show it to the user.

final TestExecutorImpl exec = new TestExecutorImpl();
PluginRegistryBean.getInstance().execute(EXTENSIONPOINT, exec);
System.out.println("Plugin results: " + exec.getResults());

Take a look at the next example to see how to write a plugin for our application.


Example 7.19. Writing a plugin for a [fleXive] application


The fxPluginRegistryBean is an application-scoped JSF bean that acts as a central registry for all [fleXive] plugins. It is initialized on application startup and scans the classpath for available plugins. An application can retrieve a list of registered plugins for a extension point or execute a plugin executor on all plugins of an extension point.