CHAPTER 3

More Advanced Groovy

Chapters 1 and 2 offered a glimpse into the power and capabilities of Groovy by providing a basic understanding of its language features and tools. But there is far more to know about Groovy. For example, Groovy provides an ideal framework for creating unit tests. It makes working with XML simple and straightforward, and it includes a great framework for templating text. Finally, Groovy has a meta programming model that you can use to do amazing things, such as enabling the creation of domain-specific languages and adding methods and functionality to the Java API classes, including classes that are marked as final, which prevents them from being extended in Java.

This chapter covers a variety of unrelated or loosely related advanced Groovy topics. It starts off by showing you how to use Groovy to write and execute unit tests, then it compares how to process XML documents with both Java and Groovy. The next section explains how you can use Groovy's templating to generate e-mails. The chapter concludes with three meta programming topics: implementing Expando classes, extending classes with Meta Object Protocol (MOP), and creating domain-specific languages (DSLs).

Groovy Unit Testing

One of Groovy's best value propositions is unit testing. Using Groovy to unit-test Groovy or Java code can make the code easier to read and maintain. Unit testing is a common way to introduce Groovy to an organization, because it doesn't affect the production runtime. Once developers and managers get comfortable with Groovy in a testing capacity, they eventually begin using it in production.

Unit testing is so fundamental to Groovy that it's built right in. You don't need to download a separate framework. Groovy already includes and extends JUnit,1 which is a popular Java unit-testing framework. The primary extension is groovy.util.GroovyTestCase, which inherits from junit.framework.TestCase and adds the additional assert methods found in Table 3-1.

__________

Table 3-1. GroovyTestCase Assert Methods

Assert Method Description
assertArrayEquals Asserts two arrays are equal and contain the same values
assertContains Asserts an array of characters contains the given characters or an array of ints contains a given int
assertEquals Asserts two Objects or two Strings are equal
assertInspect Asserts the value of the inspect() method
assertLength Asserts the length of char, int, or Object arrays
assertScript Asserts script runs without any exceptions being thrown
assertToString Asserts the value of toString()

JUnit (and therefore Groovy unit testing) works by creating a class that inherits from TestCase or one of its descendants. GroovyTestCase is the appropriate class to extend for unit testing in Groovy. Notice that GroovyTestCase is found in the, groovy.util package, so it is implicitly available and doesn't even require any imports. Tests can then be added by creating methods that have a name that begins with test and is followed by something descriptive about the test. For example, you could use testAlphaRanges for a test that validates the Groovy language feature of ranges. These test methods should take no parameters and return void. Unlike with JUnit tests written in Java, these methods don't have to declare exceptions that could be thrown, because Groovy naturally converts all checked exceptions into unchecked exceptions. This makes tests more readable than the equivalent Java implications.

Unit tests often require objects to be put into a known state. In addition, tests should be good test-harness citizens and clean up after themselves. Like JUnit tests, all Groovy tests can override the setUp and tearDown methods.

Unit tests are also a great way to learn new frameworks, libraries, and languages such as Groovy. You can use unit tests to validate your understanding of how they work. Listing 3-1 is a unit test used to validate some assumptions about Groovy ranges, including whether a range from 'a'..'z' contains uppercase letters and whether ranges can be concatenated together.

Listing 3-1. Example Unit Test That Validates Assumptions About Groovy Ranges

01 class RangeTest extends GroovyTestCase {
02
03   def lowerCaseRange = 'a'..'z'
04   def upperCaseRange = 'A'..'Z'
05
06   void testLowerCaseRange() {
07     assert 26 == lowerCaseRange.size()
08     assertTrue(lowerCaseRange.contains('b'))
09     assertFalse(lowerCaseRange.contains('B'))
10   }
11
12   void testUpperCaseRange() {
13     assert 26 == upperCaseRange.size()
14     assertTrue(upperCaseRange.contains('B'))
15     assertFalse(upperCaseRange.contains('b'))
16   }
17
18   void testAlphaRange() {
19     def alphaRange = lowerCaseRange + upperCaseRange
20     assert 52 == alphaRange.size()
21     assert alphaRange.contains('b')
22     assert alphaRange.contains('B')
23   }
24 }

