 | Note
This is a preliminary document that is provided as a preview to functionality being introduced in Pulse 2.0. The content is incomplete and subject to change during the 2.0 Early Access Program. This document will eventually be replaced by the formal 2.0 documentation. |
Introduction
One of the major new features in pulse™ 2.0 is the addition of a plugin system. This system will allow users to provide their own integrations and customisations with various tools that are not supported by the default Pulse implementation. This takes the adaptability of pulse™ to the next level.
Plugin Framework
At its core, the pulse™ plugin system relies on standard OSGi
technology. Using the standard allows both ourselves and plugin authors to leverage existing implementations, knowledge and tools. The particular implementation of OSGi that we are using is Equinox
, the basis of the Eclipse
platform and IDE.
OSGi Plugins
An OSGi plugin is simply a regular Java jar file containing a META-INF/MANIFEST.MF file that contains certain plugin headers. The OSGi specification defines the header fields available, including the plugin identifier, version and dependency information. An example manifest is shown below:
A full description of OSGi plugins is outside the scope of this document. However, there are many resources available online and elsewhere, including:
The Eclipse project also provides documentation for Equinox.
Extension Points
The plugin system also makes use of an Equinox-specific technology known as extension points. These are defined points in an application that a plugin can extend. For example, there is an extension point which allows you to contribute a new command implementation as a plugin. The plugin notifies pulse™ of the extensions that it provides by use of an XML metadata file named plugin.xml located at the root of the plugin jar. An example plugin.xml file is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension point="com.zutubi.pulse.core.commands">
<command name="make" class="com.zutubi.pulse.core.MakeCommand"/>
<command name="make.pp" class="com.zutubi.pulse.core.MakePostProcessor"/>
</extension>
</plugin>
Note that the XML file defines that this plugin is contributing to the extension point "com.zutubi.pulse.core.commands". The content between the <extension> tags is point-specific and documented individually for each extension point. In this case, two commands are contributed by providing a name for each command and the name Java class that implements each command.
Available Extension Points
We plan to open up many areas of pulse™ for extension. They will be added to this list as details become available:
| Extension Type |
Description |
| [Command] |
Allows integration with command line tools such as Ant, make etc. |
| SCM |
Allows integration with an SCM (or version control system), for example Subversion. |
| Post-processor |
Allows the addition of processors that can extract features (errors/warnings) and/or test results from build artifacts. |
Plugin Configuration
An important aspect of the pulse™ plugin framework is the support for plugin configuration. When adding some extensions to pulse™, e.g. a new SCM implementation, the plugin needs to be able to contribute pages to the pulse™ configuration UI and store configuration information. This configuration also needs to support the new templating functionality for configuration in pulse™ 2.0. To allow for all this, without placing extra burden on the plugin developer, pulse™ automatically generates the UI and manages the persistent storage for plugin configuration information. Essentially, the plugin author just needs to provide an annotated Java class (or classes) that declares the required configuration and pulse™ will do the rest.
An Example
To give some idea of how this works, consider the following configuration class lifted from the pulse™ core:
@Form(fieldOrder = {"name", "remote", "host", "port"})
@Table(columns = {"name", "location", "status"})
@SymbolicName("zutubi.agentConfig")
public class AgentConfiguration extends AbstractConfiguration implements NamedConfiguration
{
@Internal
private long agentStateId;
@ControllingCheckbox(dependentFields = {"host", "port"})
private boolean remote = true;
@NoInherit
private String name;
@Required
private String host;
@Numeric(min = 1)
private int port = 8090;
private Map<String, Resource> resources;
}
Without going into too much detail, notice that the fields required to configure an agent are just JavaBean properties on this class. The fields are annotated with UI, validation and templating details. Also not that annotations on the class are used to control how this data is displayed in an editable form and in a summary table. The properties for the summary table are actually defined on an additional class:
public class AgentConfigurationFormatter
{
private AgentManager agentManager;
public String getLocation(AgentConfiguration configuration)
{
Agent agentState = agentManager.getAgent(configuration.getHandle());
return agentState.getLocation();
}
public String getStatus(AgentConfiguration configuration)
{
Agent agentState = agentManager.getAgent(configuration.getHandle());
if (agentState.isEnabled())
{
return agentState.getStatus().getPrettyString();
}
else
{
return agentState.getEnableState().toString();
}
}
public void setAgentManager(AgentManager agentManager)
{
this.agentManager = agentManager;
}
}
Pulse finds this class by naming convention (i.e. it has the name of the configuration type with "Formatter" appended). From all of this information, UI pages are generated that are always consistent in the web interface. All persistence details are managed by pulse™ (the plugin can listen to configuration events if necessary) and the plugin itself is just handed configured objects of this class.
For more details, see the Plugin Configuration Reference page.
Schema Changes
The framework will also provide support for plugin authors to evolve the configuration schema for their plugins as they evolve. This support will fit into the existing pulse™ infrastructure for providing simple upgrades. Naturally, the task of actually transforming the data from one version to another is plugin-dependent and up to the plugin author. More details on this area to follow.