grails create-plugin <<PLUGIN NAME>>
17 Plugins
Version: 6.2.3-SNAPSHOT
Table of Contents
17 Plugins
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.
17.1 Creating and Installing Plugins
Creating Plugins
Creating a Grails plugin is a simple matter of running the command:
This will create a web-plugin project for the name you specify. For example running grails create-plugin example
would create a new web-plugin project called example
.
In Grails 3.0 you should consider whether the plugin you create requires a web environment or whether the plugin can be used with other profiles. If your plugin does not require a web environment then use the "plugin" profile instead of the default "web-plugin" profile:
grails create-plugin <<PLUGIN NAME>> --profile=plugin
Make sure the plugin name does not contain more than one capital letter in a row, or it won’t work. Camel case is fine, though.
Being a regular Grails project has a number of benefits in that you can immediately test your plugin by running (if the plugin targets the "web" profile):
./gradlew bootRun
Plugin projects don’t provide an index.gsp by default since most plugins don’t need it. So, if you try to view the plugin running in a browser right after creating it, you will receive a page not found error. You can easily create a grails-app/views/index.gsp for your plugin if you’d like.
|
The structure of a Grails plugin is very nearly the same as a Grails application project’s except that in the src/main/groovy
directory under the plugin package structure you will find a plugin descriptor class (a class that ends in "GrailsPlugin"). For example:
import grails.plugins.*
class ExampleGrailsPlugin extends Plugin {
...
}
All plugins must have this class under the src/main/groovy
directory, otherwise they are not regarded as a plugin. The plugin class defines metadata about 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:
-
title
- short one-sentence description of your plugin -
grailsVersion
- The version range of Grails that the plugin supports. eg. "1.2 > *" (indicating 1.2 or higher) -
author
- plugin author’s name -
authorEmail
- plugin author’s contact e-mail -
developers
- Any additional developers beyond the author specified above. -
description
- full multi-line description of plugin’s features -
documentation
- URL of the plugin’s documentation -
license
- License of the plugin -
issueManagement
- Issue Tracker of the plugin -
scm
- Source code management location of the plugin
Here is a slimmed down example from the Quartz Grails plugin:
package quartz
@Slf4j
class QuartzGrailsPlugin extends Plugin {
// the version or versions of Grails the plugin is designed for
def grailsVersion = "3.0.0.BUILD-SNAPSHOT > *"
// resources that are excluded from plugin packaging
def pluginExcludes = [
"grails-app/views/error.gsp"
]
def title = "Quartz" // Headline display name of the plugin
def author = "Jeff Brown"
def authorEmail = "[email protected]"
def description = '''\
Adds Quartz job scheduling features
'''
def profiles = ['web']
List loadAfter = ['hibernate3', 'hibernate4', 'hibernate5', 'services']
def documentation = "http://grails.org/plugin/quartz"
def license = "APACHE"
def issueManagement = [ system: "Github Issues", url: "http://github.com/grails3-plugins/quartz/issues" ]
def developers = [
[ name: "Joe Dev", email: "[email protected]" ]
]
def scm = [ url: "https://github.com/grails3-plugins/quartz/" ]
Closure doWithSpring()......
Plugin Configuration
Instead of directly accessing Grails configuration as grailsApplication.config.getProperty('mail.hostName', String)
, use a Spring Boot configuration bean (or a POJO) annotated with ConfigurationProperties annotation. Here is an example plugin configuration:
./src/main/groovy/example/MailPluginConfiguration.groovy
package example
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties(prefix = "mail")
class MailPluginConfiguration {
String hostName
int port
String from
}
You can inject the MailPluginConfiguration
bean into your bean like any other bean.
./grails-app/services/example/MailService.groovy
package example
class MailService {
MailPluginConfiguration mailPluginConfiguration
void sendMail() {
}
}
Please read the Spring Boot Externalized Configuration section for more information.
Installing Local Plugins
In order to install the Grails plugin to your local Maven, you could use Gradle Maven Publish plugin. You may also need to configure the publishing extension as:
publishing {
publications {
maven(MavenPublication) {
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
from components.java
}
}
}
Please refer to the Gradle Maven Publish plugin documentation for up-to-date information. |
To make your plugin available for use in a Grails application run the ./gradlew publishToMavenLocal
command:
./gradlew publishToMavenLocal
This will install the plugin into your local Maven cache. Then to use the plugin within an application declare a dependency on the plugin in your build.gradle
file and include mavenLocal()
in your repositories hash:
...
repositories {
...
mavenLocal()
}
...
implementation "org.grails.plugins:quartz:0.1"
In Grails 2.x plugins were packaged as ZIP files, however in Grails 3.x plugins are simple JAR files that can be added to the classpath of the IDE. |
Plugins and Multi-Project Builds
If you wish to setup a plugin as part of a multi project build then follow these steps.
Step 1: Create the application and the plugin
Using the grails
command create an application and a plugin:
$ grails create-app myapp
$ grails create-plugin myplugin
Step 2: Create a settings.gradle file
In the same directory create a settings.gradle
file with the following contents:
include "myapp", "myplugin"
The directory structure should be as follows:
PROJECT_DIR
- settings.gradle
- myapp
- build.gradle
- myplugin
- build.gradle
Step 3: Declare a project dependency on the plugin
Within the build.gradle
of the application declare a dependency on the plugin within the plugins
block:
grails {
plugins {
implementation project(':myplugin')
}
}
You can also declare the dependency within the dependencies block, however you will not get subproject reloading if you do this!
|
Step 4: Configure the plugin to enable reloading
In the plugin directory, add or modify the gradle.properties
file. A new property exploded=true
needs to be set in order for the plugin to add the exploded directories to the classpath.
Step 5: Run the application
Now run the application using the ./gradlew bootRun
command from the root of the application directory, you can use the verbose
flag to see the Gradle output:
$ cd myapp
$ ./gradlew bootRun --verbose
You will notice from the Gradle output that plugins sources are built and placed on the classpath of your application:
:myplugin:compileAstJava UP-TO-DATE
:myplugin:compileAstGroovy UP-TO-DATE
:myplugin:processAstResources UP-TO-DATE
:myplugin:astClasses UP-TO-DATE
:myplugin:compileJava UP-TO-DATE
:myplugin:configScript UP-TO-DATE
:myplugin:compileGroovy
:myplugin:copyAssets UP-TO-DATE
:myplugin:copyCommands UP-TO-DATE
:myplugin:copyTemplates UP-TO-DATE
:myplugin:processResources
:myapp:compileJava UP-TO-DATE
:myapp:compileGroovy
:myapp:processResources UP-TO-DATE
:myapp:classes
:myapp:findMainClass
:myapp:bootRun
Grails application running at http://localhost:8080 in environment: development
Notes on excluded Artefacts
Although the create-plugin command creates certain files for you so that the plugin can be run as a Grails application, not all of these files are included when packaging a plugin. The following is a list of artefacts created, but not included by package-plugin:
-
grails-app/build.gradle
(although it is used to generatedependencies.groovy
) -
grails-app/conf/application.yml
(renamed to plugin.yml) -
grails-app/conf/spring/resources.groovy
-
grails-app/conf/logback.groovy
-
Everything within
/src/test/*\*
-
SCM management files within
*\*/.svn/*\*
and*\*/CVS/*\*
Customizing the plugin contents
When developing a plugin you may create test classes and sources that are used during the development and testing of the plugin but should not be exported to the application.
To exclude test sources you need to modify the pluginExcludes
property of the plugin descriptor AND exclude the resources inside your build.gradle
file. For example say you have some classes under the com.demo
package that are in your plugin source tree but should not be packaged in the application. In your plugin descriptor you should exclude these:
// resources that should be loaded by the plugin once installed in the application
def pluginExcludes = [
'**/com/demo/**'
]
And in your build.gradle
you should exclude the compiled classes from the JAR file:
jar {
exclude "com/demo/**/**"
}
Inline Plugins in Grails 3.0
In Grails 2.x it was possible to specify inline plugins in BuildConfig
, in Grails 3.x this functionality has been replaced by Gradle’s multi-project build feature.
To set up a multi project build create an appliation and a plugin in a parent directory:
$ grails create-app myapp
$ grails create-plugin myplugin
Then create a settings.gradle
file in the parent directory specifying the location of your application and plugin:
include 'myapp', 'myplugin'
Finally add a dependency in your application’s build.gradle
on the plugin:
implementation project(':myplugin')
Using this technique you have achieved the equivalent of inline plugins from Grails 2.x.
17.2 Plugin Repositories
Distributing Plugins in the Grails Central Plugin Repository
The preferred way to distribute plugin is to publish to the official Grails Central Plugin Repository. This will make your plugin visible to the list-plugins command:
grails list-plugins
which lists all plugins that are in the central repository. Your plugin will also be available to the plugin-info command:
grails plugin-info [plugin-name]
which prints extra information about it, such as its description, who wrote, etc.
If you have created a Grails plugin and want it to be hosted in the central repository, you’ll find instructions for getting an account on the plugin portal website. |
17.3 Providing Basic Artefacts
Add Command Line Commands
A plugin can add new commands to the Grails 3.0 interactive shell in one of two ways. First, using the create-script you can create a code generation script which will become available to the application. The create-script
command will create the script in the src/main/scripts
directory:
+ src/main/scripts <-- additional scripts here
+ grails-app
+ controllers
+ services
+ etc.
Code generation scripts can be used to create artefacts within the project tree and automate interactions with Gradle.
If you want to create a new shell command that interacts with a loaded Grails application instance then you should use the create-command
command:
$ grails create-command MyExampleCommand
This will create a file called grails-app/commands/PACKAGE_PATH/MyExampleCommand.groovy
that extends ApplicationCommand:
import grails.dev.commands.*
class MyExampleCommand implements ApplicationCommand {
boolean handle(ExecutionContext ctx) {
println "Hello World"
return true
}
}
An ApplicationCommand
has access to the GrailsApplication
instance and is subject to autowiring like any other Spring bean.
You can also inform Grails to skip the execution of Bootstrap.groovy
files with a simple property in your command:
class MyExampleCommand implements ApplicationCommand {
boolean skipBootstrap = true
boolean handle(ExecutionContext ctx) {
...
}
}
For each ApplicationCommand
present Grails will create a shell command and a Gradle task to invoke the ApplicationCommand
. In the above example you can invoke the MyExampleCommand
class using either:
$ grails my-example
Or
$ gradle myExample
The Grails version is all lower case hyphen separated and excludes the "Command" suffix.
The main difference between code generation scripts and ApplicationCommand
instances is that the latter has full access to the Grails application state and hence can be used to perform tasks that interactive with the database, call into GORM etc.
In Grails 2.x Gant scripts could be used to perform both these tasks, in Grails 3.x code generation and interacting with runtime application state has been cleanly separated.
Adding a new grails-app artifact (Controller, Tag Library, Service, etc.)
A plugin can add new artifacts by creating the relevant file within the grails-app
tree.
+ grails-app
+ controllers <-- additional controllers here
+ services <-- additional services here
+ etc. <-- additional XXX here
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. 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. This means that you can override views provided by a plugin by creating corresponding GSPs in the application’s grails-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.
However 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"/>
Note the usage of the 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
By default Grails excludes the following files during the packaging process:
-
grails-app/conf/logback.groovy
-
grails-app/conf/application.yml
(renamed toplugin.yml
) -
grails-app/conf/spring/resources.groovy
-
Everything within
/src/test/*\*
-
SCM management files within
*\*/.svn/*\*
and*\*/CVS/*\*
The default UrlMappings.groovy
file is not excluded, so remove any mappings that are not required for the plugin to work. You are also free to add a UrlMappings definition under a different name which will be included. For example a file called grails-app/controllers/BlogUrlMappings.groovy
is fine.
The list of excludes is extensible with the pluginExcludes
property:
// resources that are excluded from plugin packaging
def pluginExcludes = [
"grails-app/views/error.gsp"
]
This is useful for example to include demo or test resources in the plugin repository, but not include them in the final distribution.
17.4 Evaluating Conventions
Before looking at providing runtime configuration based on conventions you first need to understand how to evaluate those conventions from a plugin. Every plugin has an implicit application
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 artifact classes within your application.
Artifacts implement the GrailsClass interface, which represents a Grails resource 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
has a few "magic" properties to narrow the type of artefact you are interested in. For example to access controllers you can use:
for (controllerClass in application.controllerClasses) {
println controllerClass.name
}
The dynamic method conventions are as follows:
-
*Classes
- Retrieves all the classes for a particular artefact name. For exampleapplication.controllerClasses
. -
get*Class
- Retrieves a named class for a particular artefact. For exampleapplication.getControllerClass("PersonController")
-
is*Class
- Returnstrue
if the given class is of the given artefact type. For exampleapplication.isControllerClass(PersonController)
The GrailsClass
interface has a number of useful methods that let you further evaluate and work with the conventions. These include:
-
getPropertyValue
- Gets the initial value of the given property on the class -
hasProperty
- Returnstrue
if the class has the specified property -
newInstance
- Creates a new instance of this class. -
getName
- Returns the logical name of the class in the application without the trailing convention part if applicable -
getShortName
- Returns the short name of the class without package prefix -
getFullName
- Returns the full name of the class in the application with the trailing convention part and with the package name -
getPropertyName
- Returns the name of the class as a property name -
getLogicalPropertyName
- Returns the logical property name of the class in the application without the trailing convention part if applicable -
getNaturalName
- Returns the name of the property in natural terms (e.g. 'lastName' becomes 'Last Name') -
getPackageName
- Returns the package name
For a full reference refer to the javadoc API.
17.5 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 overriding the doWithSpring
method from the Plugin class and returning a closure that defines additional beans. 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.ReloadableResourceBundleMessageSource
import grails.plugins.*
class I18nGrailsPlugin extends Plugin {
def version = "0.1"
Closure doWithSpring() {{->
messageSource(ReloadableResourceBundleMessageSource) {
basename = "WEB-INF/grails-app/i18n/messages"
}
localeChangeInterceptor(LocaleChangeInterceptor) {
paramName = "lang"
}
localeResolver(CookieLocaleResolver)
}}
}
This plugin configures the Grails messageSource
bean and a couple of other beans to manage Locale resolution and switching. It using the Spring Bean Builder syntax to do so.
Customizing the Servlet Environment
In previous versions of Grails it was possible to dynamically modify the generated web.xml
. In Grails 3.x there is no web.xml
file and it is not possible to programmatically modify the web.xml
file anymore.
However, it is possible to perform the most commons tasks of modifying the Servlet environment in Grails 3.x.
Adding New Servlets
If you want to add a new Servlet instance the simplest way is simply to define a new Spring bean in the doWithSpring
method:
Closure doWithSpring() {{->
myServlet(MyServlet)
}}
If you need to customize the servlet you can use Spring Boot’s ServletRegistrationBean:
Closure doWithSpring() {{->
myServlet(ServletRegistrationBean, new MyServlet(), "/myServlet/*") {
loadOnStartup = 2
}
}}
Adding New Servlet Filters
Just like Servlets, the simplest way to configure a new filter is to simply define a Spring bean:
Closure doWithSpring() {{->
myFilter(MyFilter)
}}
However, if you want to control the order of filter registrations you will need to use Spring Boot’s FilterRegistrationBean:
myFilter(FilterRegistrationBean) {
filter = bean(MyFilter)
urlPatterns = ['/*']
order = Ordered.HIGHEST_PRECEDENCE
}
Grails' internal registered filters (GrailsWebRequestFilter , HiddenHttpMethodFilter etc.) are defined by incrementing HIGHEST_PRECEDENCE by 10 thus allowing several filters to be inserted before or between Grails' filters.
|
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 a doWithApplicationContext
closure property.
class SimplePlugin extends Plugin{
def name = "simple"
def version = "1.1"
@Override
void doWithApplicationContext() {
def sessionFactory = applicationContext.sessionFactory
// do something here with session factory
}
}
17.6 Adding Methods at Compile Time
Grails 3.0 makes it easy to add new traits to existing artefact types from a plugin. For example say you wanted to add methods for manipulating dates to controllers. This can be done by defining a trait in src/main/groovy
:
package myplugin
@Enhances("Controller")
trait DateTrait {
Date currentDate() {
return new Date()
}
}
The @Enhances
annotation defines the types of artefacts that the trait should be applied to.
As an alternative to using the @Enhances
annotation above, you can implement a TraitInjector to tell Grails which artefacts you want to inject the trait into at compile time:
package myplugin
@CompileStatic
class ControllerTraitInjector implements TraitInjector {
@Override
Class getTrait() {
SomeTrait
}
@Override
String[] getArtefactTypes() {
['Controller'] as String[]
}
}
The above TraitInjector
will add the SomeTrait
to all controllers. The getArtefactTypes
method defines the types of artefacts that the trait should be applied to.
Applying traits conditionally
A TraitInjector
implementation can also implement the SupportsClassNode interface to apply traits to only those artefacts which satisfy a custom requirement.
For example, if a trait should only be applied if the target artefact class has a specific annotation, it can be done as below
package myplugin
@CompileStatic
class AnnotationBasedTraitInjector implements TraitInjector, SupportsClassNode {
@Override
Class getTrait() {
SomeTrait
}
@Override
String[] getArtefactTypes() {
['Controller'] as String[]
}
boolean supports(ClassNode classNode) {
return GrailsASTUtils.hasAnnotation(classNode, SomeAnnotation)
}
}
Above TraitInjector
will add the SomeTrait
to only those controllers which has the SomeAnnotation
declared.
The framework discovers trait injectors by way of a META-INF/grails.factories
descriptor that is in the .jar file. This descriptor is automatically generated. The descriptor generated for the code shown above would look like this:
#Grails Factories File
grails.compiler.traits.TraitInjector=
myplugin.ControllerTraitInjector,myplugin.DateTraitTraitInjector
Due to formatting issues, above code snippet includes a line break after equal sign. |
That file is generated automatically and added to the .jar file at build time. If for any reason the application defines its own grails.factories
file at src/main/resources/META-INF/grails.factories
, it is important that the trait injectors be explicitly defined in that file. The auto-generated metadata is only reliable if the application does not define its own src/main/resources/META-INF/grails.factores
file.
17.7 Adding Dynamic Methods at Runtime
The Basics
Grails plugins let you register dynamic methods with any Grails-managed or other class at runtime. This work is done in a doWithDynamicMethods
method.
Note that Grails 3.x features newer features such as traits that are usable from code compiled with CompileStatic . It is recommended that dynamic behavior is only added for cases that are not possible with traits.
|
class ExamplePlugin extends Plugin {
void doWithDynamicMethods() {
for (controllerClass in grailsApplication.controllerClasses) {
controllerClass.metaClass.myNewMethod = {-> println "hello world" }
}
}
}
In this case we use the implicit application object to get a reference to all of the controller classes' MetaClass instances and add a new method called myNewMethod
to each controller. If you know beforehand the class you wish the add a method to you can simply reference its metaClass
property.
For example we can add a new method swapCase
to java.lang.String
:
class ExamplePlugin extends Plugin {
@Override
void doWithDynamicMethods() {
String.metaClass.swapCase = {->
def sb = new StringBuilder()
delegate.each {
sb << (Character.isUpperCase(it as char) ?
Character.toLowerCase(it as char) :
Character.toUpperCase(it as char))
}
sb.toString()
}
assert "UpAndDown" == "uPaNDdOWN".swapCase()
}
}
Interacting with the ApplicationContext
The doWithDynamicMethods
closure gets passed the Spring ApplicationContext
instance. This is useful as it lets you interact with objects within it. For example if you were implementing a method to interact with Hibernate you could use the SessionFactory
instance in combination with a HibernateTemplate
:
import org.springframework.orm.hibernate3.HibernateTemplate
class ExampleHibernatePlugin extends Plugin{
void doWithDynamicMethods() {
for (domainClass in grailsApplication.domainClasses) {
domainClass.metaClass.static.load = { Long id->
def sf = applicationContext.sessionFactory
def template = new HibernateTemplate(sf)
template.load(delegate, id)
}
}
}
}
Also because of the autowiring and dependency injection capability of the Spring container you can implement more powerful dynamic constructors that use the application context to wire dependencies into your object at runtime:
class MyConstructorPlugin {
void doWithDynamicMethods()
for (domainClass in grailsApplication.domainClasses) {
domainClass.metaClass.constructor = {->
return applicationContext.getBean(domainClass.name)
}
}
}
}
Here we actually replace the default constructor with one that looks up prototyped Spring beans instead!
17.8 Participating in Auto Reload Events
Monitoring Resources for Changes
Often it is valuable to monitor resources for changes and perform some action when they occur. This is how Grails implements advanced reloading of application state at runtime. For example, consider this simplified snippet from the Grails ServicesPlugin
:
class ServicesGrailsPlugin extends Plugin {
...
def watchedResources = "file:./grails-app/services/**/*Service.groovy"
...
void onChange( Map<String, Object> event) {
if (event.source) {
def serviceClass = grailsApplication.addServiceClass(event.source)
def serviceName = "${serviceClass.propertyName}"
beans {
"$serviceName"(serviceClass.getClazz()) { bean ->
bean.autowire = true
}
}
}
}
}
First it defines 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 specify a Groovy file, when it is changed it will automatically be reloaded and passed into the onChange
closure in the event
object.
The event
object defines a number of useful properties:
-
event.source
- The source of the event, either the reloadedClass
or a SpringResource
-
event.ctx
- The SpringApplicationContext
instance -
event.plugin
- The plugin object that manages the resource (usuallythis
) -
event.application
- TheGrailsApplication
instance -
event.manager
- TheGrailsPluginManager
instance
These objects are available to help you apply the appropriate changes based on what changed. In the "Services" example above, a new service bean is re-registered with the ApplicationContext
when one of the service classes changes.
Influencing Other Plugins
In addition to reacting to changes, sometimes a plugin needs to "influence" another.
Take for example the Services and 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 around this, you can specify which plugins another plugin "influences". This means that when one plugin detects a change, it will reload itself and then reload its influenced plugins. For example consider this snippet from the ServicesGrailsPlugin
:
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"]
In this case when a controller is changed you will also receive the event chained from the controllers plugin.
It is also possible for a plugin to observe all loaded plugins by using a wildcard:
def observe = ["*"]
The Logging plugin does exactly this so that it can add the log
property back to any artefact that changes while the application is running.
17.9 Understanding Plugin Load Order
Controlling Plugin Dependencies
Plugins often depend on the presence of other plugins and can adapt depending on the presence of others. This is implemented with two properties. The first is called dependsOn
. For example, take a look at this snippet from the Hibernate plugin:
class HibernateGrailsPlugin {
def version = "1.0"
def dependsOn = [dataSource: "1.0",
domainClass: "1.0",
i18n: "1.0",
core: "1.0"]
}
The Hibernate plugin is dependent on the presence of four plugins: the dataSource
, domainClass
, i18n
and core
plugins.
The dependencies will be loaded before the Hibernate plugin and 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:
-
1.1
-
1.0
-
1.0.1
-
1.0.3-SNAPSHOT
-
1.1-BETA2
Controlling Load Order
Using dependsOn
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
and loadBefore
properties:
def loadAfter = ['controllers']
Here the plugin will be loaded after the 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 its doWithSpring
closure:
if (manager?.hasGrailsPlugin("controllers")) {
openSessionInViewInterceptor(OpenSessionInViewInterceptor) {
flushMode = HibernateAccessor.FLUSH_MANUAL
sessionFactory = sessionFactory
}
grailsUrlHandlerMapping.interceptors << openSessionInViewInterceptor
}
Here the Hibernate plugin will only register an 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.
You can also use the loadBefore
property to specify one or more plugins that your plugin should load before:
def loadBefore = ['rabbitmq']
Scopes and Environments
It’s not only plugin load order that you can control. You can also specify which environments your plugin should be loaded in and which scopes (stages of a build). Simply declare one or both of these properties in your plugin descriptor:
def environments = ['development', 'test', 'myCustomEnv']
def scopes = [excludes:'war']
In this example, the plugin will only load in the 'development' and 'test' environments. Nor will it be packaged into the WAR file, because it’s excluded from the 'war' phase. This allows development-only
plugins to not be packaged for production use.
The full list of available scopes are defined by the enum BuildScope, but here’s a summary:
-
test
- when running tests -
functional-test
- when running functional tests -
run
- for run-app and run-war -
war
- when packaging the application as a WAR file -
all
- plugin applies to all scopes (default)
Both properties can be one of:
-
a string - a sole inclusion
-
a list - a list of environments or scopes to include
-
a map - for full control, with 'includes' and/or 'excludes' keys that can have string or list values
For example,
def environments = "test"
will only include the plugin in the test environment, whereas
def environments = ["development", "test"]
will include it in both the development and test environments. Finally,
def environments = [includes: ["development", "test"]]
will do the same thing.
17.10 The Artefact API
You should by now understand that Grails has the concept of artefacts: special types of classes that it knows about and can treat differently from normal Groovy and Java classes, for example by enhancing them with extra properties and methods. Examples of artefacts include domain classes and controllers. What you may not be aware of is that Grails allows application and plugin developers access to the underlying infrastructure for artefacts, which means you can find out what artefacts are available and even enhance them yourself. You can even provide your own custom artefact types.
17.10.1 Asking About Available Artefacts
As a plugin developer, it can be important for you to find out about what domain classes, controllers, or other types of artefact are available in an application. For example, the Elasticsearch plugin needs to know what domain classes exist so it can check them for any searchable
properties and index the appropriate ones. So how does it do it? The answer lies with the grailsApplication
object, and instance of GrailsApplication that’s available automatically in controllers and GSPs and can be injected everywhere else.
The grailsApplication
object has several important properties and methods for querying artefacts. Probably the most common is the one that gives you all the classes of a particular artefact type:
for (cls in grailsApplication.<artefactType>Classes) {
...
}
In this case, artefactType
is the property name form of the artefact type. With core Grails you have:
-
domain
-
controller
-
tagLib
-
service
-
codec
-
bootstrap
-
urlMappings
So for example, if you want to iterate over all the domain classes, you use:
for (cls in grailsApplication.domainClasses) {
...
}
and for URL mappings:
for (cls in grailsApplication.urlMappingsClasses) {
...
}
You need to be aware that the objects returned by these properties are not instances of Class. Instead, they are instances of GrailsClass that has some particularly useful properties and methods, including one for the underlying Class
:
-
shortName
- the class name of the artefact without the package (equivalent ofClass.simpleName
). -
logicalPropertyName
- the artefact name in property form without the 'type' suffix. SoMyGreatController
becomes 'myGreat'. -
isAbstract()
- a boolean indicating whether the artefact class is abstract or not. -
getPropertyValue(name)
- returns the value of the given property, whether it’s a static or an instance one. This works best if the property is initialised on declaration, e.g.static transactional = true
.
The artefact API also allows you to fetch classes by name and check whether a class is an artefact:
-
get<type>Class(String name)
-
is<type>Class(Class clazz)
The first method will retrieve the GrailsClass
instance for the given name, e.g. 'MyGreatController'. The second will check whether a class is a particular type of artefact. For example, you can use grailsApplication.isControllerClass(org.example.MyGreatController)
to check whether MyGreatController
is in fact a controller.
17.10.2 Adding Your Own Artefact Types
Plugins can easily provide their own artefacts so that they can easily find out what implementations are available and take part in reloading. All you need to do is create an ArtefactHandler
implementation and register it in your main plugin class:
class MyGrailsPlugin {
def artefacts = [ org.somewhere.MyArtefactHandler ]
...
}
The artefacts
list can contain either handler classes (as above) or instances of handlers.
So, what does an artefact handler look like? Well, put simply it is an implementation of the ArtefactHandler interface. To make life a bit easier, there is a skeleton implementation that can readily be extended: ArtefactHandlerAdapter.
In addition to the handler itself, every new artefact needs a corresponding wrapper class that implements GrailsClass. Again, skeleton implementations are available such as AbstractInjectableGrailsClass, which is particularly useful as it turns your artefact into a Spring bean that is auto-wired, just like controllers and services.
The best way to understand how both the handler and wrapper classes work is to look at the Quartz plugin:
Another example is the Shiro plugin which adds a realm artefact.