Java for the Architecturally Challenged – Creating a valid SOAP request as a String

How to create a valid SOAP request when you’re not actually submitting it to a web-service.

So, you’ve got a WSDL for a web-service and you need to write some code to create a call for it.

Yep, I know what you’re thinking. No-brainer right? In this wonderful open source age with web-service heavyweights like CXF and Axis2 at our disposal, creating a SOAP request isn’t something you do, it’s something that happens for you.

But, in addition to your no-brainer requirement, you’ve got a no-brain architect who’s mandating you create the SOAP request in one system component and then pass it to another for submission to the web-service.  In a nutshell, create a valid SOAP request in a String but not as part of a direct call to that web-service.

Hmmm…

Of no-brains and no-brainers

The Google-cave offers unsurprisingly few suggestions here, probably because so few people are dumb enough to do it this way. The only helpful suggestions I found on the venerable StackOverflow.com were to either to cannibalise the code from SoapUI or to use Membrane SOA’s SOAPRequestCreator.

Cannibalising SoapUI is too much hard-work for a lazy sod like me and my day-rate is too low to follow a colleague’s suggestion of taking out a contract on the architect, though I admit that would be the most rewarding option. That’ll have to wait ‘til I win the lottery or become Emperor of the World – either of which is long overdue in my opinion.

Membrane’s offering is interesting but uses an XPath based field-by-field property mapping to populate the request which, as I can generate JAXB objects from the WSDL, feels like too much work for my needs again.

But then I remembered an old friend from many, many, many years ago.

SAAJ.

Creating a SOAP request with SAAJ

I originally encountered SAAJ, the SOAP with Attachments API for Java, in Sun’s Java Web Service Development Pack back when I was just a baby code-monkey starting out in web-services. It turns out that it’s still alive and kicking and conveniently part of the current JDK. It makes creating SOAP requests in a standalone situation like this as close to a no-brainer as I think I can get.

To kick off this example we’ll need a WSDL. Apropos of nothing I’ve created a simple hitmanService web-service which offers a single requestAQuote call.

<wsdl:portType name="HitmanService">
   <wsdl:operation name="requestAQuote">
      <wsdl:input message="tns:requestAQuote"
                  name="requestAQuote">
      </wsdl:input>
      <wsdl:output message="tns:requestAQuoteResponse"
                   name="requestAQuoteResponse">
      </wsdl:output>
   </wsdl:operation>
</wsdl:portType>

The model objects used by this web-service are defined in a separate, JAXB friendly hitmanService.xsd so I can easily generate code for the non top-level SOAP stuff. The hitDetails object that we pass to the requestAQuote call has four fields – type of hit along with the target’s height, weight and name:-

<xs:complexType name="hitDetails">
   <xs:sequence>
      <xs:element minOccurs="0" name="hitType" type="tns:hitType"/>
      <xs:element minOccurs="0" name="victimHeight">
          <xs:simpleType>
              <xs:restriction base="xs:int">
                  <xs:minInclusive value="0"/>
                  <xs:maxInclusive value="200"/>
              </xs:restriction>
          </xs:simpleType>
      </xs:element>
      <xs:element minOccurs="0" name="victimName" type="xs:string"/>
      <xs:element minOccurs="0" name="victimWeight">
          <xs:simpleType>
              <xs:restriction base="xs:int">
                  <xs:minInclusive value="0"/>
                  <xs:maxInclusive value="200"/>
              </xs:restriction>
          </xs:simpleType>
      </xs:element>
  </xs:sequence>
</xs:complexType>

The full WSDL, XSD, Maven project and associated code for this example are all available on GitHub.

Creating our top-level SOAP request with SAAJ is straightforward, using just a few classes from the java.xml.soap package which mirror the structure of the SOAP envelope itself:-

public String makeRequestAQuote(RequestAQuote quoteRequest)
              throws SOAPException {

   // Construct a SOAP message wrapper
   MessageFactory messageFactory = MessageFactory.newInstance();
   SOAPMessage soapMessage = messageFactory.createMessage();
   SOAPBody messageBody = soapMessage.getSOAPBody();

   // TODO
}

My makeRequestAQuote method will return a String containing a validly formed SOAP request, and we’re passing into it the RequestAQuote JAXB generated class that forms the body of the SOAP message. We’ve now got a SOAP message ready to receive it, but how do we get it in there?

