Customizing Spring Integration Web Service SOAP Envelope/Header - soap

I am trying to send a SOAP request using Spring Integration like
<int:chain input-channel="wsOutChannel" output-channel="stdoutChannel">
<int-ws:header-enricher>
<int-ws:soap-action value="..."/>
</int-ws:header-enricher>
<int-ws:outbound-gateway
uri="..."/>
</int:chain>
but you can only add the SOAP body, and Spring Integration adds the envelope, header, and body tags like
<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
<SOAP-ENV:Header>
</SOAP-ENV:Envelope>
I need to customize the envelope and header tags with specific attributes, for example:
<soapenv:Envelope attribute1="value1" attribute2="value2">
and child elements, for example:
<soapenv:Header>
<child>...<child>
<soapenv:Header>
Is this possible with Spring Integration Web Services, or should I not use int-ws:outbound-gateway and take a different approach?

You can add a ClientInterceptor (via the interceptor attribute) which allows you to modify the request before it's sent out.
EDIT
#Artem's suggestion is simpler but the interceptor gives you access to the response too; but either way, the code is similar.
For the interceptor:
public class MyInterceptor extends ClientInterceptorAdapter {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
SoapMessage request = (SoapMessage) messageContext.getRequest();
SoapEnvelope envelope = request.getEnvelope();
envelope.addAttribute(new QName("foo"), "bar");
SoapHeader header = envelope.getHeader();
header.addHeaderElement(new QName("http://fiz/buz", "baz"));
return super.handleRequest(messageContext);
}
}
For the callback version:
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SoapEnvelope envelope = ((SoapMessage) message).getEnvelope();
envelope.addAttribute(new QName("foo"), "bar");
SoapHeader header = envelope.getHeader();
header.addHeaderElement(new QName("http://fiz/buz", "baz"));
}

I thing you can inject WebServiceMessageCallback:
<xsd:attribute name="request-callback" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Reference to a Spring Web Services WebServiceMessageCallback. This enables changing
the Web Service request message after the payload has been written to it but prior
to invocation of the actual Web Service.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.ws.client.core.WebServiceMessageCallback"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
and cast the message to the SoapMessage and use its getEnvelope() to customize a desired way.

Related

How to handle soap:mustUnderstand header in Spring WS

An external system is sending my service a SOAP message and I have a listener in place
#Endpoint
public class NotificationListener {
private static final String NAMESPACE_URI = "http://test.com/test";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "Notification")
#ResponsePayload
public void getSOAPMessage(#RequestPayload HistoryMessage request) {
// calls to methods in other classes which handle the business logic
}
The request body contains the following headers:
<soap:Header>
<wsa:To soap:mustUnderstand="1"
xmlns:wsa="http://www.w3.org/2005/08/addressing">{destination endpoint}
</wsa:To>
<wsa:From
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:From>
<wsa:ReplyTo
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:FaultTo
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:FaultTo>
<wsa:Action soap:mustUnderstand="1"
xmlns:wsa="http://www.w3.org/2005/08/addressing">
</wsa:Action>
<wsa:MessageID
xmlns:wsa="http://www.w3.org/2005/08/addressing">urn:uuid:fa163e6e-ef55-1eec-b9ac-5e80af1d126a
</wsa:MessageID>
I get the following error on calling my endpoint:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:MustUnderstand</faultcode>
<faultstring xml:lang="en">One or more mandatory SOAP header blocks not understood</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I understand that the mustUnderstand attribute in the header is set to 1 which means true and that header must be handled. But how do I handle it?
I'm using Spring WS to build the listener service.
TIA:)

Generating SOAP Requests from Java objects

