Skip to main content

Mastering Grails: Understanding plug-ins

Mix in new functionality with ease

Scott Davis, Founder, ThirstyHead.com
Scott Davis
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.

Summary:  In this Mastering Grails installment, Scott Davis introduces you to the world of Grails plug-ins. Adding whole areas of new functionality to your applications couldn't be easier. You'll learn how plug-ins do their magic, and you'll use a plug-in to implement powerful search capabilities in the Blogito application.

View more content in this series

Date:  21 Jul 2009
Level:  Introductory PDF:  A4 and Letter (284KB | 18 pages)Get Adobe® Reader®
Activity:  18202 views
Comments:  

About this series

Grails is a modern Web development framework that mixes familiar Java technologies like Spring and Hibernate with contemporary practices like convention over configuration. Written in Groovy, Grails give you seamless integration with your legacy Java code while adding the flexibility and dynamism of a scripting language. After you learn Grails, you'll never look at Web development the same way again.

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).

What is a plug-in?

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

Digging deeper

You can find more information on the plug-in infrastructure in the release notes for Grails 1.1 (see Resources). You'll learn how to install plug-ins globally (so that every new project you create automatically includes the specific plug-ins), add alternate plug-in repositories to the list, limit plug-ins to run only in particular environments or only for specific Grails command-line scripts, and much more.

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


Conclusion

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.


Resources

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

  • Grails: Download the latest Grails release.

  • Blogito: Download the completed Blogito application.

Discuss

About the author

Scott Davis

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.

Comments



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development, Open source
ArticleID=415366
ArticleTitle=Mastering Grails: Understanding plug-ins
publish-date=07212009
author1-email=scott@thirstyhead.com
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers