Mastering Grails: Creating a custom plug-in

Sharing functionality among Grails applications

In this Mastering Grails installment, Scott Davis shows you how to create your own Grails plug-in. Once you see how effortless it is to create a plug-in, you'll understand why more than 250 Grails plug-ins are available now, with new ones being added all the time.

Scott Davis, Founder, ThirstyHead.com

Scott DavisScott 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.



15 September 2009

Also available in Chinese Russian Japanese Portuguese

Many articles in the Mastering Grails series have focused on intelligent code reuse. If you find yourself copying and pasting the same snippet of GroovyServer Pages (GSP) code in multiple places, you can create a partial template or a custom TagLib. If you find a method or two that are common among multiple controllers or domain classes, you can create an abstract parent class to extend or graft the methods on directly, using the ExpandoMetaClass. If you have some shared application functionality, you can refactor it out into a service or a custom codec.

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.

But all of that is at the micro level. What if you have some shared functionality at the macro level: something that requires the combined, coordinated effort of controllers and domain classes, services and codecs, and all of the other pieces of a typical Grails application? What I've just described is a plug-in.

In "Mastering Grails: Understanding plug-ins," you explored an existing plug-in: Searchable. More than 250 Grails plug-ins are available at the Grails Plugins portal (see Resources). That number keeps growing because extending your existing Grails application through plug-ins is an idea that is baked into the core of Grails. In this article, you'll learn how to build your own custom plug-in. The source code for the example plug-in is available for download.

Introducing the ShortenUrl plug-in

Testing, testing

Testing your Grails application is always important, and it's especially important when you create a plug-in. A bug in a plug-in can have an unfortunate multiplier effect, hurting each new Grails application that installs the plug-in. You'll see a heavy emphasis on testing in this article.

In this era of Twitter.com and cell-phone text messaging, the many long URLs that don't fit within the 140-character limit placed on messages can be a hassle. Thankfully, a number of URL-shortening Web services are out there practically begging to be integrated with Grails as a custom plug-in.