Listing 3-1 shows a unit test that extends from GroovyTestCase and contains two variables that include a range of lowercase letters on line 3 and a range of uppercase letters on line 4. The test case also contains three tests. The first test, shown on lines 6–10, asserts that the range has a size of 26, representing each of the letters in lowercase. It also asserts that a lowercase 'b' is in the range but that an uppercase 'B' is not. The second test, shown on lines 12–16, is basically the same test but uses the uppercase range. The third test, on the other hand, validates that the two ranges can be concatenated together to produce a new range that includes both. Therefore, the new range is twice the size and includes both the lowercase 'b' and the uppercase 'B'.

Running a Groovy unit test is just like running a script. To run this test, execute the following:

> groovy RangeTest

Because a JUnit test runner is built into Groovy, the results of the tests are printed to standard out. The results identify how many tests ran, how many failed, and how many errors occurred. Failures indicate how many tests did not pass the assertions, and errors indicate unexpected occurrences such as exceptions. In addition, because GroovyTestCase extends JUnit, you can easily integrate the Groovy tests into automated test harnesses such as Apache Ant2 and Apache Maven3 builds so they can be run continually.

__________

Working with XML

Extensible Markup Language (XML)4 is a general-purpose markup language commonly used in enterprise applications to persist or share data. Historically, creating and consuming XML documents has been easier than working with other types of formats, because XML is text-based, follows a standard, is in an easily parsable format, and features many existing frameworks and libraries to support reading and writing documents for many different programming languages and platforms. Most of these frameworks, however, are based on the World Wide Web Consortium's (W3C's)5 Document Object Model (DOM),6 which can cause the code that manipulates XML documents to become difficult to write and read. Due to the popularity and complexity of working with XML, Groovy includes a framework that uses XML in a natural way. The next section demonstrates how complicated it is to write simple XML with standard Java code, then shows you how to process XML in the simple and elegant Groovy way.

Writing XML with Java

Generating a simple XML document like the one found in Listing 3-2 in Java is difficult, time consuming, and a challenge to read and maintain.

Listing 3-2. Simple XML Output for To-Dos

<todos>
  <todo id="1">
    <name>Buy Beginning Groovy and Grails</name>
    <note>Purchase book from Amazon.com for all co-workers.</note>
  </todo>
</todos>

Listing 3-3 shows the minimum Java code necessary to generate the XML shown in Listing 3-2.

Listing 3-3. Java Code to Generate the Simple To-Do XML Found in Listing 3-2

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Result;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;

/**
 * Example of generating simple XML in Java.
 */
public class GenerateXML {
  public static void main (String[] args)
      throws ParserConfigurationException, TransformerException {
    DocumentBuilder builder =
      DocumentBuilderFactory.newInstance().newDocumentBuilder();
    Document doc = builder.newDocument();

    Element todos = doc.createElement("todos");
    doc.appendChild(todos);

    Element task = doc.createElement("todo");
    task.setAttribute("id", "1");
    todos.appendChild(task);

    Element name = doc.createElement("name");
    name.setTextContent("Buy Beginning Groovy and Grails");
    task.appendChild(name);

    Element note = doc.createElement("note");
    note.setTextContent("Purchase book from Amazon.com for all co-workers.");
    task.appendChild(note);

    // generate pretty printed XML document
    TransformerFactory tranFactory = TransformerFactory.newInstance();
    Transformer transformer = tranFactory.newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty(
        "{http://xml.apache.org/xslt}indent-amount", "2");

    Source src = new DOMSource(doc);
    Result dest = new StreamResult(System.out);
    transformer.transform(src, dest);
  }
}

__________

