Since its inception, Mastering Grails has focused on core Grails functionality. The more you understand how the basic pieces fit together, the easier it is to combine them and build a sophisticated finished application. And although I have made passing mention of plug-ins here and there, I have consciously avoided digging into them in great detail. That is, until now.
Over the next several articles, I'll explore the Grails plug-in ecosystem with you. From its inception, the Grails platform was built with pluggability in mind. With this tiny but important emphasis, you can easily take advantage of literally hundreds of prebundled bits of functionality.
At the time of this writing, the Groovy script in Listing 1 returns 225 plug-ins. (For insight into how this script works, see ""Practically Groovy: Building, parsing, and slurping XML.")
Listing 1. Simple Groovy script counting the available Grails plug-ins
def addr = "http://plugins.grails.org/.plugin-meta/plugins-list.xml"
def plugins = new XmlSlurper().parse(addr)
def count = 0
plugins.plugin.each{
println it.@name
count++
}
println "Total number of plugins: ${count}"
|
To get a more a human-friendly list, you can type grails list-plugins at the command prompt or visit the Grails Plugins site (see Resources).
Seasoned Java™ developers are adept hunters and gatherers. They wouldn't dream of writing their own logging library; instead, they simply drop the log4j JAR onto their classpath. Need an XML parser? Add the Xerces JAR to your project. These pluggable bits of functionality demonstrate the reusability of object-oriented programming.
Grails plug-ins serve the same purpose, but on a larger scale. They can include many JARs, as well as GroovyServer Pages (GSPs), controllers, TagLibs, services, and more. Much as SiteMesh merges two GSPs into one, plug-ins effectively merge two or more Grails applications into one. You can focus on your core business requirements and mix in the additional functionality you need — searching, authentication, alternate presentation layers, and more — from external resources.
And plug-ins truly are external resources. The Grails development team has written several valuable plug-ins, but the vast majority come from the community. In fact, the Grails team continues to spin off core functionality into plug-ins where appropriate, making Grails itself tinier and more stable in each release.
How does this apply to Blogito — the "tiny little blog" application you are building in this series? Suppose that the next bit of functionality you want to add is local search capability. If you'd like to mix in an existing solution, rather than build your own search infrastructure from scratch, read on.
Installing the Searchable plug-in
The Searchable plug-in brings Google-like search capabilities to your application. It uses Apache Lucene to create the indices and Compass to hook indexing into the Grails Object Relational Mapping (GORM)/Hibernate life cycle (see Resources). This means that every time you create, update, or delete a domain class, the Lucene index is updated accordingly.
To install the plug-in, type grails install-plugin searchable. (The next section digs into the technical details of what happens when you install a plug-in.)
Then, add a single line — static searchable = true — to grails-app/domain/Entry.groovy, as shown in Listing 2:
Listing 2. Making the
Entry class searchable
class Entry {
static searchable = true
static constraints = {
title()
summary(maxSize:1000)
filename(blank:true, nullable:true)
dateCreated()
lastUpdated()
}
static mapping = {
sort "lastUpdated":"desc"
}
static belongsTo = [author:User]
String title
String summary
String filename
Date dateCreated
Date lastUpdated
}
|
Notice that you must explicitly make domain classes searchable. This means that you can
continue to keep infrastructure data such as the logins and passwords stored in the User class hidden from the public eye. (The online documentation for Searchable provides more information on specifying the classes and fields to be included in the index; see Resources.)
With that one line of code, you have added all of the power of Lucene and Compass to Blogito. Type grails run-app to start the application and then visit http://localhost:9090/blogito/searchable. Type a search term like grails, and look at the results, as shown in Figure 1:
Figure 1. Default search results
Clearly some results were found, but the hits are less than descriptive. To remedy this, add a toString() method to Entry.groovy, as shown in Listing 3:
Listing 3. Adding a
toString() to Entry
class Entry {
static searchable = true
//snip
String toString(){
"${title} (${lastUpdated})"
}
}
|
Search for grails once again. The results should be slightly more user-friendly, as shown in Figure 2:
Figure 2. The search results with a
toString() method
Now that you are convinced that the raw functionality of the Searchable plug-in is in place, you're ready to take the next step: deeply integrating it into your application.
Exploring the plug-ins infrastructure
Looking around the Blogito directories, there don't seem to be any new files. If you can visit http://localhost:9090/blogito/searchable in a Web browser, there should be a grails-app/controllers/SearchableController.groovy file. Oddly, it's not there. There should probably be some Lucene and Compass JARs in the lib directory, but it is as empty as when you first typed grails create-app to start the project. In fact, the only change to Blogito is a single new line in application.properties, as shown in Listing 4:
Listing 4. application.properties, showing the newly installed Searchable plug-in
#utf-8 #Wed Jun 24 15:41:16 MDT 2009 app.version=0.4 app.servlet.version=2.4 app.grails.version=1.1.1 plugins.searchable=0.5.5 plugins.hibernate=1.1.1 app.name=blogito |
Based on the plug-ins.searchable line, you know that Blogito knows about the Searchable plug-in. So where is all of the functionality hidden? To find out, you need only go back to the screen output that flashed by when you first installed the plug-in. I'll walk you through it in detail right now.
When you type grails install-plugin searchable, the first thing that happens is that a request goes out across the Web to pull down the latest lists of plug-ins. Listing 5 shows the details:
Listing 5. Downloading the master lists of plug-ins
$ grails install-plugin searchable
//snip
Reading remote plugin list ...
[get] Getting: http://svn.codehaus.org/grails/trunk/grails-plugins/
.plugin-meta/plugins-list.xml
[get] To: /Users/sdavis/.grails/1.1.1/plugins-list-core.xml
[get] last modified = Mon Jun 22 04:16:31 MDT 2009
Reading remote plugin list ...
[get] Getting: http://plugins.grails.org/.plugin-meta/plugins-list.xml
[get] To: /Users/sdavis/.grails/1.1.1/plugins-list-default.xml
[get] last modified = Wed Jun 24 06:51:24 MDT 2009
|
The two lists — core and default — provide metadata about the plug-ins, including author, description, and version number. Most important, this is where Grails discovers the URL for the ZIP file that actually contains the plug-in. Listing 6 shows information about the Hibernate plug-in from the plugins-list-core.xml file:
Listing 6. Describing the Hibernate plug-in
<plugins revision="9011">
<plugin latest-release="1.1.1" name="hibernate">
<release tag="RELEASE_1_1" type="svn" version="1.1">
<title>Hibernate for Grails</title>
<author>Graeme Rocher</author>
<authorEmail/>
<description>A plugin that provides integration between
Grails and Hibernate through GORM</description>
<documentation>http://grails.org/doc/$version</documentation>
<file>http://svn.codehaus.org/grails/trunk/grails-plugins/
grails-hibernate/tags/RELEASE_1_1/grails-hibernate-1.1.zip
</file>
</release>
<!-- snip -->
</plugin>
</plugins>
|
Currently, the Hibernate plug-in is the only one listed in the core plug-ins file. This list contains required plug-ins — functionality that Grails cannot run without. The default list contains optional plug-ins contributed by the community.
Did you notice in Listing 5 where these files are stored? There's a .grails directory created in your home directory (/Users/whoever on UNIX®-like systems, or C:\Documents and Settings\whoever on Windows®). This directory is where the compiled classes are stored when you type grails run-app. When you type grails clean, the application directory under projects is deleted. But as you can see, .grails is also where the downloaded plug-ins are stored. Open .grails/1.1.1/plugins-list-default.xml in a text editor and look for the entry for the Searchable plug-in. Listing 7 shows the details:
Listing 7. Describing the Searchable plug-in
<plugin latest-release="0.5.5" name="searchable">
<release tag="RELEASE_0_5_5" type="svn" version="0.5.5">
<title>Adds rich search functionality to Grails domain models.
This version is recommended for JDK 1.5+</title>
<author>Maurice Nicholson</author>
<authorEmail>maurice@freeshell.org</authorEmail>
<description>Adds rich search functionality to Grails domain models.
Built on Compass (http://www.compass-project.org/) and Lucene
(http://lucene.apache.org/)
This version is recommended for JDK 1.5+
</description>
<documentation>http://grails.org/Searchable+Plugin</documentation>
<file>http://plugins.grails.org/grails-searchable/
tags/RELEASE_0_5_5/grails-searchable-0.5.5.zip</file>
</release>
<!-- snip -->
</plugin>
|
Once Grails knows where the plug-in can be downloaded from, it (not surprisingly) downloads the requested plug-in to .grails/1.1.1/plugins, as shown in Listing 8:
Listing 8. Downloading the plug-in
$ grails install-plugin searchable //download core and default plugin lists // continued... [get] Getting: http://plugins.grails.org/grails-searchable/ tags/RELEASE_0_5_5/grails-searchable-0.5.5.zip [get] To: /Users/sdavis/.grails/1.1.1/plugins/grails-searchable-0.5.5.zip [get] last modified = Thu Jun 18 22:24:45 MDT 2009 |
Finally, Grails copies and unzips the plug-in from the local cache to your project, as shown in Listing 9:
Listing 9. Adding the plug-in to your project
$ grails install-plugin searchable
//download core and default plugin lists
//download requested plugin
// continued...
[copy] Copying 1 file to /Users/sdavis/.grails/1.1.1/projects/blogito/plugins
Installing plug-in searchable-0.5.5
[mkdir] Created dir:
/Users/sdavis/.grails/1.1.1/projects/blogito/plugins/searchable-0.5.5
[unzip] Expanding:
/Users/sdavis/.grails/1.1.1/plugins/grails-searchable-0.5.5.zip into
/Users/sdavis/.grails/1.1.1/projects/blogito/plugins/searchable-0.5.5
|
Before you get too much further, be sure this makes sense to you. The line in application.properties corresponds to the unzipped directory in your project directory in .grails. This means that to uninstall a plug-in, you can either type grails uninstall-plugin myplugin, or simply remove the line from application.properties and manually delete the directory from your project directory in .grails.
Knowing that plug-ins are passed around as simple ZIP files is also valuable. In the next article, I'll show you how to create your own plug-in and install it from a local ZIP file (grails install-plugin myplugin /local/path/to/myplugin.zip). You can even install a plug-in from a remote URL — grails install-plugin myplugin http://somewhere.com/myplugin.zip.
Exploring the Searchable plug-in
Now that you know where the Searchable plug-in is installed (.grails/1.1.1/projects/blogito/plugins/searchable-0.5.5), take a look around. The directory structure (shown in Figure 3) should seem familiar — plug-ins and applications share the same basic layout:
Figure 3. The directory structure of a layout
The SearchableController is right where you'd expect it to be: grails-app/controllers. Open the file in a text editor. Listing 10 shows a portion of the source code:
Listing 10.
SearchableController
import org.compass.core.engine.SearchEngineQueryParseException
class SearchableController {
def searchableService
def index = {
if (!params.q?.trim()) {
return [:]
}
try {
return [searchResult: searchableService.search(params.q, params)]
} catch (SearchEngineQueryParseException ex) {
return [parseException: true]
}
}
//snip
}
|
As you can see, the SearchableService is injected into the controller right after the class declaration. The familiar index action is the default target. If the q parameter isn't passed in, an empty hashmap is returned to grails-app/views/searchable/index.gsp. Based on logic in the view, this displays an empty page.
At around line 100 of index.gsp, you should find the form that sets the q parameter and recursively submits itself back to the index action. Listing 11 shows the form:
Listing 11. The searchable form in index.gsp
<g:form url='[controller: "searchable", action: "index"]'
id="searchableForm"
name="searchableForm"
method="get">
<g:textField name="q" value="${params.q}" size="50"/>
<input type="submit" value="Search" />
</g:form>
|
Looking back at Listing 10, you can see that once the q parameter has a search term in it, the result of the searchableService.search() call is returned to index.gsp. At around line 150 in index.gsp, the results are displayed, as shown in Listing 12:
Listing 12. Displaying the search results
<g:if test="${haveResults}">
<div class="results">
<g:each var="result" in="${searchResult.results}" status="index">
<div class="result">
<g:set var="className" value="${ClassUtils.getShortName(result.getClass())}" />
<g:set var="link"
value="${createLink(controller: className[0].toLowerCase() +
className[1..-1],
action: 'show',
id: result.id)}" />
<div class="name"><a href="${link}">${className} #${result.id}</a></div>
<g:set var="desc" value="${result.toString()}" />
<g:if test="${desc.size() > 120}">
<g:set var="desc" value="${desc[0..120] + '...'}" />
</g:if>
<div class="desc">${desc.encodeAsHTML()}</div>
<div class="displayLink">${link}</div>
</div>
</g:each>
</div>
<!-- snip -->
</g:if>
|
I encourage you to explore the Searchable plug-in in greater detail. Look at grails-app/services/SearchableService.groovy. Notice the Lucene and Compass JARs included in the lib directory. Take a walk through the src/java and src/groovy directories to see all of the supporting classes. Review the GroovyTestCases in the tests directory. All of the parts of a typical Grails application are right here in this plug-in.
Every time you install a new plug-in, you should take a cursory glance at the implementation. This will help you identify all of the moving parts, see how they work together, and — most important — give you hints as to how you can more deeply incorporate them into your application. In the next section, you'll see how to move search capabilities from the default implementation into custom components of your own.
Incorporating search deeper into Blogito
Here is how you can add your own search for Entries. To begin, open grails-app/controllers/EntryController.groovy in a text editor. Add a simple action named search, as shown in Listing 13. (Don't forget to allow unauthenticated users to search for blog entries by adding the search action to the beforeInterceptor.)
Listing 13. Adding the
search action
class EntryController {
def beforeInterceptor =
[action:this.&auth, except:["index", "list", "show", "atom", "search"]]
def search = {
render Entry.search(params.q, params)
}
//snip
}
|
The SearchableService, as demonstrated in the preceding section, is great for doing site-wide searches across all of your domain classes. But the Searchable plug-in also does a bit of metaprogramming on your individual domain classes. Just as Grails dynamically adds list(), get(), and findBy() methods, the Searchable plug-in adds a search() method.
Test your new search action by typing http://localhost:9090/blogito/entry/search?q=groovy into your Web browser. You should see a hashmap of results, much like those in Figure 4:
Figure 4. Displaying the raw search results
Now that you know that the search() method works, the next step is to make the user interface a bit more friendly. Create a partial template named _search.gsp in grails-app/views/layouts. Add the code in Listing 14:
Listing 14. The search partial template
<div id="search">
<g:form url='[controller: "entry", action: "search"]'
id="searchableForm"
name="searchableForm"
method="get">
<g:textField name="q" value="${params.q}" />
<input type="submit" value="Search" />
</g:form>
</div>
|
Notice that the controller is set to entry, and the action is set to search.
Next, it's time to display the partial template. Open grails-app/views/layouts/_header.gsp in a text editor and add a render tag, as shown in Listing 15:
Listing 15. Adding the search template to the header
<g:render template="/layouts/search" />
<div id="header">
<p><g:link class="header-main" controller="entry">Blogito</g:link></p>
<p class="header-sub">
<g:link controller="entry" action="atom">
<img src="${createLinkTo(dir:'images',file:'feed-icon-28x28.png')}"
alt="Subscribe" title="Subscribe"/>
</g:link>
A tiny little blog
</p>
<div id="loginHeader">
<g:loginControl />
</div>
</div>
|
Add a little bit of Cascading Style Sheets (CSS) to web-app/css/main.css to ensure that the search <div> floats in the upper right corner of the screen, as shown in Listing 16:
Listing 16. CSS to adjust the position of the search form
#search {
float: right;
margin: 2em 1em;
}
|
With all of the view changes in place, refresh your browser. Your screen should look like Figure 5:
Figure 5. Adding the search form to the header
The last thing you should do is render the search results in HTML instead of the simple debug output in place right now. Adjust the search action in EntryController as shown in Listing 17:
Listing 17. A more robust search action
def search = {
//render Entry.search(params.q, params)
def searchResults = Entry.search(params.q, params)
flash.message = "${searchResults.total} results found for search: ${params.q}"
flash.q = params.q
return [searchResults:searchResults.results, resultCount:searchResults.total]
}
|
Because the action is named search, you should create the corresponding search.gsp file, shown in Listing 18, in grails-app/views/entry:
Listing 18. Search.gsp
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Blogito</title>
</head>
<body>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<div class="body">
<div class="list">
<g:each in="${searchResults}" status="i" var="entry">
<div class="entry">
<h2>
<g:link action="show"
id="${entry.id}">${entry.title}</g:link>
</h2>
<p>${entry.summary}</p>
</div>
</g:each>
</div>
</div>
<div class="paginateButtons">
<g:paginate total="${resultCount}" params="${flash}"/>
</div>
</body>
</html>
|
Search for grails one last time in your Web browser. The results should look like those in Figure 6:
Figure 6. Pretty search results in HTML
Plug-ins are an exciting, vibrant part of the Grails ecosystem. They allow you to mix in a wide variety of preexisting functionality. Once you understand where the touchpoints are with your codebase (application.properties and the .grails directory), you can explore the source code to understand better how the plug-in author implemented the magic, as well as find inspiration for deeper integration with your own code.
Next time, I'll show you how to create a plug-in of your own. Until then, have fun mastering Grails.
Learn
-
Mastering
Grails: Read more in this series to gain a further understanding of Grails and all you can do with it.
-
Grails: Visit the Grails Web site.
-
Grails Plugins: Visit the Grails plug-in portal for information about the latest plug-ins available for the Grails framework.
-
Searchable: You'll find documentation for the Searchable plug-in here.
-
Grails 1.1 Release Notes: Dig more deeply into plug-in infrastructure.
-
Lucene and Compass: The Searchable plug-in is based on search software developed by these two projects.
-
Grails Framework Reference Documentation: The Grails bible.
-
Groovy Recipes (Scott Davis, Pragmatic Programmers, 2008): Learn more about Groovy and Grails in Scott Davis' latest book.
-
Practically Groovy: This developerWorks series is dedicated to exploring the practical uses of Groovy and teaching you when and how to apply them successfully.
-
Groovy: Learn more about Groovy at the project Web site.
-
AboutGroovy.com: Keep up with the latest Groovy news and article links.
-
Technology
bookstore: Browse for books on these and other technical topics.
-
developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.
Get products and technologies
Discuss
- Get involved in the
My developerWorks community.

Scott Davis is an internationally recognized author, speaker, and software developer. He is the founder of ThirstyHead.com, a Groovy and Grails training company. His books include Groovy Recipes: Greasing the Wheels of Java, GIS for Web Developers: Adding Where to Your Application, The Google Maps API, and JBoss At Work. He writes two ongoing article series for IBM developerWorks: Mastering Grails and Practically Groovy.





