Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
50
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

82

CHAPTER 4 Using Groovy features in Java

That works just fine. It’s when I try to do the same thing in Java that I run into problems. Again, the compiler understands, but I’ve never been able to coax my IDE into believing that the factory class has a public static field called instance in it.

Still, the annotation works and the IDEs will eventually understand how to deal with it. In fact, all the cool new AST transformations work, and I encourage you to consider them significant shortcuts to writing applications.

There are other AST transformations available and more being written all the time. I encourage you to keep an eye on them in case one comes along that can simplify your code the same way the ones just discussed do.

As cool as AST transformations are, though, our last task is so much easier to do in Groovy than in Java that it practically sells Groovy to Java developers all by itself. That issue is parsing and generating XML.

4.5Working with XML

Way back in the late 90s, when XML was young, new, and still popular (as hard to imagine as that may be now), the combination of XML and Java was expected to be a very productive one. Java was the portable language (write once, run anywhere, right?), and XML was the portable data format. Unfortunately, if you’ve ever tried working with XML through the Java built-in APIs you know the results have fallen far short of the promise. Why are the Java APIs for working with XML so painful to use?

Here’s a trivial example. I have a list of books in XML format, as shown here:

<books>

<book isbn="...">

<title>Groovy in Action</title> <author>Dierk Koenig</author> <author>Paul King</author>

...

</book>

<book isbn="...">

<title>Grails in Action</title> <author>Glen Smith</author> <author>Peter Ledbrook</author>

</book>

<book isbn="...">

<title>Making Java Groovy11</title> <author>Ken Kousen</author>

</book>

</books>

Now assume that my task is to print the title of the second book. What could be easier? Here’s one Java solution, based on parsing the data into a document object model (DOM) tree and finding the right element:

11 I had to find some way to include my book in that august company, just to bask in the reflected glory.

www.it-ebooks.info

Working with XML

83

public class ProcessBooks {

public static void main(String[] args) { DocumentBuilderFactory factory =

DocumentBuilderFactory.newInstance(); Document doc = null;

try {

DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.parse("src/jag/xml/books.xml");

}catch (ParserConfigurationException e) { e.printStackTrace();

}catch (SAXException e) { e.printStackTrace();

}catch (IOException e) { e.printStackTrace();

}

if (doc == null) return;

NodeList titles = doc.getElementsByTagName("title"); Element titleNode = (Element) titles.item(1);

String title = titleNode.getFirstChild().getNodeValue(); System.out.println("The second title is " + title);

}

}

This is actually the short version of the required program. To make it any shorter I’d have to collapse the exception handling into catching just Exception, or add a throws clause to the main method.

Many APIs in Java are designed around a set of interfaces, with the assumption that there will be many different alternative implementations. In the Java API for XML Processing (JAXP) world there are many parsers available, so the API is dominated by interfaces. Of course, you can’t instantiate an interface, so using the API comes down to factories and factory methods.

To parse the XML file using a simple DOM parser, therefore, I first need to acquire the relevant factory, using its newInstance method. Then I use the factory method newDocumentBuilder, which is admittedly a really good name for a factory method. Parsing the file is then done through the parse method, as expected. Inside the DOM parser the tree is constructed using, interestingly enough, a SAX parser, which is why I need to prepare for SAX exceptions.

Assuming I get that far, the result at that point is a reference to the DOM tree. Finding my answer by traversing the tree is quite frankly out of the question. Traversals are highly sensitive to the presence of white-space nodes, and the available methods (getFirstChild, getNextSibling, and the like) aren’t really a direct method to my answer. If whoever put together the XML file had been kind enough to assign each element an ID I could have used the great getElementByID method to extract the node I need, but no such luck there. Instead I’m reduced to collecting the relevant nodes using getElementsByTagName, which doesn’t return something from the Collections framework as you might expect, but a NodeList instead. The NodeList class has an item method that takes an integer representing the zero-based index of the node I want, and at long last I have my title node.

www.it-ebooks.info

84

CHAPTER 4 Using Groovy features in Java

Then there’s the final indignity, which is that the value of a node is not the character content I want. No, I have to retrieve the first text child of the node, and only then can I get the value, which returns the text I needed.

XML and Groovy

I was once teaching a class about Java and XML, and one of the exercises was to extract a nested value. After taking the students through the awkward, ugly, Java solution, a woman in the back raised her hand.

“I kept waiting for you to say, ‘this is the hard way,’” she said, “and now here’s the easy way, but you never got to the easy way.”