Notice how difficult it is to read Listing 3-3. It begins by using DocumentBuilderFactory to create a new DocumentBuilder. With DocumentBuilder, the newDocument() factory method is called to create a new Document. Elements are created using Document's factory methods, configured by adding attributes or text content, and then finally appended to their parent element. Notice how difficult it is to follow the natural tree structure of the XML document by looking at the Java code. This is partly because most elements require three lines of code to create, configure, and append the element to its parent.

Finally, outputting the XML into a human-readable nested format isn't straightforward. Much like creating the document itself, it begins by getting a TransformerFactory instance and then using the newTransformer() factory method to create a Transformer. Then the transformer output properties are configured to indent, and the indent amount is configured. Notice that the indent amount isn't even standard. It uses an Apache Xalan7–specific configuration, which may not be completely portable. Ultimately, a source and result are passed to the transformer to transform the source DOM into XML output.

Groovy Builders

Groovy simplifies generating XML by using the concept of builders, based on the Builder design pattern from the Gang of Four.8 Groovy builders implement a concept of Groovy-Markup, which is a combination of Groovy language features such as MOP (discussed later in the chapter), closures, and the simplified map syntax to create nested tree-like structures. Groovy includes five major builder implementations, as defined in Table 3-2. They all use the same format and idioms, so knowing one builder pretty much means you'll be able to use them all.

__________

8. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Boston, MA: Addison-Wesley Professional, 1994).

Table 3-2. Groovy Builders

Name Description
AntBuilder Enables the script and execution of Apache Ant tasks
DOMBuilder Generates W3C DOMs
MarkupBuilder Generates XML and HTML
NodeBuilder Creates nested trees of objects for handling arbitrary data
SwingBuilder Creates Java Swing UIs (discussed in detail in Chapter 13)

In general, you start using a builder by creating an instance of the builder. Then you call a named closure to create the root node, which could represent a root XML element, a Swing component, or a specific builder-appropriate node. You add nodes by nesting more named closures. This format makes it easy to read the hierarchical structures.

You add attributes by using Groovy's map syntax to pass name-value pairs into the named closures. Under the covers, MOP interprets the message passed to the object, usually to determine which method to invoke. When it realizes there is no method by that name, it creates the associated node or attribute.

Writing XML with Groovy MarkupBuilder

As noted in Table 3-2, you use MarkupBuilder to create XML and HTML. Listing 3-4 shows the Groovy code in action for creating the XML shown in Listing 3-2.

Listing 3-4. Groovy Code to Generate the Simple To-Do XML Found in Listing 3-2

01 def writer = new StringWriter()
02 def builder = new groovy.xml.MarkupBuilder(writer)
03 builder.setDoubleQuotes(true)
04 builder.todos {
05     todo (id:"1") {
06         name "Buy Beginning Groovy and Grails"
07         note "Purchase book from Amazon.com for all co-workers."
08     }
09 }
10
11 println writer.toString()

Looking at Listing 3-4, you can see how much easier it is to read than the Java equivalent shown in Listing 3-3. The example begins by creating StringWriter, so the final XML can be printed to system out on line 11. Then MarkupBuilder is created using StringWriter. By default, MarkupBuilder uses single quotes for attribute values, so line 3 changes the quotes to double quotes to comply with the XML specification. Lines 4–9 actually build the XML document using named closures and map syntax. You can easily see that todos contains a todo with an id attribute of 1 and nested name and note elements.

Reading XML with XmlSlurper

Groovy makes reading XML documents equally as easy as writing XML documents. Groovy includes the XmlSlurper class, which you can use to parse an XML document or String and provide access to a GPathResult. With the GPathResult reference, you can use XPath9-like syntax to access different elements in the document.

Listing 3-5 shows how to use XmlSlurper and GPath to interrogate a todos XML document.

Listing 3-5. Reading XML in Groovy

01 def todos = new XmlSlurper().parse('todos.xml')
02 assert 3 == todos.todo.size()
03 assert "Buy Beginning Groovy and Grails" == todos.todo[0].name.text()
04 assert "1" == todos.todo[0][email protected]()

