How to intercept SOAP message and transforming it before processing? - java

I have task where I need to transform custom SOAP message. At the beginning I have to get this custom SOAP message, then I need to transform this message using XSLT, then process this message in my WebService. The reverse process is repeated.
I have XSLT file and method for transforming SOAP message, but I don't know where I need to call this method for transforming. How do I intercept SOAP message and where I have to do it? Because I have only class with one method (example below) and i don't understand how I can transform this message before processing in webservice.
#WebService
public class Calculator {
public String showCard(final CreditCard creditCard) {
return creditCard.toString();
}
}
Here is the technical mapping what I need to do.

How do I intercept SOAP message and where I have to do it?
You need a SoapHandler to capture the soap request before the execution of the bussiness logic of the endpoint, and other SoapHandler to transform the outbound response.
Define a SoapHandler class (example extracted from mkyong)
public class CalculatorSoapHandler implements SOAPHandler<SOAPMessageContext>{
#Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
//for response message only, true for outbound messages, false for inbound
if(!isRequest){
try{
SOAPMessage soapMsg = context.getMessage();
SOAPEnvelope soapEnv = soapMsg.getSOAPPart().getEnvelope();
SOAPHeader soapHeader = soapEnv.getHeader();
//process....
}catch(SOAPException e){
System.err.println(e);
}catch(IOException e){
System.err.println(e);
}
}
//continue other handler chain
return true;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
}
#Override
public void close(MessageContext context) {
}
#Override
public Set<QName> getHeaders() {
}
}
Create a soap handler XML file
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<javaee:handler-chains
xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-class>com.CalculatorSoapHandler</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>
Attach SOAP Handler to Web Service using #HandlerChain
#WebService
#HandlerChain(file="handler-chain.xml")
public class Calculator {
If you use a jax-ws framework like CXF or spring-WS, check the documentation to see specific configuration

Related

SOAP Custom Response Header - JAX-WS

In my JAX-WS web service, I need to customize my SOAP Response Envelope.
At the moment, it is like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<methodResponse xmlns="http://interfaces.webservice.ucmdb.com">
<methodReturn>202</methodReturn>
</methodResponse >
</soapenv:Body>
</soapenv:Envelope>
And i need it to look like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<code>123</code>
</soapenv:Header>
<soapenv:Body>
<getCIResponse xmlns="http://interfaces.webservice.ucmdb.com">
<getCIReturn>202</getCIReturn>
</getCIResponse>
</soapenv:Body>
</soapenv:Envelope>
So, how does one write the <soapenv:Header> part in a JAX-WS soap envelope?
Here is my WebMethod implementation (quite simple at the moment):
#WebMethod
public int operation(#WebParam(name="username", header=false)String id) {
return 202;
}
1.) In order to add a header to your SOAP message in JAX-WS you can implement the SOAPHandler interface. This will give you access to the SOAPMessage and you can use the SAAJ api to add/update the SOAP Header. Keep in mind this handler is bi-directional.
public class Handler1 implements SOAPHandler<SOAPMessageContext>{
#Override
public boolean handleMessage(SOAPMessageContext context) {
boolean isOutbound = (boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
if(isOutbound){
SOAPMessage msg = context.getMessage();
try {
//Grab the header
SOAPHeader header = msg.getSOAPHeader();
//Add whatever QName you need
header.addHeaderElement(new QName("code"));
//Save changes.
msg.saveChanges();
} catch (SOAPException e) {
e.printStackTrace();
}
}
//True continue, false halt.
return true;
}
#Override
public boolean handleFault(SOAPMessageContext context) {
return false;
}
#Override
public void close(MessageContext context) {
}
#Override
public Set<QName> getHeaders() {
//Can also add a QName here.
return null;
}
}
2.) In order to register the Handler you need to add the handler via an xml handler configuration file that should be kept in the classpath.
<?xml version="1.0" encoding="UTF-8"?>
<javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-class>com.calvinmmiller.service.Handler1</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>
3.) This file can be added to the SEI(Service Endpoint Interface) with the annotation below:
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
#WebService
#HandlerChain( file = "handlerFile.xml")
public class Service {
public int operation(String id){
return 202;
}
public static void main(String[] args){
Endpoint.publish("http://localhost:8080/soap", new Service());
}
}

SOAP-WS security header authentication

