Moab Web Services > Plugins > Plugin Developer's Guide > Packaging Plugins

Packaging Plugins

Plugin types may be packaged in two different ways to upload to MWS:

While each may be uploaded to MWS using the REST API or the User Interface as described in Add or Update Plugin Types, using a JAR file is recommended. Using a simple Groovy file is useful for testing and generating proof of concept work, but does not allow the use of several features of plugins.

The principles of packaging a plugin type or set of plugin types in a JAR file are very simple. Simply compile the classes and package in a typical JAR file. All classes ending in "Plugin" are automatically attempted to be loaded as a plugin type, all classes ending in "Translator" are attempted to be loaded as a translator, and all classes annotated as a custom component will be attempted to be loaded. It is recommended that a build framework is used to help with compiling and packaging the JAR file, such as Gradle. This makes it easy to declare a dependency on the necessary JAR files used in plugin development and to debug, compile, and test plugin code.

In addition to using utility services such as translators, packaging plugin types in JAR files allows the creation of a single project for multiple related plugin types and bundling of external dependencies. These two features are discussed in the following sections.

Plugin Projects and Metadata

Each plugin type has information attached to it, called metadata, which describes the origin and purpose of the plugin type. Additionally, a JAR file may also contain a project file which defines default metadata attributes for all plugin types in the JAR. Initial plugins, or plugins that will be created on loading of the JAR file if they do not exist, are also able to be defined on a project file. In all cases, metadata declared on a plugin type will override the metadata defined on the project file.

To define a project file, simply add a class to JAR file that ends in "Project." This file will attempted to be loaded as the project file. Every field on a project file, and even the file itself, is optional. All available fields are shown in the example below.

class SampleProject {
	// Plugin information
	String title = "Sample"
	String description = "Sample plugin types"
	String author = "Our Company."
	String website = "http://example.com"
	String email = "[email protected]"
	Integer eventComponent = 1
	// Versioning properties
	String version = "0.1"
	String mwsVersion = "7.1 > *"
	String commonsVersion = "0.9 > *"
	String license = "APACHE"

	// Documentation properties
	String issueManagementLink = "http://example.com/ticket-system/sample-plugins"
	String documentationLink = "http://example.com/docs/sample-plugins"
	String scmLink = "http://example.com/git/sample-plugins"

	// Plugins that are to be created with these properties only when they do NOT exist
	// This does not override any existing plugin instance configuration
	def initialPlugins = {
		/*
		// Multiple instances of plugins may be defined here.
		// In this case, 'sample' is the id of the plugin
		sample {
				pluginType = "Sample"
				// All properties except for "pluginType" are optional
				pollInterval = 30
				autoStart = true
				// Although it is possible to set plugin precedence, it is not recommended since this precedence
				// may already be taken and plugin creation will fail in this case
				precedence = 5
				config {
					configParam = "value"
				}
			}
		}
		// Another plugin with an ID of 'sample2'
		sample2 {
			…
		*/
	}

As can be seen, metadata information about the plugin type(s), versions, and documentation are available. These are displayed when viewing plugin information in the User Interface or through the REST API.

Any of these properties except for initialPlugins, mwsVersion, and commonsVersion may be overwritten by the plugin type class itself by using static properties. A simple example is shown below.

package example

class SamplePlugin {
	// Properties may be typed, untyped, final, or otherwise,
	// but they MUST be static
	static version = "0.2"
	static title = "Sample plugin"
	static description = "This sample plugin is used to demonstrate metadata information"
	static author = "Separate Division"
	static eventComponent = 1