Listing 3-5 begins by using XmlSlurper to parse a todos.xml file containing three todo items. Line 2 asserts there are three todos in the document. Line 3 shows how to access the value of an element, while line 4 shows how to access the value of an attribute using @.

Generating Text with Templates

Many web applications generate text for e-mails, reports, XML, and even HTML. Embedding this text in code can make it difficult for a designer or business person to maintain or manage. A better method is to store the static portion externally as a template file and process the template when the dynamic portions of the template are known.

As shown in Table 3-3, Groovy includes three template engines to make generating text easier.

__________

Table 3-3. Groovy Template Engines

Name Description
SimpleTemplateEngine Basic templating that uses Groovy expressions as well as JavaServer Pages (JSP) <% %> script and <%= %> expression syntax
GStringTemplateEngine Basically the same as SimpleTemplateEngine, except the template is stored internally as a writable closure
XmlTemplateEngine Optimized for generating XML by using an internal DOM

The SimpleTemplateEngine usually is appropriate for most templating situations. Listing 3-6 shows an HTML e-mail template that we'll use in Chapter 11 for sending e-mail during a nightly batch process.

Listing 3-6. HTML E-mail Template Found in nightlyReportsEmail.gtpl

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
  <title>
    Collab-Todo Nightly Report for
    ${String.format('%tA %<tB %<te %<tY', date)}
  </title>
</head>

<body bgcolor="#FFFFFF" style="margin:0;padding:0;">
  <div style="padding: 22px 20px 40px 20px;background-color:#FFFFFF;">
    <table width="568" border="0" cellspacing="0" cellpadding="1"
        bgcolor="#FFFFFF" align="center">
      <tr>
        <td>
          Dear ${user?.firstName} ${user?.lastName},
          <p />
          Please find your attached nightly report for
          ${String.format('%tA %<tB %<te %<tY', date)}.
        </td>
      </tr>
    </table>
    <!-- static HTML left out for brevity -->
  </div>
</body>
</html>

The template in Listing 3-6 is mostly HTML with a couple of Groovy expressions thrown in for the dynamic portions of the e-mail, such as formatting the date and user's name. The e-mail produces the image shown in Figure 3-1.

image

Figure 3-1. HTML e-mail

To process the template, you create an instance of a template engine and use the overloaded createTemplate() method, passing it a File, Reader, URL, or String containing the template text to create a template. Now with the template loaded and parsed, you call the make() method, passing it a map that binds with the variables in the template that are based on the names in the map. Listing 3-7 shows what the code that generates the e-mail in Figure 3-1 looks like.

Listing 3-7. E-mail Template-Processing Code

01 import groovy.text.SimpleTemplateEngine
02
03 /**
04 * Simple User Groovy Bean.
05 */
06 class User {
07  String firstName;
08  String lastName;
09 }
10
11 def emailTemplate = this.class.getResource("nightlyReportsEmail.gtpl")
12 def binding = [
13    "user": new User(firstName: "Christopher", lastName: "Judd"),
14    "date": new Date()
15 ]
16 def engine = new SimpleTemplateEngine()
17 def email = engine.createTemplate(emailTemplate).make(binding)
18 def body = email.toString()
19
20 println body

Listing 3–7 begins by importing the SimpleTemplateEngine on line 1 so you have access to it on line 16. Lines 6–9 declare a simple User GroovyBean. The template is loaded from the nightlyReportsEmail.gtpl file found on the classpath on line 11. It contains the template text found in Listing 3-6. Lines 12–15 create the map containing the passed user and date data, which will be bound to the template when the template is processed. SimpleTemplateEngine, created on line 16, is used on line 17 to create and process the template.

Expandos

There are times in an application when you need an object to hold data or behaviors, but it is not used enough to warrant creating an entire class definition for it. For example, the User GroovyBean from Listing 3-7 is used simply to pass data to the template engine; it provides no other value, so it's a great candidate for an Expando object.