I have developed a webservices using spring+ XSD+ Payload. I have a requirement of authenticating the request header with username and password coming in SOAP request header which i achieved with SOAPUI
I m able to generate the below header in the request
<soapenv:Envelope xmlns:jaxb="http://jaxb.miws.sg.com/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-C3092BFBAE5B212E93144378035575013">
<wsse:Username>User</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">CT1Fyo/g2WMaadE52bsnkg== </wsse:Nonce>
<wsu:Created>2015-10-02T10:05:55.750Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Now i want to validate the header elements for userName and Password.
Ex:
Case 1:
userName=User and Password=test //Authentication passed and give response Success
Case 2:
userName=User1 and Password=test1 //Authentication failed and give response Failure
Please help me to provide the suitable samples to achieve same.
Handlers in SOAP webservices (similar to Interceptors/Filters) can be used for the authentication purpose on the server side and then chaining the request further.
Please have a look at SOAPHandler to parse the header information from the payload and authenticating the username/password.
SOAP Handler at Server Side
Here are some steps to do that:
Implement a SOAPHandler class by writing a custom handleMessage method.
Within the handleMessage method, evaluate the context's MESSAGE_OUTBOUND_PROPERTY. If it is false (meaning it is an inbound message), then write code that introspects the context.getMessage(). There you can evaluate the MIME headers, the security headers & tokens and the body, to determine if you need to reject the authentication credential. If you do, return false at the end of the method.
Add the SoapHander you created to the service's Handler chain.
Example of a SOAPHandler:
public class MyCustomSoapHandler implements SOAPHandler<SOAPMessageContext>
{
public Set<QName> getHeaders()
{
return Collections.emptySet();
}
public boolean handleMessage(SOAPMessageContext messageContext)
{
Boolean outboundProperty = (Boolean)
messageContext.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
//This is for handling messages going out of the conduit
} else {
//Here is where you want to authenticate
}
return true; //return false if do not want to proceed to the next handler in the chain
}
public boolean handleFault(SOAPMessageContext messageContext)
{
return true;
}
public void close(MessageContext messageContext)
{
}
Here a starter template for your SOAPHandler that you need to add to your Service's handlerChain:
#WebService(name = "Handler", targetNamespace = "http://example.org")
#HandlerChain(file="handler-chain.xml")
public class HandlerWS
{
#Resource
WebServiceContext ctx;
#WebMethod()
public String getProperty(String propertyName)
{
return (String) ctx.getMessageContext().get(propertyName);
}
}
You'll also need to add the handler-chain.xml to your classpath:
examples.webservices.handler.Handler1
examples.webservices.handler.Handler2
For a complete guide, see Oracle's guide to creating SOAPHandlers

Soap envelope namespace prefix in Java web service

