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