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" } }