	… // Rest of the plugin type definition
}

Event component

The eventComponent field is explored in Creating Events and Notifications.

MWS and commons versions

The mwsVersion and commonsVersion fields are used to restrict the versions of MWS and plugin framework with which the plugin project may be used. Each field is of the format FIRST_VERSION > LAST_VERSION, where FIRST_VERSION is the first supported MWS or plugin framework version (inclusive), and LAST_VERSION is the last supported MWS or plugin framework version (inclusive). Each version must take the format of #.# or #.#.#, as in 7.1, or 7.1.2. An asterisk (*) is used to denote any version, and may be used for the first or the last version.

Although support for restricting both the MWS and commons versions are provided, it is recommended to use the commons version restriction always and the MWS version restriction where necessary. Restrictions on the commons version prevent plugin loading errors while restrictions on the MWS version prevent runtime errors such as missing support for certain MWS API versions.

Typically the mwsVersion and commonsVersion fields are set as shown above, with the first version set to a specific number, and the last version set to any (an asterisk). This is the recommended approach for setting both fields. It is not recommended to use any version (asterisk) for the first version. Some examples of mwsVersion and commonsVersion values are shown below with explanations of how they behave.

String mwsVersion = "7.1 > *" // Any MWS version 7.1.0 and greater is supported (including 7.2, etc)
String mwsVersion = "7.1.3 > *" // Any MWS version 7.1.3 and greater is supported (including 7.2, etc)
String mwsVersion = "7.1 > 7.1.3" // Any MWS version between 7.1.0 and 7.1.3 is supported
String mwsVersion = "* > *" // Any MWS version is supported (not recommended!)
String mwsVersion = "* > 7.2" // Any MWS version up to 7.2 is supported (not recommended!)

String commonsVersion = "0.9 > *" // Any framework version 0.9.0 and greater is supported (including 1.0, etc)
String commonsVersion = "0.9.3 > *" // Any framework version 0.9.3 and greater is supported (including 1.0, etc)
String commonsVersion = "0.9 > 0.9.3" // Any framework version between 0.9.0 and 0.9.3 is supported
String commonsVersion = "* > *" // Any framework version is supported (not recommended!)
String commonsVersion = "* > 1.0" // Any framework version up to 1.0 is supported (not recommended!)

If the mwsVersion or commonsVersion fields are formatted incorrectly, the plugin project will fail to load. If a plugin project is uploaded to MWS and the version check fails, the project will fail to load with an error message about the mwsVersion or commonsVersion.

The mwsVersion and commonsVersion fields cannot be overridden by a single plugin type, but can be set only at the plugin project level. This prevents mixing of MWS and commons version requirements within a single project.

Initial plugins

The initial plugins closure provides the flexibility to insert plugin instances when the JAR is loaded. This occurs at two points: when the plugin JAR is first uploaded to MWS, and when MWS is restarted. As shown in the example above, the ID, pluginType, and other properties may be configured for multiple plugins.

The nature of Groovy closures means that programmatic definition of initial plugins is possible. This may even be based on the MWS application configuration. Two properties are automatically available in the initialPlugins closure:

Native plugin case study

The Native JAR file utilizes many of the features discussed above. In the root of the JAR file, a compiled class called NativeProject exists which defines all of the metadata fields, including initialPlugins. Trying to create an initial plugin presents two distinct problems:

The initialPlugins closure is defined as follows:

import com.adaptc.mws.plugins.Suite

class NativeProject {
	… // Metadata fields