Well the SOAPBody class extends org.w3c.dom.Node and JAXB will marshal to a DOM Node, so we can simply create an appropriate JAXBContext and marshal our object into the SOAPBody:-

public String makeRequestAQuote(RequestAQuote quoteRequest)
              throws SOAPException, JAXBException {

   // Construct a SOAP message wrapper
   MessageFactory messageFactory = MessageFactory.newInstance();
   SOAPMessage soapMessage = messageFactory.createMessage();
   SOAPBody messageBody = soapMessage.getSOAPBody();

   // Marshall the quoteRequest into the SOAP Body
   Marshaller marshaller = JAXBContext.newInstance(
           RequestAQuote.class).createMarshaller();
   marshaller.marshal(quoteRequest, messageBody);
   soapMessage.saveChanges();

   // TODO
}

I now have a fully formed SOAP request which I just need to render to a String. The SOAPMessage class has a writeTo method which renders the request to a stream, so with a little lightweight stream handling, our job is done:-

public String makeRequestAQuote(RequestAQuote quoteRequest)
              throws SOAPException, JAXBException, IOException {

   // Construct a SOAP message wrapper
   MessageFactory messageFactory = MessageFactory.newInstance();
   SOAPMessage soapMessage = messageFactory.createMessage();
   SOAPBody messageBody = soapMessage.getSOAPBody();

   // Marshall the quoteRequest into the SOAP Body
   Marshaller marshaller = JAXBContext.newInstance(
           RequestAQuote.class).createMarshaller();
   marshaller.marshal(quoteRequest, messageBody);
   soapMessage.saveChanges();

   // Write to a string and return
   ByteArrayOutputStream soapOutputStream =
           new ByteArrayOutputStream();
   soapMessage.writeTo(soapOutputStream);
   return soapOutputStream.toString();
}

Passing a populated RequestAQuote object to this method does indeed generate a properly formed SOAP request as a plain old String:-

<SOAP-ENV:Envelope 
        xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <ns3:requestAQuote 
             xmlns:ns3="http://services.devguerrilla.com/hitman-service">
         <hitDetails>
            <hitType>
                APPARENT_AUTOEROTIC_ASPHYXIATION_IN_WOMENS_UNDERWEAR
            </hitType>
            <victimHeight>160</victimHeight>
            <victimName>Technical Architect</victimName>
            <victimWeight>180</victimWeight>
         </hitDetails>
      </ns3:requestAQuote>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

And yes, I am really pissed at that architect!

Seeing may be believing but it’s not automatable, so we should really have a unit test to validate this. One option would be to compare the resulting string with a known good SOAP representation but I’m not fond of boilerplate checks like this in unit tests; they’re painful to maintain by hand and essentially useless if developers just cut-and-paste the output they’re getting into the test when updating the class.

I’m also not fond of using the same frameworks in the test for a class as the class itself uses, but as both JAXB and SAAJ are in the JDK it’s not unreasonable to presume they’re reliable and again, it does make performing the reverse a doddle, as illustrated in this Spock scenario:-

/** Class under test */
def RequestAQuoteCreator cut = new RequestAQuoteCreator()

/** Successful request case */
def "A valid SOAP message created for a request satisfying the schema"(){
    given: "A valid request for a hitman"
        def hitRequest = new RequestAQuote(hitDetails:
          new HitDetails(
            victimName: "Technical Architect",
            victimHeight: 160,
            victimWeight: 80,
            hitType: HitType.
                   APPARENT_AUTOEROTIC_ASPHYXIATION_IN_WOMENS_UNDERWEAR))

    when: "We generate a SOAP request for that object"
        def soapRequest = cut.makeRequestAQuote(hitRequest)

    then: "A valid SOAP request is returned"
        def SOAPMessage parsedSoapRequest = MessageFactory.
             newInstance().createMessage(null, 
             IOUtils.toInputStream(soapRequest))
    and: "it contains a valid request object"
        def RequestAQuote parsedHitRequest =
             JAXBContext.newInstance(RequestAQuote).createUnmarshaller().
                 unmarshal(parsedSoapRequest.SOAPBody.firstChild)
    and: "its properties match those in the original request"
        ReflectionAssert.assertReflectionEquals(
             hitRequest, parsedHitRequest)
}

