I'm trying to develop a remote dispatcher with CXF using javax.xml.ws.Provider as explained here: http://cxf.apache.org/docs/jax-ws-dispatch-api.html. I have a CXF client configured with WSAddressing, SOAP12_HTTP_BINDING and with the following WSS4J configurations:
Client WSS4J IN interceptor:
inProps.put("action", "Timestamp Signature Encrypt");
inProps.put("passwordType", "PasswordText");
...
Client WSS4J OUT interceptor:
protected static final String WSU_NS =
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
protected static final String SOAP12_NS = "http://www.w3.org/2003/05/soap-envelope";
protected static final String SOAP11_NS = "http://schemas.xmlsoap.org/soap/envelope";
protected static final String WSA_NS = "http://www.w3.org/2005/08/addressing";
protected static final String WSSE_NS="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
String userForPswCallback = ...
Map<String, Object> props = new HashMap<String, Object>();
props.put("action", "UsernameToken Timestamp Signature Encrypt");
props.put("passwordType", "PasswordText");
...
props.put("encryptionParts", "{Content}{"+SOAP12_NS+"}Body;{Element}{"+WSSE_NS+"}UsernameToken");
props.put("signatureKeyIdentifier", "DirectReference");
props.put("signatureParts", "{Element}{"+SOAP12_NS+"}Body;" +
"{Element}{"+WSSE_NS+"}UsernameToken;" +
"{Element}{"+WSA_NS+"}Action;" +
"{Element}{"+WSA_NS+"}MessageID;" +
"{Element}{"+WSA_NS+"}To;" +
"{Element}{"+WSA_NS+"}ReplyTo");
At dispatcher side I publish the following dispatcher service:
@WebServiceProvider( targetNamespace = JettyConstants.PersistenceNameSpace,
serviceName=JettyConstants.PersistenceService,
portName=JettyConstants.PersistencePort)
@ServiceMode (Service.Mode.MESSAGE)
public class PersistenceProvider implements Provider<Source> {
private static final Logger log = Logger.getLogger(PersistenceProvider.class);
private TransformerFactory transformerFactory;
public PersistenceProvider() {
// Complete
}
private static final DocumentBuilderFactory BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
static {
BUILDER_FACTORY.setNamespaceAware(true);
}
public Source invoke(Source request) {
try {
DOMSource sourceInvoke = null;
//get userName and userPassword from the interceptor chain (added in a custom interceptor)
String userName = (String)(PhaseInterceptorChain.getCurrentMessage().get("product.username"));
String userPassword = (String)(PhaseInterceptorChain.getCurrentMessage().get("product.password"));
//insert extracted into a org.w3c.dom.Document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = null;
org.w3c.dom.Document doc = null;
try {
builder = dbf.newDocumentBuilder();
InputStream is = convertMessageToInputStream(request);
doc = builder.parse(is);
} catch (Exception e) {
e.printStackTrace();
return null;
}
NodeList node = doc.getElementsByTagNameNS("*","Security");
node.item(0).getParentNode().removeChild(node.item(0));
// Add extra header to the envelop...
...
sourceInvoke = new DOMSource(doc);
// 1. Resolve target service based on wsContext
QName targetService = new QName(JettyConstants.PersistenceNameSpace,JettyConstants.PersistenceService);
QName targetPort = new QName(JettyConstants.PersistenceNameSpace,JettyConstants.PersistencePort);
String targetEndpoint = DispatchStarter.getNewVelocityDestination(false)+"/Persistence";
/* ****************************************** INVOKE THE REMOTE SERVER *************************************** */
// 2. Create dispatcher
Dispatch<Source> dispatcher = createDispatcher(targetService, targetPort, targetEndpoint);
// 3. Invoke target service
Source response = dispatcher.invoke(sourceInvoke);
/* *********************************************************************************************************** */
// 4. Return service response to consumer
DOMSource domS = toDOMSourceFromSAX((SAXSource)response);
//get the doc to modify it:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = null;
org.w3c.dom.Document doc = null;
builder = dbf.newDocumentBuilder();
doc = (org.w3c.dom.Document) domS.getNode();
// 4.1. Take just the body content out of the response received from the server:
NodeList body_el = doc.getElementsByTagNameNS("*", "Body").item(0).getChildNodes();
DocumentBuilderFactory docf = DocumentBuilderFactory.newInstance();
Document docnew = docf.newDocumentBuilder().newDocument();
Node newNode = docnew.createElementNS(body_el.item(0).getNamespaceURI(), body_el.item(0).getLocalName());
Node nodeComplete = docnew.adoptNode(body_el.item(0).cloneNode(true));
// 4.2. Create the SAXSource to send back to the client through CXF stack
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Source xmlSource = new DOMSource(nodeComplete);
Result outputTarget = new StreamResult(outputStream);
TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
InputSource inputSource = new InputSource(is);
SAXSource finalRes = new SAXSource(inputSource);
return finalRes;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
private Dispatch<Source> createDispatcher(QName serviceName, QName portName, String targetEndpoint) {
Service service = Service.create(serviceName);
String actualBinding = SOAPBinding.SOAP12HTTP_BINDING;
service.addPort(portName, actualBinding, targetEndpoint);
Dispatch<Source> dispatcher = service.createDispatch(portName, Source.class,
Service.Mode.MESSAGE);
return dispatcher;
}
}
PersistenceProvider (the dispatcher) is published with the following WSS4JInterceptors
Dispatcher WSS4J Dispatcher IN interceptors:
Map<String, Object> inProps = new HashMap<String, Object>();
inProps.put(WSHandlerConstants.ACTION, "UsernameToken Timestamp Signature Encrypt");
inProps.put("passwordType", "PasswordText");
...
Dispatcher WSS4J Dispatcher OUT interceptors:
protected static final String WSU_NS =
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
protected static final String SOAP_NS = "http://www.w3.org/2003/05/soap-envelope";
protected static final String WSA_NS = "http://www.w3.org/2005/08/addressing";
outProps.put(WSHandlerConstants.ACTION, "Timestamp Signature Encrypt");
outProps.put("passwordType", "PasswordText");
...
outProps.put("encryptionParts", "{Content}{"+SOAP_NS+"}Body");
...
outProps.put("signatureParts", "{Element}{" + WSU_NS + "}Timestamp;" +
"{Element}{"+SOAP_NS+"}Body;" +
"{Element}{"+WSA_NS+"}Action;" +
"{Element}{"+WSA_NS+"}MessageID;" +
"{Element}{"+WSA_NS+"}To;" +
"{Element}{"+WSA_NS+"}RelatesTo");
and with WSAddressingFeature enabled. I can redirect the requests from my client to the remote server and I get the response back from the remote server at the dispatcher side. Unfortunately, when I try to send the answer back to my client (that is after the return statement of the PersistenceProvider#invoke method) I get the following exception:
{http://dbproxyservice/}PersistenceService#{http://dispatch/}invoke has thrown exception, unwinding now
org.apache.cxf.binding.soap.SoapFault: Error creating SOAPMessage
at org.apache.cxf.jaxws.interceptors.MessageModeOutInterceptor$MessageModeOutInterceptorInternal.handleMessage(MessageModeOutInterceptor.java:210)
at org.apache.cxf.jaxws.interceptors.MessageModeOutInterceptor$MessageModeOutInterceptorInternal.handleMessage(MessageModeOutInterceptor.java:182)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.interceptor.OutgoingChainInterceptor.handleMessage(OutgoingChainInterceptor.java:77)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:123)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.serviceRequest(JettyHTTPDestination.java:323)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.doService(JettyHTTPDestination.java:289)
at org.apache.cxf.transport.http_jetty.JettyHTTPHandler.handle(JettyHTTPHandler.java:72)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:942)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:878)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)
at org.eclipse.jetty.server.Server.handle(Server.java:349)
at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:441)
at org.eclipse.jetty.server.HttpConnection$RequestHandler.content(HttpConnection.java:936)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:893)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:224)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:52)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:586)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:44)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:598)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:533)
at java.lang.Thread.run(Thread.java:662)
Caused by: com.ctc.wstx.exc.WstxEOFException: Unexpected EOF in prolog
at [row,col {unknown-source}]: [1,0]
at com.ctc.wstx.sr.StreamScanner.throwUnexpectedEOF(StreamScanner.java:677)
at com.ctc.wstx.sr.BasicStreamReader.handleEOF(BasicStreamReader.java:2104)
at com.ctc.wstx.sr.BasicStreamReader.nextFromProlog(BasicStreamReader.java:2010)
at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1102)
at org.apache.cxf.staxutils.StaxUtils.copy(StaxUtils.java:551)
at org.apache.cxf.staxutils.StaxUtils.copy(StaxUtils.java:513)
at org.apache.cxf.staxutils.StaxUtils.copy(StaxUtils.java:467)
at org.apache.cxf.jaxws.interceptors.MessageModeOutInterceptor$MessageModeOutInterceptorInternal.handleMessage(MessageModeOutInterceptor.java:204)
... 24 more
[10Dec 18:59:51,881] (doLog@372) WARN PhaseInterceptorChain - Interceptor for {http://dbproxyservice/}PersistenceService#{http://dispatch/}invoke has thrown exception, unwinding now
java.lang.NullPointerException
at org.apache.cxf.ws.addressing.ContextUtils.hasEmptyAction(ContextUtils.java:358)
at org.apache.cxf.ws.addressing.MAPAggregator.assembleGeneric(MAPAggregator.java:686)
at org.apache.cxf.ws.addressing.MAPAggregator.aggregate(MAPAggregator.java:660)
at org.apache.cxf.ws.addressing.MAPAggregator.mediate(MAPAggregator.java:515)
at org.apache.cxf.ws.addressing.MAPAggregator.handleMessage(MAPAggregator.java:228)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:107)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:323)
at org.apache.cxf.interceptor.OutgoingChainInterceptor.handleMessage(OutgoingChainInterceptor.java:77)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:123)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.serviceRequest(JettyHTTPDestination.java:323)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.doService(JettyHTTPDestination.java:289)
at org.apache.cxf.transport.http_jetty.JettyHTTPHandler.handle(JettyHTTPHandler.java:72)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:942)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:878)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)
at org.eclipse.jetty.server.Server.handle(Server.java:349)
at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:441)
at org.eclipse.jetty.server.HttpConnection$RequestHandler.content(HttpConnection.java:936)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:893)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:224)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:52)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:586)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:44)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:598)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:533)
at java.lang.Thread.run(Thread.java:662)
Do you have any hints to avoid this error? In particular, what have I to do in order to send the invoke response back to the client through the CXF stack? Is it correct to get rid of all the headers got from the remote server at point 4 of the PersistenceProvider#invoke above? I did that since I thought dispatcher's CXF out interceptors would build the envelope from scratch around the Body. Is that correct?
Thank you!
Gotcha! The thing that does the trick is to transform the javax.xml.transform.Source returned by the invoke method in PersistenceProvider (dispatcher) into a DOMSource. That allows CXF filling the outgoing soap envelope body correctly.
Problem solved. The full story here!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With