[SalesForce] How to receive full XML response with CDATA embedded XML in apex callout to SOAP service

I'm currently working on a project, which requires flight status information from OAG's On-Demand Flight Status information service (WSDL).

I can test the service with SOAPui. Request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.oag.com">
       <soapenv:Header/>
       <soapenv:Body>
          <ws:Authenticate>
             <ws:UserId>test</ws:UserId>
             <ws:Password>test</ws:Password>
          </ws:Authenticate>
       </soapenv:Body>
    </soapenv:Envelope>

Response (using proper username / password):

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <AuthenticateResponse xmlns="http://ws.oag.com">
         <AuthenticateResult>1</AuthenticateResult>
         <SecurityToken xsi:type="xsd:string"><![CDATA[<OAGToken><Username>********</Username><IssueDateTime>2015-11-05T07:12:17</IssueDateTime><ExpiryDateTime>2015-11-06T07:12:17</ExpiryDateTime><CustomerNo>********</CustomerNo><Signature>***************************</Signature></OAGToken>]]></SecurityToken>
      </AuthenticateResponse>
   </soap:Body>
</soap:Envelope>

I'm both interested in the AuthenticateResult value and embedded XML part within SecurityToken.

The WSDL lacks XML element types in two spots at element SecurityToken. To be able to generate apex from the WSDL, I made these elements type string (also tested token).

After calling the webservice with the generated apex, the value of SecurityToken does not contain the full CDATA contents, but a concatenation of all XML values within the CDATA embedded XML.

So I decided to manually call the service with HTTPRequest.

Code:

public class fc_OAGFS_OnDemand_Client {
    // TODO: Place in custom setting
    private static final String username = 'test';
    private static final String password = 'test';

    private static final String authenticateEndPoint = 'https://secure.oag.com/ws_oag_com/oagflightstatusinformation.asmx';

    public static void authenticate() {
        String payLoad = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.oag.com"><soapenv:Header/><soapenv:Body><ws:Authenticate><ws:UserId>'+username+'</ws:UserId><ws:Password>'+password+'</ws:Password></ws:Authenticate></soapenv:Body></soapenv:Envelope>';

        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        req.setHeader('Content-Type','text/xml;charset=UTF-8');
        req.setHeader('SOAPAction', ''); // Empty as in SOAPui
        req.setEndpoint(authenticateEndPoint);              
        req.setHeader('Content-Length',String.valueOf(payLoad.length()));
        req.setBody(payLoad);

        HttpResponse res = h.send(req);

        System.debug('>>> Answer String '+res.getBody());
        System.debug('>>> Answer XML Document '+res.getBodyDocument().toXmlString());
        System.debug('>>> Answer BLOB '+res.getBodyAsBlob().toString());
    }
}

Interesting part of debug log:

14:22:42.956 (3956667815)|USER_DEBUG|[47]|DEBUG|>>> Answer String 1<OAGToken><Username>********</Username><IssueDateTime>2015-11-05T07:22:42</IssueDateTime><ExpiryDateTime>2015-11-06T07:22:42</ExpiryDateTime><CustomerNo>********</CustomerNo><Signature>*********************************************</Signature></OAGToken>

14:22:42.957 (3957449646)|USER_DEBUG|[48]|DEBUG|>>> Answer XML Document 1<OAGToken><Username>********</Username><IssueDateTime>2015-11-05T07:22:42</IssueDateTime><ExpiryDateTime>2015-11-06T07:22:42</ExpiryDateTime><CustomerNo>********</CustomerNo><Signature>*********************************************</Signature></OAGToken>

14:22:42.957 (3957601751)|USER_DEBUG|[49]|DEBUG|>>> Answer BLOB 1<OAGToken><Username>********</Username><IssueDateTime>2015-11-05T07:22:42</IssueDateTime><ExpiryDateTime>2015-11-06T07:22:42</ExpiryDateTime><CustomerNo>********</CustomerNo><Signature>*********************************************</Signature></OAGToken>

As above log shows, only a mangled part of the response from OAG's service is returned. Salesforce performs some processing / interpretation.

