XML rendering in Grails

Notes taken when researching how to render efficiently a possibly large object tree resulting in complex XML.

Disclaimer: tested with Grails 1.3.7, but it should be valid in more recent framework versions

Convert object to XML string using XML converter

def ping = {
    def result = new Result(response: 'OK')
    def xml = new XML(result)
    render text: xml, contentType: 'text/xml', encoding: 'UTF-8'
}
  • adds explicit <class>…</class> element to the output
  • double transformation – object -> XML string -> ServletResponse output stream
  • relies on poorly documented Grails contracts – here be dragons

Variation – deep conversion

XML.use('deep') {
    def xml = new XML(result)
    render text: xml, contentType: 'text/xml', encoding: 'UTF-8'
}

Variation – customize conversion with a custom marshaller

class ResultMarshaller implements ObjectMarshaller {
    @Override
    boolean supports(object) {
        object instanceof Result
    }

    @Override
    void marshalObject(object, Converter converter) {
        // converter is an instance of XML
        converter.build {
            response object.response
        }
    }
}

class ResultRootElementCustomizingMarshaller extends ResultMarshaller implements NameAwareMarshaller {
    @Override
    String getElementName(o) {
        'wynik'
    }
}

Unit tests

def 'ping, no marshaller'() {
    when:
    controller.ping()

    then:
    XmlAsserts.xmlsEqual controller.response.contentAsString, """<result>
    <class>${Result.name}</class>
    <response>OK</response>
</result>"""
}

def 'ping, marshaller'() {
    given:
    XML.registerObjectMarshaller new ResultMarshaller()

    when:
    controller.ping()

    then:
    XmlAsserts.xmlsEqual controller.response.contentAsString, '<result><response>OK</response></result>'
}

def 'ping, marshaller customizing root element name'() {
    given:
    XML.registerObjectMarshaller new ResultRootElementCustomizingMarshaller()

    when:
    controller.ping()

    then:
    XmlAsserts.xmlsEqual controller.response.contentAsString, '<wynik><response>OK</response></wynik>'
}

Render object to the response using XML converter

def pingConverterRendersToResponse = {
    def xml = new XML(new Result(response: 'OK'))
    xml.render response
}

Unit tests

def 'ping, converter renders to response'() {
    when:
    controller.pingConverterRendersToResponse()

    then:
    XmlAsserts.xmlsEqual controller.response.contentAsString, """<result>
<class>${Result.name}</class>
<response>OK</response>
</result>"""
}

Convert object to XML string using StreamingMarkupBuilder

def pingViaBuilder = {
    def res = new Result(response: 'OK')
    render contentType: 'text/xml', encoding: 'UTF-8', {
        result {
            response res.response
        }
    }
}
  • feature provided by render method
  • direct write into ServletResponse writer
  • could lead to repeated rendering code

Unit tests

def 'ping, direct render'() {
    when:
    controller.pingViaBuilder()

    then:
    XmlAsserts.xmlsEqual controller.response.contentAsString, '<result><response>OK</response></result>'
}

Common objects/helper methods used in examples

class Result {
    String response
}

class XmlAsserts {
    static void xmlsEqual(String actual, String expected) {
        XMLUnit.ignoreWhitespace = true
        def diff = new Diff(actual, expected)
        assert diff.similar(), "${diff.toString()}\nActual XML: $actual\n\nExpected XML: $expected"
    }
}
Advertisements