Getting raw XML SOAP-response on client side using ADB-stubs created by AXIS2
Asked Answered
I

5

10

I access a SOAP service using ADB-stubs created by AXIS2. I would like to log the raw XML response of any Axis Fault, that is returned by the service. I can catch those errors as "ServiceError". However, I do not find a way to retreive the raw XML (see example below).

I found a way to access the raw XML request / response for regular processing, using getOMElement (see example below). However, this does not work for faults.

How do I get the raw XML fault using ADB stubs?

Example Java code:

    public void testRequest(String URL) throws AxisFault {
        MyServiceStub myservice = new MyServiceStub(URL);
        MyRequest req = new MyRequest();
        try {
            TypeMyFunctionResponse response = myservice.myFunction(req);

            // logging full soap response
            System.out.println("SOAP Response: "
                    + response.getOMElement(null,
                            OMAbstractFactory.getOMFactory())
                            .toStringWithConsume());
        } catch (RemoteException e) {
            //...
        } catch (ServiceError e) {
            // how to get the raw xml?
        }
    }

Example fault response, that I would like to fetch and log:

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
    <soapenv:Body>
        <soapenv:Fault>
            <soapenv:Code>
                <soapenv:Value>soapenv:Receiver</soapenv:Value>
            </soapenv:Code>
            <soapenv:Reason>
                <soapenv:Text xml:lang="en-US">service error</soapenv:Text>
            </soapenv:Reason>
            <soapenv:Detail>
                <ns1:error xmlns:ns1="http://www.somehost.com/webservices/someservice">
                    <ns1:code>500</ns1:code>
                    <ns1:messageText>some fault message</ns1:messageText>
                </ns1:error>
            </soapenv:Detail>
        </soapenv:Fault>
    </soapenv:Body>
</soapenv:Envelope>
Infundibulum answered 29/8, 2012 at 9:41 Comment(7)
This may not be exactly what you want, but what about writing a JAX-WS handler for logging the fault? Using a handler you could access the SOAP messages. See for instance: mkyong.com/webservices/jax-ws/…Eucalyptol
Thanks! This might be the solution. However, I will need to do rather large code changes and replace AXIS2 by JAX-WS. So if there is any way to solve this with less effort by keeping the current framework, I would be more than happy.Infundibulum
Some clarification: You do not need to "replace" Axis2 with JAX-WS. JAX-WS is a specification for Java web services (@WebService annotations and so on) and Axis2 is one of its implementations. The jdk contains the reference implementation for JAX-WS which you can also use stand-alone, but Axis2 is an alternative. Nevertheless, you should be able to use handlers alongside your current setting (as Axis2 implements JAX-WS).Eucalyptol
I did now what you suggested. I was not precise enough in my comments before. I had to replace my AXIS2-ADB-stubs by the a client generated by JAX-WS (wsimport). However, this was not that time consuming than what I expected. I now will also remove AXIS2. It doesn't seem to me to make much of a difference whether I use the jdk-reference implementation or AXIS2.Infundibulum
As long as you don't need any fancy standards such as WS-Security, it doesn't really make a difference. Nice to hear my suggestion was useful. If this is the solution, an answer that demonstrates a handler which logs a SOAP fault would be appropriate. Are you providing it, or should I?Eucalyptol
Good suggestion. I added my solution. Feel free to provide your own description. I would mark it as solution. Thanks again!Infundibulum
Your solution is fine and there is nothing substantial I can add to make it better. So you deserve the rep :)Eucalyptol
I
2

As suggested by joergl I changed my ADB-stubs to JAX-WS-ones using a "SOAPHandler" to log requests, responses and faults following the description here: http://www.mkyong.com/webservices/jax-ws/jax-ws-soap-handler-in-client-side/

My handler looks like this for logging the nicely formated XML using log4j:

public class RequestResponseHandler  implements SOAPHandler<SOAPMessageContext> {

    private static Logger log = Logger.getLogger(RequestResponseHandler.class);
    private Transformer transformer = null;
    private DocumentBuilderFactory docBuilderFactory = null;
    private DocumentBuilder docBuilder = null;