I am trying to change prefix for soap envelope in response of web-service from
S="http://schemas.xmlsoap.org/soap/envelope/"
to
soap="http://schemas.xmlsoap.org/soap/envelope/":
so here's how the response looks like now:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<n:soaprequestResponse xmlns:n="http://tempuri.org/soaprequest">
<n:soaprequestResult/>
</n:soaprequestResponse>
</S:Body>
</S:Envelope>
and here's how it must look like:
<soap:Envelope xmlns:soap:="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<n:soaprequestResponse xmlns:n="http://tempuri.org/soaprequest">
<n:soaprequestResult/>
</n:soaprequestResponse>
</soap:Body>
</soap:Envelope>
How can this be attained?
EDIT:
I added soap handler class and the problem starts when I'm trying to get envelope:
package org.tempuri.soaprequest;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class SoapHandler implements SOAPHandler<SOAPMessageContext> {
#Override
public Set<QName> getHeaders() {
//do nothing
return null;
}
#Override
public boolean handleMessage(SOAPMessageContext context) {
if ((boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)) { //Check here that the message being intercepted is an outbound message from your service, otherwise ignore.
try {
SOAPEnvelope msg = context.getMessage().getSOAPPart().getEnvelope(); //just trying to get envelope
} catch (SOAPException ex) {
ex.printStackTrace();
}
return true; //indicates to the context to proceed with (normal)message processing
}
#Override
public boolean handleFault(SOAPMessageContext context) {
//do nothing
return true;
}
#Override
public void close(MessageContext context) {
//do nothing
}
}
SoapUI throws:
<S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
<faultcode>S:Server</faultcode>
<faultstring>JVMVRFY012 stack shapeinconsistent;class=com/sun/xml/messaging/saaj/soap/SOAPDocumentImpl, method=createDocumentFragment()Lorg/w3c/dom/DocumentFragment;, pc=5
</faultstring>
</S:Fault>
Tomcat log has no errors.
It doesn't occur without custom soap handler.
Perhaps the reason is in the way I implemented web method. It creates a new thread with an object that processes request and then returns empty response, thus releasing client from waiting for request processing is over:
#WebResult(name="soaprequestResult", targetNamespace="http://tempuri.org/soaprequest")
public SoaprequestResponse.SoaprequestResult soaprequest(#WebParam(name="streams", targetNamespace="http://tempuri.org/soaprequest") SoaprequestStreams streams) {
try {
new Thread(new MyProcess(streams)).start();
return new SoaprequestResponse().getSoaprequestResult();
} catch(Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String stackTrace = sw.toString();
return new SoaprequestResponse().getSoaprequestResult();
}
}
and MyProcess is class where request processing really is and stmt.executeUpdate is executed.
I think Customising JAX-WS prefix of a SOAP response summarizes your options.
Option 1: I think you just need to put this above your package.
#javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.xmlsoap.org/soap/envelope/",
xmlns = {
#javax.xml.bind.annotation.XmlNs(prefix = "soap",
namespaceURI="http://schemas.xmlsoap.org/soap/envelope/")
}
)
Option 2: Alternatively (also mentioned in the link), you could use a SOAPHandler. You could add a configuration file to bind these handlers. But in fact, you can just add them at runtime. I think this needs some explanation: The trick is to get an instance of the BindingProvider. This is different if you are a consumer or provider of the webservice.
If you are server (i.e. providing a webservice):
webservice = Class.forName(serviceClassName).newInstance();
Endpoint e = Endpoint.create(webservice);
BindingProvider bp = (BindingProvider)e.getBinding();
e.publish("http://localhost:" + serverPort + servicePath);
If you are client (i.e. consuming a webservice):
Service service = new Service(url, qname);
Port port = service.getPort();
BindingProvider bp = ((BindingProvider) port);
When you have the bindingprovider, you can bind the handler as follows.
List<Handler> chain = bp.getHandlerChain();
if (chain == null) chain = new ArrayList<Handler>();
chain.add(myCustomHandler);
bp.setHandlerChain(chain);
Now, for the chained handler itself, you should implement SOAPHandler<SOAPMessageContext>. There you can manipulate your messages however you like. (see linked post above for an example).

How to extract SOAP header from a WS response using spring-ws and jaxb

