12. Plug-ins

Griffon provides a number of extension points that allow you to extend anything from the command line interface to the runtime configuration engine. The following sections detail how to go about it.

12.1 Creating and Installing Plug-ins

Creating Plug-ins

Creating a Griffon plugin is a simple matter of running the command:

griffon create-plugin [PLUGIN NAME]

This will create a plugin project for the name you specify. Say for example you run griffon create-plugin example. This would create a new plugin project called example.

The structure of a Griffon plugin is exactly the same as a regular Griffon project's directory structure, except that in the root of the plugin directory you will find a plugin Groovy file called the "plugin descriptor".

The plugin descriptor itself ends with the convention GriffonPlugin and is found in the root of the plugin project. For example:

class ExampleGriffonPlugin {
   def version = 0.1

… }

All plugins must have this class in the root of their directory structure to be valid. The plugin class defines the version of the plugin and optionally various hooks into plugin extension points (covered shortly).

You can also provide additional information about your plugin using several special properties:

Here is an example from Swing plugin :

class SwingGriffonPlugin {
    String version = '0.9.5'
    String griffonVersion = '0.9.5 > *'
    Map dependsOn = [:]
    List pluginIncludes = []
    String license = 'Apache Software License 2.0'
    List toolkits = ['swing']
    List platforms = []
    String documentation = ''
    String source = 'https://github.com/griffon/griffon-swing-plugin'

List authors = [ [ name: 'Andres Almiray', email: 'aalmiray@yahoo.com' ] ] String title = 'Enables Swing support' String description = ''' Enables the usage of Swing based components in Views.

Usage ---- This plugin enables the usage of the following nodes inside a View.

...

Configuration ------------- There's no special configuration for this plugin.

[1]: http://groovy.codehaus.org/Swing+Builder ''' }

Installing & Distributing Plugins

To distribute a plugin you need to navigate to its root directory in a terminal window and then type:

griffon package-plugin

This will create a zip file of the plugin starting with griffon- then the plugin name and version. For example with the example plugin created earlier this would be griffon-example-0.1.zip. The package-plugin command will also generate plugin.json file which contains machine-readable information about plugin's name, version, author, and so on.

Once you have a plugin distribution file you can navigate to a Griffon project and type:

griffon install-plugin /path/to/plugin/griffon-example-0.1.zip

If the plugin is hosted on a remote HTTP server you can also do:

griffon install-plugin http://myserver.com/plugins/griffon-example-0.1.zip

Releasing Plugins into a Griffon Artifact Repository

To release a plugin call the release-plugin command while inside the plugin project. If no repository flag is specified then the default artifact repository (griffon-central) will be used. For quick testing purposes you can publish a release to griffon-local (which is always available) by issuing the following command

griffon install-plugin --repository=griffon-local

The aforementioned steps can be applied to archetypes too, you just need to change the command names from package-plugin to package-archetype; from install-plugin to install-archetype; from release-plugin to release-archetype.

Should you decide to become a plugin/archetype author and wish to publish your artifacts to the Griffon Central repository then you must follow these steps:

griffon.artifact.repositories = [
    'griffon-central': [
        username: 'yourUsername',
        password: 'yourPassword'
    ]
]

12.2 Artifact Repositories

There are 3 types of plugin repositories: local, remote and legacy. Artifact repositories can be either configured locally to a project (inside griffon-app/conf/BuildConfig) or globally to all projects (inside $USER_HOME/.griffon/settings.groovy),

Local Artifact Repositories

This type of repository is file based and can be hosted anywhere in the file system, even on shared folders over the network. Local repositories makes it easier to share snapshot releases among team mates as the network latency should be smaller. Their configuration requires but one parameter to be specified: the path where the artifacts will be placed. Here's a sample configuration for a local repository named 'my-local-repo'.

griffon.artifact.repositories = [
    'my-local-repo': [
        type: 'local',
        path: '/usr/local/share/griffon/repository'
    ]
]

