Spring WS: Add custom SOAP header - java

What's my goal?
I'm rather new to Spring WS, I got a WSDL (and along some XSDs, ofcourse) and i want to add some custom header elements to the SOAP response.
I've been searching the web, tried various code pieces, but it's all without any luck... nothing seems to work properly .
What's the problem?
The response SOAP message has a body what spring calls a Payload and my SOAP client (SOAPUI) receives the response rather well.
But here it comes: how should I add new (custom) SOAP headers to the response message?
What's the response xml expected?
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<aud:HeaderInfo xmlns:bd="http://www.myws.com/">
<bd:ID>123</bd:ID>
<bd:Type>text</bd:Type>
</aud:HeaderInfo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ne:myWS xmlns:ne="http://www.iways.com/">
<ne:INFO>
<ne:NAME>JOHN</ne:NAME>
<ne:DESIGNATION>ITA</ne:DESIGNATION>
<ne:MOBILE>9841011113</ne:MOBILE>
</ne:INFO>
</ne:myWS>
My payload
#PayloadRoot(localPart = "myWSRequest", namespace = TARGET_NAMESPACE)
public #ResponsePayload myWSResponse getInfo(#RequestPayload myWSRequest request)
{
myWSResponse response = new myWSResponse();
Person person = personService_i.getAccountDetails(request.getID());
response.setPersonDetails(person);
return response;
}
Any side info?
i use xsd which generates a load of classes based upon the XSDs I don't know how to add those custom headers to the response message,

You could implement a endpointInterceptorAdapter and do the following:
public final class MyEndpointInterceptorAdapter extends EndpointInterceptorAdapter {
#Override
public boolean handleResponse(MessageContext messageContext_, Object endpoint_)
throws IOException {
WebServiceMessage _webServiceMessage = messageContext_.getResponse();
SoapMessage _soapMessage = (SoapMessage) _webServiceMessage;
if (_soapMessage != null) {
SoapEnvelope _soapEnvelope = _soapMessage.getEnvelope();
// create your qname object
QName _myQName = ....
// adding your quname to the header
_soapEnvelope.getHeader().addHeaderElement(myQName );
}
}
}
and in your spring configuration file, just add the interceptor:
<sws:interceptors>
<ref bean="myEndpointInterceptorAdapter"/>
</sws:interceptors>

Related

Customization soap response

i am working on web services project with Apache CXF.
I wan't to handle exception and customize response:
public class FaultInterceptor extends
AbstractSoapInterceptor {
public FaultInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(SoapMessage soapMessage) throws Fault {
Fault fault = (Fault) soapMessage.getContent(Exception.class);
QName faultCode = new QName("11111");
fault.setFaultCode(faultCode);
So here is what i get in the response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:11111</faultcode>
<faultstring>Message</faultstring>
</soap:Fault>
</soap:Body>
How can i remove the text "soap:" and let only 11111?
Please help me and thanks in advance
To customize the SOAP response, you can implement either one of these:
JAX-WS handler;
CXF interceptor (extend AbstractSoapInterceptor);
Servlet Filter.
--EDIT 2019-02-20--
According to Javadoc (and SOAP spec), the faultcode must be in the form "prefix:localname" where "prefix" is the prefix of a declared XML namespace in your XML, or you can have no prefix - that you want - if it is declared as the default namespace, e.g. with xmlns="my-custom-faultcode-namespace-uri" somewhere, e.g. in the soap:Envelope element. So one way - not sure it's the easiest but it is SOAP standard compliant - consists to:
1) Make up your own custom namespace for this faultcode
2) Try changing the QName with empty string as namespace prefix:
QName faultCode = new QName("my-custom-faultcode-namespace-uri", "11111", "");
If this is not enough (I would be surprised it is that simple), you may have force CXF to use your custom namespace as the default (without prefix). According to this post, to customize namespaces and prefixes on the soap enveloppe in CXF, you change the Map in the jaxws 'soap.env.ns.map' property.
I was looking for the exactly same thing since migrating an old system to behave exactly the same.
I came up with the following Solution.
class SoapFaultEndpointInterceptor extends EndpointInterceptorAdapter
{
private static final Pattern SOAP_CODE_FAULT_SPLITTER = Pattern.compile(":");
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception
{
SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
modifySoapFaultCode(soapResponse);
return super.handleFault(messageContext, endpoint);
}
private void modifySoapFaultCode(SaajSoapMessage soapResponse)
{
try {
SOAPMessage soapMessage = soapResponse.getSaajMessage();
SOAPBody body = soapMessage.getSOAPBody();
SOAPFault soapFault = body.getFault();
modifyFaultCodeIfPresent(soapFault);
} catch (SOAPException e) {
throw new SoapModifiyingException("Modifying faultcode did not work properly.", e);
}
}
private void modifyFaultCodeIfPresent(SOAPFault fault)
{
if (fault != null) {
String newFaultCode = cleanFaultCode(fault.getFaultCode());
fault.setFaultCode(newFaultCode);
}
}
private String cleanFaultCode(String oldFaultCode)
{
String[] cleanFaultCode = SOAP_CODE_FAULT_SPLITTER.split(oldFaultCode);
Assert.isTrue(cleanFaultCode.length == 2, "No proper faultcode provided!");
return cleanFaultCode[1].trim();
}
And by adding SoapFaultEndpointInterceptor to your Interceptor, it should work.
#EnableWs
#Configuration
public class SoapServerConfig extends WsConfigurerAdapter
{
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors)
{
interceptors.add(new SoapFaultEndpointInterceptor());
}
}