I need to create a Soap request from a request object in Java. What is needed is below :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tem:tag1>
<tem:tag2>
<MyDataSet>
<!-- more elements within-->
</MyDataSet>
<tem:tag1>
<tem:tag2>
</SOAP-ENV:Body>
<atom/>
</SOAP-ENV:Envelope>
However, what I am getting is this :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<MyDataSet>
<!-- more elements within-->
</MyDataSet>
</SOAP-ENV:Body>
<atom/>
</SOAP-ENV:Envelope>
Can someone please tell me how do I add <tem:tag1> and <tem:tag2> in the soap request ? This is the code that I have written so far:
public static void main(String[] args) throws Exception
{
MyRequest request = new MyRequest();
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Marshaller marshaller = JAXBContext.newInstance(MyRequest.class).createMarshaller();
marshaller.marshal(request, document);
SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
soapMessage.getSOAPPart().getEnvelope().addNamespaceDeclaration("tem", "http://tempuri.org/");
soapMessage.getSOAPBody().addDocument(document);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
soapMessage.writeTo(outputStream);
String output = new String(outputStream.toByteArray());
System.out.println(output);
}
Assuming that the web service has published WSDL, I suggest that instead of rolling your own client code, you generate it using wsimport, a utility that is included in the Java JDK. One article on how to use it can be found here.

Spring Integration should route Message (with SOAP Headers)

