users@jax-ws.java.net

JAX-WS RI 2.2.8: Client proxy throws NullPointerException on receipt of HTTP 202 with non-anonyous ReplyTo address for ws-addressing

From: Jesus Salvo <jesus.m.salvo_at_internode.on.net>
Date: Sat, 19 Jul 2014 23:29:22 +1000

JAX-WS: RI 2.2.8 ( via -Djava.endorsed.dirs )
Client: JDK 1.7.0_51 x64
Server: JBoss EAP 6.2.0


I am getting a java.lang.NullPointerException on the client when calling an operation on a WS endpoint when using non-anonymous replyTo address.
I have simplified the scenario into a small test case that hopefully others can replicate. Since the exception is happening on the client on receipt of HTTP 202 response, I would think that the container used is irrelevant ( be it JBossEAP, which uses Apache CXF ... or GlassFish which uses Metro ), but I have specified it nonetheless.

To clarify, the exception is being thrown by the proxy.


In summary:
* Server is listening on port 8080
* Client will listen in port 8082 ( specified in the ReplyTo address ) for the callback from the server for the SOAP response

Now when I run the client, I see that the proper behaviour as far as ws-addressing is concerned. That is:
* client -- SOAP request ( on port 8080 ) --> server
* client <-- HTTP 202 ( empty HTTP body ) --- server
* client <-- new TCP connection .. sends SOAP response ( on port 8082 ) --- server

All well and good, except that I am getting a NullPointerException on the client side when I call the operation.
With debugging of the SOAP request and responses, I get the following output and you can see the:
* HTTP 202 received by the client and the
* NullPointerException from the proxy on the client.

===================== start of debug / console output =====================

