Up until now, you have been a consumer of the Grails plugin system at various points throughout the book. In Chapter 8, you used the Searchable plugin to add full-text search to your Grails application and explored how to use the Yahoo UI plugin as an alternative Ajax provider. And in the previous chapter, you had the chance to use both the Mail and Quartz plugins. Now it's time to turn the tables and become a plugin author. Plugins are, quite simply, the cornerstone of Grails. Grails itself is basically a plugin runtime with little knowledge beyond how to load and configure an installed set of plugins.
The Grails plugin system is very flexible—so much so that it would be quite reasonable to write an entire book on the subject. In this chapter, we aim to summarize the core concepts and demonstrate some common use cases for the plugin system. However, the full extent of what is achievable with the plugin system is left to your imagination.
Even if you don't plan to write a plugin to distribute to the world, we recommend you take the time to read this chapter. Grails plugins are not just a way to enhance the functionality of an existing Grails application; they are also an effective way to modularize your code. Later in this chapter, we will demonstrate how you can use plugins to split your Grails application into separate maintainable plugins that are composed together at runtime.
The core of Grails is a plugin runtime environment. However, to make it immediately useful, it ships with a default set of plugins that you have already been learning about, including GORM and Grails' MVC framework. Along with the default set of plugins, Grails ships with a set of commands to automatically discover and install new plugins. Let's take a look at these first.
The Grails plugin community is a hive of activity and one of the most exciting areas of Grails. As of this writing, more than 80 plugins are available from the central repository. Providing a range of functionality from job scheduling to search to reporting engines, all the plugins are discoverable through the grails list-plugins
command. To run the list-plugins
command, simply type grails list-plugins
in a command window, as shown in Listing 13-1.
Listing 13-1. Running the list-plugins Command
$ grails list-plugins
What this will do is go off to the Grails central repository and download the latest published plugin list. The list is then formatted and printed to the console. You can see some typical output from the list-plugins
command in Listing 13-2, shortened for brevity.
Listing 13-2. Output from the list-plugins Command
Plug-in list out-of-date, retrieving..
[get] Getting: http://plugins.grails.org/.plugin-meta/plugins-list.xml
.................................................................................
Plug-ins available in the Grails repository are listed below:
-------------------------------------------------------------
acegi <0.3> -- Grails Spring Security 2.0 Plugin
aop <no releases> -- No description available
audit-logging <0.4> -- adds hibernate audit logging and onChange
event handlers to GORM domain classes
authentication <1.0> -- Simple, extensible authentication services
with signup support
...
In the left column, you can see the name of the plugin, while in the middle is the latest released version of the plugin. Finally, on the right of the output, you can see the short description for any given plugin. If you want to obtain more information about a particular plugin, you can use the plugin-info
command. Listing 13-3 shows how to obtain more information about the audit-logging
plugin from Listing 13-2 using the plugin-info
command.
Listing 13-3. Using the plugin-info Command to Get Detailed Plugin Information
$ grails plugin-info audit-logging
...
--------------------------------------------------------------------------
Information about Grails plugin
--------------------------------------------------------------------------
Name: audit-logging | Latest release: 0.4
--------------------------------------------------------------------------
adds hibernate audit logging and onChange event handlers to GORM domain classes
--------------------------------------------------------------------------
Author: Shawn Hartsock
--------------------------------------------------------------------------
Find more info here: http://www.grails.org/Grails+Audit+Logging+Plugin
--------------------------------------------------------------------------
The Audit Logging plugin adds an instance hook to domain
objects that allows you to hang Audit events off of them.
The events include onSave, onUpdate, onChange, onDelete and
when called the event handlers have access to oldObj and newObj definitions that
will allow you to take action on what has changed.
--------------------------------------------------------------------------
Available full releases: 0.3 0.4 0.4-SNAPSHOT
As you can see with the plugin-info
command, you get more information about the plugin including a long description, a link to the documentation (in this case http://www.grails.org/Grails+Audit+Logging+Plugin), who the author is, and all the past release version numbers.
This brings us nicely to the topic of plugin installation. To install the audit-logging
plugin, you can use the install-plugin
command as follows:
$ grails install-plugin audit-logging
However, if you require a specific version of the plugin, you can use one of the version numbers displayed in the Available full releases
: field of Listing 13-3. Listing 13-4 demonstrates how to install version 0.3 of the audit-logging
plugin.
Listing 13-4. Installing a Specific Version of a Plugin with the install-plugin Command
$ grails install-plugin audit-logging 0.3
After you install a Grails plugin, you can find out what plugins you already have installed by running the list-plugins
command discussed in the previous section. You'll notice that, after the list of plugins available in the repository, the list-plugins
command shows the plugins you currently have installed, as shown in Listing 13-5.
Listing 13-5. Finding Out Which Plugins You Have Installed with list-plugins
Plugins you currently have installed are listed below:
-------------------------------------------------------------
audit-logging 0.4 -- adds hibernate audit logging and onChange
event handlers to GORM domain classes
If you have multiple Grails applications in development that share a common set of plugins, it may well be useful to install a plugin globally for all applications. To do this, you can pass the -global
flag to the install-plugin
command. For example, Listing 13-6 shows how to install the code-coverage
plugin, which provides test coverage reports powered by Cobertura, for all applications.
Listing 13-6. Installing a Plugin Globally Using the -global Flag
$ grails install-plugin -global code-coverage
If you no longer need a particular plugin, then you can use the counterpart to the install-plugin
command, which is called, unsurprisingly, uninstall-plugin
. The uninstall-plugin
command works exactly like the install-plugin
command; it simply takes the name of the plugin to uninstall, as shown in Listing 13-7.
Listing 13-7. Uninstalling Plugins with the uninstall-plugin Command
$ grails uninstall-plugin audit-logging
Of course, the plugins you create may not necessarily live in the central Grails repository. Grails plugins are packaged as simple zip files, and if you downloaded a plugin from elsewhere, you can install it by simply running the install-plugin
command and passing in the location on disk of the plugin. Listing 13-8 shows how to install a plugin located in your home directory on a Unix system.
Listing 13-8. Installing a Local Plugin
$ grails install-plugin ~/grails-audit-logging-0.3.zip
To ease distribution within your team, instead of keeping your plugins locally on disk, you may decide to host your plugins on a local web server. In that case, the install-plugin
command also supports plugin installation over HTTP. Listing 13-9 shows how to install the audit-logging
plugin over HTTP, bypassing Grails' plugin autodiscovery mechanism.
Listing 13-9. Installing Plugins Over HTTP
$ grails install-plugin http://plugins.grails.org/grails-audit-logging/tags/
LATEST_RELEASE/grails-audit-logging-0.4.zip
Now that you've learned the basics of plugin discovery and installation, let's move onto how you actually go about creating a plugin. We'll be demonstrating the basics of plugin creation and distribution. After that, we'll show you how to create some useful plugins to enhance and modularize the gTunes sample application.
Creating plugins in Grails is as simple as creating regular applications. All you need to do is run the grails create-plugin
command and specify a name for your plugin. In fact, what you will soon discover is that a Grails plugin is a Grails application. To understand this, create a simple Grails plugin called simple-cache
that can provide caching services to a Grails application. You do this using the create-plugin
command, as shown in Listing 13-10.
The result is what looks like a regular Grails application. You have all the typical resources that make up an application, including a grails-app
directory. However, on closer inspection, you'll notice there is a file called SimpleCacheGrailsPlugin.groovy
in the root of the project. This file contains a class that represents the plugin descriptor. Figure 13-1 shows the plugin descriptor residing snugly in the root of the project.
Figure 13-1. The simple-cache plugin descriptor
The plugin descriptor serves a number of purposes. The first and primary purpose is for the plugin author to provide metadata about the plugin such as the author name, version number, description, and so on. Listing 13-11 shows the SimpleCacheGrailsPlugin
class and the placeholder fields used to supply this information.
Listing 13-11. The SimpleCacheGrailsPlugin Plugin Descriptor
class SimpleCacheGrailsPlugin {
def version = 0.1
def dependsOn = [:]
// TODO Fill in these fields
def author = "Your name"
def authorEmail = ""
def title = "Plugin summary/headline"
def description = 'Brief description of the plugin.'
...
}
Properties such as author, title
and so on, appear in the list-plugins
and plugin-info
commands when a plugin is published to a Grails plugin repository. The following list summarizes the available properties and what they represent:
author
: The name of the plugin authorauthorEmail
: An e-mail contact address for the authortitle
: A short title for the plugin to appear in the right column of the list-plugins
command (see Listing 13-2)description
: A longer, more detailed description that is displayed by the plugin-info
commanddocumentation
: A link to the location of the documentation for the pluginAll the properties in this list are optional; however, providing this information will help others understand the purpose of your plugin. Listing 13-12 shows the simple-cache
plugin's metadata information.
Listing 13-12. The simple-cache Plugin Descriptor with Metadata Provided
class SimpleCacheGrailsPlugin {
def version = 0.1
def dependsOn = [:]
def author = "Graeme Rocher"
def authorEmail = "[email protected]"
def title = "A simple caching plugin"
def description = 'A plugin that provides simple caching services'
...
}
You may have noticed the dependsOn
property, which is currently assigned an empty Map
literal. This property allows you to specify which plugin or plugins this plugin depends on. As an example, say your plugin depends on the presence of GORM in a Grails application; you can specify this by using the plugin name and version number:
def dependsOn = [hibernate:"1.1"]
As well as specifying a simple version number, the dependsOn
version syntax also allows version ranges, including wildcards. As an example, the following two dependsOn
expressions are equally valid:
def dependsOn = [hibernate:"1.0 > 1.1"]
def dependsOn = [hibernate:"* > 1.1"]
The first example specifies that the plugin depends on any version of the hibernate
plugin between versions 1.0 and 1.1, while the second expression says that the plugin supports any version of the hibernate
plugin up to version 1.1, inclusive.
Once the dependsOn
property is specified, when a user installs a plugin via the install-plugin
command, Grails will automatically attempt to install any dependent plugins if they aren't already installed. This technique is often referred to as transitive dependencies resolution, and it is implemented by many build systems (such as Ivy, which is discussed in Chapter 12) for JAR dependencies.
One of the more obvious ways a plugin can enhance an existing application is by providing a new artefact, such as a controller, tag library, or service.
Note Throughout the source code and documentation of Grails, the word artefact is used to refer to a Groovy file that fulfills a certain concept (such as a controller, tag library, or domain class). It is spelled using the British English spelling of artefact as opposed to artifact, so we will be using that spelling throughout the book to maintain consistency with the APIs.
Because a Grails plugin is simply a Grails application, supplying an artefact is a simple matter of creating it just as you would in a regular application. For the simple-cache
plugin, you're going to implement a service that provides application layer caching. To do so, simply use the create-service
command from the root of the plugin:
$ grails create-service com.g2one.cache.Cache
Once completed, you'll end up with a new service at the location grails-app/services/ com/g2one/cache/CacheService.groovy
. Because it's pretty simple to do, you'll also be implementing a little tag library to perform content-level caching. To create the tag library, run the create-tag-lib
command:
$ grails create-tag-lib com.g2one.cache.Cache
Note that since a Grails plugin is simply a Grails application, you can run it just like a Grails application! Just use the grails run-app
command, and you're on your way. This has significant benefits for the plugin developer in that plugin development is not very different from regular application development. You can run your plugin like a regular application, and you can also test your plugin like a regular application using the test-app
command. You can even install other plugins into a plugin, something that is critical when developing a plugin that has dependencies on other plugins.
As for the CacheService
and the CacheTagLib
, we'll get to the implementation details of these later. For the moment, all you need to know is that, when you package up your plugin for distribution, it will provide two new artefacts: a tag library and a service.
Let's return to the plugin descriptor. As well as providing metadata about the plugin, the descriptor also enables you to supply hooks into the plugin runtime. Each hook is defined as a closure property and allows the plugin to participate in the various phases of the plugin life cycle. The hooks are listed here in the order of their execution:
doWithWebDescriptor
: This gets passed the XML for the web.xml
file that has been parsed by Groovy's XmlSlurper
into a GPathResult
. See the "Modifying the Generated WAR Descriptor" section later in the chapter for more information on this one.doWithSpring
: This allows participation in the runtime configuration of Grails' underlying Spring ApplicationContext
. See the "Providing Spring Beans" section for more information.doWithDynamicMethods
: Executed after the construction of the ApplicationContext
, this is the hook that plugins should use to provide new behavior to Grails classes. See the "Using Metaprogramming to Enhance Behavior" section later in the chapter for more information.doWithApplicationContext
: This is executed after Grails' ApplicationContext
has been constructed. The ApplicationContext
instance is passed to this hook as the first argument.By default, the simple-cache
plugin you created earlier comes with empty implementations of all of these. If you don't plan to implement any of these hooks, you can simply delete them from the plugin descriptor. Listing 13-13 shows the various plugin hooks, just waiting to be implemented.
Note If you merely want to use plugins to provide application modularity, then you may want to skip to the "Packaging and Distributing a Grails Plugin" section because the following sections go into significant detail on how to hook into all aspects of the Grails plugin system.
Listing 13-13. Plugin Hooks in the simple-cache Plugin
class SimpleCacheGrailsPlugin {
def version = 0.1
...
def doWithWebDescriptor = { xml -> }
def doWithSpring = {}
def doWithDynamicMethods = { applicationContext -> }
def doWithApplicationContext = { applicationContext -> }
}
A number of implicit variables are available within the context of these hooks that allow you to inspect the conventions within a running Grails application. The following are the available variables and associated descriptions:
application
: An instance of the org.codehaus.groovy.grails.commons.GrailsApplication
class that provides information about the loaded classes and the conventions within themmanager
: An instance of the org.codehaus.groovy.grails.plugins.GrailsPluginManager
class that allows you to find out what other Grails plugins are installedplugin
: A reference to the org.codehaus.groovy.grails.plugins.GrailsPlugin
class, which allows you to find out various information about the plugin including its name, version, and dependenciesThe GrailsApplication
class is typically the most critical to understand if you plan to implement any hooks that work with the Grails conventions. Essentially, it defines a number of dynamic properties that map to each concept in a Grails application. For example, to obtain a list of the controller classes in a GrailsApplication
, you can do this:
def controllerClasses = application.controllerClasses
Note that when we refer to classes, we're not talking about instances of the java.lang.Class
interface but of the org.codehaus.groovy.grails.commons.GrailsClass
interface that defines a number of methods to inspect the conventions within a GrailsApplication
for a particular artefact type.
For example, given the CacheService
you created earlier, Listing 13-14 demonstrates some of the methods of the GrailsClass
interface and how they behave.
Listing 13-14. Using the Grails Convention APIs
GrailsClass serviceClass =
application.getServiceClass("com.g2one.cache.CacheService")
assert "CacheService" == serviceClass.shortName
assert "Cache" == serviceClass.name
assert "com.g2one.cache.CacheService" == serviceClass.fullName
assert "cacheService" == serviceClass.propertyName
assert "cache" == serviceClass.logicalPropertyName
assert "com.g2one.cache" == serviceClass.packageName
assert true == serviceClass.getPropertyValue("transactional")
You'll notice from Listing 13-14 the usage of the getServiceClass
method to obtain the CacheService
by name. The getServiceClass
method is another dynamic method available on the GrailsApplication
class. Essentially, for each artefact type, the GrailsApplication
class provides dynamic methods to access the artefacts of that type, which are summarized here:
get*Classes
: Obtain a list of all the GrailsClass
instances for a particular artefact type, such as with getControllerClasses()
or via property access such as controllerClasses
.get*Class(String name)
: Obtain a specific GrailsClass
instance by name, as in getControllerClass("HelloController")
.is*Class(Class theClass)
: Inquire if a given java.lang.Class
is a particular artefact type, as in isControllerClass(myClass)
.The asterisk in the previous method names can be substituted for the relevant artefact type you are interested in. Table 13-1 summarizes the different artefact types, as well as shows an example of the typical usage for each.
Table 13-1. Summary of Existing Artefact Types
Artefact Type | Example |
Bootstrap |
def bootstrapClasses = application.getBootstrapClasses() |
Codec |
def codecClasses = application.getCodecClasses() |
Controller |
def controllerClasses = application.getControllerClasses() |
Domain |
def domainClasses = application.getDomainClasses() |
Filters |
def filterClasses = application.getFiltersClasses() |
Service |
def serviceClasses = application.getServiceClasses() |
TagLib |
def tagLibClasses = application.getTagLibClasses() |
UrlMappings |
def urlMappingClasses = application.getUrlMappingsClasses() |
All of the artefact types in Table 13-1 cover existing artefacts, but Grails also allows you to add your own artefact types, which we'll look at in the next section.
Out of the box, Grails ships with a set of features, including controllers, domain classes, and so on. As you saw in the previous section, you can access all aspects of these via the GrailsApplication
interface. However, what if you want to add a new artefact type? Take, for example, the existing Quartz plugin. As you discovered in Chapter 12, Quartz is a job-scheduling API that runs specified tasks on a scheduled basis. For example, you may want to run some code at 12 p.m. on the last Friday of every month. Quartz aims to solve these kinds of problems.
Now if you look at the existing artefact types, none of them models the idea of a job. So, how can you extend Grails and provide new knowledge to it about what a job is? Fortunately, you can find the answer in Grails' org.codehaus.groovy.grails.commons.ArtefactHandler
interface. Listing 13-15 shows the key methods of the ArtefactHandler
interface.
Listing 13-15. The ArtefactHandler Interface
public interface ArtefactHandler {
String getType();
boolean isArtefact(Class aClass);
GrailsClass newArtefactClass(Class artefactClass);
}
The getType()
method returns the type of the GrailsClass
, which will be one of the values shown in the first column of Table 13-1. The isArtefact(Class)
method is responsible for identifying whether a given class is of the current artefact type based on some convention. For example, does the class end with the convention Controller? If so, then it's a controller class.
The newArtefactClass(Class)
method will create a new GrailsClass
instance for the given java.lang.Class
. The ArtefactHandler
interface has other methods, but most of them are abstracted away from you because when implementing a custom ArtefactHandler
, you'll typically extend the org.codehaus.groovy.grails.commons.ArtefactHandlerAdapter
class. For example, take a look at Listing 13-16, which shows a possible implementation for the Quartz plugin.
Listing 13-16. An ArtefactHandler for the Quartz Plugin
1 class JobArtefactHandler extends ArtefactHandlerAdapter {
2
3 static final TYPE = "Job"
4
5 JobArtefactHandler() {
6 super(TYPE, GrailsClass, DefaultGrailsClass, TYPE)
7 }
8
9 boolean isArtefactClass(Class clazz) {
10 // class shouldn't be null and shoudd ends with Job suffix
11 if(!super.isArtefactClass(clazz)) return false
12 // and should have an execute method
13 return clazz.methods.find { it.name == 'execute' } != null
14 }
15 }
There are a few key things to look at in the JobArtefactHandler
in Listing 13-16. First take a look at the constructor on lines 5 to 7:
5 JobArtefactHandler() {
6 super(TYPE, GrailsClass, DefaultGrailsClass, TYPE)
7 }
The constructor calls the super
implementation, passing four arguments:
TYPE
that has the value Job
.GrailsClass
interface to provide a more specific interface such as GrailsJobClass
.DefaultGrailsClass
, but you could subclass this if you want to provide custom logic within the artefact type.java.lang.Class
to be considered of the artefact type: The default implementation of the isArtefactClass
method in ArtefactHandlerAdapter
will perform a check on the passed java.lang.Class
to ensure that the class name ends with the specified suffix. As you can see on line 11 of Listing 13-16, the logic from the superclass isArtefact
method is being reused.The next thing to note about the code in Listing 13-16 is the implementation of the isArtefactClass(Class)
method, which checks that the class ends with the appropriate suffix by calling the superclass implementation of isArtefactClass(Class)
and whether the class possesses an execute
method. You can assert your expectations of the behavior of the JobArtefactHandler
by writing a simple unit test, as shown in Listing 13-17.
Listing 13-17. Testing an ArtefactHandler
class JobArtefactHandlerTests extends GroovyTestCase {
void testIsArtefact() {
def handler = new JobArtefactHandler()
assertTrue handler.isArtefactClass(TestJob)
assertFalse handler.isArtefactClass(JobArtefactHandlerTests)
GrailsClass jobClass = handler.newArtefactClass(TestJob)
assertEquals "TestJob", jobClass.shortName
assertEquals "Test", jobClass.name
assertEquals "TestJob", jobClass.fullName
assertEquals "testJob",jobClass.propertyName
assertEquals "test",jobClass.logicalPropertyName
assertEquals "", jobClass.packageName
}
}
class TestJob {
def execute() {}
}
At this point, there is one thing left to do. You have to tell your plugin about the ArtefactHandler
. Say you were creating the Quartz plugin and you have a QuartzGrailsPlugin
descriptor. If you add an artefacts
property that contains a list of provided artefacts, the plugin will make Grails aware of the JobArtefactHandler:
def artefacts = [new JobArtefactHandler()]
So once the Quartz plugin is installed, if there is a class within the grails-app/jobs
directory that looks like the one in Listing 13-18, the JobArtefactHandler
will approve the class as being a "job."
Listing 13-18. An Example Job
class SimpleJob {
def execute() {
// code to be executed
}
}
An added bonus of going through these steps is that suddenly the GrailsApplication
object has become aware of the new artefact type you just added. With this hypothetical Quartz plugin installed, you can use all the dynamic methods on the GrailsApplication
object first shown in Listing 13-14. Listing 13-19 demonstrates a few examples using the SimpleJob
from Listing 13-18.
Listing 13-19. Using the GrailsApplication Object to Inspect Jobs
def jobClasses = application.getJobClasses()
GrailsClass simpleJobClass = application.getJobClass("SimpleJob")
assert application.isJobClass(SimpleJob)
The key thing to learn from this section is that Grails provides you with an extensible convention-based API. You are in no way restricted by the existing conventions and can easily start adding your own ideas to the mix. In the next section, we'll be looking at how the idea of Convention over Configuration (CoC) extends to the runtime configuration of Spring.
The doWithSpring
hook allows you to specify new Spring beans to configure at runtime using Grails' BeanBuilder
domain-specific language (DSL) for Spring. The intricacies of BeanBuilder
will be described in far more detail in Chapter 16; however, we'll cover some of the basics here. Essentially, Grails is built completely on the Spring Framework. Grails has what is known as an ApplicationContext
, which is essentially a container provided by Spring that holds one or more beans. By default, each bean is a singleton, meaning there is only one of them in the ApplicationContext
.
As you learned in Chapter 11, Grails allows services to be autowired into controllers and tag libraries. This autowire feature is powered by the Spring container and is often referred to as dependency injection. It is an extremely powerful pattern that allows you to effectively separate out dependencies and the construction of those dependencies. That's the theory...now let's take a look at an example.
Earlier, you created a new service in the simple-cache
plugin called CacheService
. The CacheService
is going to work in conjunction with a cache provider to provide application-layer caching to any user of the simple-cache
plugin. Since it is a little pointless to reinvent the wheel and implement your own homegrown caching implementation, you're going to take advantage of the Ehcache library.
You may remember from Chapter 8 that you defined a bean in the grails-app/conf/ spring/resources.groovy
file for the gTunes application that used the EhCacheFactoryBean
class provided by Spring. You're going to use that again here, within the context of doWithSpring
. Listing 13-20 shows how to define a globalCache
bean.
Listing 13-20. Defining Beans in doWithSpring
class SimpleCacheGrailsPlugin {
...
def doWithSpring = {
globalCache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
timeToLive = 300
}
}
}
As a reminder, the name of the bean is the name of the method, which in this case is globalCache
. The bean class is the first argument, while the closure passed as the last argument allows you to set property values on the bean. In this case, a globalCache
bean is configured to expire entries every 5 minutes (300 seconds). We've really only touched the surface of what is possible with BeanBuilder
here and in Chapter 8, so if you're keen to know more, you could skip forward to Chapter 16, which contains detailed coverage.
With that done, let's begin implementing the CacheService
. First you need to get a reference to the globalCache
bean defined by the plugin. To do this, simply add a property that matches the name of the bean to the CacheService
, as shown in Listing 13-21.
Listing 13-21. Obtaining Beans Supplied by doWithSpring
import net.sf.ehcache.Ehcache
class CacheService {
static transactional = false
Ehcache globalCache
...
}
The globalCache
property is in bold in Listing 13-15. Note that transactions have been disabled for the service by setting static transactional = false
, since transactions won't be a requirement for this service.
Now let's implement the caching logic. When implementing caching, the pattern is typically that you look up an object from the cache, and if it doesn't exist, you execute some logic that obtains the data to be cached. Listing 13-22 shows some pseudocode for this pattern.
Listing 13-22. The Caching Pattern
def obj = cache.get("myentry")
if(!obj) {
obj = ... // do some complex task to obtain obj
cache.put("myentry", obj)
}
return obj
However, given that you have the power of closures at your disposal, it makes more sense to take advantage of them to come up with a more elegant solution. Listing 13-23 shows how to implement caching of entire logical blocks using closures.
Listing 13-23. Caching the Return Value of Blocks of Code Using Closures
1 import net.sf.ehcache.Ehcache
2 import net.sf.ehcache.Element
3
4 class CacheService {
5 ...
6 def cacheOrReturn(Serializable cacheKey, Closure callable) {
7 def entry = globalCache?.get(cacheKey)?.getValue()
8 if(!entry) {
9 entry = callable.call()
10 globalCache.put new Element(cacheKey, entry)
11 }
12 return entry
13 }
14 }
To understand what the code is doing in Listing 13-23, let's step through it line by line. First, on line 7 an entry is obtained from the globalCache
bean, which is an instance of the net.sf.ehcache.Ehcache
class:
7 def entry = globalCache?.get(cacheKey)?.getValue()
Notice how you can use Groovy's safe-dereference operator ?
. to make sure that a NullPointerException
is never thrown when accessing the value, even if the globalCache
property is null!
The get
method of the globalCache
instance returns a net.sf.ehcache. Element
instance, which has a getValue()
method you can call to obtain the cached value. Next on lines 8 and 9 the code checks that the returned value is null
, and if it is, the passed closure is invoked, which returns the result that needs to be cached:
8 if(!entry) {
9 def entry = callable.call()
The return value of the call to the closure is used to place a new cache entry into the cache on line 10:
10 globalCache.put new Element(cacheKey, entry)
Finally, on line 12 the cache entry is returned regardless of whether it is the cached version:
12 return entry
With that done, let's see how to implement the CacheTagLib
that can take advantage of the CacheService
in Listing 13-24.
Listing 13-24. Adding Content-Level Caching
class CacheTagLib {
static namespace = "cache"
CacheService cacheService
def text = { attrs, body ->
def cacheKey = attrs.key
out << cacheService.cacheOrReturn(cacheKey) {
body()
}
}
}
Once again, Listing 13-24 shows how to use dependency injection to get hold of a reference to the CacheService
in the CacheTagLib
. The cacheOrReturn
method is then used to cache the body
of the tag using the key
attribute passed into the text
tag. Notice how the CacheTagLib
has been placed inside a namespace, a concept you first learned about in Chapter 5.
Users of the simple-cache
plugin can now take advantage of content-level caching simply by surrounding the body of markup code they want to cache with the <cache:text>
tag that the CacheTagLib
provides. Listing 13-25 shows an example of its usage.
Listing 13-25. Using the Tag Provided by the simple-cache Plugin
<cache:text key="myKey">
This is an expensive body of text!
</cache:text>
In the previous section, you implemented the simple-cache
plugin using an Ehcache bean registered in the Spring ApplicationContext
. What this example didn't demonstrate, though, is the ability to dynamically create beans on the fly using the conventions in the project.
In the "Custom Artefact Types" section, you explored how to create a plugin that identified Quartz jobs. In a typical Spring application, you would need to use XML to configure each individual job using the org.springframework.scheduling.quartz.JobDetailBean
class. With a Grails plugin that knows about conventions, you can do it dynamically at runtime! Listing 13-26 shows this in action in a QuartzGrailsPlugin
plugin descriptor.
Listing 13-26. Dynamically Creating Beans at Runtime
1 import org.springframework.scheduling.quartz.*
2
3 class QuartzGrailsPlugin {
4 ...
5 def doWithSpring ={
6 application.jobClasses.each { GrailsClass job ->
7 "${job.propertyName}"(JobDetailBean) {
8 name = job.name
9 jobClass = job.getClazz()
10 }
11 }
12 ...
13 }
14 }
To better understand the code in Listing 13-26, let's step through it. First, on line 6 the each method is used to iterate over all the artefacts of type Job
:
6 application.jobClasses.each { GrailsClass job ->
Then on line 7, a new bean is dynamically created using Groovy's ability to invoke methods using a String
(or a GString
) as the method name:
7 "${job.propertyName}"(JobDetailBean) {
In this case, given the SimpleJob
from Listing 13-18, you would end up with a bean called simpleJob
in the Spring ApplicationContext
that is an instance of the Quartz JobDetail
class. The JobDetailBean
class is a Spring-provided helper class for creating Quartz JobDetail
instances as Spring beans. Finally, on lines 8 and 9, the name of the job and the class of the job are set using properties of the GrailsClass
interface:
8 name = job.name
9 jobClass = job.getClazz()
To finish up the Quartz plugin, you could set up beans within doWithSpring
for the Scheduler
, using Spring's SchedulerFactoryBean
, the triggers, and so on. However, since this serves mainly as a demonstration of what is possible, we recommend you take a look at the excellent existing Quartz plugin for Grails, which is installable with the following command:
$ grails install-plugin quartz
In the previous section, you saw how plugins can participate in the configuration of the Spring ApplicationContext
. Now let's look at another area that plugins typically contribute to: the application behavior. Groovy is a fully dynamic language that allows you to completely modify the behavior of a class at runtime through its metaprogramming APIs.
Tip If you're looking for a book with significant coverage of the metaprogramming capabilities offered by Groovy, take a look at Programming Groovy by Venkat Subramaniam (Pragmatic Programmers, 2008).
Like other dynamic languages such as Smalltalk, Ruby, and Lisp, Groovy features a Meta Object Protocol (MOP). The key thing to remember is that it is the MOP that decides the behavior of Groovy code at runtime, so code that looks as though it may do one thing at compile time could be made to do something completely different. For each java.lang.Class
that Groovy knows about, there is an associated MetaClass
. The MetaClass
is what dictates how a particular method, constructor, or property behaves at runtime.
Groovy's MetaClass
allows you to add methods, properties, constructors, and static methods to any class. For example, consider the code in Listing 13-27.
Listing 13-27. Adding New Methods to a Class
class Dog {}
Dog.metaClass.bark = { "woof!" }
assert "woof!" == new Dog().bark()
Here you have a simple class called Dog
. Instances of the Dog
class cannot, as it stands, bark. However, by using the metaClass
, you can create a bark
method with this expression:
Dog.metaClass.bark = { "woof!" }
Clearly, this example has only brushed the surface of what is possible. If you refer to Appendix A, you'll find more detailed coverage of the metaprogramming APIs.
Let's look at an example within the context of a Grails plugin by trying to add the cacheOrReturn
method to all controllers to eliminate the need to inject the service via Spring first. Listing 13-28 demonstrates how, by simply delegating to the CacheService
, you can add a cacheOrReturn
method to all controllers too.
Tip If you prefer not to create a plugin but would still like to do metaprogramming in your Grails application, we recommend you do so within a Bootstrap
class, a topic covered in Chapter 12.
Listing 13-28. Adding Methods to All Controllers
class SimpleCacheGrailsPlugin {
...
def doWithDynamicMethods = { applicationContext ->
def cacheService = applicationContext.getBean("cacheService")
application
.controllerClasses
*.metaClass
*.cacheOrReturn = { Serializable cacheKey, Closure callable ->
cacheService.cacheOrReturn(cacheKey, callable)
}
}
}
Another important aspect to notice about the code in Listing 13-28 is the use of Groovy's spread dot operator *
. to obtain all the metaClass
instances from all the controllerClasses
and also the use of a spread assignment to create a cacheOrReturn
method for each MetaClass
. That's far easier than adding a for or each
loop!
As well as the plugin hooks discussed in the "Plugin Hooks" section, plugins can also participate in a number of events, including application reload events. Grails aims to minimize the number of application restarts required during development time. However, since reloading is typically different for each artefact type, the responsibility to reload is delegated to plugins.
A plugin can essentially listen for three core events: onChange, onConfigChange
, and onShutdown
. Let's take a look at onChange
first because it is the most common event dealt with by plugins. Each individual plugin can monitor a set of resources. These are defined by a property called watchedResources
. For example, as part of Grails core, there is a plugin that provides support for internationalization (covered in Chapter 7) through the use of message bundles that are found in the grails-app/i18n
directory. The i18n
plugin defines its watchedResources
property as follows:
def watchedResources = "file:./grails-app/i18n/*.properties"
What this says is that the i18n
plugin will monitor all files within the grails-app/i18n
directory ending with the file extension .properties
.
Tip If you're wondering about the file-matching patterns the watchedResources
property uses, take a look at Spring's org.springframework.core.io.support.PathMatchingResourcePatternResolver
class as well as the Spring Core IO package in general, which Grails uses under the covers.
Whenever one of the properties files in the grails-app/i18n
directory changes, Grails will automatically trigger the onChange
event of the plugin or plugins, monitoring the file passing in a change event object. The event object is essentially just a map containing the following entries:
source:
The source of the event, which is either a Spring org.springframework.core. io.Resource
instance representing the file on disk or the recompiled and changed java.lang.Class
instance if the watchResources
property refers to Groovy classesapplication:
A reference to the GrailsApplication
instancemanager:
A reference to the GrailsPluginManager
instancectx:
A reference to the Spring ApplicationContext
instanceTypically the most important entry in the event map is the source
, which contains a reference to the source of the change. In the case of the i18n
plugin, the source
entry would reference a Spring org.springframework.core.io.Resource
instance since the properties files monitored by the i18n
plugin are not Groovy classes. However, if you develop a plugin where you choose to monitor Groovy classes instead, Grails will automatically recompile the changed class and place the altered class within the source
entry in the event
map.
As an example, consider the Quartz plugin discussed in previous sections. The watchedResources
definition for the Quartz plugin would look something like this:
def watchedResources = "file:./grails-app/jobs/**/*Job.groovy"
Whenever one of the Groovy files changes, Grails will recompile the class and pass you a reference to the java.lang.Class
instance representing the job. However, that is all Grails will do. It is then up to you to make whatever changes you deem necessary to the running application to ensure it is now in the correct state. For example, in the "Dynamic Spring Beans Using Conventions" section, we showed you how to dynamically register new JobDetail
beans for each job class. To implement reloading correctly for the Quartz plugin, you would need to ensure that those beans are replaced with the new class. Listing 13-29 shows a hypothetical implementation that takes the newly recompiled class and registers new beans with the ApplicationContext
.
Listing 13-29. Implementing onChange for the Quartz Plugin
1 class QuartzGrailsPlugin {
2 def watchedResources = "file:./grails-app/jobs/**/*Job.groovy"
3 ...
4
5 def onChange = { event ->
6 Class changedJob = event.source
7 GrailsClass newJobClass = application.addArtefact(changedJob)
8 def newBeans = beans {
9 "${newJobClass.propertyName}"(JobDetailBean) {
10 name = newJobClass.name
11 jobClass = newJobClass.getClazz()
12 }
13 }
14 newBeans.registerBeans(applicationContext)
15 }
16 }
Although the code is pretty short, there are quite a few new concepts to understand, so let's walk through those starting on line 6 where a reference to the event's source
is obtained:
6 Class changedJob = event.source
With the source
in hand, the next thing the onChange
event does is register the new Class
with the GrailsApplication
instance by calling the addArtefact
method:
7 GrailsClass newJobClass = application.addArtefact(changedJob)
The code on line 8 is pretty interesting, because here the implicit beans
method is used, which takes a block of code that uses the BeanBuilder
syntax we discussed in the "Providing Spring Beans" section. The beans
method returns a BeanBuilder
instance containing the bean definitions (but not the instantiated beans themselves):
8 def newBeans = beans {
The code on lines 8 to 13 are essentially the same as you saw in Listing 13-26; all the code is doing is creating a new JobDetailBean
bean definition from the new class. Line 14 is far more interesting because it shows how you can use the registerBeans
method of the BeanBuilder
class to register all the bean definitions defined within the BeanBuilder
instance with the provided ApplicationContext:
14 newBeans.registerBeans(applicationContext)
Of course, not all plugins will need to register new beans based on an onChange
event. This is a requirement only if you registered beans in the doWithSpring
closure that require reloading behavior. It may be possible to work with the existing beans to implement effective reloading for a plugin. For example, the i18n plugin we discussed earlier simply clears the MessageSource
cache, forcing it to be rebuilt:
def messageSource = applicationContext.getBean("messageSource")
if (messageSource instanceof ReloadableResourceBundleMessageSource) {
messageSource.clearCache()
}
Other than the onChange
event, the two other events available are onConfigChange
and onShutdown
. The onConfigChange
event is fired if Grails' global configuration file found at grails-app/conf/Config.groovy
is changed by the user. In the case of the onConfigChange
event handler, the source
of the change event is the altered ConfigObject
. Often, plugins rely on settings found within Config.groovy
for configuration. Remember, Grails uses Convention over Configuration, which means that conventions are used to ease development, but configuration is still possible if required. Later in this chapter we'll show an example that uses the Grails ConfigObject
, which is obtainable using the getConfig()
method of the GrailsApplication
class.
Finally, the onShutdown
event is fired when the shutdown()
method of the GrailsPluginManager
is called. This happens, for example, when a Grails application is undeployed from a container and the Grails servlet's destroy()
method is invoked.
As discussed in Chapter 12, the web.xml
file Grails uses to integrate with servlet containers is generated programmatically. You saw in Chapter 12 that it is possible to modify the template used to generate web.xml
by using the install-templates
command. However, it is also possible for plugins to modify web.xml
programmatically using the doWithWebDescriptor
hook.
Essentially, when the web.xml
file is generated, it gets parsed into memory by Groovy's XmlSlurper
parser. This parser creates an in-memory representation of the XML that you can modify. The doWithWebDescriptor
hook is passed a reference to the XML as the first argument to the doWithWebDescriptor
closure. XmlSlurper
allows you to use a builder-like syntax to make modifications to the XML.
As an example, one of the core Grails plugins is the URL mappings plugin, which provides the functionality covered in Chapter 6. The way the plugin works is to provide a Servlet filter that rewrites requests onto the main Grails servlet. To add this Servlet filter into the mix, the doWithWebDescriptor
implementation of the URL mappings plugin looks something like the code in Listing 13-30.
Listing 13-30. Example doWithWebDescriptor That Adds a New Servlet Filter
1 def doWithWebDescriptor = { webXml ->
2 def filters = webXml.filter
3 def lastFilter = filters[filters.size()-1]
4 lastFilter + {
5 filter {
6 'filter-name'('urlMapping')
7 'filter-class'(UrlMappingsFilter.getName())
8 }
9 }
10 ...
11 }
To understand what the code in Listing 13-30 is doing, let's take a look at it line by line. First, on line 2, a GPath expression is used to get a list of all the existing <filter>
elements contained within the web.xml
file:
2 def filters = webXml.filter
Then, on line 3, a reference to the last <filter>
element in the list is obtained:
3 def lastFilter = filters[filters.size()-1]
As you can see from the previous two examples, using Groovy's XML APIs is nothing like using a Java XML parser. The XML object parsed by XmlSlurper
almost feels like a first-class object, with very little evidence that the underlying data structure is in fact XML. Finally, on lines 4 through 9, the overridden +
operator is used to add a new <filter>
element directly after the last <filter>
element:
4 lastFilter + {
5 filter {
6 'filter-name'('urlMapping')
7 'filter-class'(UrlMappingsFilter.getName())
8 }
9 }
Notice how in Groovy you can use strings for method names; for instance, you can choose an idiomatic XML element name like <filter-name>
as the name of a method. The previous code will append the following equivalent XML snippet to the web.xml
document:
<filter>
<filter-name>urlMapping</filter-name>
<filter-class>org.codehaus.groovy.grails.web.mapping.filter.
UrlMappingsFilter</filter-class>
</filter>
As you can see, Grails makes it pretty easy to participate in the generation of the web.xml
file. Although not a common thing to do in a plugin, it is sometimes useful when you want to integrate legacy servlets, filters, and so on. As mentioned previously, you could have used the grails install-templates
command and modified the web.xml
template directly, but this technique allows you to create plugins that automatically do this configuration for you. Reducing configuration, as well as embracing simplicity, is very much the Grails way, and doWithWebDescriptor
is just another example of that.
Once you are confident that your plugin is ready for distribution, you can package it using the grails package-plugin
command. In the command window, simply type grails package-plugin
from the root of your plugin project, as shown in Listing 13-31.
Listing 13-31. Packaging a Plugin
$ grails package-plugin
...
[zip] Building zip: /Developer/grails/simple-cache/grails-simple-cache-0.1.zip
As you can see from the output in Listing 13-31, the package-plugin
command generates a zip file using the name and version number of your plugin. In this case, you're packaging up the simple-cache
plugin you developed earlier. Figure 13-2 shows an example of the resulting zip file.
Figure 13-2. The simple-cache plugin's packaged zip file
Using the steps explained earlier in this chapter in the "Plugin Installation" section, you can now install the simple-cache
plugin into other applications and make use of the tag library and services it provides.
If you want to distribute your plugin within the Grails central repository, you first need to obtain a plugin developer account for the Grails central Subversion (SVN) repository. You can find the steps to do so on the Grails web site at http://grails.org/Creating+Plugins.
Once you have obtained an account, releasing your plugin is as simple as typing the following command:
$ grails release-plugin
The release-plugin
command will prompt you for the SVN username and password that you obtained when you set up a developer account. Grails does all the heavy lifting for you in making sure that the appropriate resources have been published in the repository and been tagged appropriately. The release-plugin
command will also generate an updated plugin list so that your plugin appears whenever a Grails user types the list-plugins
command.
If you want to take advantage of Grails' plugin distribution and discovery mechanism on your own local network, then you can set up a local plugin repository. Grails' plugin repositories are currently backed by the SVN version control system, so all you need to do is set up an SVN repository on your local network, which you can do using the svnadmin
command provided by SVN:
$ svnadmin create /path/to/repo
Once your SVN repository is created, you can configure additional repositories inside the grails-app/conf/BuildConfig.groovy
file for each application or globally by creating a file in your USER_HOME
directory at the location USER_HOME/.grails/settings.groovy
. Either way, you can then provide additional named URLs of SVN repositories used for discovery and distribution. Listing 13-32 presents an example of configuring an additional plugin repository.
Listing 13-32. Configuring Additional Plugin Repositories
grails.plugin.repos.discovery.myRepository="http://foo.bar.com"
grails.plugin.repos.distrubtion.myRepository="https://foo.bar.com"
Notice in Listing 13-28 how Grails groups repositories under discovery
and distribution
. The URLs under discovery
are used by the list-plugins, install-plugin
, and plug-info
commands discussed in the section on "Plugin Installation" to produce the plugin list that is presented to the user. The URLs under distribution
are used by the release-plugin
command, as discussed in the previous section.
By default, the release-plugin
command will always try to publish to the Grails central repository. To tell the release-plugin
command to publish to one of the repositories configured as in Listing 13-32, you need to add the name of the repository as an argument to the release-plugin
command. For example:
$ grails release-plugin -repository=myRepository
And with that, we've reached the end of this tour of the plugin system. As you can imagine, you can take advantage of the plugin system in many different ways. In this section, we've touched on some ideas for plugins such as the simple-cache
plugin and the Quartz plugin, but we think the plugin system is such a critical part of the Grails ecosystem that the lessons learned in this chapter should be put to further use. In the next section, you'll be applying what you've learned so far to create two new plugins for the gTunes application. Along the way, you'll discover how Grails' plugins can be used as both a way to extend the functionality of an existing application and as a way to effectively modularize your codebase.
So, you've learned what plugins are and the basics of creating plugins. It is now time to put that knowledge to work by developing a couple of plugins for the gTunes application. The first one you're going to create is a plugin that makes the album art service and tag library you developed in Chapter 8 into a reusable plugin. This is a perfect example of developing a plugin to add functionality and enhance behavior.
To start with, run the create-plugin
command to create the basis of an album-art
plugin:
$ grails create-plugin album-art
The next step is to move the AlbumArtService.groovy
file and the AlbumArtTagLib.groovy
file into the newly created plugin project. Once this is done, your plugin should be structured like Figure 13-3.
Figure 13-3. The structure of the album-art plugin
Of course, the AlbumArtService
relies heavily on the Amazon web services library, so you should move those from the application into the plugin too. Figure 13-4 shows the lib
directory with the necessary JAR files in place.
Also, don't forget to move the two tests that provide coverage for the AlbumArtService
and AlbumArtTagLib
from the application into the plugin. As mentioned previously, the great thing about plugins is that they can be developed and tested separately, which makes them useful for larger projects with multiple developers. With the AlbumArtServiceTests
and AlbumArtTagLibTests
test cases included in the album-art
plugin, you can now immediately test whether your plugin is working by running the test-app
command:
$ grails test-app
Figure 13-4. The album-art plugin's dependencies
With the tests passing, you can add the plugin metadata to the plugin descriptor that describes what this plugin is all about. Listing 13-33 shows the updated plugin descriptor with the metadata provided.
Listing 13-33. Providing Metadata to the album-art Plugin
class AlbumArtGrailsPlugin {
def version = 0.1
def author = "Graeme Rocher"
def authorEmail = "[email protected]"
def title = "Album art look-up plugin"
def description = 'A plug-in that provides facilities to look-up album art'
...
}
One thing to consider is that when you developed the AlbumArtService
in Chapter 8, it was designed to work in conjunction with an albumArtCache
that used Ehcache provided by the application's grails-app/conf/spring/resources.groovy
file. One solution to this would be to update the doWithSpring
of the AlbumArtGrailsPlugin
descriptor, as shown in Listing 13-34.
Listing 13-34. Providing the albumArtCache with doWithSpring
class AlbumArtGrailsPlugin {
def version = 0.1
...
def doWithSpring = {
albumArtCache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
timeToLive = 300
}
}
}
However, since you previously developed a simple-cache
plugin earlier in the chapter, it makes a lot more sense to take advantage of it. To do so, let's modify the dependsOn
property on the album-art
plugin descriptor, as shown in Listing 13-35.
Listing 13-35. Using dependsOn to Depend on the simple-cache Plugin
class AlbumArtGrailsPlugin {
def dependsOn = [simpleCache:'0.1 > *']
...
}
Tip When specifying dependencies, you need to use bean conventions instead of the hyphen-separated, lowercase name simple-cache
. The reason for this Grails design decision is that a hyphen isn't valid in a variable name or map key in Groovy unless you put quotes around it.
To enable the ability to continue to test the album-art
plugin in isolation, you can install the simple-cache
plugin into the album-art
plugin using the install-plugin
command from the root of the album-art
plugin directory:
$ grails install-plugin /path/to/simple-cache/grails-simple-cache-0.1.zip
When you package the album-art
plugin, Grails will not include the simple-cache
plugin within the album-art
zip. It is your responsibility to ensure that when you install the album-art
plugin into the target application, you install the simple-cache
plugin first. If you don't, you will get an error because Grails will be unable to resolve the album-art
plugins' dependency on the simple-cache
plugin, unless the simple-cache
plugin is available in one of the configured repositories.
Moving on, you now need to update the album-art
plugin to use the CacheService
provided by the simple-cache
plugin. Listing 13-36 shows the changes made to the AlbumArtService
highlighted in bold.
Listing 13-36. Updating the AlbumArtService to Use the simple-cache Plugin
class AlbumArtService {
...
def cacheService
String getAlbumArt(String artist, String album) {
...
def key = new AlbumArtKey(album:album, artist:artist)
return cacheService.cacheOrReturn(key) {
try {
def request = new ItemSearchRequest()
...
def response = client.itemSearch(request)
// get the URL to the amazon image (if one was returned)
return response.items[0].item[0].largeImage.URL
}
catch(Exception e) {
log.error "Problem calling Amazon: ${e message}", e
return DEFAULT_ALBUM_ART_IMAGE
}
}
...
}
}
The changes in Listing 13-36 will cause the tests for the AlbumArtService
to fail with a NullPointerException
because the cacheService
is null
within the context of the test. Instead of using a real implementation in the unit test, you can use duck typing to specify a mock implementation using Groovy's Map
literal syntax, as shown in Listing 13-37.
Listing 13-37. Mocking the cacheService
albumArtService.cacheService = [cacheOrReturn:{key, callable-> callable() }]
Groovy allows maps, where the value of a given key is a closure, to act as if they are callable methods. In the example in Listing 13-37, by providing a cacheOrReturn
key, you are able to mock the methods of the CacheService
.
To spice things up even further, you're going to do a bit of metaprogramming, first by adding a getAlbumArt
method to all controllers and second by allowing instances of the Album
class from the gTunes application to retrieve their art simply by calling a getArt()
method. The first case, in Listing 13-38, shows the necessary code, which just gets the AlbumArtService
instance and adds a method to all controllers that delegates to the AlbumArtService
.
Listing 13-38. Adding a getAlbumArt Method to All Controllers
class AlbumArtGrailsPlugin {
...
def doWithDynamicMethods = { ctx ->
def albumArtService = ctx.getBean("albumArtService")
application.controllerClasses
*.metaClass
*.getAlbumArt = { String artist, String album ->
return albumArtService.getAlbumArt(artist, album)
}
}
}
Adding a getArt()
method to the Album
class is a little trickier, because the plugin doesn't know anything about the Album
class. So to implement this enhancement, you'll search the GrailsApplication
instance for a domain class called Album
and, if it exists, add the getArt()
method to it. Listing 13-39 shows the modifications to the doWithDynamicMethods
plugin hook.
Listing 13-39. Adding a getAlbumArt Method to All Controllers
class AlbumArtGrailsPlugin {
...
def doWithDynamicMethods = { ctx ->
...
def albumClass = application.domainClasses.find { it.shortName == 'Album' }
if(albumClass) {
albumClass.metaClass.getArt ={->
albumArtService.getAlbumArt( delegate.artist?.name,
delegate.title)
}
}
}
}
Notice how within the body of the new getArt
method you can use the closure delegate
to obtain the artist
and title
. The delegate
property of a closure, when used in this context, is equivalent to referring to this
in a regular method. With the code in Listing 13-39 in place, you can now obtain the URL to an Album
instance's album art with the code shown in Listing 13-40.
Listing 13-40. Using the getArt() Method to Obtain Album Art
def album = Album.get(10)
println "The art for this album is at ${album.art}"
Note that, in Groovy, methods that follow bean conventions are accessible via the property access notation, so the expression album.art
is equivalent to album.getArt()
. And with that, you have completed the album-art
plugin that can now be installed into any application that has a requirement to look up album art. The gTunes application is one such application. However, before you can install the album-art
plugin, you need to install the simple-cache
plugin that the album-art
plugin is dependent on into the gTunes application:
$ grails install-plugin ../simple-cache/grails-simple-cache-0.1.zip
With that done, install the album-art
plugin next:
$ grails install-plugin ../simple-cache/grails-album-art-0.1.zip
Now you can start up the gTunes application, and it will behave exactly as before, except it is utilizing the album-art
plugin's functionality instead! One thing to note about the album-art
plugin is that although it provides new functionality in the form of services, tag libraries, and new methods, it does not comprise an entire self-contained application. We'll be looking at how you can achieve this in the next section.
As well as making it possible to extend the available APIs within a Grails application, plugins can also provide entire modules of application functionality. Many newcomers dismiss plugins as purely for plugin developers who are willing to jump into the core Grails APIs, but in fact, plugins are an extremely effective way to modularize your application. In this section, we'll explain how you can create an entire application as a plugin that can be installed into the gTunes application.
To keep things simple, you'll tackle a very commonly demonstrated application in screen-casts and presentations around Grails: the blog. Yes, as with any self-respecting modern Web 2.0 application, the gTunes application needs a blog where the proprietors of the gTunes store can make big announcements about new music, events, and so on. Luckily, a simple blog takes about five minutes to implement in Grails, so it shouldn't be too complicated.
The first step is to run the create-plugin
command to create the blog plugin:
$ grails create-plugin blog
This will create the blog
plugin and associated BlogGrailsPlugin
descriptor. You can populate the descriptor with some plugin metadata; Listing 13-41 shows a sample blog
plugin descriptor.
Listing 13-41. Adding Metadata to the blog Plugin
class BlogGrailsPlugin {
def version = 0.1
def author = "Graeme Rocher"
def authorEmail = "[email protected]"
def title = "A blogging plugin"
def description = 'A plugin that provides a blog facility'
}
Now it's time to create a domain class that models a blog post:
$ grails create-domain-class com.g2one.blog.Post
After these two commands are complete, you should have a directory structure similar to that pictured in Figure 13-5.
Figure 13-5. The Post domain class
Thinking about the Post
domain class for a moment, it's going to have the obvious things like a title and a body, as well as a date posted. Putting this into practice, Listing 13-42 shows the Post
domain class containing the necessary properties.
Listing 13-42. The Post Domain Class
package com.g2one.blog
class Post {
String title
String body
Date dateCreated
Date lastUpdated
static constraints = {
title blank:false
body type:"text", blank:false
}
}
Note that the Post
domain class is using the property names dateCreated
and lastUpdated
to take advantage of Grails' auto time stamping capabilities that were first discussed in Chapter 10. With an appropriate domain class in place, to help you get started, you can use scaffolding to quickly generate a controller and views for the Post
domain class:
$ grails generate-all com.g2one.blog.Post
For this first revision of the blog plugin, you're going to support the creation of new entries only; hence, you can remove the generated edit, update
, and delete
actions. In addition, you need to show only the first five posts; therefore, you can use the max
parameter to the static list
method of the Post
class to specify that. Listing 13-43 shows the full code for the PostController
.
Listing 13-43. The PostController for the blog Plugin
package com.g2one.blog
class PostController {
def index = { redirect(action:list,params:params) }
def allowedMethods = [save:'POST']
def list = {
[ postList: Post.list( max:5) ]
}
def create = {
[post: new Post(params) ]
}
def save = {
def post = new Post(params)
if(!post.hasErrors() && post.save()) {
flash.message = "Post ${post.id} created"
redirect(action:list)
}
else {
render(view:'create',model:[post:post])
}
}
}
Now let's move onto the views. In the case of the blog
plugin, the list.gsp
view is the most important because it will be responsible for showing each blog entry. However, Grails' default scaffolding displays the list view as a table, which is not very useful in this case. You can correct that by modifying the list.gsp
view to render a _post.gsp
template instead. Listing 13-44 shows the updated list.gsp
code.
Listing 13-44. The blog Plugin's list.gsp View
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="${params.layout ?: 'main'}" />
<title>Post List</title>
</head>
<body>
<div class="nav">
<span class="menuButton">
<g:link class="create" action="create">New Post</g:link>
</span>
</div>
<div class="blog">
<h1>${grailsApplication.config.blog.title ?: 'No Title'}</h1>
<g:render plugin="blog"
template="post"
var="post"
collection="${postList?.reverse()}" />
</div>
</body>
</html>
There are a few key things to mention about the list.gsp
view in Listing 13-44. First, note that when using the <g:render>
tag to render a template in a plugin view, you must specify the plugin that this template belongs to; otherwise, Grails will attempt to resolve the template within the application it is installed into. Second, take note of the usage of the grailsApplication
variable to specify the blog title:
<h1>${grailsApplication.config.blog.title ?: 'No Title'}</h1>
Here the implicit grailsApplication
object is used to read a configuration setting from the grails-app/conf/Config.groovy
file. If the setting called blog.title
is specified in Config.groovy
, then the view will use that. Hence, users of this plugin are able to configure the blog to their needs. An alternative approach to doing this would be to use the <g:message>
tag, in which case the plugin user has to specify the message in the grails-app/i18n/ messages.properties
file. The choice is up to you.
Finally, take note of the HTML <meta>
tag that dictates what layout the list.gsp
uses:
<meta name="layout" content="${params.layout ?: 'main'}" />
What this does is if there is a layout
parameter within the params
object, it will use that for the layout; otherwise, use the main
layout. The main
layout will, of course, resolve to grails-app/views/layouts/main.gsp
, but why the decision to allow customization via a parameter? The idea here is that the user of the plugin can very easily customize the layout of the blog through URL mappings. For example, consider the URL mapping in Listing 13-45.
Listing 13-45. Using a URL Mapping to Customize the blog Plugin's Layout
"/blog"(controller:"post", action:"list") {
layout = "funky"
}
If you add the URL mapping in Listing 13-45 to your grails-app/conf/UrlMappings.groovy
file, users can go to the /blog
URL and have the list
action of the PostController
execute, which in turn renders the list.gsp
view. However, notice how a property called layout
is set inside the body of the closure passed to the URL mapping definition. As you learned in Chapter 6, it is possible to pass parameters in this way. The result is that for the /blog
mapping, a layout called grails-app/views/layouts/funky.gsp
will be used instead! This is a pretty powerful pattern because it allows you to apply a different layout simply by applying a new URL mapping to the same controller and action.
As for the _post.gsp
template used in the <g:render>
method of Listing 13-44, it is pretty simple and just formats each Post
instance appropriately. You can see the code for the _post.gsp
template in Listing 13-46.
Listing 13-46. The _post.gsp Template
<div id="post${post.id}" class="blogPost">
<h2>${post.title}</h2>
<div class="body">
${post.body}
</div>
<div class="desc">
Posted on <g:formatDate date="${post.dateCreated}"
format="dd MMMMMM yy" />
</div>
</div>
And with that, you have pretty much completed the list.gsp
view. Figure 13-6 shows what the list.gsp
view looks like when you run the blog
plugin and head off to the list
action of the PostController
.
Figure 13-6. The list view of the blog plugin
Since the view renders each Post
directly in the list.gsp
view, the show.gsp
view has been made redundant and can be deleted. Also, for the first revision, you're interesting in creating new posts only, so edit.gsp
can be deleted too—you can always add editing later!
Moving on to the create.gsp
view, it too could use a little cleaning up. Also, it would be nice to provide a rich text–editing capability for authoring the post. One of the plugins available for Grails is the fckeditor
plugin, which adds support for FCKeditor (http://www.fckeditor.net/), a rich text–editing component. To install the fckeditor
plugin into the blog
plugin, run the following command:
$ grails install-plugin fckeditor
In addition to this, you need to update the BlogGrailsPlugin
descriptor and add a dependsOn
setting to ensure that when others install the blog
plugin, FCKeditor is resolved too. Listing 13-47 shows the dependsOn
set appropriately.
Listing 13-47. Making the blog Plugin Depend on the fckeditor Plugin
class BlogGrailsPlugin {
def dependsOn = [fckeditor:'0.8 > *']
...
}
With that done, let's enable FCKeditor in create-gsp
by using the <fck:editor>
tag provided by the fckeditor
plugin. Listing 13-48 shows the updated create.gsp
file with the usage of the <fck:editor>
tag highlighted in bold. You will notice the logical name printed when you ran the blog
plugin with grails run-app
. Grails prints out a message such as this:
Loading with installed plug-ins: ["fckeditor", "blog"]
Listing 13-48. Using the fckeditor to Enable Rich Text Editing
<html>
...
<body>
<h1>Create Post</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<g:hasErrors bean="${post}">
<div class="errors">
<g:renderErrors bean="${post}" as="list" />
</div>
</g:hasErrors>
<g:form action="save" method="post" >
<div class="dialog">
<div id="titleField">
<label for="title">Title:</label>
<g:textField name="title"
value="${fieldValue(bean:post,field:'title')}"/>
</div>
<div id="bodyField">
<fck:editor name="body"
width="500"
height="300"
toolbar="Basic">
${fieldValue(bean:post,field:'body')}
</fck:editor>
</div>
</div>
<div class="buttons">
<span class="button">
<input class="save" type="submit" value="Post" />
</span>
</div>
</g:form>
</body>
</html>
Using the toolbar
attribute of the <fck:editor>
tag, you can specify that you want only a simple toolbar with basic formatting options; otherwise, you'll get a toolbar with almost as many options as a word processor like Microsoft Word. Figure 13-7 shows the create.gsp
view with the <fck:editor>
tag doing the job of rendering a rich text–editing component.
Figure 13-7. Creating a post with FCKeditor
Of course, both the list.gsp
and create.gsp
pages currently look rather uninspiring, but it is up to the application you install the blog
plugin into to provide useful style information via CSS. Speaking of installing the blog
plugin into an application, it is time to do exactly that! First package up the blog
plugin by running the package-plugin
command:
$ grails package-plugin
Then navigate to the gTunes application, and use install-plugin
to install the blog
plugin:
$ grails install-plugin ../blog/grails-blog-0.1.zip
Note how, in this case, since the FCKeditor plugin exists in the Grails central repository, the install-plugin
command will automatically resolve the dependency. Now it would be useful to configure the blog's title using the grails-app/conf/Config.groovy
file. Remember, the blog.title
setting allows you to customize the blog title; simply adding the following setting to Config.groovy
will do the trick:
// configuration for the blog
blog.title="The gTunes Weblog"
Run the gTunes application using the run-app
command, and then navigate to the URL http://localhost:8080/gTunes/post/list. Like magic, you have the blog
plugin running inside the gTunes application exactly as it was before—except that it is now taking advantage of the gTunes application's main layout. Clicking the "New Post" button will take you to the create.gsp
view you developed earlier. Figure 13-8 shows the FCKeditor component running within the gTunes application.
Figure 13-8. Creating blog posts in the gTunes application
If you type some content, including a title and body, and then hit the "Post" button, you're able to create new posts on the gTunes application blog, as shown in Figure 13-9.
Figure 13-9. A blog post in the gTunes application
Clearly, this is a very basic blog plugin at the moment with no support for RSS, comments, calendars, archives, and all that jazz. However, as a demonstration of the concept of using plu-gins to separate your application in reusable modules, it's a perfect example. A separate team of developers could happily work on the blog
plugin and gradually integrate its functionality into the primary application over time. You could even create an automated build, as you learned in Chapter 12, to build and test all your plugins and install them into your main application for integrating testing. So, plugins are definitely worth a look, even if you don't intend to become an expert on Grails internals.
In this chapter, we hope you have learned the power of the Grails plugin system not just for plugins that provide API enhancements but equally for use cases that provide fully functional application modules like you saw in the previous section. Plugin development is a very broad topic, and this chapter only brushed the surface of what is possible; however, this chapter has given you enough knowledge to investigate developing your own plugins.
From the basics of creating and populating plugin metadata to the intricacies of developing the plugin itself and finally to the packaging and distribution of your plugins, this chapter has covered a lot of ground. As you have seen, Grails provides a broad set of functionality out of the box that can be extended without limits through its plugin system.
One thing you will have noticed during the development of the blog
plugin in the previous section is that at the moment it allows pretty much anyone to post. Clearly, this is not desirable in the long term, so in the next chapter, we'll cover how you can refactor the simple security implementation in the gTunes application into one of the more fully featured security plugins that are available. Role-based security, here we come!