    public RequestResponseHandler() {
        try {
            transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "5");
            docBuilderFactory = DocumentBuilderFactory.newInstance();
            docBuilder = docBuilderFactory.newDocumentBuilder();
        } catch (TransformerConfigurationException
                | TransformerFactoryConfigurationError
                | ParserConfigurationException e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void close(MessageContext arg0) {
    }

    @Override
    public boolean handleFault(SOAPMessageContext messageContext) {
        log(messageContext);
        return true;
    }

    @Override
    public boolean handleMessage(SOAPMessageContext messageContext) {
        log(messageContext);
        return true;
    }

    private void log(SOAPMessageContext messageContext) {
        String xml = "";
        SOAPMessage msg = messageContext.getMessage();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            msg.writeTo(out);
            xml = out.toString("UTF-8");
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }       

        String direction = "";
        Boolean outbound = (Boolean) messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); 
        if (outbound) { 
            direction += "Request: \n"; 
        } else { 
            direction += "Response: \n";
        } 

        log.info(direction + getXMLprettyPrinted(xml));     
    }

    @Override
    public Set<QName> getHeaders() {
        return Collections.emptySet();
    }


    public String getXMLprettyPrinted(String xml) {

        if (transformer == null || docBuilder == null)
            return xml;

        InputSource ipXML = new InputSource(new StringReader(xml));
        Document doc;

        try {
            doc = docBuilder.parse(ipXML);
            StringWriter stringWriter = new StringWriter();
            StreamResult streamResult = new StreamResult(stringWriter);
            DOMSource domSource = new DOMSource(doc);
            transformer.transform(domSource, streamResult);
            return stringWriter.toString();
        } catch (SAXException | IOException | TransformerException e) {
            log.error(e.getMessage(), e);
            return xml;
        }
    }
}

In addition I wanted to reuse the raw XML in my application code. So I had to transfer this data from the SOAPHandler back to my client code. How to this was not too obvious. More about this problem can be found in this article: How to send additional fields to soap handler along with soapMessage?

Infundibulum answered 3/9, 2012 at 0:0 Comment(0)
W
10

Below is what you probably are looking for, yourStub is what you generated via wsdl2java and use below lines after you make your request. The message is set to lastOperation and sends it when you make the actual call:

request = yourStub._getServiceClient().getLastOperationContext().getMessageContext("Out")
              .getEnvelope().toString());

response = yourStub._getServiceClient().getLastOperationContext().getMessageContext("In")
              .getEnvelope().toString());

Hope that was helpful.

Waybill answered 3/1, 2014 at 22:27 Comment(0)
R
5

While this question is already answered well, I needed to do this earlier and had trouble finding a suitable answer that fit my constraints so I'm adding my own for posterity.

I needed to do this with Axis 2 version 1.4.1 for a recent project running JDK 1.4, which from what I'd read was not supported by the JAX-WS stubs. I ended up keeping the ADB stubs while capturing the input by wrapping SoapBuilder with my own builder class, copying the input stream and passing the copy to the SoapBuilder:

public class SOAPBuilderWrapper implements Builder {
    private String lastResponse;

    private SOAPBuilder builder = new SOAPBuilder();

    private static final int BUFFER_SIZE = 8192;

    public OMElement processDocument(InputStream inputStream,
            String contentType, MessageContext messageContext) throws AxisFault {
        ByteArrayOutputStream copiedStream = new ByteArrayOutputStream();
        try {
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead = inputStream.read(buffer);
            while (bytesRead > -1) {
                copiedStream.write(buffer, 0, bytesRead);
                bytesRead = inputStream.read(buffer);
            }
            lastResponse = copiedStream.toString();

        } catch (IOException e) {
            throw new AxisFault("Can't read from input stream", e);
        }
        return builder.processDocument(
                new ByteArrayInputStream(copiedStream.toByteArray()),
                contentType, messageContext);
    }

    public String getLastResponse() {
        return lastResponse;
    }
}

For various reasons, configuring using axis2.xml was problematic, so the wrapper was added programmatically with the following:

SoapBuilderWrapper responseCaptor = new SoapBuilderWrapper();
AxisConfiguration axisConfig = stub._getServiceClient().getAxisConfiguration();
axisConfig.addMessageBuilder("application/soap+xml", responseCaptor);
axisConfig.addMessageBuilder("text/xml", responseCaptor);

This allows responses to be retrieved using responseCaptor.getLastResponse() after the service is invoked.

Redblooded answered 26/9, 2012 at 7:11 Comment(1)
Hi Taufiq, But how do I print request ? I am on axis2 but don't have option to switch to JAX-WSBuckram
C
3

Related to Ducane's reply:

response = yourStub._getServiceClient().getLastOperationContext().getMessageContext("In")
          .getEnvelope().toString());

fails with a com.ctc.wstx.exc.WstxIOException exception and the message: Attempted read on closed stream.

Cinema answered 6/2, 2014 at 15:44 Comment(0)
S
3

For Axis2, those who do not have luxury of changing implementation/ or do not want to use JAS-WS for xyz reasons,

Found @Ducane's useful

request = >yourStub._getServiceClient().getLastOperationContext().getMessageContext("Out")
         .getEnvelope().toString());

response = >yourStub._getServiceClient().getLastOperationContext().getMessageContext("In")
         .getEnvelope().toString());

As suggested in @dayer's answer

response = >yourStub._getServiceClient().getLastOperationContext().getMessageContext("In")
     .getEnvelope().toString());

fails with a com.ctc.wstx.exc.WstxIOException exception and the message: >Attempted read on closed stream.

Not sure what is issue with "In" Message Lable,

But while searching, found following JIRA ticket https://issues.apache.org/jira/browse/AXIS2-5469 which points to https://issues.apache.org/jira/browse/AXIS2-5202 And in discussion found one of the WA to solve this issue using following code, I am able to listen Response message for the soapRequest.

stub._getServiceClient().getAxisService().addMessageContextListener(
new MessageContextListener() {
    public void attachServiceContextEvent(ServiceContext sc,
        MessageContext mc) {}
    public void attachEnvelopeEvent(MessageContext mc) {
        try
        { mc.getEnvelope().cloneOMElement().serialize(System.out); }
        catch (XMLStreamException e) {}
    }
});

As here MessageContextListner is Argument-Defined Anonymous Inner Classes it will have access to all enclosing variables, So i just defined a string class variable as latestSoapResponse and stored response for further use.

ByteArrayOutputStream baos = new ByteArrayOutputStream();
mc.getEnvelope().cloneOMElement().serialize(baos); 
latestSoapResponse=baos.toString();

Note that you need to add listener before generating soap request. and Request MessageContext will only be available once you generate soap request.

Also those who are just want raw soap request response for debugging purpose may see answer from @Sanker, here to enable Apache commons logging using JVM arguments.

Solvency answered 21/4, 2015 at 19:38 Comment(0)
I
2

As suggested by joergl I changed my ADB-stubs to JAX-WS-ones using a "SOAPHandler" to log requests, responses and faults following the description here: http://www.mkyong.com/webservices/jax-ws/jax-ws-soap-handler-in-client-side/

My handler looks like this for logging the nicely formated XML using log4j:

public class RequestResponseHandler  implements SOAPHandler<SOAPMessageContext> {

    private static Logger log = Logger.getLogger(RequestResponseHandler.class);
    private Transformer transformer = null;
    private DocumentBuilderFactory docBuilderFactory = null;
    private DocumentBuilder docBuilder = null;

    public RequestResponseHandler() {
        try {
            transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "5");
            docBuilderFactory = DocumentBuilderFactory.newInstance();
            docBuilder = docBuilderFactory.newDocumentBuilder();
        } catch (TransformerConfigurationException
                | TransformerFactoryConfigurationError
                | ParserConfigurationException e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void close(MessageContext arg0) {
    }

    @Override
    public boolean handleFault(SOAPMessageContext messageContext) {
        log(messageContext);
        return true;
    }

    @Override
    public boolean handleMessage(SOAPMessageContext messageContext) {
        log(messageContext);
        return true;
    }

    private void log(SOAPMessageContext messageContext) {
        String xml = "";
        SOAPMessage msg = messageContext.getMessage();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            msg.writeTo(out);
            xml = out.toString("UTF-8");
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }       

        String direction = "";
        Boolean outbound = (Boolean) messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); 
        if (outbound) { 
            direction += "Request: \n"; 
        } else { 
            direction += "Response: \n";
        } 

        log.info(direction + getXMLprettyPrinted(xml));     
    }

    @Override
    public Set<QName> getHeaders() {
        return Collections.emptySet();
    }


    public String getXMLprettyPrinted(String xml) {

        if (transformer == null || docBuilder == null)
            return xml;

        InputSource ipXML = new InputSource(new StringReader(xml));
        Document doc;

        try {
            doc = docBuilder.parse(ipXML);
            StringWriter stringWriter = new StringWriter();
            StreamResult streamResult = new StreamResult(stringWriter);
            DOMSource domSource = new DOMSource(doc);
            transformer.transform(domSource, streamResult);
            return stringWriter.toString();
        } catch (SAXException | IOException | TransformerException e) {
            log.error(e.getMessage(), e);
            return xml;
        }
    }
}

In addition I wanted to reuse the raw XML in my application code. So I had to transfer this data from the SOAPHandler back to my client code. How to this was not too obvious. More about this problem can be found in this article: How to send additional fields to soap handler along with soapMessage?

Infundibulum answered 3/9, 2012 at 0:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.