I am trying to write configure a gateway, which should take a complete SOAP Message and then delegate it to another SOAP Provider (incl. all SOAP headers of the first request).
What I have done so far:
1) web.xml
MessageDispatcherServlet with Mapping:
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/appservices/*</url-pattern>
</servlet-mapping>
2) Configuration with an Endpoint-Mapping
<bean class="org.springframework.ws.server.endpoint.mapping.UriEndpointMapping">
<property name="defaultEndpoint" ref="ws-in-gw"/>
</bean>
3) Configuration of Spring Integration inbound-gateway and outbound-gateway
<int-ws:inbound-gateway id="ws-in-gw"
request-channel="in"
reply-channel="out"
mapped-request-headers="*" />
<int:channel id="in" />
<int:channel id="out" />
<int-ws:outbound-gateway
id="ws-out-gw-status"
request-channel="in-status"
reply-channel="out-status"
uri="http://${delegationServer}/${delegation.contextroot}/soap/AnotherService"
interceptor="soapEnricher"
</int-ws:outbound-gateway>
<bean id="soapEnricher" class="foo.bar.SoapHeaderEnricher" />
public class SoapHeaderEnricher implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
try {
SoapMessage soapMessage = (SoapMessage) messageContext.getRequest();
SoapHeader sh = soapMessage.getSoapHeader();
// can use sh.addHeaderElement(new QName(...)) now, but where are the original Headers???
} catch () {
}
}
My first Problem was, that the original SOAP Headers had been cut of, so I introduced the ' mapped-request-headers="*" ' attribute at the inbound gateway.
When I now configure a wire-tap, I see the Headers (myToken:MySecretToken) are received:
DEBUG 10:46:53 - [Payload DOMSource content=javax.xml.transform.dom.DOMSource#24a6ce98][Headers={errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#43456ff4, myToken:MySecretToken=org.springframework.ws.soap.saaj.SaajSoapHeaderElement#3b91ead, ...}]
This is the SOAP Message for my test:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:stat="http://status.service.promakler.provinzial.com/">
<soapenv:Header>
<myToken:MySecretToken xmlns=""
xmlns:myToken="http://foo.bar">12345</myToken:MySecretToken>
</soapenv:Header>
<soapenv:Body>
<stat:getStatus/>
</soapenv:Body>
</soapenv:Envelope>
So the Headers are now in my Message, but in the ClientInterceptor, there is no way to get the Headers (just the payload)?! I can add new Headers, but how can I get the original Header?
Can anybody give me a hint (or perhaps there is even a quiet simpler solution??)
Regards
Timo
Try to introduce a custom extension of DefaultSoapHeaderMapper and override populateUserDefinedHeader to extract those SaajSoapHeaderElement from the MessageHeaders and populate them to the SoapHeader. And finally inject your solution to the header-mapper of your <int-ws:outbound-gateway>.

Mule SOAP client wrapper as parameter instead of object array

I created a sample Mule flow by first generating client classes with CXF per http://www.mulesoft.org/documentation/display/current/Consuming+Web+Services+with+CXF guide.
The flow is started by going to localhost:8081/test. The parametersObjectArray will transform any message into a hardcoded object array required for the web service method call, like this:
package com.test.example.transformers;
import org.mule.api.transformer.TransformerException;
import org.mule.transformer.AbstractTransformer;
public class GetCustomersArrayTransformer extends AbstractTransformer {
#Override
protected Object doTransform(Object src, String enc)
throws TransformerException {
Object[] msg = new Object[3];
msg[0] = 10;
msg[1] = 0;
msg[2] = null;
return msg;
}
}
When this transformer is used in a flow to pass a message to a jaxws-client node, everything works as expected:
<custom-transformer name="parametersObjectArray" class="com.test.example.transformers.GetCustomersArrayTransformer" doc:name="Java"/>
<flow name="mulecartFlow" doc:name="mulecartFlow">
<http:inbound-endpoint exchange-pattern="one-way" host="localhost" port="8081" doc:name="HTTP" path="test"/>
<transformer ref="parametersObjectArray" doc:name="Java"></transformer>
<https:outbound-endpoint exchange-pattern="request-response" host="12.34.56.78" port="1234" path="services/SOAP/TestEndpoint" doc:name="HTTP" connector-ref="httpsConnector" method="POST">
<cxf:jaxws-client clientClass="com.test.TestEndpointService" enableMuleSoapHeaders="true" doc:name="SOAP" operation="getCustomers" port="TestEndpoint" />
</https:outbound-endpoint>
<transformer ref="customerInfoTypesToString" doc:name="Transformer Reference"/>
<logger level="INFO" doc:name="Logger" message="#[message:payload]"/>
</flow>
I would like to use a wrapper object, so that parameters are legible and type-safe:
package com.test.example.transformers;
import org.mule.api.transformer.TransformerException;
import org.mule.transformer.AbstractTransformer;
import com.test.GetCustomers;
public class GetCustomersObjectTransformer extends AbstractTransformer {
#Override
protected Object doTransform(Object src, String enc)
throws TransformerException {
GetCustomers soapRequest = new GetCustomers();
soapRequest.setStartIndex(0);
soapRequest.setMaxBatchSize(1);
return soapRequest;
}
}
However, that does not seem to work. I noticed that the manual page states:
Note: the CXF transport doesn't support wrapper-style web service
method calls. You may need to create a binding file or change the WSDL
directly
What does that mean? How can I send a wrapper object that wraps all method parameters to the web service method?
Add:
<jaxws:bindings xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
<jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>
inside wsdl:portType and CXF will generate the wrapper objects you're after.
Also, note that creating a Java transformer to set the payload is overkill: use set-payload with a simple MEL expression and you'll be good.

Change HTTP response code in CXF SOAP Fault

I have this error
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Message part {http://soap.ws.server.wst.fit.cvut.cz/}createOrders was not recognized. (Does it exist in service WSDL?)</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
which is completely fine. But the problem is that CXF is returning Internal Server Error (HTTP 500). I would expect it to return 400 Bad Request because a wrong request is causing this. How can I change that?
DETAILS
The wrong request which produces the error above - there should be <soap:createOrder/> instead of <soap:createOrders/>.
POST http://localhost:8080/wst-server-1.0.0/services/soap/order
POST data:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.ws.server.wst.fit.cvut.cz/">
<soapenv:Header/>
<soapenv:Body>
<soap:createOrders/>
</soapenv:Body>
</soapenv:Envelope>
[no cookies]
Request Headers:
Content-Length: 238
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
I know maybe this is too late for the answer but another way to handle HTTP status code is using CXF interceptors.
you can add an interceptor in your server CXF configure
public class ServiceConfigurer implements CxfConfigurer {
#Override
public void configureServer(Server server) {
server.getEndpoint().getOutFaultInterceptors().add(new ChangingStatusCodeInterceptor();
}
}
and in your interceptor, you can change this
public class SaveInfluxDataInterceptor extends WSS4JOutInterceptor {
#Override
public void handleMessage(SoapMessage soapMessage) {
((Fault)exchange.get(Exception.class)).setStatusCode(202); // this is because of soap12OutFaultInterceptor look to this fault
// or you can use this
// exchange.getOutFaultMessage().put(Message.RESPONSE_CODE, 202);
}
}
I used Camel with CXF. maybe this solution should change in yours.