2014-10-23

Tags: groovy, SOAP

All I wanted was a JVM-based script exemplifying a simple SOAP call. However, I wasn’t satisfied with their complexity and pieces.

Best practices have established using a contract-first approach is more reliable and resilient. Spring also documented the prototypical Java SOAP client: Consuming a SOAP web service. The example has a clear classes & separation of concerns, but still requires a build file to generate code from the WSDL using JAXB.

My Simple Example: in Groovy

My example breaks the client into 3 parts:

  1. Creating the Request

  2. Get the Response

  3. Extracting Data

Creating the Request

The traditional approach creates an object, then marshalls it (converts object to a string).

String buildRequest(String zip) {
    def writer = new StringWriter()
    def builder = new MarkupBuilder(writer)

    builder.GetCityForecastByZIP(xmlns: "http://ws.cdyne.com/WeatherWS/") {
        ZIP(zip)
    }
    return writer.toString()
}

Groovy’s MarkupBuilder makes it super easy to safely build XML (or HTML). It can handle attributes, escaping content, and even namespaces! The XML is patterned from the WSDL’s Request object manually, or using SoapUI to build it from the WSDL.

Get the Response

Both examples use Spring’s WebServiceTemplate methods.

String callSoapWeather(String body) {
    def msgFactory = new SaajSoapMessageFactory()
    msgFactory.afterPropertiesSet()

    def wsTemplate = new WebServiceTemplate(msgFactory)
    wsTemplate.setDefaultUri("http://wsf.cdyne.com/WeatherWS/Weather.asmx")

    def writer = new StringWriter()
    try {
        wsTemplate.sendSourceAndReceiveToResult(
            new StringSource(body),
            new SoapActionCallback("http://ws.cdyne.com/WeatherWS/GetCityForecastByZIP"),
            new StreamResult(writer)
        )
    } catch (Exception e) { println "ERROR: ${e.message} - ${e.cause}" }
    return writer.toString()
}

Spring’s WebServices and Templates handles calling the SOAP service, callback status and converting the response stream back into a string.

Extracting Data

Instead of unmarshalling (converting a string to an object):

void printResults(String response) {
    final xml = new XmlSlurper().parseText(response)
    def nodes = xml.GetCityForecastByZIPResult
    println "Forecast for ${nodes.City}, ${nodes.State}"

    def format = new SimpleDateFormat("yyyy-MMM-dd")
    nodes.ForecastResult.children().each { forecast ->
        def inDate = new Date().parse("yyyy-MM-dd'T'HH:mm:ss", forecast.Date as String)
        println "${format.format(inDate)} ${forecast.Description} ${forecast.Temperatures.MorningLow} - ${forecast.Temperatures.DaytimeHigh}"
    }
}

Groovy’s XmlSlurper takes the place of traditional JAXB unmarshalling. Instead of mapping XML into Objects, XmlSlurper parses the string into Nodes and GPathResults. As long as the names of the nodes you need don’t change, you can name the path to the data, or even .depthFirst().collect{ it }.findAll{ it.name() == "NODE_NAME" } which allows the path to change and still work.

Conclusion

SOAP doesn’t have to be intimidating on the JVM platform. I hope others find the contrast with the traditional JAXB approach informative. My full working SimpleSoap.groovy is posted as a single file in a gist.