	def initialPlugins = {
		// Initialize the cloud-native plugin only if the suite is CLOUD
		if (suite==Suite.CLOUD) {
			'cloud-native' {
				pluginType = "Native"
				pollInterval = 30
				config {
					// Use the appConfig property to retrieve the current MWS_HOME location
					getCluster = "file://${appConfig.mws.home.location}/etc/nodes.txt"
				}
			}
		}
	}
}

Managing External Dependencies

External dependencies (e.g. JAR files) may be included and referenced in JAR files. Certain rules must also be followed in order to have the dependencies loaded from the JAR file correctly:

The plugin type must bundle all external dependency JARs in the root of the plugin type JAR file.

An entry must be included in the MANIFEST.MF file that references each of these bundled JAR files as a space separated list:

Class-Path: dependency1.jar dependency2.jar dependency3.jar

Assuming that these rules are followed and that the plugin type is uploaded using the REST API or the User Interface, the dependent JARs will first be loaded and then the new plugin type and associated files will be loaded.

Documenting Plugin Types

Documentation may also be included in JAR files by placing one or more Markdown formatted files in the root of the project JAR file. These files will be processed dynamically by MWS and presented as documentation pages for the respective plugin types within the MWS plugin user interface pages. Markdown is a simple text-to-HTML format used in some of the most popular open-source repositories such as GitHub and BitBucket. To help provide plugin developers use a single place or file for documentation, the conventional use of "README.md" as documentation was followed within MWS.

Documentation file naming

Each documentation filename must start with "README" and end with ".md". If only one documentation file is needed for bundled plugin type(s), it is recommended to call the file "README.md". For multiple plugin types, the file name must contain the plugin type name without the "Plugin" suffix in the format of "README-<PluginName>.md". For example, if a plugin project JAR file contained the plugin type classes "MyPlugin", "ABTestPlugin", and "ImportantPlugin", the documentation files would be located in the root of the JAR file and would be called "README-My.md", "README-ABTest.md", and "README-Important.md" respectively. If a "README" file does not exist for a certain plugin type, the main "README.md" file (if provided) will be used as documentation for that plugin type.

Markdown syntax

The Markdown syntax supported by MWS is very close to GitHub Flavored Markdown. Internally, the pegdown Markdown processor is used to generate the HTML with the TABLES, ABBREVIATIONS, FENCED_CODE_BLOCKS, SMARTYPANTS, DEFINITIONS, and QUOTES extensions enabled. HTML tags may also be used directly in order to create more refined formatting of the documentation, but this is discouraged with the exception of inserting the configuration reference table discussed below.

For example, the TABLES extension may be used to easily create HTML tables:

Name | Notes
------ | -------
Bob    | Knows how to use MWS plugins but has never created one
George | Writes MWS plugins in his spare time

The only main difference from standard Markdown processors is that block quotes (marked by lines prepended with '> ') are shown as highlighted information boxes when displayed in MWS. This may be used to draw more attention to informational or warning messages without writing custom HTML.