Spring gives HTTP Status 400 when receiving a soap request having an xml declaration with special character escape slashes

I am receiving an xml post request from my vendor having a declaration of this format.
<?xml version=\"1.0\" encoding=\"utf-8\"?>
With this type of xml declarion (I am using Spring MVC with JAXB) I am getting the HTTP Status 400 error which states that "The request sent by the client was syntactically incorrect." I tried to post the same request to my site using postman and i get the very same error.
But on changing the xml declarion by removing all the backslashes( see below)
<?xml version="1.0" encoding="utf-8"?>
the error vanishes and i get the correct response with HTTP Status 200, Ok.
My question is how can i intercept this request and modify the xml declaration by removing the forward slashes (My vendor does not comply with modify this from their end).
Below is the sample of my controller
#RequestMapping(method = {RequestMethod.GET,RequestMethod.POST}, value ="/listeningurl", consumes = "application/xml", produces = "application/xml")
public ResponseObject lodgementNotifications(#RequestBody RequesObject reqObject)
{
//do stuffs with reqObject;
// Initialize ResponseObject
return responseObject
}
Thanks for the help.
You can extends the HandlerInterceptorAdapter which is :
Abstract adapter class for the AsyncHandlerInterceptor interface, for
simplified implementation of pre-only/post-only interceptors.
#Component
public class MyHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// then access your request body (xml content) to update it
// see link bellow for how to retrieve an xml content from the HttpServletRequest
return super.preHandle(request, response, handler);
}
}
After that you override the addInteceptors of WebMvcConfigurerAdapter by creating a custom class that extends from WebMvcConfigurerAdapter :
#Configuration
public class CustomWebMvc extends WebMvcConfigurerAdapter {
#Autowired
MyHandlerInterceptor myHandlerInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myHandlerInterceptor);
super.addInterceptors(registry);
}
}
To know how to retrieve the xml content from your HttpServletRequest read this Get Posted XML from HttpServletRequest Object
Edit
If your goal is simply to retrieve the http body ( xml content ) and then do what ever you want with it inside your controller you can simply inject an InputStream or a Reader in the #RequestMapping handler method (which is your controller method) like so :
#PostMapping(value = "/topics/xml", consumes = "application/xml")
public void getXmlRequest(InputStream data) throws IOException {
String requestBody = IOUtils.toString(data);
// format the xml and do something with it
}
Spring web doc : Handler Methods :
#RequestMapping handler methods have a flexible signature and can choose from a range of supported controller method arguments and return values.
I was able to resolve the issue. I had to receive the xml request in String format, removed the backslashes then unmarshalled it into its corresponding object. Thanks to #Harry Coder for the hints. Here is the solution that worked for me.
#RequestMapping(method = {RequestMethod.GET,RequestMethod.POST}, value ="/listeningurl", consumes = "application/xml", produces = "application/xml")
public ResponseObject lodgementNotifications(#RequestBody String reqObjectXMLStr)
{
//Replace the backslashes from the xml string
String cleanReqObjectXMLStr = reqObjectXMLStr.replaceAll("\\\\", "");
//Unmarshal the string into the corresponding object using JAXB library
RequestObject reqObject = JAXB.unmarshal(new StringReader(cleanReqObjectXMLStr), RequestObject.class);
//do stuffs with reqObject;
// Initialize and set ResponseObject
return responseObject
}

How to customize automatic marshaling in Spring RestTemplate to produce/modify XML headers (encoding, DOCTYPE)

I'd like to find how to influence how Spring automatically marshals Java objects to XML when sending a POST request via RestTemplate. Particularly, how to configure what is in the XML headers (encoding, DOCTYPE, ...).
There are plenty of questions closely touching the topic (Include DOCTYPE for Spring Jaxb2Marshaller, How to add DOCTYPE and xml processing instructions when marshalling with JAXB?, how to add DOCTYPE in jaxb marshaller, How to declare doctype ,xml version and encoding in XML file using DOM parser in java?) but none of them seems to help here.
I am aware that I may marshal the object to XML string first and then send the XML string. However, I'd like to use the automatic marshaling as it seems more elegant and proper.
I have a class like
#XmlRootElement(name = "MyRequest")
public class MyRequest {
#XmlAttribute(required = true)
String field1;
#XmlAttribute(required = true)
String field2;
...
}
The code sending the HTTP POST request is like:
final MyRequest requestBody = new MyRequest("VALUE1", "VALUE2");
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML, MediaType.ALL));
final HttpEntity<MyRequest> requestHttpEntity = new HttpEntity<>(requestBody, headers);
return restTemplate.postForEntity(url, requestHttpEntity, MyResponse.class);
When I intercept what is sent, it is something like this:
POST /webservice HTTP/1.1
Accept: application/xml, */*
Content-Type: application/xml
Host: example.com:8080
Content-Length: ...
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<MyRequest field1="VALUE1" field2="VALUE2">
</MyRequest>
and what I want to receive is
POST ...
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE MyRequest SYSTEM "MyRequest.dtd">
<MyRequest field1="VALUE1" field2="VALUE2">
</MyRequest>
Question: How can I customize the marshaling without completely avoiding the automagic behavior of Spring RestTemplate? I want to change the encoding, remove the standalone attribute (where does it come from?) and add the <!DOCTYPE> element.
You can replace the XML converter that RestTemplate uses with a customized one:
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
for (int i = 0; i != converters.size(); i++) {
if (converters.get(i) instanceof Jaxb2RootElementHttpMessageConverter) {
Jaxb2RootElementHttpMessageConverter xmlConverter = new Jaxb2RootElementHttpMessageConverter(){
#Override
protected void customizeMarshaller(Marshaller marshaller) {
marshaller.setProperty( "com.sun.xml.internal.bind.xmlHeaders", "<!DOCTYPE MyRequest SYSTEM \"MyRequest.dtd\">");
// add other customizations
}
};
converters.set(i, xmlConverter);
break;
}
}
Wrap the whole thing in a method annotated with #Bean #Qualified, and use that to autowire the RestTemplate wherever you need it, if you have many places to inject it to

Header Values are coming as null in Apache camel Exchange

Below are my web-service request, Route and Request-Validator,
Web-service request:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<stlh:SabreHeader xmlns:stlh="http://services.sabre.com/STL_Header/v02_01">
<stlh:Service version="1.0.0">GetHotelMediaRQ</stlh:Service>
<stlh:Identification>
<stlh:CustomerID>CID12345</stlh:CustomerID>
<stlh:CustomerAppID>AppTest</stlh:CustomerAppID>
<stlh:ConversationID>05EFPElI2A4KudU75863JIxqAhQJtAx0</stlh:ConversationID>
<stlh:MessageID>4DTTQaHGSifFUtmSoMHAiq</stlh:MessageID>
<stlh:TimeStamp>2014-11-07T14:45:42.725-06:00</stlh:TimeStamp>
</stlh:Identification>
</stlh:SabreHeader>
<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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" wsu:Id="athId">${athId}</wsse:BinarySecurityToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<GetHotelMediaRQ xmlns="http://services.sabre.com/hotel/media/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://services.sabre.com/hotel/media/v1 GetHotelMediaRQ.xsd">
<HotelRefs>
<HotelRef HotelCode="184769" CodeContext="Sabre">
<ImageRef MaxImages="1">
<Images>
<Image Type="ORI"/>
</Images>
<AdditionalInfo>
<Info Type="CAPTION">true</Info>
</AdditionalInfo>
<Languages>
<Language Code="EN"/>
</Languages>
</ImageRef>
</HotelRef>
</HotelRefs>
</GetHotelMediaRQ>
</soap:Body>
</soap:Envelope>
RequestValidator:
public void validate(GetHotelMediaRQ request, Exchange exchange) throws Exception {
TransactionContext context = BusExtensions.getTransactionContext(exchange);
Collection<HotelRef> hotelRefList = getInstance().convert(request, Collection.class);
Set<Property> properties = new HashSet<>();
String customerAppId = exchange.getIn().getHeader("customerAppID", String.class);
String customerId = exchange.getIn().getHeader("customerID", String.class);
But customerAppId(AppTest) and CustomerId(CI12345) is coming as null when I try to access via Exchange object.
"Custom" Soap headers are not copied to camel header . you have to manually add soap header into camel exchange header .
Approach 1 )
CamelCxfMessage - you can extract/process custom soap header camel cxf message which is present in camel exchange header
in camel -
SoapMessage soapMessage = (SoapMessage)exchange.getIn().getHeader("CamelCxfMessage");
this will give you soap message and its soapMessage.getExchange and try to get soap headers from soap message and process it .
Approach 2)
Camel Cxf Binding -you can use camel cxf binding feature in endpoint definition like cxfBinding=#bindingName .
Create a class and extend with org.apache.camel.component.cxf.DefaultCxfBinding and bean name should be bindingName .
it has one method which you have to overwrite - propagateHeadersFromCxfToCamel(camelmessage ,cxfmessage ,exchage ).
Here get your soap header and put it in camel header with identifier and access header in camel exchange header in processor or routes with same identifier.
Set logging for org.apache.camel to DEBUG and the header values will be logged, and you can determine if the component is dropping them or not.
Also, it looks like you might be using the cxf soap endpoint. Look into the [Description of relayHeaders option] section of the docs here:
http://camel.apache.org/cxf.html
i had to extract header information but got null in the object at the first attempt. then after a while i could fish it out. here is how (in a processor):
#Override
public void process(Exchange exc) throws Exception {
#SuppressWarnings("unchecked")
List<SoapHeader> headers = exc.getIn().getHeader(Header.HEADER_LIST, List.class);
for (int i=0; i < headers.size(); i++) {
if (headers.get(i).getObject() instanceof ElementNSImpl) {
ElementNSImpl elementNSImpl = (ElementNSImpl) headers.get(i).getObject();
Node firstChild = elementNSImpl.getFirstChild();
log.trace("header: name=" + elementNSImpl.getLocalName() + ", value=" + firstChild.getNodeValue());
}
}

Sending SOAP request with MessageCallback and MessageExtractor

When sending a SOAP request through Spring's WebServiceTemplate, I would like to provide my payload and perform operation on both the request and the response of it. This is because I need some details from the headers of the request/response.
In the Spring documentation I have found it's possible to alter the request with a WebServiceMessageCallback and the response with a WebServiceMessageExtractor.
The problem I'm having is that the WebServiceTemplate seems to choose between providing a payload and providing MessageCallback/MessageExtractor.
With that, I mean there are the following methods available:
marshalSendAndReceive(Object requestPayload, WebServiceMessageCallback requestCallback)
sendAndReceive(WebServiceMessageCallback requestCallback, WebServiceMessageExtractor<T> responseExtractor)
sendAndReceive(WebServiceMessageCallback requestCallback, WebServiceMessageCallback responseCallback)
But nothing to provide all three. So providing the payload, a WebServiceMessageCallback for operations on the request and a WebServiceMessageCallback/WebServiceMessageExtractor for operations on the response.
In the documentation they do provide the following snippet:
public void marshalWithSoapActionHeader(final Source s) {
final Transformer transformer = transformerFactory.newTransformer();
webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) {
transformer.transform(s, message.getPayloadResult());
},
new WebServiceMessageExtractor() {
public Object extractData(WebServiceMessage message) throws IOException
// do your own transforms with message.getPayloadResult()
// or message.getPayloadSource()
}
});
}
But passing your payload into an innerclass just to pass in a payload doesn't seem like clean code.
It doesn't really seem logical that you can provide a payload and callback for tampering the request, but not the response. Or tampering the request and response without providing a payload. How would I go about if I'd like to send a payload and access both the request and response?

Categories

Resources