---[HTTP request - http://localhost:8080/servertest/RandomTest]---
Accept: text/xml, multipart/related
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://services.nowhere.org/RandomTest/nextRandomRequest"
User-Agent: JAX-WS RI 2.2.8 svn-revision#13980
<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><S:Header><To xmlns="http://www.w3.org/2005/08/addressing">http://localhost:8080/servertest/RandomTest</To><Action xmlns="http://www.w3.org/2005/08/addressing">http://services.nowhere.org/RandomTest/nextRandomRequest</Action><ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://localhost:8082/servertest/RandomCallback</Address>
</ReplyTo><FaultTo xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://localhost:8082/servertest/RandomCallbackFault</Address>
</FaultTo><MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:bcd2f6ef-3034-49e8-b837-dbd6a772fb93</MessageID></S:Header><S:Body><ns2:nextRandom xmlns:ns2="http://services.nowhere.org/"><arg0>false</arg0></ns2:nextRandom></S:Body></S:Envelope>--------------------

---[HTTP response - http://localhost:8080/servertest/RandomTest - 202]---
null: HTTP/1.1 202 Accepted
Content-Length: 0
Content-Type: text/xml;charset=UTF-8
Date: Fri, 18 Jul 2014 08:34:36 GMT
Server: Apache-Coyote/1.1
--------------------

java.lang.NullPointerException
at com.sun.proxy.$Proxy38.nextRandom(Unknown Source)
at test.wsclient.ClientTest.testAddressing(ClientTest.java:43)
at test.wsclient.ClientTest.main(ClientTest.java:18)
---[HTTP request]---
Cache-control: no-cache
Host: localhost:8082
Content-type: text/xml; charset=UTF-8
Content-length: 704
Connection: keep-alive
Pragma: no-cache
User-agent: Apache CXF 2.7.7.redhat-1
Accept: */*
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><Action xmlns="http://www.w3.org/2005/08/addressing">http://services.nowhere.org/RandomTest/nextRandomResponse</Action><MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:65d8d7fc-09e4-494a-a9c5-0a01faf4d7e6</MessageID><To xmlns="http://www.w3.org/2005/08/addressing">http://localhost:8082/servertest/RandomCallback</To><RelatesTo xmlns="http://www.w3.org/2005/08/addressing">uuid:bcd2f6ef-3034-49e8-b837-dbd6a772fb93</RelatesTo></soap:Header><soap:Body><ns2:nextRandomResponse xmlns:ns2="http://services.nowhere.org/"><return>2870062781194370669</return></ns2:nextRandomResponse></soap:Body></soap:Envelope>--------------------

Asynch response received
2870062781194370669

===================== end of debug / console output =====================

Here is the test case code that I am using:


1) WebService:

package test.webservice;

import java.util.Random;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.soap.Addressing;

@WebService(targetNamespace="http://services.nowhere.org/")
@Addressing(required=true)
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT)
public class RandomTest {

        @WebMethod
        public long nextRandom(@WebParam boolean forceException) throws Exception {
                if( forceException ) {
                        throw new Exception("Some exception");
                }
                Random rand = new Random();
                return rand.nextLong();
        }
}


2) ant build.xml to generate the client code from WSDL

<?xml version="1.0" encoding="UTF-8"?>
<project default="build" basedir="..">
        <property name="jaxws.classpath" location="C://jaxws-2.2.8/jaxws-ri/lib/*.jar"/>
        <taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport">
                <classpath path="${jaxws.classpath}"/>
        </taskdef>

        <target name="build" >
                <!-- For these to work, the JAR files in tools/jaxws-ri must be included in Ant's classpath -->
                <wsimport wsdl="http://localhost:8080/servertest/RandomTest?wsdl"
                verbose="true"
                sourcedestdir="src"
                destdir="bin"
                keep="true">
                        <xjcarg value="-enableIntrospection"/>
                </wsimport>
        </target>
</project>

3) Client code

3a) ClientTest.java - Actual client run from client

package test.wsclient;

import java.util.ArrayList;
import java.util.List;

import javax.xml.ws.BindingProvider;
import javax.xml.ws.Endpoint;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.soap.AddressingFeature;

import org.nowhere.services.RandomTest;
import org.nowhere.services.RandomTestService;

public class ClientTest {

        public static void main(String args[]) throws Exception {
                ClientTest app = new ClientTest();
                app.testAddressing();
        }

        public void testAddressing() throws Exception {
                String REPLY_TO_ADDRESS = "http://localhost:8082/servertest/RandomCallback";
                String FAULT_TO_ADDRESS = "http://localhost:8082/servertest/RandomCallbackFault";

                RandomTestService service = new RandomTestService();
                RandomTest port = service.getRandomTestPort(new AddressingFeature());

                BindingProvider provider = (BindingProvider) port;

                // pass the replyTo address to the handler
                provider.getRequestContext().put("ReplyTo", REPLY_TO_ADDRESS);
                provider.getRequestContext().put("FaultTo", FAULT_TO_ADDRESS);

                // Register handlers to set the ReplyTo and FaultTo on the SOAP request sent to the WS endpoint
                List<Handler> handlerChain = new ArrayList<Handler>();
                handlerChain.add(new ClientHandler());
                provider.getBinding().setHandlerChain(handlerChain);

                // Start endpoint to receive callbacks from WS
                Endpoint endpoint = Endpoint.publish(REPLY_TO_ADDRESS, new CallbackSEI());

                try {
                        port.nextRandom(false);
                } catch( Exception ex ) {
                        ex.printStackTrace();
                } finally {
                        Thread.sleep(10000);
                }

                endpoint.stop();
                System.exit(0);
        }
}

3b) ClientHandler.java - Used to set the wsa ReplyTo address and FaultTo address when sending SOAP request from client to server

package test.wsclient;

import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.MessageContext.Scope;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ClientHandler implements SOAPHandler<SOAPMessageContext> {

        public ClientHandler() {};

        @Override
        public Set<QName> getHeaders() {
                return null;
        }

        @Override
        public void close(MessageContext arg0) {}

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

        protected void setAnAddress(SOAPHeader header, String tagName, String address) {
                NodeList nodeListReplyTo = header.getElementsByTagName(tagName);
                NodeList nodeListAddress = nodeListReplyTo.item(0).getChildNodes();
                for (int i = 0; i < nodeListAddress.getLength(); i++) {
                        Node node = nodeListAddress.item(i);
                        if ("Address".equals(node.getLocalName())) {
                                node.setTextContent(address);
                                break;
                        }
                }
        }

        protected String getMessageID(SOAPHeader header) {
                NodeList nodeListMessageId = header.getElementsByTagName("MessageID");
                return nodeListMessageId.item(0).getTextContent();
        }

        @Override
        public boolean handleMessage(SOAPMessageContext context) {
                Boolean isOutbound = (Boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
                if (isOutbound) {
                        try {
                                SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
                                SOAPHeader header = envelope.getHeader();

                                /* extract the generated MessageID */
                                String messageID = getMessageID(header);
                                context.put("MessageID", messageID);
                                context.setScope("MessageID", Scope.APPLICATION);

                                /* change ReplyTo address */
                                setAnAddress(header, "ReplyTo", (String) context.get("ReplyTo"));
                                setAnAddress(header, "FaultTo", (String) context.get("FaultTo"));
                        } catch (Exception ex) {
                                throw new RuntimeException(ex);
                        }
                }
                return true;
        }
}

4c) CallbackSEI.java - endpoint on the client for server to send the SOAP response back to the client

package test.wsclient;

import javax.annotation.Resource;
import javax.jws.Oneway;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.xml.ws.Action;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.soap.Addressing;

@WebService
@Addressing
//_at_HandlerChain(file = "/handler-chain.xml")
public class CallbackSEI {

        @Resource
        private WebServiceContext context;

        /**
        * If there is no namespace specified in the method below, then the CallbackSEI needs to be in the same package as the
        * WS endpoint.
        */
        @Oneway
        @Action(input="http://services.nowhere.org/RandomTest/nextRandomResponse")
        @RequestWrapper(localName="nextRandomResponse", targetNamespace="http://services.nowhere.org/")
        public void handleNotification(@WebParam(name="return")long random) {
                System.out.println("Asynch response received");
                System.out.println( random );
                //System.out.println("This response relates to the message ID: "+ getMessageID());
        }
}


Any feedback would be appreciated.


Regards,

Jesus Salvo