How can I receive the complete response, including SOAP envelope?

Best Answer

I tried replicating your issue, using both WebServiceCallout.invoke and HttpRequest. This was somewhat limited as I didn't have valid credentials to the web service. I've applied for a trial account, so I'll see what comes of that.

As you mentioned in the question, I needed to add a type attribute in the WSDL for the two elements named SecurityToken that were missing the value.

<s:element minOccurs="0" maxOccurs="1" name="SecurityToken" type="s:string" /> <!-- Added missing type -->

From there I could use Wsdl2Apex to generate the required Apex classes (OK, I actually used the FuseIT SFDC Explorer Wsdl2Apex implementation, but the result should be the same. Disclose, this is a free tool from my current employer).

Using the following anonymous Apex it appeared to process fine (aside from the invalid credentials).

wsOagCom.OAGFlightStatusInformationSoap oagService = new wsOagCom.OAGFlightStatusInformationSoap();
wsOagCom.AuthenticateResponse_element result = oagService.Authenticate('UserId','Password','SecurityToken');

System.debug(result);
System.debug('AuthenticateResult: ' + result.AuthenticateResult);
System.debug('SecurityToken : ' + result.SecurityToken)

enter image description here

I also tried generating the equivalent HttpRequest. Again, no immediate issue processing the response into an AuthenticateResponse_element.

I then tried making a test case and HttpCalloutMock implementation that returned the same response that you get from SoapUI.

Mock

@isTest
public class wsOagComMock implements HttpCalloutMock {

    protected Integer code = 200;
    protected String status = 'OK';

    protected String bodyAsString = 
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
        '<soap:Body>' +
            '<AuthenticateResponse xmlns="http://ws.oag.com">' +
                '<AuthenticateResult>1</AuthenticateResult>' +
                '<SecurityToken xsi:type="xsd:string">' + 
                    '<![CDATA[<OAGToken><Username>UN</Username><IssueDateTime>2015-11-05T07:12:17</IssueDateTime><ExpiryDateTime>2015-11-06T07:12:17</ExpiryDateTime><CustomerNo>CN</CustomerNo><Signature>Sig</Signature></OAGToken>]]>' + 
                '</SecurityToken>'+
            '</AuthenticateResponse>' +
        '</soap:Body>' +
    '</soap:Envelope>';

    protected Map<String, String> responseHeaders;


    public wsOagComMock() {
    }

    public HTTPResponse respond(HTTPRequest req) {
        HttpResponse resp = new HttpResponse();
        resp.setStatusCode(code);
        resp.setStatus(status);

        resp.setBody(bodyAsString);

        if (responseHeaders != null) {
            for (String key : responseHeaders.keySet()) {
                resp.setHeader(key, responseHeaders.get(key));
            }
        }
        return resp;
    }
}

Test

@isTest
public class wsOagComTest {
    public static testmethod void testAccountCallout() {
        wsOagComMock fakeResponse = new wsOagComMock();
        Test.setMock(HttpCalloutMock.class, fakeResponse);
        wsOagCom.OAGFlightStatusInformationSoap oagService = new wsOagCom.OAGFlightStatusInformationSoap();
        //wsOagCom.AuthenticateResponse_element result = oagService.Authenticate('UserId','Password','SecurityToken');

        wsOagCom.AuthenticateResponse_element result = oagService.Authenticate_Http('UserId','Password','SecurityToken');

        System.debug(result);

        System.debug('AuthenticateResult: ' + result.AuthenticateResult);
        System.debug('SecurityToken : ' + result.SecurityToken);

        System.assertEquals(1, result.AuthenticateResult);
    }
}

That test gives a positive response.

enter image description here

A couple of thoughts:

  1. Change your line
    req.setHeader('SOAPAction', ''); // Empty as in SOAPui to req.setHeader('SOAPAction', 'blank'); // Empty as in SOAPui
    See Missing SOAP action header
  2. What are you using to view the debug log? I checked both the Developer Console and out FuseIT Explorer. It is possible it is having encoding issues and is hiding some of the data.
Related Topic