12 Plug-ins - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: 1.3.9
Table of Contents
12 Plug-ins
Grails is first and foremost a web application framework, but it is also a platform. By exposing a number of extension points that let you extend anything from the command line interface to the runtime configuration engine, Grails can be customised to suit almost any needs. To hook into this platform, all you need to do is create a plugin.Extending the platform may sound complicated, but plugins can range from trivially simple to incredibly powerful. If you know how to build a Grails application, you'll know how to create a plugin for sharing a data model or some static resources.12.1 Creating and Installing Plug-ins
Creating Plugins
Creating a Grails plugin is a simple matter of running the command:grails create-plugin [PLUGIN NAME]
grails create-plugin example
. This would create a new plugin project called example
.The structure of a Grails plugin is exactly the same as a regular Grails project's directory structure, except that in the root of the plugin directory you will find a plugin Groovy file called the "plugin descriptor".Being a regular Grails project has a number of benefits in that you can immediately get going testing your plugin by running:grails run-app
GrailsPlugin
and is found in the root of the plugin project. For example:class ExampleGrailsPlugin { def version = 0.1 … }
title
- short one sentence description of your pluginversion
- The version of your problem. Valid versions are for example "0.1", "0.2-SNAPSHOT", "0.1.4" etc.grailsVersion
- The version of version range of Grails that the plugin supports. eg. "1.1 > *"author
- plug-in author's nameauthorEmail
- plug-in author's contact e-maildescription
- full multi-line description of plug-in's featuresdocumentation
- URL where plug-in's documentation can be found
class QuartzGrailsPlugin { def version = "0.1" def grailsVersion = "1.1 > *" def author = "Sergey Nebolsin" def authorEmail = "nebolsin@gmail.com" def title = "This plugin adds Quartz job scheduling features to Grails application." def description = ''' Quartz plugin allows your Grails application to schedule jobs to be executed using a specified interval or cron expression. The underlying system uses the Quartz Enterprise Job Scheduler configured via Spring, but is made simpler by the coding by convention paradigm. ''' def documentation = "http://grails.org/Quartz+plugin" … }
Installing & Distributing Plugins
To distribute a plugin you need to navigate to its root directory in a terminal window and then type:grails package-plugin
grails-
then the plugin name and version. For example with the example plug-in created earlier this would be grails-example-0.1.zip
. The package-plugin
command will also generate plugin.xml
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 Grails project and type:grails install-plugin /path/to/plugin/grails-example-0.1.zip
grails install-plugin http://myserver.com/plugins/grails-example-0.1.zip
Notes on excluded Artefacts
Although the create-plugin command creates certain files for you so that the plug-in can be run as a Grails application, not all of these files are included when packaging a plug-in. The following is a list of artefacts created, but not included by package-plugin:grails-app/conf/DataSource.groovy
grails-app/conf/UrlMappings.groovy
build.xml
- Everything within
/web-app/WEB-INF
WEB-INF
it is recommended you use the _Install.groovy
script (covered later), which is executed when a plug-in is installed, to provide such artefacts. In addition, although UrlMappings.groovy
is excluded you are allowed to include a UrlMappings
definition with a different name, such as FooUrlMappings.groovy
.Specifying Plugin Locations
An application can load plugins from anywhere on the file system, even if they have not been installed. Simply add the location of the (unpacked) plugin to the application'sgrails-app/conf/BuildConfig.groovy
file:// Useful to test plugins you are developing. grails.plugin.location.jsecurity = "/home/dilbert/dev/plugins/grails-jsecurity"// Useful for modular applications where all plugins and // applications are in the same directory. grails.plugin.location.'grails-ui' = "../grails-grails-ui"
- You are developing a plugin and want to test it in a real application without packaging and installing it first.
- You have split an application into a set of plugins and an application, all in the same "super-project" directory.
Global plugins
Plugins can also be installed globally for all applications for a particular version of Grails using the-global
flag, for example:grails install-plugin webtest -global
grails.global.plugins.dir
setting in BuildConfig.groovy
.
12.2 Plugin Repositories
Distributing Plugins in the Grails Central Plugins Repository
The preferred way of plugin distribution is to publish your under Grails Plugins Repository. This will make your plugin visible to the list-plugins command:grails list-plugins
grails plugin-info [plugin-name]
If you have created a Grails plugin and want it to be hosted in the central repository take a look at the wiki page , which details how to go about releasing your plugin in the repository.When you have access to the Grails Plugin repository to release your plugin you simply have to execute the release-plugin command:
grails release-plugin
Configuring Additional Repositories
The way in which you configure repositories in Grails differs between Grails versions. For version of Grails 1.2 and earlier please refer to the Grails 1.2 documentation on the subject. The following sections cover Grails 1.3 and above.Grails 1.3 and above use Ivy under the hood to resolve plugin dependencies. The mechanism for defining additional plugin repositories is largely the same as defining repositories for JAR dependencies. For example you can define a remote Maven repository that contains Grails plugins using the following syntax ingrails-app/conf/BuildConfig.groovy
:repositories {
mavenRepo "http://repository.codehaus.org"
}
grailsRepo
method:repositories {
grailsRepo "http://myserver/mygrailsrepo"
}
repositories { grailsCentral() }
repositories {
grailsRepo "http://myserver/mygrailsrepo"
grailsCentral()
}
def sshResolver = new SshResolver(user:"myuser", host:"myhost.com") sshResolver.addArtifactPattern( "/path/to/repo/grails-[artifact]/tags/LATEST_RELEASE/grails-[artifact]-[revision].[ext]") sshResolver.latestStrategy = new org.apache.ivy.plugins.latest.LatestTimeStrategy() sshResolver.changingPattern = ".*SNAPSHOT" sshResolver.setCheckmodified(true)
Publishing to Maven Compatible Repositories
In general it is recommended for Grails 1.3 and above to use standard Maven-style repositories to self host plugins. The benefits of doing so include the ability for existing tooling and repository managers to interpret the structure of a Maven repository. In addition Maven compatible repositories are not tied to SVN as Grails repositories are.In order to publish a plugin to a Maven repository you need to use the Maven publisher plugin. Please refer to the section of the Maven deployment user guide on the subject.Publishing to Grails Compatible Repositories
To publish a Grails plugin to a Grails compatible repository you specify thegrails.plugin.repos.distribution.myRepository
setting within the grails-app/conf/BuildConfig.groovy file:grails.plugin.repos.distribution.myRepository = "https://svn.codehaus.org/grails/trunk/grails-test-plugin-repo"
repository
argument of the release-plugin
command to specify the repository you want to release the plugin into:grails release-plugin -repository = myRepository
12.3 Understanding a Plug-ins Structure
As as mentioned previously, a plugin is merely a regular Grails application with a contained plug-in descriptors. However when installed, the structure of a plugin differs slightly. For example, take a look at this plugin directory structure:+ grails-app + controllers + domain + taglib etc. + lib + src + java + groovy + web-app + js + css
grails-app
directory will go into a directory such as plugins/example-1.0/grails-app
. They will not be copied into the main source tree. A plugin never interferes with a project's primary source tree.Dealing with static resources is slightly different. When developing a plugin, just like an application, all static resources can go in the web-app
directory. You can then link to static resources just like in an application (example below links to a javascript source):<g:resource dir="js" file="mycode.js" />
/js/mycode.js
. However, when the plugin is installed into an application the path will automatically change to something like /plugin/example-0.1/js/mycode.js
and Grails will deal with making sure the resources are in the right place.There is a special pluginContextPath
variable that can be used whilst both developing the plugin and when in the plugin is installed into the application to find out what the correct path to the plugin is.At runtime the pluginContextPath
variable will either evaluate to an empty string or /plugins/example
depending on whether the plugin is running standalone or has been installed in an applicationJava & Groovy code that the plugin provides within the lib and src/java
and src/groovy
directories will be compiled into the main project's web-app/WEB-INF/classes
directory so that they are made available at runtime.
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 + grails-app + controllers + services + etc. + lib
Adding a new Controller, Tag Library or Service
A plugin can add a new controller, tag libraries, service or whatever by simply creating the relevant file within thegrails-app
tree. Note that when the plugin is installed it will be loaded from where it is installed and not copied into the main application tree.+ ExamplePlugin.groovy + scripts + grails-app + controllers <-- additional controllers here + services <-- additional services here + etc. <-- additional XXX here + lib
Providing Views, Templates and View resolution
When a plugin provides a controller it may also provide default views to be rendered. This is an excellent way to modularize your application through plugins. The way it works is that Grails' view resolution mechanism will first look for the view in the application it is installed into and if that fails will attempt to look for the view within the plugin. In other words, you can override views provided by a plugin by creating corresponding GSPs in the application'sgrails-app/views
directory.For example, consider a controller called BookController
that's provided by an 'amazon' plugin. If the action being executed is list
, Grails will first look for a view called grails-app/views/book/list.gsp
then if that fails it will look for the same view relative to the plugin.Note however that if the view uses templates that are also provided by the plugin then the following syntax may be necessary:<g:render template="fooTemplate" plugin="amazon"/>
plugin
attribute, which contains the name of the plugin where the template resides. If this is not specified then Grails will look for the template relative to the application.Excluded Artefacts
Note that by default, Grails will exclude the following files from packaged plugins during the packaging process:- grails-app/conf/DataSource.groovy
- grails-app/conf/UrlMappings.groovy
- Everything under web-app/WEB-INF
web-app/WEB-INF
directory it is recommended that you modify the plugin's scripts/_Install.groovy
Gant script to install these artefacts into the target project's directory tree.In addition, the default UrlMappings.groovy
file is excluded to avoid naming conflicts, however you are free to add a UrlMappings definition under a different name which will be included. For example a file called grails-app/conf/BlogUrlMappings.groovy
is fine.Additionally the list of includes is extensible via the pluginExcludes
property:// resources that are excluded from plugin packaging
def pluginExcludes = [
"grails-app/views/error.gsp"
]
12.5 Evaluating Conventions
Before moving onto looking at providing runtime configuration based on conventions you first need to understand how to evaluated those conventions from a plug-in. Essentially every plugin has an implicitapplication
variable which is an instance of the GrailsApplication interface.The GrailsApplication
interface provides methods to evaluate the conventions within the project and internally stores references to all classes within a GrailsApplication using the GrailsClass interface.A GrailsClass
represents a physical Grails resources such as a controller or a tag library. For example to get all GrailsClass
instances you can do:for (grailsClass in application.allClasses) {
println grailsClass.name
}
GrailsApplication
instance possesses that allow you to narrow the type of artefact you are interested in. For example if you only want to controllers you can do:for (controllerClass in application.controllerClasses) {
println controllerClass.name
}
*Classes
- Retrieves all the classes for a particular artefact name. Exampleapplication.controllerClasses
.get*Class
- Retrieves a named class for a particular artefact. Exampleapplication.getControllerClass("ExampleController")
is*Class
- Returns true if the given class is of the given artefact type. Exampleapplication.isControllerClass(ExampleController.class)
GrailsClass
interface itself provides a number of useful methods that allow you to further evaluate and work with the conventions. These include:
getPropertyValue
- Gets the initial value of the given property on the classhasProperty
- Returns true if the class has the specified propertynewInstance
- Creates a new instance of this class.getName
- Returns the logical name of the class in the application without the trailing convention part if applicablegetShortName
- Returns the short name of the class without package prefixgetFullName
- Returns the full name of the class in the application with the trailing convention part and with the package namegetPropertyName
- Returns the name of the class as a property namegetLogicalPropertyName
- Returns the logical property name of the class in the application without the trailing convention part if applicablegetNaturalName
- Returns the name of the property in natural terms (eg. 'lastName' becomes 'Last Name')getPackageName
- Returns the package name
12.6 Hooking into Build Events
Post-Install Configuration and Participating in Upgrades
Grails plug-ins can do post-install configuration and participate in application upgrade process (the upgrade command). This is achieved via two specially named scripts underscripts
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 grails-app
directory and install a configuration template:ant.mkdir(dir: "${basedir}/grails-app/jobs") ant.copy(file: "${pluginBasedir}/src/samples/SamplePluginConfiguration.groovy", todir: "${basedir}/grails-app/conf")// To access Grails home you can use following code: // ant.property(environment:"env") // grailsHome = ant.antProject.properties."env.GRAILS_HOME"
Scripting events
It is also possible to hook into command line scripting events through plug-ins. These are events triggered during execution of Grails 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 artefacts.A plug-in 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.7 Hooking into Runtime Configuration
Grails provides a number of hooks to leverage the different parts of the system and perform runtime configuration by convention.Hooking into the Grails Spring configuration
First, you can hook in Grails runtime configuration by providing a property calleddoWithSpring
which is assigned a block of code. For example the following snippet is from one of the core Grails plugins that provides i18n support:import org.springframework.web.servlet.i18n.CookieLocaleResolver import org.springframework.web.servlet.i18n.LocaleChangeInterceptor import org.springframework.context.support.ReloadableResourceBundleMessageSourceclass I18nGrailsPlugin { def version = 0.1 def doWithSpring = { messageSource(ReloadableResourceBundleMessageSource) { basename = "WEB-INF/grails-app/i18n/messages" } localeChangeInterceptor(LocaleChangeInterceptor) { paramName = "lang" } localeResolver(CookieLocaleResolver) } }
messageSource
bean and a couple of other beans to manage Locale resolution and switching. It using the Spring Bean Builder syntax to do so.Participating in web.xml Generation
Grails generates theWEB-INF/web.xml
file at load time, and although plugins cannot change this file directly, they can participate in the generation of the file. Essentially a plugin can provide a doWithWebDescriptor
property that is assigned a block of code that gets passed the web.xml
as a XmlSlurper
GPathResult
.Add servlet
and servlet-mapping
Consider the below example from the ControllersPlugin
:def doWithWebDescriptor = { webXml -> def mappingElement = webXml.'servlet-mapping' def lastMapping = mappingElement[mappingElement.size()-1] lastMapping + { 'servlet-mapping' { 'servlet-name'("grails") 'url-pattern'("*.dispatch") } } }
<servlet-mapping>
element and appends Grails' servlet to the end of it using XmlSlurper's ability to programmatically modify XML using closures and blocks.
Add filter
and filter-mapping
Adding a filter with its mapping works a little differently. The location of the <filter>
element doesn't matter since order is not important, so it's simplest to insert your custom filter definition immediately after the last <context-param>
element. Order is important for mappings, but the usual approach is to add it immediately after the last <filter>
element like so:def doWithWebDescriptor = { webXml -> def contextParam = webXml.'context-param' contextParam[contextParam.size() - 1] + { 'filter' { 'filter-name'('springSecurityFilterChain') 'filter-class'(DelegatingFilterProxy.name) } } def filter = webXml.'filter' filter[filter.size() - 1] + { 'filter-mapping'{ 'filter-name'('springSecurityFilterChain') 'url-pattern'('/*') } } }
def doWithWebDescriptor = { webXml ->
... // Insert the Spring Security filter after the Spring
// character encoding filter.
def filter = webXml.'filter-mapping'.find {
it.'filter-name'.text() == "charEncodingFilter"
} filter + {
'filter-mapping'{
'filter-name'('springSecurityFilterChain')
'url-pattern'('/*')
}
}
}
Doing Post Initialisation Configuration
Sometimes it is useful to be able do some runtime configuration after the Spring ApplicationContext has been built. In this case you can define adoWithApplicationContext
closure property.class SimplePlugin { def name="simple" def version = 1.1 def doWithApplicationContext = { appCtx -> SessionFactory sf = appCtx.getBean("sessionFactory") // do something here with session factory } }
12.8 Adding Dynamic Methods at Runtime
The Basics
Grails plugins allow you to register dynamic methods with any Grails managed or other class at runtime. New methods can only be added within adoWithDynamicMethods
closure of a plugin.For Grails managed classes like controllers, tag libraries and so forth you can add methods, constructors etc. using the ExpandoMetaClass mechanism by accessing each controller's api:http://groovy.codehaus.org/api/groovy/lang/MetaObjectProtocol.html:class ExamplePlugin {
def doWithDynamicMethods = { applicationContext ->
application.controllerClasses.each { controllerClass ->
controllerClass.metaClass.myNewMethod = {-> println "hello world" }
}
}
}
myNewMethod
to each controller.
Alternatively, if you know before hand the class you wish the add a method to you can simple reference that classes metaClass
property:class ExamplePlugin { def doWithDynamicMethods = { applicationContext -> String.metaClass.swapCase = {-> def sb = new StringBuffer() delegate.each { sb << (Character.isUpperCase(it as char) ? Character.toLowerCase(it as char) : Character.toUpperCase(it as char)) } sb.toString() } assert "UpAndDown" == "uPaNDdOWN".swapCase() } }
swapCase
to java.lang.String
directly by accessing its metaClass
.Interacting with the ApplicationContext
ThedoWithDynamicMethods
closure gets passed the Spring ApplicationContext
instance. This is useful as it allows you to interact with objects within it. For example if you where implementing a method to interact with Hibernate you could use the SessionFactory
instance in combination with a HibernateTemplate
:
import org.springframework.orm.hibernate3.HibernateTemplateclass ExampleHibernatePlugin { def doWithDynamicMethods = { applicationContext -> application.domainClasses.each { domainClass -> domainClass.metaClass.static.load = { Long id-> def sf = applicationContext.sessionFactory def template = new HibernateTemplate(sf) template.load(delegate, id) } } } }
class MyConstructorPlugin { def doWithDynamicMethods = { applicationContext ->
application.domainClasses.each { domainClass ->
domainClass.metaClass.constructor = {->
return applicationContext.getBean(domainClass.name)
}
} }
}
12.9 Participating in Auto Reload Events
Monitoring Resources for Changes
Often it is valuable to monitor resources for changes and then reload those changes when they occur. This is how Grails implements advanced reloading of application state at runtime. For example, consider the below simplified snippet from theServicesPlugin
that Grails comes with:class ServicesGrailsPlugin { … def watchedResources = "file:./grails-app/services/*Service.groovy" … def onChange = { event -> if(event.source) { def serviceClass = application.addServiceClass(event.source) def serviceName = "${serviceClass.propertyName}" def beans = beans { "$serviceName"(serviceClass.getClazz()) { bean -> bean.autowire = true } } if(event.ctx) { event.ctx.registerBeanDefinition(serviceName, beans.getBeanDefinition(serviceName)) } } } }
watchedResources
as either a String or a List of strings that contain either the references or patterns of the resources to watch. If the watched resources is a Groovy file, when it is changed it will automatically be reloaded and passed into the onChange
closure inside the event
object.The event
object defines a number of useful properties:
event.source
- The source of the event which is either the reloaded class or a Spring Resourceevent.ctx
- The SpringApplicationContext
instanceevent.plugin
- The plugin object that manages the resource (Usually this)event.application
- TheGrailsApplication
instance
ApplicationContext
and so forth based on the conventions, etc. In the "Services" example above, a new services bean is re-registered with the ApplicationContext
when one of the service classes changes.Influencing Other Plugins
As well as being able to react to changes that occur when a plugin changes, sometimes one plugin needs to "influence" another plugin.Take for example the Services & Controllers plugins. When a service is reloaded, unless you reload the controllers too, problems will occur when you try to auto-wire the reloaded service into an older controller Class.To get round this, you can specify which plugins another plugin "influences". What this means is that when one plugin detects a change, it will reload itself and then reload all influenced plugins. See this snippet from theServicesGrailsPlugin
:def influences = ['controllers']
Observing other plugins
If there is a particular plugin that you would like to observe for changes but not necessary watch the resources that it monitors you can use the "observe" property:def observe = ["controllers"]
def observe = ["*"]
log
property back to any artefact that changes while the application is running.
12.10 Understanding Plug-in Load Order
Controlling Plug-in Dependencies
Plug-ins 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 two properties. The first is calleddependsOn
. For example, take a look at this snippet from the Grails Hibernate plugin:class HibernateGrailsPlugin { def version = 1.0 def dependsOn = [dataSource:1.0, domainClass:1.0, i18n:1.0, core: 1.0]}
dataSource
plugin, The domainClass
plugin, the i18n
plugin and the core
plugin.Essentially the dependencies will be loaded first and then the Hibernate 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 > *"]
- 1.1
- 1.0
- 1.0.1
- 1.0.3-SNAPSHOT
- 1.1-BETA2
Controlling Load Order
UsingdependsOn
establishes a "hard" dependency in that if the dependency is not resolved, the plugin will give up and won't load. It is possible though to have a "weaker" dependency using the loadAfter
property:def loadAfter = ['controllers']
controllers
plugin if it exists, otherwise it will just be loaded. The plugin can then adapt to the presence of the other plugin, for example the Hibernate plugin has this code in the doWithSpring
closure:if(manager?.hasGrailsPlugin("controllers")) { openSessionInViewInterceptor(OpenSessionInViewInterceptor) { flushMode = HibernateAccessor.FLUSH_MANUAL sessionFactory = sessionFactory } grailsUrlHandlerMapping.interceptors << openSessionInViewInterceptor }
OpenSessionInViewInterceptor
if the controllers
plugin has been loaded. The manager variable is an instance of the GrailsPluginManager interface and it provides methods to interact with other plugins and the GrailsPluginManager
itself from any plugin.