In reply I had to say, “Want to see the easy way? Let’s look at the Groovy solution to this problem.”

def root = new XmlSlurper().parse('books.xml') println root.book[1].title

How’s that for easy? I instantiated an XmlSlurper, called its parse method on the XML file, and just walked the tree to the value I want.

If I ever need to parse or generate XML I always add a Groovy module to do it.

Let’s look at another, somewhat more practical, example. Remember the Google geocoder used in chapter 3? When the geocoder went to version 3, Google removed the requirement to register for a key (good) but also removed the CSV output type (unfortunate). Now the only available output types are either JSON or XML. Google also changed the URL for accessing the web service (pretty typical when versioning a web service, actually), embedding the two available output types into the new URLs. In chapter 9 on RESTful web services I’ll have a lot more to say about the choice of output types (formally known as content negotiation), but here the type is embedded in the URL.

From a Java point of view, working with JSON output is a bit of a complication because it requires an external library to parse the JSON data. That’s not too much of a burden because there are several good JSON libraries available, but you still have to pick one and learn to use it. We’ve already talked about how involved it is to work with XML data in Java, so that’s not a great alternative either.

Groovy, however, eats XML for lunch. Let’s see just how easy it is for Groovy to access the new geocoder and extract the returned latitude and longitude data.

First, here’s a sample of the XML output returned from the web service for the input address of Google’s home office:

<GeocodeResponse>

<status>OK</status>

<result> <type>street_address</type>

<formatted_address>1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA</ formatted_address>

www.it-ebooks.info

Working with XML

85

...

<geometry>

<location>

<lat>37.4217550</lat> <lng>-122.0846330</lng>

</location>

...

</geometry>

</result>

</GeocodeResponse>

A lot of child elements have been omitted from this response in order to focus on what I actually want. The latitude and longitude values are buried deep inside the output. Of course, digging to that point is easy enough for Groovy. Here’s a script that creates the required HTTP request, transmits it to Google, and extracts the response, all in less than a dozen lines:

String street = '1600 Ampitheatre Parkway' String city = 'Mountain View'; state = 'CA'

String base = 'http://maps.google.com/maps/api/geocode/xml?' String url = base + [sensor:false,

address:[street, city, state].collect { v -> URLEncoder.encode(v,'UTF-8')

}.join(',')].collect {k,v -> "$k=$v"}.join('&') def response = new XmlSlurper().parse(url)

latitude = response.result[0].geometry.location.lat longitude = response.result[0].geometry.location.lng

The code strongly resembles the version 2 client presented earlier, in that I have a base URL for the service (note that it includes the response type, XML, as part of the URL) and a parameters map that I convert into a query string. Transmitting the request and parsing the result is done in one line of code, because the XmlSlurper class has a parse method that takes a URL. Then extracting the latitude and longitude is simply a matter of walking the tree.

Several times I’ve written applications that took this script, after converting it to a class that used a Location like before, and added it as a service. The code savings over the corresponding Java version is just too great to ignore.

Parsing is one thing, but what about generation? For that, Groovy provides a builder class called groovy.xml.MarkupBuilder.

Consider another POJO representing a Song, as shown here:

public class Song { private int id; private String title; private String artist; private String year;

public Song() {}

public Song(int id, String title, String artist, String year) { this.id = id;

this.title = title;

www.it-ebooks.info

86 CHAPTER 4 Using Groovy features in Java

this.artist = artist; this.year = year;

}

public int getId() { return id; }

public void setId(int id) { this.id = id; } public String getTitle() { return title; }

public void setTitle(String title) { this.title = title; } public String getArtist() { return artist; }

public void setArtist(String artist) { this.artist = artist; } public String getYear() { return year; }

public void setYear(String year) { this.year = year; }

}

The Song class, implemented in Java, contains an id and strings for the title, artist, and year. The rest is just constructors, getters, and setters. In a real system the class would also probably have overrides of toString, equals, and hashCode, but I don’t need that here.

How should Song instances be represented in XML? One simple idea would be to treat the ID as an attribute of the song, and have title, artist, and year as child elements. In the following listing I show part of a Groovy class that converts Song instances to XML and back.

Listing 4.10 Converting songs to XML and back