 > **Warning:** The use of this plugin type requires that MWS and MWM are configured correctly as described in
 > the MWS user guide.

Configuration reference table

A table of available configuration parameters is often constructed in documentation for each plugin type. To ease the burden on the plugin developer of maintaining this documentation and the constraints on the plugin type, a table generated from the constraints (see Configuration Constraints) and included messages is available by using the following HTML in the README file(s):

<div class="configuration-table">This section will be replaced by MWS with the configuration parameters table</div>

The text within the div container may be anything, but should state something helpful such as that it is placeholder in cases where the documentation may be viewed within other contexts such as on GitHub.

The generated table includes the following columns for each configuration parameter listed in the constraints: name, key, required, type, description. The "name" and "description" values are retrieved from the "help" and "label" messages bundled in the plugin JAR (see the labels and help messages section in Configuration Constraints for more information).

Web services reference sections

Documentation for exposed web services (see Exposing Web Services) is also able to be generated automatically. Instead of a single table as done with configuration parameters, a section with several tables (possible URL access points, URL parameters, and response fields) and additional information is generated for each exposed web service. This is available by using the following HTML in the README file(s):

<div class="webservice-sections">This section will be replaced by MWS with the web service documentation</div>

The text within the div container may be anything, but should state something helpful such as that it is placeholder in cases where the documentation may be viewed within other contexts such as on GitHub.

Changing heading sizes

The generated sections each begin with an <h2> heading with the name of the web service. If a different heading size (h3, h4, etc.) is desired, this may be done with the following HTML:

<div class="webservice-sections" data-level="3">This section will be replaced by MWS with the web service documentation</div>

Notice the data-level attribute, which contains the number used in the HTML h tag.

Message codes

Just as with the configuration table, the data for the content is generated automatically from the web service method name and from i18n messages (see i18n Messaging) bundled in the plugin JAR file. Message codes are available to customize the label and description of the web service. Codes are also available to define an arbitrary number of URL parameters and response fields. These do not need to be defined, but are helpful. The following table defines each message used in generating the documentation for web services.

Name Message code Description
Web Service Label <pluginType>.webServices.<webServiceMethod>.label The label used as the heading for the section, defaults to the naturally capitalized method name if not present.
Web Service Description <pluginType>.webServices.<webServiceMethod>.help Paragraph text describing the web service and its functionality, outputs, etc.
Parameter Key <pluginType>.webServices.<webServiceMethod>.parameter<n>.key The nth URL parameter, starting at 1 (example: id).
Parameter Label <pluginType>.webServices.<webServiceMethod>.parameter<n>.label The label for the nth URL parameter, defaults to the naturally capitalized key if not present.
Parameter Type <pluginType>.webServices.<webServiceMethod>.parameter<n>.type The type for the nth URL parameter, defaults to String if not present.
Parameter Description <pluginType>.webServices.<webServiceMethod>.parameter<n>.help The description or help text for the nth URL parameter.
Response Field Key <pluginType>.webServices.<webServiceMethod>.return<n>.key The nth response field, starting at 1 (example: success).
Response Field Label <pluginType>.webServices.<webServiceMethod>.return<n>.label The label for the nth response field, defaults to the naturally capitalized key if not present.
Response Field Type <pluginType>.webServices.<webServiceMethod>.return<n>.type The type for the nth response field, defaults to String if not present.
Response Field Description <pluginType>.webServices.<webServiceMethod>.return<n>.help The description or help text for the nth response field.

As an example, suppose that a web service method called "doSomething" exists on a plugin type named "MyExamplePlugin". This web service expects two URL parameters: id, an integer, and action, a string. The response body consists of a JSON object with two fields: success, a boolean value, and messages, a list of strings. The following messages would serve to generate helpful documentation:

messages.properties
------------------------------------

# web service messages
myExamplePlugin.webServices.doSomething.label=Do Something Important
myExamplePlugin.webServices.doSomething.help=This web service does something important with the input parameters.
# parameters
myExamplePlugin.webServices.doSomething.parameter1.key=id
myExamplePlugin.webServices.doSomething.parameter1.label=ID
myExamplePlugin.webServices.doSomething.parameter1.type=Integer
myExamplePlugin.webServices.doSomething.parameter1.help=The identifier of an object
myExamplePlugin.webServices.doSomething.parameter2.key=action
myExamplePlugin.webServices.doSomething.parameter2.label=Action # same as the default would be
myExamplePlugin.webServices.doSomething.parameter2.type=String # same as the default would be
myExamplePlugin.webServices.doSomething.parameter2.help=The action to perform

# response fields
myExamplePlugin.webServices.doSomething.return1.key=success
myExamplePlugin.webServices.doSomething.return1.label=Success # same as the default would be
myExamplePlugin.webServices.doSomething.return1.type=Boolean
myExamplePlugin.webServices.doSomething.return1.help=True if the request succeeded, false otherwise
myExamplePlugin.webServices.doSomething.return1.key=messages
myExamplePlugin.webServices.doSomething.return1.label=Error Messages
myExamplePlugin.webServices.doSomething.return1.type=List of Strings
myExamplePlugin.webServices.doSomething.return1.help=Error messages describing the reason why success is false.

Note that if the first URL parameter key is id, the listed resource URLs will include the optional URL with the id parameter inline, such as /rest/plugins/<pluginId>/services/<webService>/<id>. Therefore, it is recommended to use id as parameter 1 if the web service expects a parameter with that key.

Related Topics 

© 2015 Adaptive Computing