Groovy XML ProcessingVladimir Forfutdinov
• Java vs Groovy short cuts
• XML parsing with Groovy
• XML generating with Groovy
• How to access EO inside Groovy
Outline
Java vs Groovy
• Java Virtual Machine thinks Groovy is Java
• Inspired by Python, Ruby and Smalltalk
• Java developers will have almost-zero learning curve
• Supports Domain-Specific Languages
• Powerful processing primitives, OO abilities and an Ant DSL
• Works with existing Java objects and libraries
Setup
• Easy to find and learn at: http://groovy.codehaus.org/
• Eclipse plug-in at: http://groovy.codehaus.org/Eclipse+Plugin
• groovyShell
• groovy-all-1.7.0.jar
Hello World!
//JAVAclass myfirstjavaprog{ public static void main(String args[]) { System.out.println("Hello World!"); }}
//GROOVYprintln "Hello World!"
//GROOVY command linegroovy -e "println 'Hello ' + args[0]" World!
GROOVY SCRIPT ARG 1
Method call on a Null Object
//JAVAMyObject obj = nullif (obj != null){ return obj.getChildren().getFirst()} else { return null}
//GROOVYMyObject obj = nullreturn obj?.getChildren()?.getFirst()
Closures
Functions that are first class objects - a chunk of code that can be passed around as if it were a string or an integer.
square = { it * it }
square(4)
//RESULT16
[ 1, 2, 3, 4 ].collect(square)
//RESULT[ 1, 4, 9, 16 ]
printMapClosure = { key, value -> println key + "\t= " + value }
[ "Name" : "John", "Address" : "Here", "Likes" : "WOWODC" ].each(printMapClosure)
//RESULTName = JohnAddress= HereLikes = WOWODC
Meta Class - Methods
Intercepting Method Calls
class MyClass{ def hello(){ 'invoked hello directly' } def invokeMethod(String name, Object args){ return "unknown method $name(${args.join(', ')})" }}
def mine= new MyClass()
println mine.hello()
//RESULTinvoked hello directly
println mine.foo("Mark", 19)
//RESULTunknown method foo(Mark, 19)
Meta Class - Properties
Intercepting Property Accesses
class MyClass{ def greeting = 'accessed greeting directly' Object getProperty(String property) { "read from property $property" } void setProperty(String property, Object newValue) { throw new Exception("wrote to property $property") }}def mine = new MyClass()
//try to read ‘greeting’ propertyprintln mine.greeting
//RESULTread from property greeting
//try to set ‘greeting’ propertytry { mine.greeting = 'hi' } catch(e) { println e.message }
//RESULTwrote to property greeting
//we can access a property directly using .@ syntaxprintln mine.@greeting
//RESULTaccessed greeting directly
Meta Class - At Run-time
Adding new property and method to a class during Run-time
class A{}
//add new property ‘hi’A.metaClass.hi = 'Hi!!!'
def a1 = new A()
println a1.hi
//RESULTHi!!!
//add new method ‘hello’A.metaClass.hello = {-> "Hello!"}
def a2 = new A()
println a2.hello()
//RESULTHello!
XML Parsing
• XmlParser - supports GPath expressions for XML documents
• XmlSlurper - lower overheads than XmlParser due to lazy evaluation
XML Input sample
class XmlExamples { static def CAR_RECORDS = ''' <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 in 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> '''}
XmlParser vs XmlSlurper
def records_p = new XmlParser().parseText(XmlExamples.CAR_RECORDS)def records_s = new XmlSlurper().parseText(XmlExamples.CAR_RECORDS)
def allRecords_p = records_p.car.size()assert allRecords_p == 3def allRecords_s = records_s.car.size()assert allRecords_s == 3
def allNodes = records_p.depthFirst().size()assert allNodes_p == 10def allNodes_s = records_s.depthFirst().collect{ it }.size()assert allNodes_s == 10
def firstRecord_p = records_p.car[0]def firstRecord_s = records_s.car[0]
assert 'car' == firstRecord_p.name()assert 'car' == firstRecord_s.name()
assert 'Holden' == firstRecord_p.'@make'assert 'Holden' == [email protected]()
assert 'Australia' == firstRecord_p.country.text()assert 'Australia' == firstRecord_s.country.text()
<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 in 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>
XmlParser vs XmlSlurper
// 2 cars have an 'e' in the makeassert records_p.car.findAll{ it.'@make'.contains('e') }.size() == 2
// option 1assert records_s.car.findAll{ [email protected]().contains('e') }.size() == 2
// option 2assert records_s.car.findAll{ it.@make =~ '.*e.*' }.size() == 2
<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 in 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>
XmlSlurper
println records.depthFirst().grep{ it.@type != '' }.'@type'*.text()//Result[speed, size, price]
println records.'**'.grep{ it.@type != '' }.'@type'*.text()//Result[speed, size, price]
def countryOne = records.car[1].countryprintln countryOne.parent()[email protected]()//ResultPeel
println countryOne.'..'[email protected]()//ResultPeel
// names of cars with records sorted by yearprintln records.car.list().sort{ [email protected]()}.'@name'*.text()//Result[Royale, P50, HSV Maloo]
// names of countries with ‘s’ in the nameprintln records.'**'.grep{ it.@type =~ 's.*' }*.parent().country*.text()//Result[Australia, Isle of Man]
<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 in 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>
XmlSlurper
class XmlExamples { static def CF_MESSAGE = ''' <cf:Message xmlns:cf='http://controlsforce.com/schema/message.xsd'> <cf:Properties> <cf:Name>ck_mstr</cf:Name> <cf:Process>FREUDENBERG_DEMO</cf:Process> <cf:User></cf:User> <cf:Date>Fri Dec 25 17:37:55 EST 2009</cf:Date> <cf:MessageCount>0</cf:MessageCount> <cf:UID>uniqueId-0</cf:UID> </cf:Properties> </cf:Message> '''}
def xml = new XmlSlurper().parseText(XmlExamples.CF_MESSAGE)
xml.Properties.children().each { println "${it.name()} -> ${it.text()}" }
Name -> ck_mstrProcess -> FREUDENBERG_DEMOUser -> Date -> Fri Dec 25 17:37:55 EST 2009MessageCount -> 0UID -> uniqueId-0
XML Generation
• Building XML using simple Java
• StreamingMarkupBuilder - A builder class for creating XML markup
Java
//assuming we have a Writer created alreadywriter.write("<root>"); writer.write(“<a a1=‘one’>”); writer.write("<b>3 < 5</b>"); writer.write("<c a2=‘two’>blah</c>"); writer.write("<d>"); writer.write("<f>hello</f>"); writer.write("</d>"); writer.write("</a>");writer.write("</root>");
//Result<root> <a a1='one'> <b>3 < 5</b> <c a2='two'>blah</c> <d> <f>hello</f> </d> </a></root>
StreamingMarkupBuilder
import groovy.xml.StreamingMarkupBuilder
new StreamingMarkupBuilder().bind { root { a ( a1:'one' ) { b { mkp.yield( '3 < 5' ) } c ( a2:'two', 'blah' ) d { f (‘hello’) } } }}.toString()
//Result<root> <a a1='one'> <b>3 < 5</b> <c a2='two'>blah</c> <d> <f>hello</f> </d> </a></root>
writer.write("<root>"); writer.write(“<a a1=‘one’>”); writer.write("<b>3 < 5</b>"); writer.write("<c a2=‘two’>blah</c>"); writer.write("<d>"); writer.write("<f>hello</f>"); writer.write("</d>"); writer.write("</a>");writer.write("</root>");
StreamingMarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def dic = ['a':123, 'b':456, 'c':949]
new StreamingMarkupBuilder().bind { root { dic.each { "${it.key}" (it.value) } }}.toString()
//Result<root> <a>123</a> <b>456</b> <c>949</c></root>
StreamingMarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def dic = ['a':['name':'apple', 'type':123], 'b':['name':'cheese', 'type':456], 'c':['name':'milk', 'type':949]]
new StreamingMarkupBuilder().bind { root { dic.each { "${it.key}" (it.value) } }}.toString()
//Result<root> <a name='apple' type='123'/> <b name='cheese' type='456'/> <c name='milk' type='949'/></root>
EOs inside Groovy
• Setup
• Demo
Setup
• Main Component with a set of tables that will list the data from each table
• Main.java replaced with Main.groovy (just for some extra fun)
• MySQL Database with three tables:
• Employee
• Manager
• Job
*0..1
*0..1
*0..1salary
name
manager
job
age
Employee
owner
name
employees
Job
name
jobs
employees
Manager
DEMO
Questions
Bonus
ConfigSlurper
• Utility class within Groovy for writing properties file
• Unlike regular Java properties files ConfigSlurper scripts support native Java types and are structured like a tree.
Reading configuration with ConfigSlurper
def config = new ConfigSlurper().parse (configFile)//get the all of the config dataprintln config.WOWODC
//Result["Bonus":["testId":1, "testName":"Hello Bonus Test string"], "BonusList":["key1":"val1", "hi":"hello"]]
//get only the Bonus sectionprintln config.WOWODC.Bonus
//Result["testId":1, "testName":"Hello Bonus Test string"]
//get only the testId valueprintln config.WOWODC.Bonus.testId
//Result1
//list keys and values under BonusList sectionconfig.WOWODC.BonusList.each {println "${it.key} = ${it.value}"}
//Resultkey1 = val1hi = hello
def configFile = ''' WOWODC { Bonus { testId = 1 testName = "Hello Bonus Test string" }
BonusList { key1 = 'val1' hi = 'hello' } }'''
Updating configuration with ConfigSlurper
//get the all of the config dataconfig.Hi.GoodBuy.say = 'Hello'
def sw = new StringWriter()println config?.writeTo(sw).toString()
//Result -->WOWODC { Bonus { testId=1 testName="Hello Bonus Test string" } BonusList { key1="val1" hi="hello" }}Hi.GoodBuy.say="Hello" // got updated
WOWODC { Bonus { testId = 1 testName = "Hello Bonus Test string" }
BonusList { key1 = 'val1' hi = 'hello' } }
Updating configuration with ConfigSlurper
//get the all of the config dataconfig.Hi.GoodBuy.tell = 'Hi'
def sw = new StringWriter()println config?.writeTo(sw).toString()
//ResultWOWODC { Bonus { testId=1 testName="Hello Bonus Test string" } BonusList { key1="val1" hi="hello" }}Hi { GoodBuy { say="Hello" tell="Hi" // got updated }}
WOWODC { Bonus { testId=1 testName="Hello Bonus Test string" } BonusList { key1="val1" hi="hello" }}Hi.GoodBuy.say="Hello"
Updating configuration with ConfigSlurper
//get the all of the config dataconfig.WOWODC.Bonus.testId = 2
def sw = new StringWriter()println config?.writeTo(sw).toString()
//Result -->WOWODC { Bonus { testId=2 // got updated testName="Hello Bonus Test string" } BonusList { key1="val1" hi="hello" }}Hi { GoodBuy { say="Hello" tell="Hi" }}
WOWODC { Bonus { testId=1 testName="Hello Bonus Test string" } BonusList { key1="val1" hi="hello" }}Hi { GoodBuy { say="Hello" tell="Hi" }}
DEMO