Simple plugin system
Gaelyk supports a plugin system which helps you modularize your applications and enable you to share commonalities between Gaelyk applications.
This page is about creating new plugins. The list of existing plugins can be found in the Plugins section.
What a plugin can do for you
A plugin lets you:
- provide additional groovlets and templates
- contribute new URL routes
- define and bind new variables in the binding (the "global" variables available in groovlets and templates)
- provide any kind of static content, such as JavaScript, HTML, images, etc.
- add new libraries (ie. additional JARs)
- and more generally, let you do any initialization at the startup of your application
Possible examples of plugins can:
- provide a "groovy-fied" integration of a third-party library, like nicer JSON support
- create a reusable CRUD administration interface on top of the datastore to easily edit content of all your Gaelyk applications
- install a shopping cart solution or payment system
- setup a lightweight CMS for editing rich content in a rich-media application
- define a bridge with Web 2.0 applications (Facebook Connect, Twitter authentication)
- and more...
Anatomy of a Gaelyk plugin
Binary plugins
Binary plugin is a JAR file added to your WEB-INF/lib
folder or more preferable declared as an dependency in your build file e.g. the
Gradle one shipped with the template project so the build system can handle dependencies for you. Binary plugins can add groovlets, templates
and even static content (with the help of the resources plugin) to your Gaelyk application
without cluttering it with many additional files. Binary plugins are installed automatically, there is no other action needed to start using them.
They can be removed easily as well. You just delete the JAR file from the WEB-INF/lib
folder or remove the dependency from your build file.
To let Gaelyk application to recognize your binary plugin, you'll have to create a plugin descriptor which
will allow you to define new binding variables, new routes, and any initialization code your plugin may need on application startup.
This plugin descriptor must extend groovyx.gaelyk.plugins.PluginBaseScript
manually.
Place your plugin descriptor's code inside the method run
.
Binary plugins are resolved using Java's ServiceLoader so you also
need to place binary name of your plugin descriptor e.g. com.example.plugin.ExamplePlugin
in META-INF/services/groovyx.gaelyk.plugins.PluginBaseScript
file inside your JAR.
If you are not using templates you can just place your groovlets into your Groovy source folder to let the compiler compile them.
Otherwise you will need Gradle Gaelyk Plugin v.0.3.1 and higher to help you package the plugin. As long as your structure follow Gaelyk convetions
you only need to call jar
task of the plugin. You may also need to enable the jar
task placing jar.enabled=true
into your build file.
Hierarchy
Note:Don't forget to declare packages for Groovlets otherwise they will be placed in wrong destination folders!
Using standard Gradle layout, your plugin project should look like:
src/main // Gradle main sources set | +-- groovy // groovy source folder | | | +-- com/example/plugin // plugin package | | | +-- myGroovletInSrc.groovy // groovlet placed in source folder | | | +-- ExamplePlugin.groovy // plugin descriptor | +-- resources // resources folder | | | +-- META-INF/services // services folder used by ServiceLoader | | | | | +-- groovyx.gaelyk.plugins.PluginBaseScript // service implementation descriptor with | | // single line com.example.plugin.ExamplePlugin | | | +-- resources // your static resources (requires resources plugin) | | | +-- main.css | +-- webapp // web application folder | +-- com/example/plugin // package-like folder to prevent name clash | | | +-- myTemlate.gtpl // your Groovy template | +-- WEB-INF/groovy // your regular groovlets | +-- com/example/plugin // use packages to prevent name clash | +-- myRegularGroovlet.groovy // your groovlet
The same project packaged will look like:
plugin.jar // JAR file | +-- com/example/plugin // your plugin package | | | +-- myGroovletInSrc.class // compiled groovlet | | | +-- myRegularGroovlet.class // compiled groovlet | | | +-- $gtpl$myTemplate.class // template compiled with $gtpl$prefix | | | +-- ExamplePlugin.class // compiled plugin descriptor | +-- META-INF/services // services folder used by ServiceLoader | | | +-- groovyx.gaelyk.plugins.PluginBaseScript // service implementation descriptor with | // single line com.example.plugin.ExamplePlugin | +-- resources // your bundled static resources | +-- main.css
Exploded plugins
Exploded plugin is actually just some content you'll drop in your war/
folder, at the root of your Gaelyk application!
This is why you can add all kind of static content, as well as groovlets and templates, or additional JARs in WEB-INF/lib
.
Furthermore, plugins don't even need to be external plugins that you install in your applications,
but you can just customize your application by using the conventions and capabilities offered by the plugin system.
Then, you really just need to have /WEB-INF/plugins.groovy
referencing
/WEB-INF/plugins/myPluginDescriptor.groovy
, your plugin descriptor.
To let Gaelyk application to recognize your exploded plugin, you'll have to create a plugin descriptor in WEB-INF/plugins
.
Also, this plugin descriptor script should be referenced in the plugins.groovy
script in WEB-INF/
Hierarchy
The content of exploded plugin would look something like the following hierarchy:
/ +-- war | +-- someTemplate.gtpl // your templates | +-- css +-- images // your static content +-- js | +-- WEB-INF | +-- plugins.groovy // the list of plugins | // descriptors to be installed +-- plugins | | | +-- myPluginDescriptor.groovy // your plugin descriptor | +-- groovy | | | +-- myGroovlet.groovy // your groovlets | +-- includes | | | +-- someInclude.gtpl // your includes | +-- classes // compiled classes | +-- lib | +-- my-additional-dependency.jar // your JARs
The bare minimum to have an exploded plugin in your application is to have a plugin descriptor,
like /WEB-INF/plugins/myPluginDescriptor.groovy
in this example,
that is referenced in /WEB-INF/plugins.groovy
.
Developing a plugin is just like developing a normal Gaelyk web application. Follow the usual conventions and describe your plugin in the plugin descriptor. Then afterwards, package it, share it, and install it in your applications.
The plugin descriptor
The plugin descriptor is where you'll be able to tell the Gaelyk runtime to:
- add new variables in the binding of groovlets and templates
- add new routes to the URL routing system
- define before / after request actions
- and do any initialization you may need
Here's what a plugin descriptor can look like:
// add imports you need in your descriptor import net.sf.json.* import net.sf.json.groovy.* // add new variables in the binding binding { // a simple string variable jsonLibVersion = "2.3" // an instance of a class of a third-party JAR json = new JsonGroovyBuilder() } // add new routes with the usual routing system format routes { // always specify the first route index to prevent routes conflict // by setting the value to negative number the routes from this plugin // will have precedence before the ones in routes.groovy script startRoutingAt -1500 get "/json", forward: "/json.groovy" } before { log.info "Visiting ${request.requestURI}" binding.uri = request.requestURI request.message = "Hello" } after { log.info "Exiting ${request.requestURI}" log.info "groovlet returned $result from its exection" } // any other initialization code you'd need // ...
All of the GAE specific variables are available in plugin descriptors as implicit variables. You can also access ServletContext instance of your application using the servletContext
implicit variable.
Inside the binding
closure block, you just assign a value to a variable.
And this variable will actually be available within your groovlets and templates as implicit variables.
So you can reference them with ${myVar}}
in a template,
or use myVar
directly inside a groovlet, without having to declare or retrieve it in any way.
Note: a plugin may overwrite the default Gaelyk variable binding, or variable bindings defined by the previous plugin in the initialization chain. In the plugin usage section, you'll learn how to influence the order of loading of plugins.Inside the
routes
closure block, you'll put the URL routes following the same syntax
as the one we explained in the URL routing section.
Note: Contrary to binding variables, the first route that matches is the one which is chosen. This means a plugin cannot overwrite the existing application routes, or routes defined by previous plugins in the chain.
Important: If your plugins contribute routes, make sure your application has also configured the routes filter,
as well as defined a WEB-INF/routes.groovy
script, otherwise no plugin routes will be present.
In the before
and after
blocks,
you can access the request
, response
, log
, and binding
variables.
The logger name is of the form gaelyk.plugins.myPluginName
.
The binding
variables allows you to update the variables
that are put in the binding of Groovlets and templates.
In after
block you can access the result of groovlet execution as result
variable.
Wherever in your plugin descriptor, you can put any initialization code you may need in your plugin.
Important: The plugins are loaded once, as soon as the first request is served. So your initialization code, adding binding variables and routes, will only be done once per application load. Knowing that Google App Engine can load and unload apps depending on traffic, this is important to keep in mind as well.
Using a plugin
This section applies only on exploded plugins. Binary plugins are installed automatically.
If you recall, we mentioned the plugins.groovy
script.
This is a new script since Gaelyk 0.4, that lives alongside the routes.groovy
script
(if you have one) in /WEB-INF
.
If you don't have a plugins.groovy
script, obviously, no exploded plugin will be installed —
or at least none of the initialization and configuration done in the various plugin descriptors will ever get run.
This plugins.groovy
configuration file just lists the plugins you have installed and want to use.
An example will illustrate how you reference a plugin:
install jsonPlugin
Note: For each plugin, you'll have aninstall
method call, taking as parameter the name of the plugin. This name is actually just the plugin descriptor script name. In this example, this means Gaelyk will loadWEB-INF/plugins/jsonPlugin.groovy
.
As mentioned previously while talking about the precedence rules, the order with which the plugins are loaded may have an impact on your application or other plugins previously installed and initialized. But hopefully, such conflicts shouldn't happen too often, and this should be resolved easily, as you have full control over the code you're installing through these plugins to make the necessary amendments should there be any.
When you are using two plugins with before / after request actions,
the order of execution of these actions also depends on the order in which you installed your plugins.
For example, if you have installed pluginOne
first and pluginTwo
second,
here's the order of execution of the actions and of the Groovlet or template:
- pluginOne's before action
- pluginTwo's before action
- execution of the request
- pluginTwo's after action
- pluginOne's after action
How to distribute and deploy a plugin
The best way to distribute your binary plugin is through the Maven Central repository. You can use Sonatype OSS repository to get your plugins there.
If you want to share an explodeded plugin you've worked on, you just need to zip everything that constitutes the plugin.
Then you can share this zip, and someone who wishes to install it on his application will just need to unzip it
and pickup the various files of that archive and stick them up in the appropriate directories
in his/her Gaelyk war/
folder, and reference that plugin, as explained in the previous section.
The best way how to share your exploded plugin is by using the plugin catalogue. Fill the form and wait until the plugin is approved.