The Expando class is found in the groovy.util package and is a dynamically expandable bean, meaning you can add properties or closures to it at runtime. Combine this with Groovy's duck typing, and Expandos are also a great way to implement mock objects during unit testing.


Note Duck typing refers to the concept that if it walks like a duck and talks like a duck, it must be a duck. In Groovy speak, if an object has properties and methods similar to another object, the two objects must be of the same type.


The best way to understand Expandos is to see them in action. Listing 3-8 shows code that creates the User GroovyBean from Listing 3-7 using Expandos rather than a concrete class.

Listing 3-8. Alternative to the User GroovyBean

01 def user = new Expando()
02
03 user.firstName = 'Christopher'
04 user.lastName = 'Judd'
05
06 user.greeting = { greeting ->
07   "${greeting} ${firstName} ${lastName}"
08 }
09
10 assert user.greeting("Hello") == 'Hello Christopher Judd'

Listing 3-8 uses an Expando as a replacement for a concrete GroovyBean class. On line 1, a new instance of the Expando is created. On lines 3 and 4, the firstName and lastName properties are added to the object dynamically and assigned values. Properties are not the only things that you can add. You can also add behaviors using closures. Lines 6–8 create and assign a closure to greeting that concatenates the greeting parameter with the properties for the first and last names. Finally, line 10 executes greeting.

You can also initialize Expandos with properties using the overloaded constructor that takes a map. Listing 3-9 uses this technique to reimplement the template example found in Listing 3-7.

Listing 3-9. Template Example Using Expandos Instead of User GroovyBean

import groovy.text.SimpleTemplateEngine

def emailTemplate = this.class.getResource("nightlyReportsEmail.gtpl")
def binding = [
        "user": new Expando([ firstName: 'Christopher', lastName:'Judd']),
        "date": new Date()
]
def engine = new SimpleTemplateEngine()
def template = engine.createTemplate(emailTemplate).make(binding)
def body = template.toString()

println body

Notice that Listing 3-9 reduces the amount of code and increases the readability of the earlier template example by replacing the User class definition with Expando, which contains the firstName and lastName properties.

Meta Object Protocol

Another important concept to Groovy and Grails is Meta Object Protocol (MOP). This protocol enables you to add properties and behaviors to existing classes, much like the Expando class discussed in the previous section. The capability of MOP to extend classes doesn't just exist for classes you write or Groovy classes. MOP enables you to add functionality to the Java API, including classes such as java.lang.String, which is marked as final to prevent standard Java classes from extending it and adding functionality. This is how the Groovy JDK10 provides more Groovy idiomatic behavior to the standard Java classes. You will learn in Chapter 6 how Grails uses this technique to add amazing persistence features to your domain classes.

__________


Note An in-depth explanation of the entire MOP and dispatch mechanism is beyond the scope of a book aimed toward beginners. However, this section provides a brief introduction to the topic, so you can understand how parts of the Grails framework might be implemented.


In Groovy, all classes—including all Java classes—have a property of metaClass of type groovy.lang.MetaClass, similar to how all Java classes have a property of class of type java.lang.Class. The groovy.lang.MetaClass interface is similar to the Expando object in that you can add behavior at runtime. During the dispatching of a message to an object, the MetaClass helps to determine which behavior should be invoked. In addition, you can use the MetaClass to provide behavior if the class doesn't implement the specific behavior requested. This is how the builders discussed earlier in the chapter work. When you add closures to the builder, MetaClass interprets this as a call to a missing method and provides the specific builder functionality. For example, it might add an XML element to the enclosing parent element.

In the previous template examples, a template URL was found for the template file on the classpath using the following line, which by now may seem a little more like Java than Groovy:

def emailTemplate = this.class.getResource("nightlyReportsEmail.gtpl")

Listing 3-10 shows how MOP could make this a little more Groovy by adding the getResourceAsText() method to java.lang.Class, which actually loads the file and gets the contents of the file as text rather than just the URL to the file.