We use MessageFactory again, this time to create a message from an InputStream, extract the DOM Node from the body and then use JAXB to marshal it to our model object. Finally I’m using Unitils very handy assertReflectionEquals method to verify the object in the SOAP message matches what I started with property-by-property.

Validating against the schema

The definition of hitDetails in our XSD has some restrictions present – the maximum weight and maximum height of the target. It would appear our hitman service provider doesn’t like biting off more than they can chew!

Let’s add another test scenario to cover the case where the values in our model object don’t satisfy those restrictions. Our architect, picking up on his unpopularity within the team, has taken to quite a bit of comfort eating of late- we’ll try to take out a hit on a 400lb man:-

/** Failure case - request does not meet schema constraints */
def "Exception thrown for a request which does not satisfy the schema"(){
    given: "An invalid request for a hitman"
       def hitRequest = new RequestAQuote(hitDetails:
            new HitDetails(
                 victimName: "Technical Architect",
                 victimHeight: 200,
                 victimWeight: 400,
                 hitType: HitType.DROWNING))

    when: "We attempt to generate a SOAP request for that object"
        cut.makeRequestAQuote(hitRequest)

    then: "An exception is thrown"
        def e = thrown(MarshalException.class)
    and: "the message identifies the restriction that was not satisfied"
        …
}

This test fails, not because he’s so fat he’d float, but because while the JAXB classes we’ve generated will pick up types, enumerations and so forth, they don’t enforce all schema restrictions.

We can make this test pass by loading the XSD and assigning it to our Marshaller before adding the JAXB object to the SOAPMessage:-

public String makeRequestAQuote(RequestAQuote quoteRequest)
              throws SOAPException, JAXBException, IOException {

    // Construct a SOAP message wrapper
    MessageFactory messageFactory = MessageFactory.newInstance();
    SOAPMessage soapMessage = messageFactory.createMessage();
    SOAPBody messageBody = soapMessage.getSOAPBody();

    // Marshall the quoteRequest into the SOAP Body
    Marshaller marshaller = JAXBContext.newInstance(
            RequestAQuote.class).createMarshaller();
    Schema schema = 
           SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).
                 newSchema(new StreamSource(
                      this.getClass().getResourceAsStream(
                            "/hitmanService.xsd")));
   marshaller.setSchema(schema);
   marshaller.marshal(quoteRequest, messageBody);
   soapMessage.saveChanges();

   // Write to a string and return
   ByteArrayOutputStream soapOutputStream =
           new ByteArrayOutputStream();
   soapMessage.writeTo(soapOutputStream);
   return soapOutputStream.toString();
}

Now our test passes with a meaningful underlying exception thrown:-

org.xml.sax.SAXParseException; lineNumber: 0; columnNumber: 0;
    cvc-maxInclusive-valid: Value '400' is not facet-valid with respect to
    maxInclusive '200' for type '#AnonType_victimWeighthitDetails'

As it’s always good to verify expected failures in our unit tests are the right failures, we’ll verify these details in our test scenario:-

/** Failure case - request does not meet schema constraints */
def "Exception thrown for a request which does not satisfy the schema"(){
   given: "An invalid request for a hitman"
       def hitRequest = new RequestAQuote(hitDetails:
           new HitDetails(
                victimName: "Technical Architect",
                victimHeight: 200,
                victimWeight: 400,
                hitType: HitType.DROWNING))

    when: "We attempt to generate a SOAP request for that object"
         cut.makeRequestAQuote(hitRequest)

    then: "An exception is thrown"
        def e = thrown(MarshalException.class)
    and: "the message identifies the restriction that was not satisfied"
        e.getCause().getMessage() ==~ /(.*)cvc-maxInclusive(.*)/
}

If you need to work with SOAP requests outside of the typical transport-oriented use cases, there aren’t a huge number of open source offerings to go for. It’s always worth bearing in mind though that in the early days of web-service programming nuts-and-bolts was the norm, and since the JDK has been around an awful long time, the nuts and bolts you need may very well be lurking in there somewhere.

Leave a Reply

Your email address will not be published. Required fields are marked *