Groovy Xml Web Services Paul King Apache Con2008 - Presentation Transcript
XML and Web Services
with Groovy
Dr Paul King
paulk@asert.com.au
ASERT, Australia
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
Why Groovy?
• Minimal learning curve
• Compiles to bytecode
• Java object model & integration
• Annotations
• Optional static typing
• Both run-time and compile-time
metaprogramming
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
... 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
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>
'''
...
... An Xml Example
...
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
}
}
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
XmlSlurper...
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 trip.flight.@hours.list() == ['13', '4']
assert trip.flight.@hours*.toInteger().sum() == 17
• Lazy evaluation of expressions
– Consider this for streaming scenarios
...XmlSlurper...
• What does Lazy mean?
def trip = new XmlSlurper().parseText(Flights.XML)
def moreThanFiveHours = { f -> f.@hours.toInteger() > 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 Namespace Example
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>
''' ...
XmlParser and Namespaces
import groovy.xml.Namespace
def book = new XmlParser().parseText(Books.XML)
def rdf = new Namespace(
'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf')
def dc = new Namespace(
'http://purl.org/dc/elements/1.0/', 'dc')
def bibterm = new Namespace(
'http://www.book-stuff.com/terms/', 'bibterm')
def b = book[rdf.Description][bibterm.book]
assert b[dc.title].text() == 'Groovy in Action'
assert b[bibterm.year].text() == '2007'
XmlSlurper and Namespaces
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'
What about non-XML?
def neko = new org.cyberneko.html.parsers.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 }
def neko = new org.cyberneko.html.parsers.SAXParser()
def page = new XmlSlurper(neko).parse('http://groovy.codehaus.org/')
def data = page.depthFirst().grep{
it.name() == 'A' && it.@href.toString().endsWith('.html')
}.'@href'
data.each { println it }
http://groovy.codehaus.org/apidocs/index.html
/faq.html
/groovy-jdk.html
...
Raw DOM
import groovy.xml.DOMBuilder
def trip = DOMBuilder.parse(Flights.reader).documentElement
def 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
MarkupBuilder
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.records() {
car(name:'HSV Maloo', make:'Holden', year:2006) {
country('Australia')
record(type:'speed', 'Production Pickup Truck with speed of 271kph')
}
car(name:'P50', make:'Peel', year:1962) {
country('Isle of Man')
record(type:'size', 'Smallest Street-Legal Car at 99cm wide and 59 kg in weight')
}
car(name:'Royale', make:'Bugatti', year:1931) {
country('France')
record(type:'price', 'Most Valuable Car at $15 million')
}
}
StreamingMarkupBuilder
def xml = new StreamingMarkupBuilder().bind{
records {
car(name:'HSV Maloo', make:'Holden', year:2006) {
country('Australia')
record(type:'speed', 'Production Pickup Truck with speed of 271kph')
}
car(name:'P50', make:'Peel', year:1962) {
country('Isle of Man')
record(type:'size', 'Smallest Street-Legal Car at 99cm wide and 59 kg in weight')
}
car(name:'Royale', make:'Bugatti', year:1931) {
country('France')
record(type:'price', 'Most Valuable Car at $15 million')
}}}
Updating with XmlParser
def root = new XmlParser().parseText(input)
// modify groceries: quality items please
def groceries = root.category.findAll{ it.@type == 'groceries' }.item
groceries.each { g ->
g.value = 'Luxury ' + g.text()
}
// modify supplies: we need extra pens
def supplies = root.category.findAll{ it.@type == 'supplies' }.item
supplies.findAll{ it.text() == 'Pens' }.each { s ->
s.@quantity = s.@quantity.toInteger() + 2
s.@when = 'Urgent'
}
// modify presents: August has come and gone
def presentCategory = root.category.find{ it.@type == 'present' }
presentCategory.children().clear()
presentCategory.appendNode('item', \"Mum's Birthday\")
presentCategory.appendNode('item', [when:'Oct 15'], \"Monica's Birthday\")
Updating with XmlSlurper
def root = new XmlSlurper().parseText(input)
// modify groceries: quality items please
def 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
def pens = root.category.find{ it.@type == 'supplies' }.item.findAll{
it.text() == 'Pens' }
pens.each { p ->
p.@quantity = (p.@quantity.toInteger() + 2).toString()
p.@when = 'Urgent'
}
// modify presents: August has come and gone
def presents = root.category.find{ it.@type == 'present' }
presents.replaceNode{ node ->
category(type:'present'){
item(\"Mum's Birthday\")
item(\"Monica's Birthday\", when:'Oct 15')
}
}
Groovy and Web Services
• Straight Web Services
– JAXB out of the box for Java 6
– CXF, Axis2, Spring Web Services
– GroovySOAP using XFire for Java 1.4
– GroovyWS using CXF for Java 1.5+
• Frameworks layered upon SOA
– Synapse, Tuscany, ServiceMix
GroovySOAP
class MathService {
• Based on
double add(double a, double b) {
a+b
Xfire, Java 1.4
}
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.0
assert math.square(3.0) == 9.0
GroovyWS
class MathService {
double add(double arg0, double arg1) {
• CXF based
arg0 + arg1
}
• Java 1.5+
double square(double arg0) {
arg0 * arg0
}
}
import groovyx.net.ws.WSServer
def server = new WSServer()
server.setNode MathService.name,
\"http://localhost:6980/MathService\"
import groovyx.net.ws.WSClient
def 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.0d
result = proxy.square(3.0d)
assert result == 9.0d
JAXB Server
import javax.xml.ws.Endpoint
import javax.jws.WebService
import javax.jws.soap.SOAPBinding
import 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 ...'
JAXB Client
import javax.xml.namespace.QName
import 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).echoPort
println echoServer.echo(\"Today is ${new Date()}\")
JAXB Build
wsimport -d ../build -p com.asert.jaxws http://localhost:8080/Echo?wsdl
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
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.
php/10928_3570031_2
RESTful options
• Several Options
– Already supported in discussed
frameworks, e.g. CXF
– Groovy Restlet DSL
http://docs.codehaus.org/display/GROOVY/GroovyRestlet
– Jersey
http://wikis.sun.com/display/Jersey/Main
– restlet.org
http://www.restlet.org
restlet.org
import 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 status
response.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)
}
// ...
Jersey...
package com.asert
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import 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.wadl
App available at ${baseUri}helloworld
\"\"\"
create(baseUri, initParams)
...Jersey...
import javax.ws.rs.PathParam
import 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, to
static 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())
}
}
...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)
}
}
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
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
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
...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) {
Compile your Groovy using groovyc
n1 * n2
(with or without annotations) then
}
</tuscany:implementation.script> Need
treat just like normal Java.
</component>
groovy jar in your runtime classpath.
<component name=\"DivideServiceComponent\">
<tuscany:implementation.script
script=\"calculator/DivideServiceImpl.groovy\"/>
</component>
</composite>
...Tuscany
<composite ...>
With Groovy scripts either
<component name=\"CalculatorServiceComponent\" .../>
<component name=\"AddServiceComponent\" .../>
embedded or in files – no
<component name=\"SubtractServiceComponent\">
compilation necessary.
<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>
More Information
• Groovy in Action
• Processing XML
http://groovy.codehaus.org
/Processing+XML
• GroovyWS
http://groovy.codehaus.org
/GroovyWS
• Coming soon ...
0 comments
Post a comment