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 administration GUI. For example, a plugin developer may extend main menu entries, add new buttons or create whole new administration areas inside the administration GUI.
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.22. 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.23. Writing a plugin for a [fleXive] application
Now that our application supports plugins as described in the previous example, we'll show how to write a plugin for it. Remember that the executor interface allows us to send numbers to the application. We'll create a very simple plugin that always sends the number 42.
public class TestPlugin implements Plugin<TestExecutor> { public void apply(TestExecutor executor) { executor.pushResult(42); } }
The executor is passed to the plugin, which then calls the method defined in the executor's interface.
If we're distributing the plugin as (part of) a [fleXive] component,
we can add a
PluginFactory
to register the plugin during [fleXive] startup.
public class TestPluginFactory implements PluginFactory { public void initialize(PluginRegistryBean registry) { registry.registerPlugin(EXTENSIONPOINT, new TestPlugin()); } }
Finally we register the factory with its fully qualified classname as shown in
Example 7.24, “Specifying a
PluginFactory
in
flexive-plugins-config.xml
”.
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.
The plugin registry has a
registerPlugin
method for registering
plugins at extension points. You can call this method at any time, for example
from the constructor of an application-scoped JSF bean, or use the
PluginFactory
interface to be called by [fleXive] on startup.
To register your plugin automatically during the startup of [fleXive], you have to
implement the
PluginFactory
interface,
set the factory name in
META-INF/flexive-plugins-config.xml
of your package, and
register your plugins in the
PluginFactory
's
initialize
method.
The
PluginFactory
specifies a simple callback
interface that lets you register plugins with a
fxPluginRegistryBean
. You have to specify the fully
qualified classname of your implementation in a file called
META-INF/flexive-plugins-config.xml
,
for example:
Example 7.24. Specifying a
PluginFactory
in
flexive-plugins-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <plugins-config xmlns="http://www.flexive.com/plugins-config"> <plugin-factory> com.flexive.faces.plugin.DemoPluginFactory </plugin-factory> </plugins-config>
For an example of a concrete
PluginFactory
implementation, refer to
Example 7.23, “Writing a plugin for a [fleXive] application
”.