XML and WebServices with Groovy
Dr Paul King, ASERT: @paulk_asert, [email protected]
SpringOne2GX - 1
© A
SE
RT
2006-2
009
What is Groovy?
• “Groovy is like a super version
of Java. It can leverage Java's
enterprise capabilities but also
has cool productivity features like closures,
DSL support, builders and dynamic typing.”
Groovy = Java – boiler plate code+ optional dynamic typing+ closures+ domain specific languages+ builders+ metaprogramming
SpringOne2GX - 2
Growing Acceptance …
A slow and steady start but now gaining in
momentum, maturity and mindshare
Now free
… Growing Acceptance …
© A
SE
RT
2006-2
009
SpringOne2gx_Oct2009 - 4
… Growing Acceptance …
© A
SE
RT
2006-2
009
SpringOne2gx_Oct2009 - 5
Groovy and Grails downloads:
70-90K per month and growing
… Growing Acceptance …
© A
SE
RT
2006-2
009
SpringOne2gx_Oct2009 - 6
Source: http://www.grailspodcast.com/
Source: http://www.micropoll.com/akira/mpresult/501697-116746
… Growing Acceptance …
© A
SE
RT
2006-2
009
SpringOne2gx_Oct2009 - 7http://www.java.net
http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes
… Growing Acceptance …
© A
SE
RT
2006-2
009
SpringOne2gx_Oct2009 - 8http://www.leonardoborges.com/writings
What alternative JVM language are you using or intending to use
… Growing Acceptance …
© A
SE
RT
2006-2
009
SpringOne2gx_Oct2009 - 9
http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com)
… Growing Acceptance
SpringOne2gx_Oct2009 - 10
© A
SE
RT
2006-2
009
Better XML Manipulation...
AUG 2009 - 11
© A
SE
RT
2006-2
009
<records>
<car name='HSV Maloo' make='Holden' year='2006'>
<country>Australia</country>
<record type='speed'>Production Pickup Truck with speed of 271kph</record>
</car>
<car name='P50' make='Peel' year='1962'>
<country>Isle of Man</country>
<record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg weight</record>
</car>
<car name='Royale' make='Bugatti' year='1931'>
<country>France</country>
<record type='price'>Most Valuable Car at $15 million</record>
</car>
</records>
records.xml
...Better XML Manipulation...
AUG 2009 - 12
© A
SE
RT
2006-2
009
import org.w3c.dom.Document;import org.w3c.dom.NodeList;import org.w3c.dom.Node;import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.ParserConfigurationException;import java.io.File;import java.io.IOException;
public class FindYearsJava {public static void main(String[] args) {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();try {
DocumentBuilder builder = builderFactory.newDocumentBuilder();Document document = builder.parse(new File("records.xml"));NodeList list = document.getElementsByTagName("car");for (int i = 0; i < list.getLength(); i++) {
Node n = list.item(i);Node year = n.getAttributes().getNamedItem("year");System.out.println("year = " + year.getTextContent());
}} catch (ParserConfigurationException e) {
e.printStackTrace();} catch (SAXException e) {
e.printStackTrace();} catch (IOException e) {
e.printStackTrace();}
}}
...Better XML Manipulation...
AUG 2009 - 13
© A
SE
RT
2006-2
009
import org.w3c.dom.Document;import org.w3c.dom.NodeList;import org.w3c.dom.Node;import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.ParserConfigurationException;import java.io.File;import java.io.IOException;
public class FindYearsJava {public static void main(String[] args) {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();try {
DocumentBuilder builder = builderFactory.newDocumentBuilder();Document document = builder.parse(new File("records.xml"));NodeList list = document.getElementsByTagName("car");for (int i = 0; i < list.getLength(); i++) {
Node n = list.item(i);Node year = n.getAttributes().getNamedItem("year");System.out.println("year = " + year.getTextContent());
}} catch (ParserConfigurationException e) {
e.printStackTrace();} catch (SAXException e) {
e.printStackTrace();} catch (IOException e) {
e.printStackTrace();}
}}
boilerplate
...Better XML Manipulation
AUG 2009 - 14
© A
SE
RT
2006-2
009
def records = new XmlParser().parse("records.xml")records.car.each {
println "year = ${it.@year}"}
year = 2006year = 1962year = 1931
SpringOne 2GX 2009. All rights reserved. Do not distribute without permission.
XML
Groovy and XML ...
• Reading XML
– Special Groovy support:
XmlParser, XmlSlurper, DOMCategory
– Or Groovy sugar for your current favorites:
DOM, SAX, StAX, DOM4J, JDom, XOM, XPath,
XSLT, XQuery, etc.
• Creating XML
– Special Groovy support:
MarkupBuilder and StreamingMarkupBuilder
– Or again, enhanced syntax for your current
favorites
© A
SE
RT
2006-2
009
SpringOne2GX - 16
... Groovy and XML ...
• Updating XML
– Using above: read followed by create
– Can be done with XmlParser, XmlSlurper,
DOMCategory
– Or with your Java favorites
• Verifying XML
– Also DTD, W3C XML Schema, Relax NG in a
similar fashion to Java mechanisms for these
features
© A
SE
RT
2006-2
009
SpringOne2GX - 17
... Groovy and XML
• So many technologies – how to choose?
– Normally just use XmlSlurper and
StreamingMarkupBuilder
– Or if you want a DOM, use XmlParser and
MarkupBuilder or DOMBuilder
– Or if you must have a W3C DOM, use
DOMCategory
– Or if you expect to have a large amount of
legacy or Java parsing code, you can stick
with your favorite Java XML API/stack
© A
SE
RT
2006-2
009
SpringOne2GX - 18
An Xml Example ...
import groovy.xml.dom.DOMCategory
class Flights {static final String XML = '''
<trip><flight hours="13">
<from>Brisbane</from><to>Los Angeles</to>
</flight><flight hours="4">
<from>Los Angeles</from><to>New Orleans</to>
</flight></trip>
'''...
© A
SE
RT
2006-2
009
SpringOne2GX - 19
... An Xml Example
© A
SE
RT
2006-2
009
...static final Reader getReader() {
new StringReader(XML)}static final Set getCities(flights) {
Set cities = []use(DOMCategory) {
flights.each { f ->cities += f.to[0].text()cities += f.from[0].text()
}}cities
}}
SpringOne2GX - 20
You can mostly ignore
the details here for now.
We’ll cover DOMCategory
in more detail shortly.
XmlParser
def trip = new XmlParser().parseText(Flights.XML)assert trip.flight[0].to.text() == 'Los Angeles'assert trip.flight[1].@hours == '4'
Set cities = trip.flight.from*.text() +trip.flight.to*.text()
assert cities == ['Brisbane', 'Los Angeles','New Orleans'] as Set
assert trip.flight.@hours == ['13', '4']assert trip.flight.@hours*.toInteger().sum() == 17
• Builds an in-memory DOM tree
© A
SE
RT
2006-2
009
SpringOne2GX - 21
XmlParser – Under the covers• For a JavaBean, this Groovy expression:
• Is “roughly” converted to:
where getFlight() and getTo() return a List
• But for XML Parser, it overrides this mechanism
and “roughly” converts to:
where getByName is a Groovy method similar to
getElementsByTagName in org.w3c.dom.Element
trip.flight[0].to[0].text()
trip.getflight().get(0).getTo().get(0).text()
trip.getByName('flight').get(0).getByName('to').get(0).text()
© A
SE
RT
2006-2
009
SpringOne2GX - 22
XmlSlurper...
• The same again using XmlSlurper– Mostly identical syntax and capabilities
– But features lazy evaluation of expressions
– Consider this for streaming scenarios
def trip = new XmlSlurper().parseText(Flights.XML)assert trip.flight[0].to.text() == 'Los Angeles'assert trip.flight[1].@hours == '4'
Set cities = trip.flight.from*.text() +trip.flight.to*.text()
assert cities == ['Brisbane', 'Los Angeles','New Orleans'] as Set
assert [email protected]() == ['13', '4']assert trip.flight.@hours*.toInteger().sum() == 17
© A
SE
RT
2006-2
009
SpringOne2GX - 23
...XmlSlurper...
• What does Lazy mean?
def trip = new XmlSlurper().parseText(Flights.XML)def moreThanFiveHours = { f -> [email protected]() > 5 }def arrivingLax = { f -> f.to == 'Los Angeles' }def departingOz = { f -> f.from == 'Brisbane' }
def longFlights = trip.flight.findAll(moreThanFiveHours)def longLaxFlights = longFlights.findAll(arrivingLax)def longOzLaxFlights = longLaxFlights.findAll(departingOz)assert longOzLaxFlights.@hours == '13'
© A
SE
RT
2006-2
009
SpringOne2GX - 24
• This may look puzzling at first– But the good news is you don’t normally have to care
def trip = new XmlSlurper().parseText(Flights.XML)def moreThanFiveHours = { f -> [email protected]() > 5 }def arrivingLax = { f -> f.to == 'Los Angeles' }def departingOz = { f -> f.from == 'Brisbane' }
def longFlights = trip.flight.findAll(moreThanFiveHours)def longLaxFlights = longFlights.findAll(arrivingLax)def longOzLaxFlights = longLaxFlights.findAll(departingOz)assert longOzLaxFlights.@hours == '13'
Light-weight scanning here
Lazy expression storage
but deferred evaluationUsage triggers evaluation
...XmlSlurper
© A
SE
RT
2006-2
009
SpringOne2GX - 25
class Books {
static final String XML = '''
<rdf:rdf xmlns:bibterm="http://www.book-stuff.com/terms/"
xmlns:dc="http://purl.org/dc/elements/1.0/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:description rdf:about="http://www.book-stuff.com/bib">
<bibterm:book rdf:parseType="Resource">
<bibterm:year>2007</bibterm:year>
<dc:title>Groovy in Action</dc:title>
<bibterm:author rdf:parseType="Resource">
<bibterm:last>König</bibterm:last>
<bibterm:first>Dierk</bibterm:first>
</bibterm:author>
<rdf:comment>
Coauthors: Andrew Glover, Paul King,
Guillaume Laforge and Jon Skeet
</rdf:comment>
</bibterm:book>
</rdf:description>
</rdf:rdf>
''' ...
A Namespace Example
© A
SE
RT
2006-2
009
SpringOne2GX - 26
• Recommended syntax:
• Options
// use string style matching (exact match on prefix or wildcard or URI)assert b.'bibterm:year'.text() == '2007'assert b.'*:year'.text() == '2007'assert b.'http://www.book-stuff.com/terms/:year'.text() == '2007'
// Namespace is a QName factory but you can use QName directlydef bibtermYear = new QName('http://www.book-stuff.com/terms/', 'year')assert b[bibtermYear].text() == '2007'
// use QName with wildcardsdef anyYear = new QName('*', 'year')assert b[anyYear].text() == '2007'
import groovy.xml.*def book = new XmlParser().parseText(Books.XML)def rdf = new Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')def dc = new Namespace('http://purl.org/dc/elements/1.0/')def bibterm = new Namespace('http://www.book-stuff.com/terms/')def b = book[rdf.description][bibterm.book]assert b[dc.title].text() == 'Groovy in Action'assert b[bibterm.year].text() == '2007'
XmlParser and Namespaces
SpringOne2GX - 27
© A
SE
RT
2006-2
009
def book = new XmlSlurper().parseText(Books.XML)book.declareNamespace(
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',dc: 'http://purl.org/dc/elements/1.0/',bibterm: 'http://www.book-stuff.com/terms/')
def b = book.'rdf:description'.'bibterm:book'assert b.'dc:title' == 'Groovy in Action'assert b.'bibterm:year' == '2007'
XmlSlurper and Namespaces
© A
SE
RT
2006-2
009
SpringOne2GX - 28
Other GPath Features• Similar syntax for XmlSlurper and DOMCategory
SpringOne2GX - 29
© A
SE
RT
2006-2
009
def trip = new XmlParser().parseText(Flights.XML)assert trip.'**'*.name() ==
['trip', 'flight', 'from', 'to', 'flight', 'from', 'to']assert trip.depthFirst()*.name() ==
['trip', 'flight', 'from', 'to', 'flight', 'from', 'to']assert trip.breadthFirst()*.name() ==
['trip', 'flight', 'flight', 'from', 'to', 'from', 'to']assert trip.'**'.from*.text() == ['Brisbane', 'Los Angeles']
What about non-XML?@Grab('nekohtml:nekohtml:1.9.6.2')
import org.cyberneko.html.parsers.SAXParser
def neko = new SAXParser()neko.setFeature('http://xml.org/sax/features/namespaces', false)def page = new XmlParser(neko).parse('http://groovy.codehaus.org/')def data = page.depthFirst().A.'@href'.grep{
it != null && it.endsWith('.html')}data.each { println it }
http://groovy.javanicus.com/search.html
http://www.dcs.napier.ac.uk/~cs05/groovy/groovy.html
http://www.ej-technologies.com/products/jprofiler/overview.html
© A
SE
RT
2006-2
009
SpringOne2GX - 30
def neko = new SAXParser() // alternate style
def page = new XmlSlurper(neko).parse('http://groovy.codehaus.org/')def data = page.depthFirst().grep{
it.name() == 'A' && [email protected]().endsWith('.html')}.'@href'data.each { println it }
Raw DOM
import groovy.xml.DOMBuilder
def trip = DOMBuilder.parse(Flights.reader).documentElementdef flights = trip.getElementsByTagName('flight')
def dest = flights.item(0).getElementsByTagName('to').item(0)assert dest.firstChild.nodeValue == 'Los Angeles'
assert flights.item(1).getAttribute('hours') == '4'
assert Flights.getCities(flights) ==['Brisbane', 'Los Angeles', 'New Orleans'] as Set
© A
SE
RT
2006-2
009
SpringOne2GX - 31
DOM plus metaprogramming
import groovy.xml.DOMBuilderimport org.w3c.dom.Element
def trip = DOMBuilder.parse(Flights.reader).documentElement
Element.metaClass.element = { t, i ->delegate.getElementsByTagName(t).item(i) }
Element.metaClass.text = {-> delegate.firstChild.nodeValue }
assert trip.element('flight', 0).element('to', 0).text() =='Los Angeles'
assert trip.element('flight', 1).getAttribute('hours') == '4'
© A
SE
RT
2006-2
009
SpringOne2GX - 32
DOMCategory
import groovy.xml.DOMBuilderimport groovy.xml.dom.DOMCategory
def doc = DOMBuilder.parse(Flights.reader)def trip = doc.documentElement
use(DOMCategory) {assert trip.flight[0].to[0].text() ==
'Los Angeles'assert trip.flight[1].'@hours' == '4'assert Flights.getCities(trip.flight) ==
['Brisbane', 'Los Angeles','New Orleans'] as Set
}
© A
SE
RT
2006-2
009
SpringOne2GX - 33
DOM4J
@Grab('dom4j:dom4j:1.6.1')import org.dom4j.io.SAXReader
def trip = new SAXReader().read(Flights.reader).rootElement
assert trip.elements()[0].elementText('to') == 'Los Angeles'assert trip.elements()[1].attributeValue('hours') == '4'
© A
SE
RT
2006-2
009
SpringOne2GX - 34
JDOM
@Grab('org.jdom:jdom:1.1')import org.jdom.input.SAXBuilder
def b = new SAXBuilder()def trip = b.build(Flights.reader).rootElement
assert trip.children[0].getChildText('to') == 'Los Angeles'assert trip.children[1].getAttribute('hours').value == '4'
© A
SE
RT
2006-2
009
SpringOne2GX - 35
XOM
@Grab('xom:xom:1.1')
import nu.xom.Builder
def doc = new Builder().build(Flights.reader)
def flights = doc.rootElement.childElements
def to = flights.get(0).getFirstChildElement('to')
assert to.value == 'Los Angeles'
def hours = flights.get(1).getAttribute('hours')
assert hours.value == '4'
© A
SE
RT
2006-2
009
SpringOne2GX - 36
StAXimport static javax.xml.stream.XMLInputFactory.newInstance as staxFactoryimport javax.xml.stream.XMLStreamReader as StaxReader
def flights = []def flightdef seenTagStaxReader.metaClass.attr = { s -> delegate.getAttributeValue(null, s) }
def reader = staxFactory().createXMLStreamReader(Flights.reader)while (reader.hasNext()) {
def name = reader.localNameif (reader.startElement) {
if (name == 'flight') flight = [hours:reader.attr('hours')]else if (name in ['from', 'to']) seenTag = name
} else if (reader.characters) {if (seenTag) flight[seenTag] = reader.text
} else if (reader.endElement) {if (name == 'flight') flights += flightseenTag = null
}reader.next()
}
assert flights[0].to == 'Los Angeles'assert flights[1].hours == '4'
© A
SE
RT
2006-2
009
SpringOne2GX - 37
import javax.xml.parsers.SAXParserFactoryimport org.xml.sax.*import org.xml.sax.helpers.DefaultHandler
class TripHandler extends DefaultHandler {def flights = []private flight, seenTag
void startElement(String ns, String localName, String qName, Attributes atts) {if (qName == 'flight') flight = [hours:atts.getValue('hours')]else if (qName in ['from', 'to']) seenTag = qName
}
public void endElement(String uri, String localName, String qName) {if (qName == 'flight') flights += flightseenTag = null
}
public void characters(char[] ch, int start, int length) {if (seenTag) flight[seenTag] = new String(ch, start, length)
}}
def handler = new TripHandler()def reader = SAXParserFactory.newInstance().newSAXParser().xMLReaderreader.setContentHandler(handler)reader.parse(new InputSource(Flights.reader))
assert handler.flights[0].to == 'Los Angeles'assert handler.flights[1].hours == '4'
SAX
© A
SE
RT
2006-2
009
SpringOne2GX - 38
XPath
import javax.xml.xpath.*import groovy.xml.DOMBuilder
def xpath = XPathFactory.newInstance().newXPath()def trip = DOMBuilder.parse(Flights.reader).documentElementassert xpath.evaluate('flight/to/text()', trip) ==
'Los Angeles'assert xpath.evaluate('flight[2]/@hours', trip) == '4'
def flights = xpath.evaluate( 'flight', trip,XPathConstants.NODESET )
def hoursAsInt = { n ->xpath.evaluate('@hours', n).toInteger() }
assert flights.collect(hoursAsInt).sum() == 17
assert Flights.getCities(flights) == ['Brisbane','Los Angeles', 'New Orleans'] as Set
© A
SE
RT
2006-2
009
SpringOne2GX - 39
XPath with DOMCategory
import groovy.xml.DOMBuilderimport groovy.xml.dom.DOMCategoryimport static javax.xml.xpath.XPathConstants.*
def trip = DOMBuilder.parse(Flight.reader).documentElementuse (DOMCategory) {
assert trip.xpath('flight/to/text()') == 'Los Angeles'assert trip.xpath('flight[2]/@hours', NUMBER) == 4flights = trip.xpath('flight', NODESET)def hoursAsNum = { n -> n.xpath('@hours', NUMBER) }assert flights.collect(hoursAsNum).sum() == 17
}
© A
SE
RT
2006-2
009
SpringOne2GX - 40
Xalan XPath
@Grab('xalan:xalan:2.7.1')import static org.apache.xpath.XPathAPI.*import groovy.xml.DOMBuilder
def trip = DOMBuilder.parse(Flights.reader).documentElement
assert eval(trip, 'flight/to/text()').str() =='Los Angeles'
assert eval(trip, 'flight[2]/@hours').str() == '4'
def flights = selectNodeList(trip, '//flight')def hoursAsInt = { n ->
eval(n, '@hours').str().toInteger() }assert flights.collect(hoursAsInt).sum() == 17
assert Flights.getCities(flights) == ['Brisbane','Los Angeles', 'New Orleans'] as Set
© A
SE
RT
2006-2
009
SpringOne2GX - 41
Jaxen XPath
@Grab('jaxen#jaxen;1.1.1')import org.jaxen.dom.DOMXPathimport groovy.xml.DOMBuilder
def trip = DOMBuilder.parse(Flights.reader).documentElementassert new DOMXPath('flight/to/text()').
stringValueOf(trip) == 'Los Angeles'assert new DOMXPath('flight[2]/@hours').
stringValueOf(trip) == '4'
def flights = new DOMXPath('flight').selectNodes(trip)def hoursAsInt = { n ->
new DOMXPath('@hours').numberValueOf(n) }assert flights.collect(hoursAsInt).sum() == 17
assert Flights.getCities(flights) == ['Brisbane','Los Angeles', 'New Orleans'] as Set
© A
SE
RT
2006-2
009
SpringOne2GX - 42
JSR 225 - XQJ
import net.sf.saxon.xqj.SaxonXQDataSourceimport javax.xml.xquery.XQSequenceXQSequence.metaClass.collect = { Closure c ->
def items = []while (delegate.next()) items += c(delegate)items
}def asString = { seq -> seq.getItemAsString(null) }def hourAttr = { it.item.node.getAttribute('hours') as int }def flights = "document { ${Flights.XML} }"def exp =
new SaxonXQDataSource().connection.createExpression()def seq = exp.executeQuery("$flights/trip/flight/to/text()")assert seq.collect(asString) == ['Los Angeles',
'New Orleans']seq = exp.executeQuery("$flights/trip/flight")assert seq.collect(hourAttr).sum() == 17
© A
SE
RT
2006-2
009
SpringOne2GX - 43
import static javax.xml.transform.TransformerFactory.newInstance as xsltFactoryimport javax.xml.transform.stream.*def xslt = '''<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><xsl:template match="/trip"><html><body><h1>Flights</h1><ul><xsl:apply-templates select="flight"/>
</ul></body>
</html></xsl:template><xsl:template match="flight"><li><xsl:value-of select="from"/> => <xsl:value-of select="to"/>
</li></xsl:template>
</xsl:stylesheet>'''.trim()def transformer = xsltFactory().newTransformer(
new StreamSource(new StringReader(xslt)))transformer.transform(new StreamSource(Flights.reader),
new StreamResult(System.out))
XSLT...
© A
SE
RT
2006-2
009
SpringOne2GX - 44
import static javax.xml.transform.TransformerFactory.newInstance as xsltFactoryimport javax.xml.transform.stream.*def xslt = '''<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><xsl:template match="/trip"><html><body><h1>Flights</h1><ul><xsl:apply-templates select="flight"/>
</ul></body>
</html></xsl:template><xsl:template match="flight"><li><xsl:value-of select="from"/> => <xsl:value-of select="to"/>
</li></xsl:template>
</xsl:stylesheet>'''.trim()def transformer = xsltFactory().newTransformer(
new StreamSource(new StringReader(xslt)))transformer.transform(new StreamSource(Flights.reader),
new StreamResult(System.out))
...XSLT
<html>
<body>
<h1>Flights</h1>
<ul>
<li>Brisbane => Los Angeles</li>
<li>Los Angeles => New Orleans</li>
</ul>
</body>
</html>
© A
SE
RT
2006-2
009
SpringOne2GX - 45
MarkupBuilder
import groovy.xml.MarkupBuilder
def writer = new StringWriter()def xml = new MarkupBuilder(writer)xml.flights {
flight(hours:13) {from('Brisbane')to('Los Angeles')
}flight(hours:4) {
from('Los Angeles')to('New Orleans')
}}println writer
© A
SE
RT
2006-2
009
SpringOne2GX - 46
StreamingMarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def writer = new StreamingMarkupBuilder().bind {flights {
flight(hours: 13) {from('Brisbane')to('Los Angeles')
}flight(hours: 4) {
from('Los Angeles')to('New Orleans')
}}
}println writer
© A
SE
RT
2006-2
009
SpringOne2GX - 47
DOMBuilder
import groovy.xml.*
def builder = DOMBuilder.newInstance()def root = builder.flights {
flight(hours: 13) {from('Brisbane')to('Los Angeles')
}flight(hours: 4) {
from('Los Angeles')to('New Orleans')
}}new XmlNodePrinter().print(root)
© A
SE
RT
2006-2
009
SpringOne2GX - 48
MarkupBuilder with Namespaces
import groovy.xml.MarkupBuilder
def writer = new StringWriter()def xml = new MarkupBuilder(writer)xml.'rdf:description'(
'xmlns:bibterm': "http://www.book-stuff.com/terms/",'xmlns:dc': "http://purl.org/dc/elements/1.0/",'xmlns:rdf': "http://www.w3.org/1999/02/22-rdf-syntax-
ns#") {'bibterm:book' {
'dc:title'('Groovy in Action')'bibterm:year'('2007')
}}println writer
© A
SE
RT
2006-2
009
SpringOne2GX - 49
<rdf:description xmlns:dc='http://purl.org/dc/elements/1.0/'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:bibterm='http://www.book-stuff.com/terms/'>
<bibterm:book>
<dc:title>Groovy in Action</dc:title>
<bibterm:year>2007</bibterm:year>
</bibterm:book>
</rdf:description>
StreamingMarkupBuilder with Namespaces
import groovy.xml.*
XmlUtil.serialize(new StreamingMarkupBuilder().bind {mkp.declareNamespace(
bibterm: "http://www.book-stuff.com/terms/",dc: "http://purl.org/dc/elements/1.0/",rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
'rdf:description' {'bibterm:book' {
'dc:title'('Groovy in Action')'bibterm:year'('2007')
}}
}, System.out)
© A
SE
RT
2006-2
009
SpringOne2GX - 50
<rdf:description xmlns:dc='http://purl.org/dc/elements/1.0/'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:bibterm='http://www.book-stuff.com/terms/'>
<bibterm:book>
<dc:title>Groovy in Action</dc:title>
<bibterm:year>2007</bibterm:year>
</bibterm:book>
</rdf:description>
DOMBuilder with Namespaces
import groovy.xml.DOMBuilder
def b = DOMBuilder.newInstance()def root = b.'rdf:description'(
'xmlns:bibterm': "http://www.book-stuff.com/terms/",'xmlns:dc': "http://purl.org/dc/elements/1.0/",'xmlns:rdf': "http://www.w3.org/1999/02/22-rdf-syntax-ns#") {
'bibterm:book' {'dc:title'('Groovy in Action')'bibterm:year'('2007')
}}new XmlNodePrinter().print(root)
© A
SE
RT
2006-2
009
SpringOne2GX - 51
<rdf:description xmlns:dc='http://purl.org/dc/elements/1.0/'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:bibterm='http://www.book-stuff.com/terms/'>
<bibterm:book>
<dc:title>Groovy in Action</dc:title>
<bibterm:year>2007</bibterm:year>
</bibterm:book>
</rdf:description>
DOMBuilder with NamespaceBuilder
import groovy.xml.*def b = NamespaceBuilder.newInstance(DOMBuilder.newInstance())b.namespace('http://www.book-stuff.com/terms/', 'bibterm')b.namespace('http://purl.org/dc/elements/1.0/', 'dc')b.namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf')
def root = b.'rdf:description' {'bibterm:book' {'dc:title'('Groovy in Action')'bibterm:year'('2007')
}}new XmlNodePrinter().print(root)
© A
SE
RT
2006-2
009
SpringOne2GX - 52
<?xml version="1.0" encoding="UTF-8"?>
<rdf:description xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<bibterm:book xmlns:bibterm="http://www.book-stuff.com/terms/">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.0/">Groovy in Action</dc:title>
<bibterm:year>2007</bibterm:year>
</bibterm:book>
</rdf:description>
StaxBuilder
import javax.xml.stream.XMLOutputFactoryimport groovy.xml.StaxBuilder
def factory = XMLOutputFactory.newInstance()def writer = new StringWriter()def xmlwriter = factory.createXMLStreamWriter(writer)def builder = new StaxBuilder(xmlwriter)builder.flights {
flight(hours: 13) {from('Brisbane')to('Los Angeles')
}flight(hours: 4) {
from('Los Angeles')to('New Orleans')
}}println writer
© A
SE
RT
2006-2
009
SpringOne2GX - 53
StaxBuilder for non-XML (JSON)@Grab('org.codehaus.jettison#jettison;1.1')
import org.codehaus.jettison.mapped.*
import groovy.xml.StaxBuilder
def writer = new StringWriter()
def con = new MappedNamespaceConvention()
def mappedWriter = new MappedXMLStreamWriter(con,
writer)
def builder = new StaxBuilder(mappedWriter)
builder.flights {
flight(hours: 13) {
from('Brisbane')
to('Los Angeles')
}
flight(hours: 4) {
from('Los Angeles')
to('New Orleans')
}
}
println writer
© A
SE
RT
2006-2
009
SpringOne2GX - 54
{"flights":{"flight":[
{ "@hours":"13",
"from":"Brisbane",
"to":"Los Angeles" },
{ "@hours":"4",
"from":"Los Angeles",
"to":"New Orleans" }
]}}
Updating XML
<shopping>
<category type="groceries">
<item>Chocolate</item>
<item>Coffee</item>
</category>
<category type="supplies">
<item>Paper</item>
<item quantity="4">Pens</item>
</category>
<category type="present">
<item when="Aug 10">
Kathryn's Birthday
</item>
</category>
</shopping>
<shopping>
<category type="groceries">
<item>Luxury Chocolate</item>
<item>Luxury Coffee</item>
</category>
<category type="supplies">
<item>Paper</item>
<item quantity="6"
when="Urgent">Pens</item>
</category>
<category type="present">
<item>Mum's Birthday</item>
<item when="Oct 15">
Monica's Birthday
</item>
</category>
</shopping>
© A
SE
RT
2006-2
009
SpringOne2GX - 55
Updating with XmlParser
© A
SE
RT
2006-2
009
SpringOne2GX - 56
...def root = new XmlParser().parseText(Shopping.XML)
// modify groceries: luxury items pleaseroot.category.find{ it.@type == 'groceries' }.item.each{ g ->
g.value = 'Luxury ' + g.text()}
// modify supplies: we need extra pens nowroot.category.find{
it.@type == 'supplies'}.item.findAll{ it.text() == 'Pens' }.each{ p ->
p.@quantity = [email protected]() + 2p.@when = 'Urgent'
}
// modify presents: August has come and gonedef presents = root.category.find{ it.@type == 'present' }presents.children().clear()presents.appendNode('item', "Mum's Birthday")presents.appendNode('item', [when:'Oct 15'], "Monica's Birthday")...
Updating with XmlSlurper...// modify groceries: luxury items pleasedef groceries = root.category.find{ it.@type == 'groceries' }(0..<groceries.item.size()).each {
groceries.item[it] = 'Luxury ' + groceries.item[it]}
// modify supplies: we need extra pens nowroot.category.find{
it.@type == 'supplies'}.item.findAll{ it.text() == 'Pens' }.each { p ->
p.@quantity = ([email protected]() + 2).toString()p.@when = 'Urgent'
}
// modify presents: August has come and goneroot.category.find{ it.@type == 'present' }.replaceNode{ node ->
category(type:'present'){item("Mum's Birthday")item("Monica's Birthday", when:'Oct 15')
}}...
© A
SE
RT
2006-2
009
SpringOne2GX - 57
Updating with DOMCategoryuse(DOMCategory) {
// modify groceries: luxury items pleasedef groceries = root.category.find{ it.'@type' == 'groceries' }.itemgroceries.each { g ->
g.value = 'Luxury ' + g.text()}
// modify supplies: we need extra pens nowdef supplies = root.category.find{ it.'@type' == 'supplies' }.itemsupplies.findAll{ it.text() == 'Pens' }.each { p ->
p['@quantity'] = p.'@quantity'.toInteger() + 2p['@when'] = 'Urgent'
}
// modify presents: August has come and gonedef presents = root.category.find{ it.'@type' == 'present' }presents.item.each { presents.removeChild(it) }presents.appendNode('item', "Mum's Birthday")presents.appendNode('item', [when:'Oct 15'], "Monica's Birthday")...
}
© A
SE
RT
2006-2
009
SpringOne2GX - 58
Validating against a DTD...def xml = '''<!DOCTYPE records [
<!ELEMENT car (country,record)><!ATTLIST car
make NMTOKEN #REQUIREDname CDATA #REQUIREDyear NMTOKEN #REQUIRED
><!ELEMENT country (#PCDATA)><!ELEMENT record (#PCDATA)><!ATTLIST record type NMTOKEN #REQUIRED><!ELEMENT records (car+)>
]><records><car name="HSV Maloo" make="Holden" year="2006"><country>Australia</country><record
type="speed">Production Pickup Truck with speed of 271kph</record></car>...
</records>'''.trim()
© A
SE
RT
2006-2
009
SpringOne2GX - 59
...Validating against a DTD
def validating = truedef namespaceAware = false
© A
SE
RT
2006-2
009
SpringOne2GX - 60
new XmlParser(validating, namespaceAware).parseText(xml)
new XmlSlurper(validating, namespaceAware).parseText(xml)
import groovy.xml.DOMBuilderDOMBuilder.parse(new StringReader(xml), validating, namespaceAware)
Validating against a W3C Schema...def xsd = '''<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="records"><xs:complexType>
<xs:sequence><xs:element maxOccurs="unbounded" ref="car"/>
</xs:sequence></xs:complexType>
</xs:element><xs:element name="car">
<xs:complexType><xs:sequence>
<xs:element ref="country"/><xs:element ref="record"/>
</xs:sequence><xs:attribute name="make" use="required" type="xs:NCName"/><xs:attribute name="name" use="required"/><xs:attribute name="year" use="required" type="xs:integer"/>
</xs:complexType></xs:element><xs:element name="country" type="xs:string"/><xs:element name="record">
<xs:complexType mixed="true"><xs:attribute name="type" use="required" type="xs:NCName"/>
</xs:complexType></xs:element>
</xs:schema>'''.trim()
© A
SE
RT
2006-2
009
SpringOne2GX - 61
...Validating against a W3C Schema
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URIimport javax.xml.transform.stream.StreamSourceimport javax.xml.validation.SchemaFactory
def factory = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)// println factory.isSchemaLanguageSupported(W3C_XML_SCHEMA_NS_URI)
def schema =factory.newSchema(new StreamSource(new StringReader(xsd)))
def validator = schema.newValidator()validator.validate(
new StreamSource(new StringReader(XmlExamples.CAR_RECORDS)))
© A
SE
RT
2006-2
009
SpringOne2GX - 62
Validating against a RelaxNG Schema...def rng = '''<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"><start>
<ref name="records"/></start><define name="car">
<element name="car"><attribute name="make">
<data type="token"/></attribute><attribute name="name">
<text/></attribute><attribute name="year">
<data type="integer"/></attribute><ref name="country"/><ref name="record"/>
</element></define><define name="country">
<element name="country"><text/>
</element></define>...
© A
SE
RT
2006-2
009
SpringOne2GX - 63
...<define name="record">
<element name="record"><attribute name="type">
<data type="token"/></attribute><text/>
</element></define><define name="records">
<element name="records"><oneOrMore>
<ref name="car"/></oneOrMore>
</element></define>
</grammar>'''.trim()
...Validating against a RelaxNG Schema
// require isorelax.jar, isorelax-jaxp-bridge.jar// require one of Jing, MSV, ...import static javax.xml.XMLConstants.RELAXNG_NS_URIimport javax.xml.transform.stream.StreamSourceimport javax.xml.validation.SchemaFactory
def factory = SchemaFactory.newInstance(RELAXNG_NS_URI)def schema =
factory.newSchema(new StreamSource(new StringReader(rng)))def validator = schema.newValidator()validator.validate(new StreamSource(new StringReader(CAR_RECORDS)))
© A
SE
RT
2006-2
009
SpringOne2GX - 64
SpringOne 2GX 2009. All rights reserved. Do not distribute without permission.
WEB SERVICES
Groovy and Web Services
• SOAP Web Services
– GroovySOAP using XFire for Java 1.4
– GroovyWS using CXF for Java 1.5+
– JAXB out of the box for Java 6
– CXF, Axis2, Spring Web Services
• RESTful Options– Restlet.org, RESTlet DSL, roll your own
– JAX-RS: Jersey, CXF, JBoss RESTeasy
• Frameworks layered upon SOA
– Synapse, Tuscany, ServiceMix
© A
SE
RT
2006-2
009
SpringOne2GX - 66
GroovySOAP
• XFire based
• Java 1.4
class MathService {double add(double a, double b) {
a + b}double square(double c) {
c * c}
} import groovy.net.soap.SoapServer
def server = new SoapServer('localhost', 6789)server.setNode('MathService')server.start()
import groovy.net.soap.SoapClient
def url = 'http://localhost:6789/MathServiceInterface?wsdl'def math = new SoapClient(url)assert math.add(1.0, 2.0) == 3.0assert math.square(3.0) == 9.0
© A
SE
RT
2006-2
009
SpringOne2GX - 67
GroovyWS
• CXF based
• Java 1.5+
class MathService {double add(double a, double b) {
a + b}double square(double c) {
c * c}
}import groovyx.net.ws.WSServerdef server = new WSServer()server.setNode MathService.name,
"http://localhost:6980/MathService"
import groovyx.net.ws.WSClientdef url = "http://localhost:6980/MathService?wsdl"def proxy = new WSClient(url, this.class.classLoader)def result = proxy.add(1.0d, 2.0d)assert result == 3.0dresult = proxy.square(3.0d)assert result == 9.0d
© A
SE
RT
2006-2
009
SpringOne2GX - 68
JAXB Server
import javax.xml.ws.Endpointimport javax.jws.WebServiceimport javax.jws.soap.SOAPBindingimport javax.jws.WebMethod
@WebService(name="Echo", serviceName="EchoService",targetNamespace="http://jaxws.asert.com")
@SOAPBinding(style=SOAPBinding.Style.RPC)class EchoImpl {
@WebMethod(operationName = "echo")String echo(String message) {
println "Received: $message""\nYou said: " + message
}}
Endpoint.publish("http://localhost:8080/Echo", new EchoImpl())println 'EchoService published and running ...'
© A
SE
RT
2006-2
009
SpringOne2GX - 69
JAXB Client
• JAXB Client
• Build instructions
import javax.xml.namespace.QNameimport com.asert.jaxws.EchoService
def url = new URL("http://localhost:8080/Echo?wsdl")def qname = new QName("http://jaxws.asert.com", "EchoService")def echoServer = new EchoService(url, qname).echoPortprintln echoServer.echo("Today is ${new Date()}")
wsimport -d ../build -p com.asert.jaxws http://localhost:8080/Echo?wsdl
© A
SE
RT
2006-2
009
SpringOne2GX - 70
Raw CXF• Apache CXF helps you build and develop
services. You can use frontend programming
APIs, like JAX-WS and support is provided for
SOAP, XML/HTTP, RESTful HTTP, ...
over HTTP, JMS, JBI, ...
– Follow instructions for Java but there is also
some special things you can do with Groovy
© A
SE
RT
2006-2
009
SpringOne2GX - 71
Axis2• Apache Axis is a comprehensive
implementation of SOAP
– Follow the instructions for Java but use
Groovy instead and precompile
– Use GroovyShell to call script at runtime
• Another article:
– http://www.developer.com/services/article.ph
p/10928_3570031_2
© A
SE
RT
2006-2
009
SpringOne2GX - 72
RESTful options• Several Options
– Already supported in discussed
frameworks, e.g. CXF
– Groovy Restlet DSLhttp://docs.codehaus.org/display/GROOVY/GroovyRestlet
– Jersey
http://wikis.sun.com/display/Jersey/Main
– restlet.org
http://www.restlet.org
© A
SE
RT
2006-2
009
SpringOne2GX - 73
restlet.orgimport org.restlet.*import org.restlet.data.*class MailboxResource extends Restlet {
void handle(Request request, Response response) {switch (request.method) {
case Method.GET:handleGet(request, response)break
case Method.PUT:handlePut(request, response)break
case Method.POST:handlePost(request, response)break
default:// The request method is not allowed; set an error statusresponse.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)response.setAllowedMethods([Method.GET, Method.PUT, Method.POST] as Set)
}}void handleGet(request, response) {
response.setEntity("Hello, world!", MediaType.TEXT_PLAIN)}
// ...
© A
SE
RT
2006-2
009
SpringOne2GX - 74
GroovyRestlet DSLbuilder.component {current.servers.add(protocol.HTTP, 8182)application(uri: "") {router {def guard = guard(uri: "/docs", scheme: challengeScheme.HTTP_BASIC,
realm: "Restlet Tutorials")guard.secrets.put("scott", "tiger".toCharArray())guard.next = directory(root: "", autoAttach: false)restlet(uri: "/users/{user}", handle: {req, resp ->resp.setEntity("Account of user \"${req.attributes.get('user')}\"",
mediaType.TEXT_PLAIN)})restlet(uri: "/users/{user}/orders", handle: {req, resp ->resp.setEntity("Orders or user \"${req.attributes.get('user')}\"",
mediaType.TEXT_PLAIN)})restlet(uri: "/users/{user}/orders/{order}", handle: {req, resp ->def attrs = req.attributesdef message ="Order \"${attrs.get('order')}\" for User \"${attrs.get('user')}\""resp.setEntity(message, mediaType.TEXT_PLAIN)
})}
}}.start() Source: http://docs.codehaus.org/display/GROOVY/GroovyRestlet
© A
SE
RT
2006-2
009
SpringOne2GX - 75
Jersey...package com.asert
import javax.ws.rs.GETimport javax.ws.rs.Pathimport javax.ws.rs.Producesimport static com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory.*
@Path ("/helloworld")class HelloWorldResource {
@GET @Produces("text/plain")String getPlainMessage() {
"Hello World"}
}
def baseUri = "http://localhost:9998/"def initParams = ["com.sun.jersey.config.property.packages": "com.asert"]
println """Starting grizzly with Jersey...App WADL available at ${baseUri}application.wadlApp available at ${baseUri}helloworld"""create(baseUri, initParams)
© A
SE
RT
2006-2
009
SpringOne2GX - 76
...Jersey...import javax.ws.rs.PathParamimport javax.xml.bind.annotation.*
class PrettyXml {static print(node) {
def writer = new StringWriter()new XmlNodePrinter(new PrintWriter(writer)).print(node)writer.toString()
}}
@XmlRootElement@XmlAccessorType (XmlAccessType.FIELD)class FlightInfoBean {
@XmlAttribute String hours@XmlElement String from, tostatic populate(num) {
def trip = new XmlParser().parse(Flights.reader)def f = trip.flight[num as int]new FlightInfoBean(hours:f.@hours,
from:f.from.text(),to:f.to.text())
}}
© A
SE
RT
2006-2
009
SpringOne2GX - 77
...Jersey@Path("/flight/xml/{flightnum}")class FlightInfoXml {
@GET @Produces("text/xml")String getXmlMessage(@PathParam('flightnum') String num) {
def trip = new XmlParser().parse(Flights.reader)PrettyXml.print(trip.flight[num as int])
}}
@Path("/flight/json/{flightnum}")class FlightInfoJson {
@GET @Produces("application/json")FlightInfoBean getJsonMessage(@PathParam('flightnum') String num) {
FlightInfoBean.populate(num)}
}
@Path("/flight/atom/{flightnum}")class FlightInfoAtom {
@GET @Produces("application/atom")FlightInfoBean getAtomMessage(@PathParam('flightnum') String num) {
FlightInfoBean.populate(num)}
}
© A
SE
RT
2006-2
009
SpringOne2GX - 78
Jersey output
© A
SE
RT
2006-2
009
> curl http://localhost:9998/helloworld
Hello World
> curl http://localhost:9998/flight/xml/1
<flight hours="4">
<from>
Los Angeles
</from>
<to>
New Orleans
</to>
</flight>
> curl http://localhost:9998/flight/json/1
{"@hours":"4","from":"Los Angeles","to":"New Orleans"}
> curl http://localhost:9998/flight/atom/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<flightInfoBean hours="4">
<from>Los Angeles</from>
<to>New Orleans</to>
</flightInfoBean>
SpringOne2GX - 79
Synapse• Apache Synapse is a simple, lightweight and
high performance Enterprise Service Bus (ESB)
with support for XML, Web services, binary and
text formats
– Groovy scripting, endpoints, Synapse DSL
– https://svn.apache.org/repos/asf/synapse/
trunk/java/src/site/resources/presentations/
makingsoagroovyfremantle.pdf
© A
SE
RT
2006-2
009
SpringOne2GX - 80
ServiceMix• ServiceMix is an open source Enterprise Service
Bus (ESB) combining Service Oriented
Architecture (SOA), Event Driven Architecture
(EDA) and Java Business Integration (JBI)
functionality
– You can use ServiceMix Scripting
– You can use legacy ScriptComponent and
GroovyComponent
– You can write Groovy JBI components
© A
SE
RT
2006-2
009
SpringOne2GX - 81
Tuscany...• Tuscany embodies Service Component
Architecture (SCA) which defines a simple,
service-based model for construction, assembly
and deployment of a network of services
(existing and new ones) that are defined in a
language-neutral way.”
– You can define your services using Groovy
either using Java mechanisms
or Scripting integration
© A
SE
RT
2006-2
009
SpringOne2GX - 82
...Tuscany...
<composite ...>
<component name="CalculatorServiceComponent" .../>
<component name="AddServiceComponent" .../>
<component name="SubtractServiceComponent">
<tuscany:implementation.java
class="calculator.SubtractServiceImpl"/>
</component>
<component name="MultiplyServiceComponent">
<tuscany:implementation.script language="groovy">
def multiply(n1, n2) {
n1 * n2
}
</tuscany:implementation.script>
</component>
<component name="DivideServiceComponent">
<tuscany:implementation.script
script="calculator/DivideServiceImpl.groovy"/>
</component>
</composite>
Compile your Groovy using groovyc
(with or without annotations) then
treat just like normal Java. Need
groovy jar in your runtime classpath.
© A
SE
RT
2006-2
009
SpringOne2GX - 83
...Tuscany
<composite ...>
<component name="CalculatorServiceComponent" .../>
<component name="AddServiceComponent" .../>
<component name="SubtractServiceComponent">
<tuscany:implementation.java
class="calculator.SubtractServiceImpl"/>
</component>
<component name="MultiplyServiceComponent">
<tuscany:implementation.script language="groovy">
def multiply(n1, n2) {
n1 * n2
}
</tuscany:implementation.script>
</component>
<component name="DivideServiceComponent">
<tuscany:implementation.script
script="calculator/DivideServiceImpl.groovy"/>
</component>
</composite>
With Groovy scripts either
embedded or in files – no
compilation necessary.
© A
SE
RT
2006-2
009
SpringOne2GX - 84
More Information: on the web
• Web sites– http://groovy.codehaus.org
– http://grails.codehaus.org
– http://pleac.sourceforge.net/pleac_groovy (many examples)
– http://www.asert.com.au/training/java/GV110.htm (workshop)
• Mailing list for users– [email protected]
• Information portals– http://www.aboutgroovy.org
– http://www.groovyblogs.org
• Documentation (1000+ pages)– Getting Started Guide, User Guide, Developer Guide, Testing
Guide, Cookbook Examples, Advanced Usage Guide
• Books– Several to choose from ...
© A
SE
RT
2006-2
009
SpringOne2GX - 85
More Information: Groovy in Action
© A
SE
RT
2006-2
009
SpringOne2GX - 86
Top Related