Wednesday 10 April 2013

Groovy Xml Series: JAXB

Although the Groovy way of dealing with XML is simply great, sometimes it's nice to use Jaxb for things such as schema validation or maybe for establishing what fields are involved in the process and what others are not.

This is not going to be an entry of JAXB specially when there are hundreds of blog entries and even books covering the topic widely.

I'm going to create a very simple example to unmarshall a given object instance to XML. So here we have the XML source

                                                                                                            
                       
                       
                   
                   Don Xijote       
                   Manuel De Cervantes
                       
                   
                   Catcher in the Rye
                  JD Salinger
                      
                  
                  Alice in Wonderland
                  Lewis Carroll
              
                   
                  Don Xijote       
                  Manuel De Cervantes
                      
                     
                     
  

Before doing anything, just remind you that the code is available at Github.

And now we should be creating the classes which are going to map the XML fragments in which I'm interested.
  • Author
package github.groovy.xml.jaxb                                                                                                          
                              
   import javax.xml.bind.annotation.*
   
   @XmlRootElement(name="author")
   @XmlAccessorType(XmlAccessType.FIELD)
   class Author{              
   
       @XmlAttribute Long id  
      @XmlElement String name
        
 } 
  • Book
 package github.groovy.xml.jaxb                                                                                                          
                              
   import javax.xml.bind.annotation.*
   
   @XmlRootElement(name="book")
   @XmlAccessorType(XmlAccessType.FIELD)
   class Book{                
   
       @XmlAttribute Long id  
      @XmlElement String title
      @XmlElement Author author
          
 }


Basically I'm using the following annotations:

  • @XmlRootElement: for be able to marshal the object without having to include it in a another object having this annotation. Only objects annotated with this annotation can be marshaled directly.
  • @XmlAccessorType(XmlAccessType.FIELD): Because we're using Groovy, and there is "a lot of magic" at runtime you should tell JAXB to stick to the fields, otherwise it will try to unmarshall crazy things. 
  • @XmlElement: To tell JAXB what's going to be a tag element
  • @XmlAttribute: To tell JAXB what's going to be an attribute element

Unmarshalling (XML --> Object)

Well let's say we have an XML full of books and authors and we want to unmarshal them into objects. If we wanted to unmarshall the XML above to get only books and authors, we should pre-process the XML to get rid of the response/data tags. And only then unmarshal the remaining XML.

import javax.xml.bind.*

//...Omitted code

      def "Unmarshalling the first book with boilerplate code"(){
          setup: "Building the unmarshaller"
              def jaxbContext= JAXBContext.newInstance(Book)
              def unmarshaller = jaxbContext.createUnmarshaller()
          and: "Filtering the xml"
              def response = new XmlSlurper().parse(xmlFile) 
          and: "Getting only the first book"
              def firstBook = response.'**'.find{it.name() =='book'}
              def jaxbSource= XmlUtil.serialize(firstBook)
              def newXmlSource = new StringReader(jaxbSource)
          when: "Unmarshalling the source" 
              def jaxbBook = unmarshaller.unmarshal(newXmlSource)
          then: "We make sure the conversion took place"
              jaxbBook instanceof Book        
          and: "Checking Book properties"
              jaxbBook.title 
              jaxbBook.author
              jaxbBook.author.id
      }
I've created an utility class to avoid most of the repeated code when creating a Marshaller/Unmarshaller:

package github.groovy.xml.jaxb

import javax.xml.bind.*
import github.groovy.xml.util.ResourcesUtil

/**
 * This class helps us to handle marshalling and unmarshalling of JAXB objects
**/
class JaxbUtils {

 def object2MarshalType
 def source2Unmarshall 
 
 def marshal(object){
  object2MarshalType = object.getClass()
  this
 }

 def to(File file){
  throwIfNull(object2Marshal)
  throwIfNull(file)  

  buildMarshaller(object2MarshalType).marshal(object2MarshalType,file)
 }

 def unmarshal(source){
  source2Unmarshall = source 
  this
 }

 def to(Class anyType){
  throwIfNull(source2Unmarshall)
  throwIfNull(anyType)

  buildUnmarshaller(anyType).unmarshal(source2Unmarshall)
 }

 def buildMarshaller(Class type){
  def jaxbContext= JAXBContext.newInstance(type)
  def marshaller = jaxbContext.createMarshaller()

  marshaller
 }

 def buildUnmarshaller(Class type){
  def jaxbContext= JAXBContext.newInstance(type)
  def unmarshaller = jaxbContext.createUnmarshaller()

  unmarshaller
 }


 def throwIfNull(value,message="You should have provided any value"){
  if (!value){
   throw new Exception(message)
  }
 }

}


So the previous code becomes:
      def "Unmarshalling the first book the good way"(){
          setup: "Parsing the document"
              def response = new XmlSlurper().parse(xmlFile)
          and: "Getting only the first book"
              def firstBook = response.'**'.find{it.name() =='book'}
              def jaxbSource= XmlUtil.serialize(firstBook)
          when: "Convert to Jaxb object"
              def newXmlSource = new StringReader(jaxbSource)
              def jaxbBook = unmarshal(newXmlSource).to(Book)
          then: "We make sure the conversion took place"
              jaxbBook instanceof Book
          and: "Checking Book properties"
              jaxbBook.title
              jaxbBook.author
              jaxbBook.author.id
      }

Marshalling (Object --> XML)

I was a little bit lazy and I didn't build any example for marshalling an object to XML. Maybe I will update this entry later on with a working example.

Resources


2 comments:

  1. I have a Xml which i am not able to marshall these is:

    30

    121

    ReplyDelete
    Replies
    1. STATUSCODE
      20
      DISPLAYID
      30
      DISPLAYID
      STATUSCODE

      Delete