We're using spring-ws 2.2 on our application to consume web services. We've been happily doing this for quite some time and everything is working fine, except that now I need to access the SOAP header in the response and I just can't find a way to do this.
We are using a WebServiceTemplate (from springs-ws) configured with a Jaxb2Marshaller. The jaxb files are generated from the wsdl using xjc. The header element in my responses look something like this:
<soapenv:Header>
<v1:ResponseHeader status="OK">
<v1:description>test</v1:description>
</v1:ResponseHeader>
</soapenv:Header>
In my java class, the code that parses the response looks like this (I've stripped some irrelevant code):
public CalculationData getValues(Integer id) throws IntegrationException {
WebServiceMessageCallback callback = createCallback(soapAction);
GetValuesRequest request = toGetValues(id);
GetValuesResponse response = null;
try {
response = (GetValuesResponse) webServiceTemplate.marshalSendAndReceive(request, callback);
} catch (SOAPFaultException fault) {
log.error("Soap fault occurred during getValues " + id);
throw new IntegrationException(fault);
}
CalculationData data = fromGetValues(response);
return data;
}
Please help me find a solution for extracting the information from the SOAP header out of the response. I must be able to parse the status code which is sent as an attribute.
By the way. I also have a ResponseHeader.java jaxb class which has been generated from the schemas.
Update from final changes:
This is how my handleResponse method looks like after inlining a ClientInterceptor implementation:
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
SoapMessage message = (SoapMessage) messageContext.getResponse();
Iterator<SoapHeaderElement> responseHeaderElements =
message.getSoapHeader().examineAllHeaderElements();
SoapHeaderElement header = null;
if (responseHeaderElements.hasNext()) {
header = responseHeaderElements.next();
} else {
log.error("Error! No ResponseHeader found in response.");
return false;
}
String responseCode = header.getAttributeValue(new QName(STATUS_QNAME));
responseMsg.put(RESPONSE_MSG_KEY, responseCode);
return true;
}
I tried getting the ResponseHeader element by QName, but that did not seem to work for some reason. However, I only expect to get one element in the soap header anyhow, is this will work fine.
For this use case, the best solution is to use a custom WebServiceMessageExtractor, as described here:
http://veithen.github.io/2015/01/03/spring-ws-extracting-soap-headers-from-responses.html
Implement a ClientInterceptor, specifically see handleResponse() method.
In order to access the Soap Headers, convert to a SoapMessage
public final boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
QName v1ResponseHeaderQName = null;//todo
QName statusAttrQName = null;//todo
SoapMessage message = (SoapMessage) messageContext.getResponse();
Iterator<SoapHeaderElement> matchingHeaders = message.getSoapHeader().examineHeaderElements(v1ResponseHeaderQName);
String status = matchingHeaders.next().getAttributeValue(statusAttrQName);
}
then call webServiceTemplate.setInterceptors(..)
For some further examples of this stuff see AbstractWsSecurityInterceptor and its subclasses. Be aware however that those interceptors deal with replacing the request message, you just want to read the response message.
Problem is you are dealing with the raw soap message now so you've lost the nice spring marshalling and need to start dealing with namespaces (QNames) and w3c Dom stuff.
In order for the interceptor to pass the header back to the calling code, you could make the interceptor an anonymous inner class that is setup inside your getValues(...) method.
final Map<String,String> headers = new HashMap<>();
template.setInterceptors(new ClientInterceptor[]{new ClientInterceptor() {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
headers.put("foo", "bar");
return false;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
}
}});
template.marshalSendAndReceive(....);

Can I create a generic web service/dispatch method that responds to ALL requests with JAX-WS?

I'm trying to create a generic web service that will always respond with "OK", regardless of the request's header or body contents. I can do this in Axis2 with a RawXMLInOutMessageReceiver, but I'd prefer to use JAX-WS (which I am completely new to) if at all possible. So far I've got a simple interface:
#WebService
public interface DummyService {
#WebMethod String processMessage(Object obj);
}
and a simple implementaion:
#WebService(endpointInterface = "com.dummyservice.DummyService")
public class DummyServiceImpl implements DummyService {
#Override
public String processMessage(Object obj) {
return "OK";
}
}
I can successfully publish the service with javax.xml.ws.Endpoint#publish(...), but when I hit it with a simple SOAP request, e.g.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<derp/>
</soapenv:Body>
</soapenv:Envelope>
I'm greeted with a SOAPFault stating Cannot find dispatch method for {}derp.
Is it even possible to create a generic/dumb web service that will ACK everything with JAX-WS? If so, could someone point me in the right direction?
EDIT
Thanks to the tip from McDowell, I was able to do this with a SOAPHandler:
public class DummySOAPHandler implements SOAPHandler {
#Override
public boolean handleMessage(MessageContext context) {
return process((SOAPMessageContext) context);
}
#Override
public boolean handleFault(MessageContext context) {
return process((SOAPMessageContext) context);
}
#Override
public void close(MessageContext context) { }
#Override
public Set<QName> getHeaders() {
return null;
}
private boolean process(SOAPMessageContext ctx) {
try {
SOAPMessage message = ctx.getMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPBody body = message.getSOAPBody();
if ((Boolean) ctx.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY)) {
Iterator<SOAPElement> bodyChildren = body.getChildElements();
while (bodyChildren.hasNext()) {
SOAPElement child = bodyChildren.next();
child.detachNode();
}
body.addBodyElement(envelope.createName("OK"));
message.saveChanges();
}
} catch (SOAPException e) {
e.printStackTrace();
}
return true;
}
}
I expect your service is expecting something of the form:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dum="http://yournamespace/">
<soapenv:Header/>
<soapenv:Body>
<dum:processMessage>
<!-- xsd:anyType -->
</dum:processMessage>
</soapenv:Body>
</soapenv:Envelope>
Add ?WSDL to your endpoint and inspect the operation input XML type and the namespaces.
You might be able to do something with a logical handler (javadoc) to transform the incoming request to this form - I haven't tried.

Categories