There's a local repository available to you at all times. It's name is 'griffon-local' and it's default path is $USER_HOME/.griffon/repository. This repository is the default place where downloaded plugins will be installed for speeding up retrievals at a later time.

Remote Artifact Repositories

This type of repository allows developers to publish releases via SCP or web. The repository is handled by a Grails application whose code is freely available at https://github.com/griffon/griffon-artifact-portal .

This code has been released under Apache Software License 2.0. Follow the instructions found in the README to run your own artifact portal. Configuring a remote repository requires a different set of properties than those exposed by local repositories. For example, if your organization is running a remote artifact repository located at http://acme.com:8080/portal then use the following configuration

griffon.artifact.repositories = [
    'acme': [
        type: 'remote',
        url: 'http://acme.com:8080/portal'
    ]
]

You may specify additional properties such as

griffon.artifact.repositories = [
    'acme': [
        type: 'remote',
        url: 'http://acme.com:8080/portal',
        username: 'wallace',
        password: 'gromit',
        port: 2345,
        timeout: 60
    ]
]

Where the following defaults apply

You may leave both username and password out however you will be asked for this credentials when publishing a release to this particular repository. Adding your credentials in the configuration avoids typing them when releasing artifacts.

Legacy Artifact Repository

This is a very special type of repository that exists only for backward compatibility during the migration of the old Griffon plugin repository to the new infrastructure in http://artifacts.griffon-framework.org .

There are no configuration options for this repository, neither you can publish a release to it; it's effectively read-only.

12.3 Understanding a Plugins Structure

As mentioned previously, a plugin is merely a project with an structure similar to a Griffon application with the addition of a contained plugin descriptor. However when installed, the structure of a plugin differs slightly. For example, take a look at this plugin directory structure:

+ griffon-app
     + controllers
     + models
     + views
     …
 + lib
 + src
     + main
     + cli
     + doc

Essentially when a plugin is installed into a project, the contents of the zip file will go into a directory such as plugins/example-1.0/. Plugin contents will not be copied into the main source tree. A plugin never interferes with a project's primary source tree.

12.4 Providing Basic Artefacts

Adding a new Script

A plugin can add a new script simply by providing the relevant Gant script within the scripts directory of the plugin:

+ MyPlugin.groovy
   + scripts     <-- additional scripts here
   + griffon-app
        + controllers
        + models
        + etc.
    + lib

Adding a new Controller, Model, View or Service

A plugin can add a new MVC Group, service or whatever by simply creating the relevant file within the griffon-app tree. However you'll need to create an Addon in order to package them properly.

+ ExamplePlugin.groovy
   + scripts
   + griffon-app
        + controllers  <-- additional controllers here
        + services <-- additional services here
        + etc.  <-- additional XXX here
    + lib

12.5 Hooking into Build Events

Post-Install Configuration and Participating in Upgrades

Griffon plugins can do post-install configuration and participate in application upgrade process (the upgrade command). This is achieved via two specially named scripts under scripts directory of the plugin - _Install.groovy and _Upgrade.groovy.

_Install.groovy is executed after the plugin has been installed and _Upgrade.groovy is executed each time the user upgrades his application with upgrade command.

These scripts are normal Gant scripts so you can use the full power of Gant. An addition to the standard Gant variables is the pluginBasedir variable which points at the plugin installation basedir.

As an example the below _Install.groovy script will create a new directory type under the griffon-app directory and install a configuration template:

ant.mkdir(dir:"${basedir}/griffon-app/jobs")
ant.copy(file:"${pluginBasedir}/src/samples/SamplePluginConfiguration.groovy",
         todir:"${basedir}/griffon-app/conf")

// To access Griffon home you can use following code: // ant.property(environment:"env") // griffonHome = ant.antProject.properties."env.GRIFFON_HOME"

Scripting events

It is also possible to hook into command line scripting events through plugins. These are events triggered during execution of Griffon target and plugin scripts.

For example, you can hook into status update output (i.e. "Tests passed", "Server running") and the creation of files or artifacts.

