Sunday, 6 July 2008

Groovy for XML transformation

I have been playing with groovy recently, specifically with the MarkupBuilder, the groovy native support for markup languages. It basically means writing XML using native groovy syntax. Pretty neat.

In my job I have more often than not, have to write software to integrate two or more systems. This, often, means writing code that reads an XML stream onto a Java object for manipulation and eventually writes the result into another XML stream.

This typically involves creating binding objects for the input XML and the output XML and then a sequence of calls to getters on the input object and setters on output object to implement the mapping.

This task is very error prone and tedious. A good solution is using the MarkupBuilder (and, yes, I know about XSLT, but this is not the point here!)

So, suppose that you have the following POJO

public class Contact {
private String id;
private String name;
private String surname;
// ...
}
and you want to serialize the instance

Contact c = new Contact();
c.setId("123");
c.setName("John");
c.setSurname("Bloggs");
into the following XML

<contact>
<key>123</key>
<firstname>John</firstname>
<secondname>Bloggs</secondname>
</contact>


Note the difference between the POJO attribute names and the XML tag names

Using groovy and its MarkupBuilder, the first thing to do is to create a groovy converter, a class with a method that reads the data from the bean and produces the XML. Create a file src/groovy/ContactConverter.groovy with the following code:
class ContactConverter{
def convert(bean){
def writer = new StringWriter();
def xml = new MarkupBuilder();
xml.contact {
firstname(bean.name)
secondname(bean.surname)
}
writer.toString()
}
}

If you execute the code
println new COntactConverter().convert(c)

in a groovy shell, you get the XML shown above.

The next step is then to make this code executable from your Java service.
You can use this class (an adaptation of the code available in the groovy documentation):
public class GroovyConverterInvoker {
public String invoke(String fileName, Object bean){
GroovyObject groovyObject = createObjectFromStreamName(strName);
String result = (String)groovyObject.invokeMethod("convert", new Object[]{bean});
return result;
}

public GroovyObject createObjectFromStreamName(String fileName) {
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass;
File f = new File(fileName);
try {
groovyClass = loader.parseClass(f);
return (GroovyObject)groovyClass.newInstance();
} catch (CompilationFailedException e) {
throw new IllegalStateException("Unable to compile " + fileName, e);
} catch (IOException e) {
throw new IllegalStateException("Unable to open file " + fileName, e);
} catch (InstantiationException e) {
throw new IllegalStateException("Unable instantiate object for class in " + fileName, e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable access object of class in " + fileName, e);
}
}
}

You can then invoke the conversion:
public String invokeContactConverter(Object bean){
String fName = "src/groovy/ContactConverter.groovy";
return new GroovyConverterInvoker().invoke(fName, bean);
}

You can now use JAXB/XStream or XmlBeans to parse the result of invokeContactConverter and initialize a new POJO for further use, or send the XML on the wire.

The solution can be generalised and optimised to a point where you only need to write groovy converters and load/modify them dynamically when necessary.

4 comments:

jau said...

And in Scala you do it like this



class Person(

  val key: Int,

  val name: String,

  val secondName: String

)

{

   def toXml = 

     <contact>

       <key> { key } </key>

       <firstname> {name } </firstname>

     <secondname> { secondName } </secondname>

     </contact>

}

Anonymous said...

@jau -- okay you scala smartass -- in Grails (which sits atop groovy):

p = new Person(key:blah, name:"bob", secondName:"smartass")

render p as XML

smartrics said...

I promised myself to look at Scala a bit closer.

@jau: the approach shown in is similar to any other template technology (jsp, velocity, ...): they all suffer of the same painful problems: mixing xml syntax and native language, lack of IDE support. I find XmlSlurper+MarkupBuilder neater

@anonymous: Grails approach (which can be implemented in Groovy as well with few meta-programming) does't solve my problem. That is mapping

class Customer{
  def id
  def name
  def surname
}

into

<client>
  <key>...</key>
  <firstname>...</firstname>
  <secondname>...</secondname>
</client>

Note the difference between the attribute name and the tag name

Christian Lipp said...

In Groovy you can also have templates as in Scala, see here: http://groovy.codehaus.org/Groovy+Templates

In your code you are missing the key in the result XML.

And it is only the half of the solution, see http://tatiyants.com/xml-transformation-performance-groovy-vs-xslt/