Listing 3-10. Adding the getResourceAsText() Method to java.lang.Class

01 import groovy.text.SimpleTemplateEngine
02
03 Class.metaClass.getResourceAsText = { resource ->
04   this.class.getResourceAsStream(resource).getText()
05 }
06
07 def emailTemplate = this.class.getResourceAsText('nightlyReportsEmail.gtpl')
08 def binding = [
09         "user": new Expando([ firstName: 'Christopher', lastName:'Judd']),
10         "date": new Date()]
11 def engine = new SimpleTemplateEngine()
12 def template = engine.createTemplate(emailTemplate).make(binding)
13 def body = template.toString()
14
15 println body

Notice how line 7 expresses much more explicitly that it is loading the template as text and not as a URL. This is accomplished by extending the final java.lang.Class on lines 3–5 by accessing the metaClass property and adding the getResourceAsText() method that takes a parameter of resource, which is the name of a file on the classpath. The implementation of this method found on line 4 uses the getResourceAsStream() technique to load a file as a stream. This is generally safer than using a URL, because not everything is easily addressable with a URL. The closure then finishes by using the getText() method, which Groovy includes in the Groovy JDK on all java.io.InputStreams by means of MOP. Finally, line 7 shows what a call to the getResourceAsText() method would look like on java.lang.Class.

There are additional ways in which you might want to do the same thing and make it a little more expressive. Listing 3-11 shows another implementation of doing the same thing by adding behavior to java.lang.String, which is also a class marked as final.

Listing 3-11. Adding the fileAsString() Method to java.lang.String

01 String.metaClass.fileAsString = {
02   this.class.getResourceAsStream(delegate).getText()
03 }
04
05 println 'nightlyReportsEmail.gtpl'.fileAsString()

Listing 3-11 begins by adding the fileAsString() method to the metaClass property of the java.lang.String class, similar to the previous example. However, it uses a delegate variable instead of a passed-in parameter. The delegate is a reference to the object instance of which the method was called, which in this case would be the String containing the file name to be loaded. Notice how nicely line 5 reads. It is almost like reading an English sentence. The next section continues with a technique for making code easier to read.

Domain-Specific Languages

With the realization that code is read more frequently than it is written and the popularity of more expressive and flexible languages such as Groovy, domain-specific languages (DSLs)—languages written to solve a problem using the particular problem's vernacular—are becoming increasingly popular.

For example, using Groovy's optional parameters and MOP, you can turn this code that only a programmer can love:

println this.class.getResourceAsStream('readme.txt').getText()

into:

write 'readme.txt'.contents()

Notice that with the second option, even a nonprogrammer has a chance of understating the intent of the code.

Listing 3-12 shows how to implement this simple DSL for writing files.

Listing 3-12. Implementation of a Simple DSL

01 String.metaClass.contents = {
02   this.class.getResourceAsStream(delegate).getText()
03 }
04
05 def write = { file ->
06   println file
07 }
08
09 write 'readme.txt'.contents()

Lines 1–3 use the same metaprogramming implementation from the previous section to add a contents closure to the String class. The contents closure loads a file from the classpath as text based on the value of the String. Lines 5–7 implement a closure named write that simply does a println on whatever is passed as a parameter. This ultimately enables line 9 to read like a sentence when the optional parentheses for the write call are not included.

Summary

Combined with the Groovy topics from the previous chapters, the Advanced Groovy topics in this chapter—such as unit testing, XML processing, templates, Expandos, and meta-programming—have prepared you for developing web-based applications using the Groovy-based Grails framework that is the focus of the remainder of the book.


Note You can explore many other advanced topics in Groovy. To learn more, check out the Groovy documentation11 or Groovy in Action12 by Dierk Koenig.


__________

12. Dierk Koenig with Andrew Glover, Paul King, Guillaume Laforge, and Jon Skeet, Groovy in Action (Greenwich, CT: Manning Publications, 2007), http://www.manning.com/koenig/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset