Mrz 08 2008

How to modify JAX-WS SOAP-Messages

Published by at 1:02 AM under Java,NetBeans,web services



This blog is depending on my previous blog Creating SOAP Web Services with NetBeans 6. You can download the needed sourcecode there.

Here i will show how you can modify the JAX-WS SOAP-Message before the outgoing SOAP-Response will pass the specified Web Service Operation. This is sometimes necessary, when some recipients want to receive their own special SOAP-Headers. You can also modify the incoming Request to read special informations, which are included in the SOAP-Header, but this is not part of this posting (maybe i will show it in an other upcoming blog).

For this example i am using JAX-WS 2.1 with NetBeans 6.0 .


ServerSOAPHandler.java

This is the core-class of this example. Before the Request/Response pass the
Web Service Operation, it can be handled here in the method

public boolean handleMessage(SOAPMessageContext context)


The outbound-part (line 101) adds the SOAP-Header with four additional elements and one element for the SOAP-Body. The generated SOAP-Message is dumped to the console at runtime. Read the included comments to see how it works.

SOAP Message


Create the following file:

ServerSOAPHandler.java

package eu.jdevelop.soapwebservices.handler;

import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.MessageContext;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.namespace.QName;
import java.util.Set;
import java.io.ByteArrayOutputStream;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPPart;

/**
 * This class handles the SOAP-Requests before they reach the
 * Web Service Operation. It is possible to read and manipulate
 * the SOAP-Message.
 *
 * @author Siegfried Bolz
 */
public class ServerSOAPHandler implements SOAPHandler<soapmessageContext> {

    /**
     * Is called after constructing the handler and before executing any othe method.
     */
    @PostConstruct
    public void init() {
    }


    /**
     * Returns the <code>Set</code> of supported SOAP headers
     */
    public Set<qname> getHeaders() {
        return null;
    }

    /**
     * Returns the message encoding (e.g. utf-8)
     *
     * @param msg
     * @return
     * @throws javax.xml.soap.SOAPException
     */
    private String getMessageEncoding(SOAPMessage msg) throws SOAPException {
        String encoding = "utf-8";
        if (msg.getProperty(SOAPMessage.CHARACTER_SET_ENCODING) != null) {
            encoding = msg.getProperty(SOAPMessage.CHARACTER_SET_ENCODING).toString();
        }
        return encoding;
    }


    /**
     * Dump SOAP Message to console
     *
     * @param msg
     */
    private void dumpSOAPMessage(SOAPMessage msg) {
        if (msg == null) {
            System.out.println("SOAP Message is null");
            return;
        }
        System.out.println("");
        System.out.println("--------------------");
        System.out.println("DUMP OF SOAP MESSAGE");
        System.out.println("--------------------");
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            msg.writeTo(baos);
            System.out.println(baos.toString(getMessageEncoding(msg)));

            // show included values
            String values = msg.getSOAPBody().getTextContent();
            System.out.println("Included values:" + values);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * This method handles the incoming and outgoing SOAP-Message.
     * It's an excellent point to manipulate the SOAP.
     *
     * @param SOAPMessageContext
     * @return boolean
     */
    public boolean handleMessage(SOAPMessageContext context) {

        //Inquire incoming or outgoing message.
        boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        try {

            if (outbound) {
                // OUTBOUND

                System.out.println("Direction=outbound (handleMessage)");
                SOAPMessage msg = ((SOAPMessageContext) context).getMessage();

                // get SOAP-Part
                SOAPPart sp = msg.getSOAPPart();

                //edit Envelope
                SOAPEnvelope env = sp.getEnvelope();

                // add namespaces
                env.addNamespaceDeclaration("blog", "http://blog.jdevelop.eu");
                env.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");
                env.addNamespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");
                env.addNamespaceDeclaration("soap", "http://schemas.xmlsoap.org/soap/envelope");

                // add the Header with additional Elements
                SOAPElement soapElement1 = env.addHeader().addHeaderElement(new QName("http://blog.jdevelop.eu/securityprotocol", "Security"));
                soapElement1.addTextNode("1");

                SOAPElement soapElement2 = env.getHeader().addHeaderElement(new QName("http://blog.jdevelop.eu/securityprotocol", "Compression"));
                soapElement2.addTextNode("gzip");

                SOAPElement soapElement3 = env.getHeader().addHeaderElement(new QName("http://blog.jdevelop.eu/securityprotocol", "Username"));
                soapElement3.addTextNode("jdevelop");

                SOAPElement soapElement4 = env.getHeader().addHeaderElement(new QName("http://blog.jdevelop.eu/securityprotocol", "Password"));
                soapElement4.addTextNode("blog");

                // get the SOAP-Body
                SOAPBody body = env.getBody();

                // add an additional Element to the SOAP-Body
                SOAPBodyElement bodyElement = body.addBodyElement(new QName("http://blog.jdevelop.eu/securitymessage", "message"));
                bodyElement.addTextNode("Security through obscurity");

                // print SOAP-Message
                dumpSOAPMessage(msg);


            } else {
                // INBOUND

                System.out.println("Direction=inbound (handleMessage)");
                SOAPMessage msg = ((SOAPMessageContext) context).getMessage();
                dumpSOAPMessage(msg);

             }

        } catch (Exception e) {

            //All other unhandled problems.
            e.printStackTrace();
        }
        return true;
    }


    /**
     * Handles SOAP-Errors.
     *
     * @param context
     * @return
     */
    public boolean handleFault(SOAPMessageContext context) {
        System.out.println("ServerSOAPHandler.handleFault");
        boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (outbound) {
            System.out.println("Direction=outbound (handleFault)");
        } else {
            System.out.println("Direction=inbound (handleFault)");
        }
        if (!outbound) {
            try {
                SOAPMessage msg = ((SOAPMessageContext) context).getMessage();
                dumpSOAPMessage(msg);
                if (context.getMessage().getSOAPBody().getFault() != null) {
                    String detailName = null;
                    try {
                        detailName = context.getMessage().getSOAPBody().getFault().getDetail().getFirstChild().getLocalName();
                        System.out.println("detailName=" + detailName);
                    } catch (Exception e) {
                    }
                }
            } catch (SOAPException e) {
                e.printStackTrace();
            }
        }
        return true;
    }


    public void close(MessageContext messageContext) {
    }


    /**
     * Is executed before this handler is being destroyed -
     * means after close() has been executed.
     */
    @PreDestroy
    public void destroy() {
    }

} // .EOF



serversoaphandler.xml

This is the configuration-file for the handler-chain. Create it at the source-root.

serversoaphandler.xml

<?xml version="1.0" encoding="UTF-8"?>
<jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">

  <jws:handler-chain>
    <jws:handler>
      <jws:handler-name>ServerSOAPHandler</jws:handler-name>
      <jws:handler-class>eu.jdevelop.soapwebservices.handler.ServerSOAPHandler</jws:handler-class>
    </jws:handler>
  </jws:handler-chain>

</jws:handler-chains>



ServiceImpl.java

To load the configuration-file, just add this line of code under the “@WebService” -annotation.

@HandlerChain(name = "ServerSOAPHandler", file = "serversoaphandler.xml")


ServiceImpl.java

package eu.jdevelop.soapwebservices.service;

import eu.jdevelop.soapwebservices.CalculateValues;
import eu.jdevelop.soapwebservices.CalculateValuesResponse;
import eu.jdevelop.soapwebservices.SOAPWebServices;
import eu.jdevelop.soapwebservices.wrapper.impl.CalculateValuesWrapper;
import javax.jws.WebService;
import javax.jws.WebService;
import javax.jws.HandlerChain;


/**
 * This is the Service-Implementation of the Web Service. Here are the
 * operations which can be called from web clients.
 *
 * @author Siegfried Bolz
 */
@WebService(serviceName = "SOAPService", portName = "WebServices", endpointInterface = "eu.jdevelop.soapwebservices.SOAPWebServices", targetNamespace = "soapwebservices.jdevelop.eu", wsdlLocation = "WEB-INF/wsdl/ServiceImpl/webservices.wsdl")
@HandlerChain(name = "ServerSOAPHandler", file = "serversoaphandler.xml")
public class ServiceImpl implements SOAPWebServices {

    public CalculateValuesResponse getCalculateValues(CalculateValues calculateValues) {

        try {
            CalculateValuesWrapper wrapper = new CalculateValuesWrapper();
            return wrapper.getResult(calculateValues);

        } catch (Exception x) {
            throw new IllegalStateException(x);
        }
    }

} // .EOF


Your package-structure should now look like this:
SOAP WebServices Part 2 - Package Structure


Testing

Build, deploy and start the application. Use soapui to submit a request.
soapui web service test

You will receive the following SOAP-Message:

<s:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:blog="http://blog.jdevelop.eu" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <s:Header>
      <security xmlns="http://blog.jdevelop.eu/securityprotocol">1</security>
      <compression xmlns="http://blog.jdevelop.eu/securityprotocol">gzip</compression>
      <username xmlns="http://blog.jdevelop.eu/securityprotocol">jdevelop</username>
      <password xmlns="http://blog.jdevelop.eu/securityprotocol">blog</password>
   </s:Header>
   <s:Body>
      <ns2:calculateValuesResponse xmlns:ns2="soapwebservices.jdevelop.eu">
         <result>15.33</result>
      </ns2:calculateValuesResponse>
      <message xmlns="http://blog.jdevelop.eu/securitymessage">Security through obscurity</message>
   </s:Body>
</s:Envelope>


For comparison, the response without using the soaphandler looks like this:

<s:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:blog="http://blog.jdevelop.eu" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <s:Body>
      <ns2:calculateValuesResponse xmlns:ns2="soapwebservices.jdevelop.eu">
         <result>15.33</result>
      </ns2:calculateValuesResponse>
   </s:Body>
</s:Envelope>


And here the request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="soapwebservices.jdevelop.eu">
<soapenv:Header>
	<security xmlns:soap="http://blog.jdevelop.eu/securityprotocol">1</security>
	<username xmlns:soap="http://blog.jdevelop.eu/securityprotocol">jdevelop</username>
	<password xmlns:soap="http://blog.jdevelop.eu/securityprotocol">blog</password>
</soapenv:Header>
   <soapenv:Body>
      <soap:calculateValues>
         <value1>10</value1>
         <value2>5.33</value2>
      </soap:calculateValues>
   </soapenv:Body>
</soapenv:Envelope>



Download

NetBeans 6 Project: download

Technorati Tags: , , , , ,

13 responses so far

13 Responses to “How to modify JAX-WS SOAP-Messages”

  1. Salford sagt:

    I have followed this but the new added header dont display, but body ones displays, what might be the cause

  2. Please download my example and try it to see if everything is working fine.

  3. Dieter sagt:

    Hi Siegfried,

    vielen Dank, Dein Beispiel ist aufschlussreich, was das zippen in Jax-Ws angeht. Ich habe aber noch ein anderes Problem zu lösen. Ich muß für einen .net Webservice mit jax-ws eine SOAP-Message in der Version 1.1 anstatt Standard 1.2 erzeugen. Kann ich da was im apt einstellen oder hilft mir die Implementierung des Handlers mit Bezug auf eine SOAP-Version dabei?

    Vielen Dank und Grüsse aus Essen

    Dieter

  4. Hi Dieter,
    es müsste möglich sein die SOAP-Message so zu manipulieren das daraus eine Version 1.1 entsteht, allerdings gibt es da große Unterschiede die du beachten must (siehe Links unten). Mit dem ServerSoapHandler müsste diese Manipulation möglich sein.

    http://www.w3.org/2003/06/soap11-soap12.html
    http://www.idealliance.org/papers/xmle02/dx_xmle02/papers/02-02-02/02-02-02.html

    Viele Grüße
    Siegfried

  5. Diego sagt:

    Hi, thanks a lot for this part of the code:

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    msg.writeTo(baos);

    I was trying to write the content of the message to a log. Previously i was using msg.writeTo(System.out), and when I changed my System.out logs to log4J, it was driving me crazy (I haven’t even think about dropping the content to an Output Stream variable!).

    Now with
    logger.info(“Message: ” + baos.toString) i can log my messages again.

    Best regards,
    Diego.

  6. Siddhartha sagt:

    Hi Siegfried,

    This code is able to handle the request & response
    but when the webservice is throwing an exception it’s not able to handle it.

    My WS Code.

    @WebService(name = “TestWS”,targetNamespace=”http://ws.test”)
    @SOAPBinding(style = SOAPBinding.Style.DOCUMENT,
    use = SOAPBinding.Use.LITERAL)
    @HandlerChain(name=”ServerSOAPHandler”,file=”../../handler/serversoaphandler.xml”)
    public class TestWS {
    public String testWs(String str) throws Exception{
    if(some condition){
    throw new Exception(“Sidd Exp”);
    }
    return str;
    }
    }

  7. Rajiv sagt:

    I’m trying to get the exception handling to work. I’m trying to get the below-

    ABC
    xyz
    blah

    I created my own exception, and I’m able to set the faultstring. but when I use @HandlerChain(file=’xxx.xml’) the faultstring is changed to

    Exception raised from invocation of public java.lang.String …..

    could you advise why this could be happening.

    Thanks in advace,
    Rajiv.

  8. Rajiv sagt:

    oops the xml tags were ripped in the above -

    <SOAP-ENV:Fault>
    <faultcode>ABC</faultcode>
    <faultstring>xyz</faultstring>
    </SOAP-ENV:Fault>

  9. SS sagt:

    Suppose I want to change SOAP response Envelope attribute from to standard format like using handler class. Could you please tell me what changes need to be done here?

    Thanks in Advance

  10. SS sagt:

    More over , is there any way to identify the service name from Soap Fault in the Handler

  11. David sagt:

    Hi a good article

    I try to modify a soap incoming using a Soap Handler

    private void logToSystemOut(SOAPMessageContext smc) {

    try {

    Boolean outboundProperty = (Boolean) smc.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
    if (outboundProperty.booleanValue()) {
    HttpServletRequest req = (HttpServletRequest) smc.get(“javax.xml.ws.servlet.request”);

    SOAPMessage msg = ((SOAPMessageContext) smc).getMessage();
    //SOAPMessageContext smc = (SOAPMessageContext) context;
    //SOAPMessage msg = smc.getMessage();
    SOAPPart sp = msg.getSOAPPart();

    SOAPEnvelope se = sp.getEnvelope();

    raise a next exception
    error:Unable to create envelope from given source
    by
    Caused by: javax.xml.transform.TransformerException: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

    Caused by: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

    may i help me

    I am using metro 1.5 , java 6

  12. viral sagt:

    I am looking for logging soap request and response xml.

    I tried this but it doesn’t work for me.

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    msg.writeTo(baos);

    it prints blank but webservice works fine and gives me result

    anyone have any other solution or api to convert soapmessage to XML using java?

  13. Andrew sagt:

    Hi,
    love your work! I’m trying to add an attribute to an element and hoped your approach would help. I have an element thus:

    and I want it the element in the response to be have attribute values as below:

    Any clues as to what to set to arrive at this?

    Any advice is much appreciated,

    Andrew