To create a custom plug-in, you must change your Grails routine slightly. Instead of typing grails create-app as you would normally, you must type grails create-plugin, as shown in Listing 1. (Be sure to type this command in a new, empty directory, not in an existing Grails directory. You'll learn how to integrate this new plug-in with an existing Grails application at the end of this article.)

Listing 1. Creating a custom plug-in
$ grails create-plugin shortenurl

The resulting directory structure is identical to a typical Grails application. However, in the root directory you'll see one new file that identifies this project as a plug-in: ShortenurlGrailsPlugin.groovy. Listing 2 shows a snippet:

Listing 2. The plug-in configuration file
class ShortenurlGrailsPlugin {
    // the plugin version
    def version = "0.1"
    // the version or versions of Grails the plugin is designed for
    def grailsVersion = "1.1.1 > *"
    // the other plugins this plugin depends on
    def dependsOn = [:]
    // resources that are excluded from plugin packaging
    def pluginExcludes = [
            "grails-app/views/error.gsp"
    ]

    // TODO Fill in these fields
    def author = "Your name"
    def authorEmail = ""
    def title = "Plugin summary/headline"
    def description = '''\\
Brief description of the plugin.
'''

    //snip
}

This file contains the metadata for your plug-in: the version number, the version of Grails your plug-in depends on, any other plug-ins that your plug-in depends on, and so forth. (Full details on configuration files are in the online documentation; see Resources.)

If you want to make this a public plug-in that other developers can download from the Plugins portal, you should fill in the author details and give it a compelling description. The file's contents are read and automatically displayed on the Grails Web site every time you check your plug-in into the public Subversion repository. (For more information on publishing your plug-in, see Resources.) In this article, you'll keep this a private plug-in, so filling in the author details is less important than it would be otherwise.

Even though the ShortenUrl plug-in doesn't require any changes to ShortenurlGrailsPlugin.groovy, that doesn't mean that you are finished. Now that you have the directory structure scaffolded out, the next step is to write the implementation.


Create the TinyUrl class

TinyUrl.com is a popular URL-shortening service. Once one person submits a long URL for shortening, it is saved behind the scenes as the official shortened URL for all subsequent requests. For example, visit the site, type in http://www.grails.org/The+Plug-in+Developers+Guide, and click the Make TinyURL! button. The resulting shortened URL — http://tinyurl.com/73495c — is half the length of the original, as you can see in Figure 1:

Figure 1. TinyURL.com shortening a URL
TinyURL.com shortening a URL

Now that you know how TinyURL.com works, it's time to focus on integrating the site's underlying service with the ShortenUrl plug-in. Type the following into your Web browser:

http://tinyurl.com/api-create.php?url=http://www.grails.org/The+Plug-in+Developers+Guide

This spartan Web service interface returns only the shortened URL — not the HTML — for the specified page.

The next step is to encapsulate your newfound knowledge in a Groovy class. This class is a Plain Old Groovy Object (POGO) in the truest sense of the word — it's not a service, a controller, or any other special-purpose Grails component. Therefore, the best place to put it is in src/groovy. Create an org/grails/shortenurl directory under src/groovy. Then create TinyUrl.groovy and add the code in Listing 3:

Listing 3. The TinyUrl utility class
package org.grails.shortenurl

class TinyUrl{
  static String shorten(String longUrl){
    def addr = "http://tinyurl.com/api-create.php?url=${longUrl}"
    return addr.toURL().text
  }
}

Packages in plug-ins

It's considered good practice to put the classes of a plug-in in a package. This dramatically lessens the chance of accidentally creating a conflict with an existing class in the user's Grails project.

You can also package domain classes, controllers, and so on. For simple projects, I feel that this less common practice adds an unnecessary level of complexity, but other seasoned Grails developers swear by it.


Testing the TinyUrl class

Before you put any code into production, it really should have a corresponding test, don't you think? Because you are making a live Web call, this should be an integration test. Create the same org/grails/shortenurl directory structure you created earlier under test/integration. Create TinyUrlTests.groovy and add the code in Listing 4. (Yes, I appreciate the irony that in this simple case the allegedly tiny URL is longer than the original URL it is encoding.)

Listing 4. Testing the TinyUrl class
package org.grails.shortenurl

class TinyUrlTests extends GroovyTestCase{
  def transactional = false

  void testShorten(){    
    def shortUrl = TinyUrl.shorten("http://grails.org")
    assertEquals "http://tinyurl.com/3xfpkv", shortUrl
  }
}

Note the def transactional = false line in the integration test. If you leave it out, you will be greeted with the nasty error message shown in Listing 5:

Listing 5. What happens if your test doesn't set def transactional = false
Error running integration tests: java.lang.RuntimeException: 
There is no test TransactionManager defined 
and integration test ${test.name} does not set transactional = false

Grails tries to wrap each test in a database transaction. In a normal Grails application, this is not an issue. But because you are in a plug-in instead of a full Grails application, you cannot assume that a database will be in place. You could install the Hibernate plug-in, or do as the error message tells you and set def transactional = false in your integration test.

Type grails test-app and verify that your test passes.

I'll implement one more URL-shortening service here, just to give the user of this plug-in a choice of services.


Creating the IsGd class

The Is.Gd (pronounced is good) service boasts a shorter domain name and shorter encoded URLs than TinyUrl.com. Visit http://is.gd to experiment with the Web interface.

In another nod to irony, I'll take this opportunity to show you a longer implementation of the two-line method I used in TinyUrl.groovy (see Listing 3). This implementation gives you a bit more information to react to if the call to the service fails. Create IsGd.groovy, shown in Listing 6, in src/groovy/org/grails/shortenurl:

Listing 6. The IsGd utility class
package org.grails.shortenurl

class IsGd{
  static String shorten(String longUrl){
    def addr = "http://is.gd/api.php?longurl=${longUrl}"
    def url = addr.toURL()
    def urlConnection = url.openConnection()
    if(urlConnection.responseCode == 200){
      return urlConnection.content.text
    }else{
      return "An error occurred: ${addr}\n" + 
      "${urlConnection.responseCode} : ${urlConnection.responseMessage}"
    }
  }
}

As you can see, you explicitly verify that the response code is 200 — the HTTP response code for OK. (See Resources for more on HTTP response codes.) For the sake of simplicity, I just return the error message if the call fails. But with this extended structure in place, you could make the method more robust by retrying the call a few more times or failing over to another URL-shortening service.

Create the corresponding IsGdTests.groovy file, shown in Listing 7, in the test/integration/org/grails/shortenurl directory. Type grails test-app and confirm that the IsGd class works as expected.

Listing 7. Testing the IsGd class
package org.grails.shortenurl

class IsGdTests extends GroovyTestCase{
  def transactional = false
  
  void testShorten(){
    def shortUrl = IsGd.shorten("http://grails.org")
    assertEquals "http://is.gd/2oCZR", shortUrl        
  }
  
  void testBadUrl(){
    def shortUrl = IsGd.shorten("IAmNotAValidUrl")
    println shortUrl
    assertTrue shortUrl.startsWith("An error occurred:")
  }
}

To see the details of exactly how the IsGd service fails when you pass in IAmNotAValidUrl, I recommend getting out to a command line and using curl, as shown in Listing 8. (The cURL utility is native on UNIX®/Linux®/Mac OS X, and downloadable for Windows®; see Resources.) Testing the bad URL in the browser allows you to see the error message, but not the error code. Using cURL, you can clearly see that the Web service returns a 500 code instead of the expected 200.

Listing 8. Using curl to see the details of the failed Web service class
$ curl --verbose "http://is.gd/api.php?longurl=IAmNotAValidUrl"
* About to connect() to is.gd port 80 (#0)
*   Trying 78.31.109.147... connected
* Connected to is.gd (78.31.109.147) port 80 (#0)
> GET /api.php?longurl=IAmNotAValidUrl HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3
                 OpenSSL/0.9.7l zlib/1.2.3
> Host: is.gd
> Accept: */*
> 
< HTTP/1.1 500 Internal Server Error
< X-Powered-By: PHP/5.2.6
< Content-type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 19 Aug 2009 17:33:04 GMT
< Server: lighttpd/1.4.22
< 
* Connection #0 to host is.gd left intact
* Closing connection #0
Error: The URL entered was not valid.

Now that the plug-in's core functionality is implemented and tested, you should create a convenience service that exposes the two utility classes in a Grails-friendly way.


Creating the ShortenUrl service

To create a service, type grails create-service ShortenUrl. Add the code in Listing 9 to grails-app/services/ShortenUrlService.groovy:

Listing 9. The ShortenUrl service
import org.grails.shortenurl.*

class ShortenUrlService {
    boolean transactional = false

    def tinyurl(String longUrl) {
      return TinyUrl.shorten(longUrl)
    }

    def isgd(String longUrl) {
      def shortUrl = IsGd.shorten(longUrl)
      if(shortUrl.contains("error")){
        log.error(shortUrl)
      }
      return shortUrl
    }
}

As with the previous integration tests, be sure to set the transactional flag to false. No database is involved in these calls, so there is no need to wrap them in a transaction.

Note that the isgd() method logs any attempt to shorten an invalid URL. All Grails artifacts are injected with a log object at run time. You can call methods on the log object that correspond to the desired log level: debug, info, error, and so on. (See Resources for more information on logging.) As you'll see in just a moment, dealing with this injected log object when writing unit tests requires an extra step.

When Grails created the service for you, it added the corresponding test to the test/unit directory. Normally I'd have you move ShortenUrlServiceTests.groovy to the test/integration directory, because it is semantically an integration test instead of a unit test — testing the service relies on external resources. Instead, leave it in the test/unit directory so that I can show you a couple of unit-testing tricks. Add the code in Listing 10 to ShortenUrlServiceTests.groovy:

Listing 10. Testing the ShortenUrl service
import grails.test.*

class ShortenUrlServiceTests extends GrailsUnitTestCase {
    def transactional = false
    def shortenUrlService
  
    protected void setUp() {
        super.setUp()
        shortenUrlService = new ShortenUrlService()
    }

    protected void tearDown() {
        super.tearDown()
    }

    void testTinyUrl() {
      def shortUrl = shortenUrlService.tinyurl("http://grails.org")
      assertEquals "http://tinyurl.com/3xfpkv", shortUrl
    }

    void testIsGd() {
      def shortUrl = shortenUrlService.isgd("http://grails.org")
      assertEquals "http://is.gd/2oCZR", shortUrl        
    }

    void testIsGdWithBadUrl() {
      def shortUrl = shortenUrlService.isgd("IAmNotAValidUrl")
      assertTrue shortUrl.startsWith("An error occurred:")
    }
}

Notice that after you set the transactional flag to false, you declare the shortenUrlService variable. You then initialize the service in the setUp() method. The setUp() and tearDown() methods are called for each test.

If this were an integration test, it would run without errors. But because it's a unit test, The testIsGdWithBadUrl() method fails with: No such property: log for class: ShortenUrlService. Open test/reports/html/index.html in your Web browser to see the error message shown in Figure 2:

Figure 2. Unit-test failure resulting from the injected log object
Unit-test failure resulting from the injected log object

As it turns out, the log object doesn't get injected into the service for unit testing. (Remember: Unit tests are meant to run in complete isolation.) Thankfully, you can get around this by adding a single line — mockLogging(ShortenUrlService) — to the setUp() method, as shown in Listing 11:

Listing 11. Mocking the injected log object
protected void setUp() {
    super.setUp()
    mockLogging(ShortenUrlService)
    shortenUrlService = new ShortenUrlService()
}

The mockLogging() method injects a mock log object into the service. This mock logger sends its output to System.out instead of to any of the defined log4j appenders. To see the output (shown in Figure 3), type grails test-app once again and click on the System.out link at the bottom of the HTML report page for ShortenUrlServiceTests.

Figure 3. Mock-logger output
Mock-logger output

You could bundle plenty of other Grails artifacts with this plug-in — a custom TagLib to shorten URLs in GSPs, a custom codec — but you've done enough already to get the idea of what a plug-in can offer. Next, you'll package this plug-in as it stands now and integrate it with another Grails project.


Packaging and deploying the plug-in

To prepare a full Grails application for deployment, you normally type grails war. For plug-ins, you type grails package-plugin instead. You should end up with a grails-shortenurl-0.1.zip file in your project's root directory.

Recall from "Mastering Grails: Understanding Plug-ins" that all Grails plug-ins are distributed as ZIP files. If you look in the .grails/1.1.1/plugins directory in your home directory, you should see similarly named plug-ins, such as grails-hibernate-1.1.1.zip and grails-searchable-0.5.5.zip.

If ShortenUrl were a public plug-in, you could type grails release-plugin to push your changes to the Grails Plugins portal. Anyone could then type grails install-plugin shortenurl to integrate it with their project. But you can install private plug-ins locally just as easily: all you need to do is supply the full path to the ZIP file on your local file system.

To test this out, create a new, empty directory outside of the shortenurl directory. Type grails create-app foo to create a simple application. Change to the foo directory and type grails install-plugin /local/path/to/grails-shortenurl-0.1.zip, of course substituting the real path to the plug-in. You should see something similar to the output shown in Listing 12:

Listing 12. Installing a local plug-in
$ grails install-plugin /code/grails-shortenurl-0.1.zip
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /opt/grails

Base Directory: /code/foo
Running script /opt/grails/scripts/InstallPlugin.groovy
Environment set to development
     [copy] Copying 1 file to /Users/sdavis/.grails/1.1.1/plugins
     Installing plug-in shortenurl-0.1
     [mkdir] Created dir: 
     /Users/sdavis/.grails/1.1.1/projects/foo/plugins/shortenurl-0.1
     [unzip] Expanding: 
     /Users/sdavis/.grails/1.1.1/plugins/grails-shortenurl-0.1.zip into 
     /Users/sdavis/.grails/1.1.1/projects/foo/plugins/shortenurl-0.1
Executing shortenurl-0.1 plugin post-install script ...
Plugin shortenurl-0.1 installed

As you can see, the plug-in life cycle for local, private plug-ins is identical to that of public plug-ins.

Open the foo/application.properties file in a text editor. Verify that plugins.shortenurl is listed, as shown in Listing 13:

Listing 13. Verifying that the plug-in appears in application.properties
#utf-8
#Wed Aug 19 14:38:24 MDT 2009
app.version=0.1
app.servlet.version=2.4
app.grails.version=1.1.1
plugins.hibernate=1.1.1
plugins.shortenurl=0.1
app.name=foo

Now that the plug-in is installed, you should verify that it works as expected. Type grails create-controller test. Open grails-app/controllers/TestController.groovy and add the code in Listing 14:

Listing 14. Injecting the service into a controller
class TestController {
    def shortenUrlService

    def index = { 
      render "This is a test for the ShortenUrl plug-in 
" + "Type test/tinyurl?q=http://grails.org to try it out." } def tinyurl = { render shortenUrlService.tinyurl(params.q) } }

Notice that def shortenUrlService injects the service into the controller. Type grails run-app to start the application. Visit http://localhost:9090/foo/test/tinyurl?q=http://grails.org in your Web browser. You should see results as shown in Figure 4:

Figure 4. Verifying successful plug-in installation
Verifying successful plug-in installation

And if you go to http://tinyurl.com/3xfpkv, sure enough you'll find yourself at grails.org.


Conclusion

As you can see, creating a Grails plug-in is not much different from creating a typical Grails application. Instead of typing grails create-app, you type grails create-plugin. Instead of typing grails war, you type grails package-plugin. And with the exception of adding the salient details to the GrailsPlugin.groovy descriptor file, all of the steps in between (creating services, writing tests, and so on) are identical.

In this article, I briefly touched on the mocking capabilities of Grails unit tests with the mockLogging() method. Next time, I'll demonstrate many other incredibly valuable mock methods: mockDomain(), mockForConstraintsTests(), and more. Until then, have fun mastering Grails.


Download

DescriptionNameSize
Source codej-grails09159.tar820KB

Resources

Learn

Get products and technologies

  • Grails: Download the latest Grails release.
  • cURL: cURL is installed by default on most UNIX, Linux(r), and Mac OS X systems. You can download a version for Windows and virtually every other OS.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development, Open source
ArticleID=428437
ArticleTitle=Mastering Grails: Creating a custom plug-in
publish-date=09152009