- •Credits
- •About the Authors
- •About the Reviewers
- •www.PacktPub.com
- •Table of Contents
- •Preface
- •Introduction
- •Installing Groovy on Windows
- •Installing Groovy on Linux and OS X
- •Executing Groovy code from the command line
- •Using Groovy as a command-line text file editor
- •Running Groovy with invokedynamic support
- •Building Groovy from source
- •Managing multiple Groovy installations on Linux
- •Using groovysh to try out Groovy commands
- •Starting groovyConsole to execute Groovy snippets
- •Configuring Groovy in Eclipse
- •Configuring Groovy in IntelliJ IDEA
- •Introduction
- •Using Java classes from Groovy
- •Embedding Groovy into Java
- •Compiling Groovy code
- •Generating documentation for Groovy code
- •Introduction
- •Searching strings with regular expressions
- •Writing less verbose Java Beans with Groovy Beans
- •Inheriting constructors in Groovy classes
- •Defining code as data in Groovy
- •Defining data structures as code in Groovy
- •Implementing multiple inheritance in Groovy
- •Defining type-checking rules for dynamic code
- •Adding automatic logging to Groovy classes
- •Introduction
- •Reading from a file
- •Reading a text file line by line
- •Processing every word in a text file
- •Writing to a file
- •Replacing tabs with spaces in a text file
- •Deleting a file or directory
- •Walking through a directory recursively
- •Searching for files
- •Changing file attributes on Windows
- •Reading data from a ZIP file
- •Reading an Excel file
- •Extracting data from a PDF
- •Introduction
- •Reading XML using XmlSlurper
- •Reading XML using XmlParser
- •Reading XML content with namespaces
- •Searching in XML with GPath
- •Searching in XML with XPath
- •Constructing XML content
- •Modifying XML content
- •Sorting XML nodes
- •Serializing Groovy Beans to XML
- •Introduction
- •Parsing JSON messages with JsonSlurper
- •Constructing JSON messages with JsonBuilder
- •Modifying JSON messages
- •Validating JSON messages
- •Converting JSON message to XML
- •Converting JSON message to Groovy Bean
- •Using JSON to configure your scripts
- •Introduction
- •Creating a database table
- •Connecting to an SQL database
- •Modifying data in an SQL database
- •Calling a stored procedure
- •Reading BLOB/CLOB from a database
- •Building a simple ORM framework
- •Using Groovy to access Redis
- •Using Groovy to access MongoDB
- •Using Groovy to access Apache Cassandra
- •Introduction
- •Downloading content from the Internet
- •Executing an HTTP GET request
- •Executing an HTTP POST request
- •Constructing and modifying complex URLs
- •Issuing a REST request and parsing a response
- •Issuing a SOAP request and parsing a response
- •Consuming RSS and Atom feeds
- •Using basic authentication for web service security
- •Using OAuth for web service security
- •Introduction
- •Querying methods and properties
- •Dynamically extending classes with new methods
- •Overriding methods dynamically
- •Adding performance logging to methods
- •Adding transparent imports to a script
- •DSL for executing commands over SSH
- •DSL for generating reports from logfiles
- •Introduction
- •Processing collections concurrently
- •Downloading files concurrently
- •Splitting a large task into smaller parallel jobs
- •Running tasks in parallel and asynchronously
- •Using actors to build message-based concurrency
- •Using STM to atomically update fields
- •Using dataflow variables for lazy evaluation
- •Index
Chapter 9
How it works...
Every class in the class loader has a reference to an object of type metaClass. This metaClass maintains the list of all methods and properties of a given class, starting with the bytecode information and adding the additional methods that Groovy knows about by default (DefaultGroovyMethods). Normally, all instances of a class share the same metaClass. However, Groovy allows per instance metaclasses, that is, different instances of a class that may refer to different metaclasses.
The first example in Step 1 is nothing more than calling getClass().getName() on a Java class; therefore, piggybacking on the Java reflection capabilities. The remaining examples are more interesting because they leverage the dynamic properties of the Groovy language through the metaClass attribute. The respondsTo method, in particular, is useful when writing a dynamic code; for example, populating an arbitrary object from some data on a file.
See also
ff http://groovy.codehaus.org/api/groovy/lang/MetaClass.html
ff http://groovy.codehaus.org/api/org/codehaus/groovy/runtime/ DefaultGroovyMethods.html
Dynamically extending classes with new methods
One of the exciting characteristics of Groovy is Meta Object Protocol (MOP). In a nutshell, the term metaprogramming refers to writing code that can dynamically change its behavior at runtime. A Meta Object Protocol refers to the capabilities of a dynamic language that enable metaprogramming. In this recipe, we are going to look at one of the capabilities of the MOP, which is ExpandoMetaClass.
Groovy's ExpandoMetaClass lets you assign behavior and state to classes at runtime without editing the original source code; it is essentially a layer above the original class. In the next section of the recipe, we will show you how to achieve such a result.
293
www.it-ebooks.info
Metaprogramming and DSLs in Groovy
How to do it...
Adding a new method to a Groovy (or Java) class is straightforward. We are going to perform the following given steps to add a getInEuros method to the BigDecimal Java class in order to convert US dollars to euros:
1.In a new Groovy script, type the following code: import java.text.NumberFormat
BigDecimal.metaClass.getInEuros = { -> def exchangeRate = 0.763461
def nf = NumberFormat.getCurrencyInstance(Locale.US) nf.setCurrency(Currency.getInstance('EUR')) nf.format(delegate * exchangeRate)
}
2.Now, we are ready to test the conversion rate: assert 1500.00.inEuros == 'EUR1,145.19'
How it works...
Each Groovy object has an accompanying instance of a class named ExpandoMetaClass (reachable through the metaClass property) that holds a reference to the methods that can be called on an object, including:
ff The base methods that the type allows
ff More methods added for that type by Groovy (such as, find on collections) ff Methods added at runtime, using metaClass
Every time a method on an object is invoked, a dynamic dispatcher mechanism is used to delegate to the companion ExpandoMetaClass.
The getInEuros closure, which we have added to the BigDecimal class, contains only the US dollar/euro conversion code. The only aspect worth mentioning is the delegate variable, which refers to the object on which we call the method.
It is important to note that a method added to a class using ExpandoMetaClass will effectively modify the class across all the threads of the application; therefore, it is not a local modification. If you need to modify only a single instance of a class, you can access the metaClass property on an instance, as in the following code snippet:
class Customer { Long id String name
294
www.it-ebooks.info
Chapter 9
String lastName
}
def c = new Customer() c.metaClass.fullName { "$name $lastName" } c.name = 'John'
c.lastName = 'Ross'
assert c.fullName() == 'John Ross'
There's more...
ExpandoMetaClass can also be used to add:
ff Constructors to a class:
// defines a new constructor Customer.metaClass.constructor << {
String name -> new Customer(name: name)
}
def c = new Customer('John') assert 'John' == c.name
In this example, we use the left shift operator to append a new constructor. The << operator can be used for chaining constructors or methods, like in this example:
Customer.metaClass.constructor << { String name -> new Customer(name: name)
}<< { Long id, String fullName -> new Customer(
id: id,
name: fullName.split(',')[0], lastName: fullName.split(',')[1]
)
}
def c0 = new Customer('Mike') c0.name = 'Mike'
def c1 = new Customer(1000, 'Mike,Whitall') assert c1.name == 'Mike'
assert c1.lastName == 'Whitall'
295
www.it-ebooks.info
Metaprogramming and DSLs in Groovy
The Customer class has been enhanced with two concatenated constructors: the first accepting a string and the second accepting a Long and a string. The second constructor uses the split function to assign the name and last name of
the customer.
ff Static methods:
Customer.metaClass.'static'.sayHello = { -> "hello! I'm your customer"
}
assert "hello! I'm your customer" == Customer.sayHello()
ff Properties:
Customer.metaClass.gsm = null def c = new Customer()
c.gsm = '123456'
assert '123456' == c.gsm
See also
ff http://groovy.codehaus.org/ExpandoMetaClass
ff http://groovy.codehaus.org/api/groovy/lang/MetaClass.html
ff http://groovy.codehaus.org/api/groovy/lang/ExpandoMetaClass.html
Overriding methods dynamically
In the previous recipe, Dynamically extending classes with new methods, we learned how to dynamically add a method to a class through one of the metaprogramming features of Groovy,
named ExpandoMetaClass.
In this recipe, we will see how to intercept and replace a call to an existing method of a class. This technique can be very handy when writing unit tests for classes that have dependencies to other classes.
Getting ready
Let's introduce three classes for which we want to write a unit test:
ff Customer.groovy:
package org.groovy.cookbook class Customer {
String name
}
296
www.it-ebooks.info
Chapter 9
ff CustomerDao.groovy:
package org.groovy.cookbook class CustomerDao {
Customer getCustomerById(Long id) { // DO SOME DATABASE RELATED QUERY
...
}
}
ff CustomerService.groovy:
package org.groovy.cookbook class CustomerService {
CustomerDao dao
void setCustomerDao(CustomerDao dao) { this.dao = dao
}
Customer getCustomer(Long id) { dao.getCustomerById(id)
}
}
The fictional CustomerService class has a dependency towards CustomerDao that is somehow injected at runtime (dependency injection is a well-known pattern made famous by the ubiquitous Spring framework). The CustomerDao is a class that would normally access some kind of data store to retrieve the customer data. We are not interested in the details of how the DAO accesses the database. What we know is that unit testing the
CustomerService class would be very difficult without satisfying the hard dependency of the DAO and the database. In other words, the CustomerService class can be only tested if the database required by the DAO is actually running.
In the context of this recipe, the DAO's method getCustomerById does nothing; but in real life, it would contain code to query the database.
How to do it...
A unit test is all we need to put this simple mocking technique into practice:
1.Let's write a unit test for CustomerService, as follows: package org.groovy.cookbook
import static org.junit.Assert.* import org.junit.*
297
www.it-ebooks.info
Metaprogramming and DSLs in Groovy
class TestCustomerService { @Test
void testGetCustomer() { boolean daoCalled = false
CustomerDao.metaClass.getCustomerById = { Long id -> daoCalled = true
new Customer(name:'Yoda')
}
def cs = new CustomerService() def cDao = new CustomerDao() cs.setCustomerDao(cDao)
def customer = cs.getCustomer(100L) assertTrue(daoCalled) assertEquals(customer.name, 'Yoda')
}
}
How it works...
The unit test has the usual plumbing code to instantiate the class under test (CustomerService), and injects the CustomerDao into the service. Before that part,
we override the getCustomerById method by overwriting the method with our own closure. The intercept and replace mechanism is again based on the ExpandoMetaClass class and the metaprogramming features of Groovy. When the mocked method is invoked, the closure is triggered, and the local daoCalled variable gets updated.
A small detail that you should remember is you need to qualify the first argument with the type if the method has a typed parameter; otherwise Groovy won't override the behavior. These two examples would not work:
CustomerDao.metaClass.getCustomerById = { ->
...
}
CustomerDao.metaClass.getCustomerById = { id ->
...
}
298
www.it-ebooks.info