class SongXMLConverter { String song2xml(Song s) {

StringWriter sw = new StringWriter() MarkupBuilder builder = new MarkupBuilder(sw) builder.song(id:s.id) {

title s.title artist s.artist year s.year

}

return sw.toString()

}

Output buffer for XML

Script the XML using Groovy builder

Song xml2song(String xml) {

def root = new XmlSlurper().parseText(xml) return new Song(id:root.@id.toInteger(),

title:root.title, artist:root.artist, year:root.year)

}

String songlist2xml(songs) { StringWriter sw = new StringWriter()

MarkupBuilder builder = new MarkupBuilder(sw) builder.songs {

songs.each { s -> song(id:s.id) {

title s.title artist s.artist year s.year

}

}

Script the XML using Groovy builder

www.it-ebooks.info

Working with XML

87

}

return sw.toString()

}

List<Song> xml2songlist(String xml) { def result = []

def root = new XmlSlurper().parseText(xml) root.song.each { s ->

result << new Song(id:s.@id.toInteger(),title:s.title, artist:s.artist,year:s.year)

}

return result

}

}

The SongXMLConverter class has four methods: one to convert a single song to XML, one to convert XML to a single song, and two to do the same for a collection of songs. Converting from XML to Song instances is done with the XmlSlurper illustrated earlier. The only new part is that the slurper accesses the song ID value using the @id notation, where the @ is used to retrieve an attribute. Figure 4.7 shows the job of the XmlSlurper, or its analogous class, XmlParser.

Going the other direction, from song to XML, is done with a MarkupBuilder. The MarkupBuilder class writes to standard output by default. In this class I want to return the XML as a string, so I used the overloaded MarkupBuilder constructor that takes a java.io.Writer as an argument. I supply a StringWriter to the constructor, build the XML, and then convert the output to a String using the normal toString method.

Once I have a MarkupBuilder I write out the song’s properties as though I was building the XML itself. Let’s focus on the conversion of a single song to XML form, as shown next:

MarkupBuilder builder = new MarkupBuilder(sw) builder.song(id:s.id) {

title s.title artist s.artist year s.year

}

The job of the MarkupBuilder is illustrated in figure 4.8.

This is an example of Groovy’s metaprogramming capabilities, though it doesn’t look like it at first. The idea is that inside the builder, whenever I write the name of a

<song id="...">

 

 

 

 

 

Song

 

 

 

 

 

 

<title>...</title>

 

 

 

 

 

int id

 

 

XmlSlurper /

 

 

<artist>...</artist>

 

 

 

 

 

 

XmlParser

 

 

String title

<year>...</year>

 

 

 

 

 

 

 

 

 

String artist

</emp>

 

 

 

 

 

 

 

 

 

 

String year

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 4.7 Using an XmlSlurper or XmlParser to populate an object from XML data

www.it-ebooks.info

88

 

 

 

CHAPTER 4 Using Groovy features in Java

 

 

 

 

 

 

 

 

Song

 

 

 

 

<song id="...">

 

 

 

 

 

 

 

int id

 

 

 

 

<title>...</title>

 

 

 

MarkupBuilder

 

 

 

 

 

<artist>...</artist>

 

String title

 

 

 

 

 

 

 

 

<year>...</year>

 

String artist

 

 

 

 

 

 

 

 

 

</emp>

 

String year

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 4.8 Generating an XML representation of an object using a groovy.xml.MarkupBuilder

method that doesn’t exist, the builder interprets it as an instruction to create an XML element. For example, I invoke the song method on the builder with the argument being a map with a key called id and a value being the song’s ID. The builder doesn’t have a song method, of course, so it interprets the method call as a command to build an element called song, and the argument is an instruction to add an id attribute to the song element whose value is the song ID. Then, when it encounters the curly brace it interprets that as an instruction to begin child elements.

I have three more method calls: one to title, one to artist, and one to year. The lack of parentheses can be misleading in this case, but each is actually a method call. Once again the builder interprets each of the non-existent methods as commands to create XML elements, and the arguments this time, because they’re not in map form, become character data contained in the elements. The result of the builder process is the XML shown next:

<song id="..."> <title>...</title> <artist>...</artist> <year>...</year>

</song>

The method that converts a list of songs into a larger XML file just does the same thing for each song.

Lessons learned (XML)

1Groovy’s XmlParser and XmlSlurper make parsing XML trivial, and values can be extracted by walking the resulting DOM tree.

2Generating XML is just as easy, using MarkupBuilder.

GROOVY SWEET SPOT Groovy is excellent at parsing and generating XML. If your Java application works with XML, strongly consider delegating to a Groovy module.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]