A plugin merely has to provide an _Events.groovy script to listen to the required events. Refer the documentation on Hooking into Events for further information.

12.6 Addons

Understanding Addons

Addons are a plugin's best friend. While plugins can only contribute build-time artifacts (such as scripts) and participate on build events, addons may contribute runtime artifacts (such as MVC Groups or services) and participate on application events.

Often times whenever you'd like to package a reusable runtime artifact you'd have to create an Addon as well.

Addon responsibilities

Addons may contribute any of the following to your application: MVC Groups and application event handlers. They can also contribute the following to the CompositeBuilder: factories, methods, properties and FactoryBuilderSupport delegates (attribute, preInstantiate, postInstantiate, postNodeCompletion).

Addons are created using a template that suggests all of the properties and methods you can use configure. The complete list follows:

Configuring Addons

This task is done automatically for you when you package an addon inside a plugin. The plugin's _Install and _Uninstall scripts will make sure that griffon-app/conf/Builder.groovy stays up to date. When you install a plugin that contains an addon you'll notice that Builder.groovy may get updated with a line similar to the next one

root.'CustomGriffonAddon'.addon=true

This means that all factories, methods and props defined on the Addon will be available to View scripts. However you need to explicitly specify which contributions should be made to other MVC members. You can list them one by one, or use a special group qualified by '*'. In recent releases of Griffon the default configuration is assumed meaning you won't see any changes in the Builder.groovy file. You can still apply modifications as explained below.

The following snippet shows how to configure an Addon to contribute all of its methods to Controllers, and all of its contributions to Models.

root.'CustomGriffonAddon'.controller='*:methods'
root.'CustomGriffonAddon'.model='*'

The special groups are: '*', '*:factories', '*:methods', '*:props'

Should you encounter a problem with duplicate node names you can change the default prefix (root) of the addon to something more suitable to your needs. All nodes contributed by the addon will now be accessible using that prefix. Here's an example

nx.'CustomGriffonAddon'.addon=true

Assuming CustomGriffonAddon is defined as follows

class CustomGriffonAddon {
    def factories = [
        button: com.acme.CustomButton
    ]
}

Then instances of CustomButtom can be obtained by using nxbutton, whereas regular instances of JButton will be accessible with button.

12.7 Understanding Plugin Order

Controlling Plugin Dependencies

Plugins often depend on the presence of other plugins and can also adapt depending on the presence of others. To cover this, a plugin can define a dependsOn property. For example, take a look at this snippet from the Griffon Clojure plugin:

class ClojureGriffonPlugin {
   def version = 0.3
   def dependsOn = ["lang-bridge": "0.2.1"]

}

As the above example demonstrates the Clojure plugin is dependent on the presence of a single plugin: the lang-bridge plugin.

Essentially the dependencies will be loaded first and then the Clojure plugin. If all dependencies do not load, then the plugin will not load.

The dependsOn property also supports a mini expression language for specifying version ranges. A few examples of the syntax can be seen below:

def dependsOn = [foo:"* > 1.0"]
def dependsOn = [foo:"1.0 > 1.1"]
def dependsOn = [foo:"1.0 > *"]

When the wildcard * character is used it denotes "any" version. The expression syntax also excludes any suffixes such as -BETA, -ALPHA etc. so for example the expression "1.0 > 1.1" would match any of the following versions:

Controlling Addon Load Order

Addons will be loaded in the order determined by the dependencies set forth in their containing plugins. Using dependsOn establishes a "hard" dependency. Any addons provided by the dependencies will be added first to the builder configuration file when installed.

12.8 CLI Dependencies

Plugins can provide compile time classes that should not be bundled with runtime classes (i.e, addon sources). Sources and resources placed under $basedir/src/cli will be automatically compiled and packaged into a jar whose name matches griffon-${plugin.name}-${plugin.version}-compile.jar. A typical use case for these type of classes is a custom AST transformation that should be run during compile time but not at runtime.