Wednesday, May 9, 2012

Grails : Rest Service with JAXB parsing - PART 2

In my last post on Rest Service with JAXB parsing, I showed a very basic example of developing REST Service with Grails Controller and JAXB. In this blog I am going to cover little more advanced features and will mainly be covering following.
  • Collection mapping in JAXB
  • Exception Handling
  • Returning response in XML
  • URLMappings for Grails Controller


The input XML that will be sent to REST Service is as follows. It's about storing order details via REST Service.

<?xml version="1.0" encoding="utf-8"?>
<Order_Details>
    <order_number>1234</order_number>
    <customer_name>XYZ</customer_name>
    <items>
        <item>
            <name>Item1</name>
            <quantity>4</quantity>
        </item>
        <item>
            <name>Item2</name>
            <quantity>2</quantity>
        </item>
    </items>
</Order_Details>

Let's create domain class representing this XML. We have two domain classes here -
Order - For storing Order Details

import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlElementWrapper
import javax.xml.bind.annotation.XmlAccessorType
import javax.xml.bind.annotation.XmlAccessType

@XmlRootElement(name="Order_Details")
@XmlAccessorType(XmlAccessType.FIELD)
class Order {
    @XmlElement(name="order_number")
    int orderNumber

    @XmlElement(name="customer_name")
    String custName

    @XmlElementWrapper(name="items")
    @XmlElement(name="item")
    List<Item> items = new ArrayList<Item>()
}

Item - For storing individual Item details

@XmlRootElement(name="item")
@XmlAccessorType(XmlAccessType.FIELD)
class Item {
    String name
    int quantity
}


Let's first discuss about annotations in Order and Item classes.
  • @XmlRootElement has been used to map classes with their respective XML elements. e.g Order class has name "Order_Details" which maps directly to root element of XML.
  • @XmlAccessorType is must for Groovy classes. Through this we tell JAXB parser to apply xml bindings at field level. If not defined, there is bright chance of getting exception related to metaClass since JAXB tries to do mapping for metaClass property.
  • @XmlElement annotation is used to map class property with XML element. e.g. In Order class, property orderNumber has been mapped with it's xml node via @XmlElement(name="order_number"). However there is no such annotation in Item class. That's because if no annotation has been applied, then element name is assumed to be same that of property name.
  • @XmlElementWrapper annotation is used to wrap collection of elements inside an element. In this case, "item" elements are wrapped inside "items" element.
So that was all about annotation for JAXB bindings. We also need to send back response in XML format. Again for this we will use JAXB. So let's create a class representing REST Service response and then we will convert instance of this class in XML using JAXB.

@XmlRootElement(name="Order_Response")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = ['status','errorCode','errorMessage'])
class OrderResponse {
    String status
    String errorCode
    String errorMessage
}

So our response will have three elements.
  • Status - SUCCESS/ERROR
  • ErrorCode - Error Code in case status is ERROR
  • ErrorMessage - Error Message corresponding to ErrorCode
So now it's time to write controller which will be exposed as a REST Service

class OrderRestController {

    def messageSource

    def save = {
        String responseXml
        try{
            String xml = request.inputStream.text
            Order order = XmlUtil.unMarshal(xml, Order.class)
            println "items..${order.items*.quantity}"
            order.save()
            responseXml = XmlUtil.marshal(new OrderResponse(status: "SUCCESS"))
        }catch(Exception e){
            log.error("An error occurred while saving order details : $e")
            String errorCode = "order.save.error"
            OrderResponse orderResponse = new OrderResponse(status : "ERROR", errorCode: errorCode,
                    errorMessage: messageSource.getMessage(errorCode,null,"",Locale.ENGLISH))
            responseXml = XmlUtil.marshal(orderResponse)
        }
        render (text : responseXml, contentType: "text/xml", encoding: "UTF-8")
    }

}


Here the save method of OrderRestController will be exposed as REST Service. We will see how to map this to a URL via URLMappings file in Grails. But for now just assume this method is exposed as a service. So first thing we do in this method is to get request XML in String format via "request.inputStream.text". Then we ask XMLUtil to convert this XML to Order object via it's unMarshal method. We have wrapped the logic of converting XML to Object in XMLUtil since there is some amount of boiler plate code which would be required to be written for every conversion. So it's generally good idea to move this logic in separate Utility file.(XmlUtil in this case).

XmlUtil Class

class XmlUtil {

     static String marshal(def source){
        StringWriter writer = new StringWriter()
        JAXBContext context = JAXBContext.newInstance(source.class)
        Marshaller marshaller = context.createMarshaller()
        marshaller.marshal(source, writer)
        return writer.toString()
    }

    static def unMarshal(String strXml, Class targetClass){
        JAXBContext context = JAXBContext.newInstance(targetClass)
        def unMarshaller = context.createUnmarshaller()
        return unMarshaller.unmarshal(new StringReader(strXml))
    }

}


Once we have got Order object from request XML, we can perform any business validation if required or can directly save in database. Once done, a success message needs to be sent back. For this we create OrderResponse object and set status as "SUCCESS". Then we again ask XmlUtil to convert this object in XML String via marshal method. After we have received XML String, we render it as XML by setting contentType : "text/xml".

In case of any exception during XML parsing or performing business validation rules or saving Order instance in DB, catch block in OrderRestController will be executed. In this, we create OrderResponse object with status as "ERROR" and populate required errorCode and errorMessage fields. This instance is again converted into XML by XmlUtil.marshal method and rendered back.

Sample success response XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Order_Response>
    <status>SUCCESS</status>
</Order_Response>

Sample error response XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Order_Response>
    <status>FAILURE</status>
    <errorCode>rawResponseData.batchId.blank</errorCode>
    <errorMessage>Batch ID is mandatory</errorMessage>
</Order_Response>

Finally we need to provide URL Mapping for OrderRestController. Under normal circumstances Grails convention URL i.e. http://<server>/<appName>/<controller>/<action> should suffice. But there is a chance that all REST services are grouped together and exposed under custom URL, something like http://<server>/<appName>/rest/ServiceName/action. For such customization we need to edit URLMappings.groovy placed under grails-app/conf. Following configuration should do the trick.

class UrlMappings {

    static mappings = {
        "/rest/OrderService/$action" (controller : "orderRest"){

        }
    }
    
}


So OrderService is now exposed at
http://localhost:8080/RestExample/rest/OrderService/save

So this was all about exposing OrderController as REST Service via JAXB and Grails Controller. Do let me know if you have any queries or comments.

No comments:

Post a Comment