Set spring integration header into soap header in spring integration (without interceptor/ threadlocal) - spring

Is there anyway to set SOAP-ENV header in spring integration xml from spring integration headers ?
I tried below approach and expected bId to be populated to soap headers
<int:chain id="soapcall" input-channel="soapchannel">
<int-xml:header-enricher>
<int-xml:header name="bId" expression="headers['bId']"/>
</int-xml:header-enricher>
<int-ws:outbound-gateway uri="${soap.url}" interceptor="myInterceptor">
</int-ws:outbound-gateway>
</int:chain>
However Actual output doesn't have the header. see below:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:n1="http://namespace/n1">
<SOAP-ENV:Header/>
<SOAP-ENV:Body> ...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I also looked into below approach inside chain before calling outbound gateway. It seems this header enricher takes only soap action as header. So this throws exception action headers not found
<int-ws:header-enricher >
<int-ws:soap-action expression="headers['bId']"/>
</int-ws:header-enricher>
I also looked into this post. But I couldn't get this compiled inside a chain.

I'm not sure what is the <int-xml:header-enricher>, but it doesn't matter.
What you need is called header-mapper on the <int-ws:outbound-gateway>.
Its code looks like:
#Override
protected void populateStandardHeaders(Map<String, Object> headers, SoapMessage target) {
String soapAction = getHeaderIfAvailable(headers, WebServiceHeaders.SOAP_ACTION, String.class);
if (!StringUtils.hasText(soapAction)) {
soapAction = "\"\"";
}
target.setSoapAction(soapAction);
}
#Override
protected void populateUserDefinedHeader(String headerName, Object headerValue, SoapMessage target) {
SoapHeader soapHeader = target.getSoapHeader();
if (headerValue instanceof String) {
QName qname = QNameUtils.parseQNameString(headerName);
soapHeader.addAttribute(qname, (String) headerValue);
}
}
So, by default it maps WebServiceHeaders.SOAP_ACTION as a standard one, and all those user defined if they are String and only as attribute of the target SOAP-ENV:Header element.
Those user defined headers can be mapped by the:
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of SOAP Headers to be mapped from the SOAP request into the MessageHeaders.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
If you need to populate sub-elements to the <SOAP-ENV:Header>, you don't have choice unless extend DefaultSoapHeaderMapper and override that populateUserDefinedHeader() and use SoapHeader.addHeaderElement() API.
Also look into the Reference Manual.

Related

Fail to consume SOAP WS with Spring-WS in Spring boot but works from SOAPUI

I'm using Spring Boot to consume a SOAP WS which I generate from a WSDL. I added the spring-ws-security so I can pass the user/password as security header as shown in the configuration:
#Configuration
public class ClientConfig {
public static final String SIEBEL_ENDPOINT = "http://...";
#Bean
public CustomerClient customerClient() {
CustomerClient client = new CustomerClient();
client.setDefaultUri(SIEBEL_ENDPOINT);
client.setWebServiceTemplate(webServiceTemplate(marshaller()));
return client;
}
#Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate template = new WebServiceTemplate(marshaller, marshaller);
template.setDefaultUri(SIEBEL_ENDPOINT);
ClientInterceptor[] interceptors = new ClientInterceptor[] {new LogHttpHeaderClientInterceptor(), wsSecurityInterceptor()};
template.setInterceptors(interceptors);
return template;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.test.dms.gen");
return marshaller;
}
#Bean
public Wss4jSecurityInterceptor wsSecurityInterceptor() {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementActions(WSHandlerConstants.USERNAME_TOKEN);
wss4jSecurityInterceptor.setSecurementPasswordType(WSConstants.PW_TEXT);
wss4jSecurityInterceptor.setSecurementUsername("rf_USER");
wss4jSecurityInterceptor.setSecurementPassword("rf_USER");
return wss4jSecurityInterceptor;
}
}
And the service call:
public class CustomerClient extends WebServiceGatewaySupport {
public CustomerInfoOutput getCustomerInfo(String vin) {
ObjectFactory request = new ObjectFactory();
final CustomerInfoInput custInfoInput = request.createCustomerInfoInput();
custInfoInput.setVINNumber(vin);
return (CustomerInfoOutput) getWebServiceTemplate().marshalSendAndReceive(ClientConfig.SIEBEL_ENDPOINT, custInfoInput);
}
}
everything is well generated, and this logged output:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV: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"
SOAP-ENV:mustUnderstand="1">
<wsse:UsernameToken wsu:Id="UsernameToken-e8f183db-44db-4c0b-90d9-ca57e89225fd">
<wsse:Username>rf_USER</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">rf_USER</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns3:CustomerInfo_Input xmlns:ns2="http://siebel.com/testdashboard"
xmlns:ns3="http://test.com/rf/customerinfo" xmlns:ns4="http://test.com/rf"
xmlns:ns5="http://www.siebel.com/xml/IBM%20test%20Dashboard">
<ns3:VINNumber>123456789</ns3:VINNumber>
</ns3:CustomerInfo_Input>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
When I send this request using SOAP UI, it works perfectly. But when it's sent using the generated objects from the WSDL, I have this error:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>There is no active Web Service with operation named
'http://test.com/rf/customerinfo:CustomerInfo_Input'.(SBL-EAI-04313)
</faultstring>
<detail>
<siebelf:siebdetail xmlns:siebelf="http://www.siebel.com/ws/fault">
<siebelf:logfilename>EAIObjMgr_enu_0023_24117286.log</siebelf:logfilename>
<siebelf:errorstack>
<siebelf:error>
<siebelf:errorcode>SBL-EAI-04313</siebelf:errorcode>
<siebelf:errorsymbol>IDS_EAI_WS_OP_NOT_FOUND</siebelf:errorsymbol>
<siebelf:errormsg>There is no active Web Service with operation named
'http://test.com/rf/customerinfo:CustomerInfo_Input'.(SBL-EAI-04313)
</siebelf:errormsg>
</siebelf:error>
</siebelf:errorstack>
</siebelf:siebdetail>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Any ideas please?
PS: please don't focus on URIs because I changed them, but the generated request works fine in SOAPUI.
With the hint of #RanjithR I processed as follow:
I traced the SOAP Call using Wireshark, and I discovered that Spring WS doesn't include the header SOAPAction. The following SOF question talks about this, and how to configure it (it sends you to the spring documentation which explains well the thing). Spring-WS client not setting SOAPAction header
The second thing is that, even if I added the header, it continues to tell me that the endpoint is not sent.
In my WSDL, the SOAPAction is defined as follows:
<soap:operation soapAction="document/http://test.com/rf/customerinfo:CustomerInfo"></soap:operation>
And my Spring ws call was like that:
return getWebServiceTemplate().marshalSendAndReceive(ClientConfig.SIEBEL_ENDPOINT,
customerInfoInput,
webServiceMessage -> ((SoapMessage)webServiceMessage).setSoapAction("document/http://ripl.com/rforce/customerinfo:CustomerInfo"));
In Wireshark, I had:
SOAPAction: "http://ripl.com/rforce/customerinfo:CustomerInfo"
But when I trace the same call from SOAP UI I have:
SOAPAction: "document/http://ripl.com/rforce/customerinfo:CustomerInfo"
So I tried to send a string literal as a SOAPAction:
"\"document/http://ripl.com/rforce/customerinfo:CustomerInfo\"";
Note the \"\" around the action and it works :)
Maybe it can help someone that have to integrate with Jurasik Park systems...
This post saved me a lot of time by pointing me in the right direction. The Action header was not added for me using the method described above, but the following method worked fine:
// add action header
final String ActionHeaderName = "Action";
final String ActionHeaderPrefix = "wsa";
final String ActionHeaderNamespace = "http://www.w3.org/2005/08/addressing";
final String ActionHeaderContent = "http://example/ActionHeader";
var actionName = soapEnvelope.createName(ActionHeaderName, ActionHeaderPrefix, ActionHeaderNamespace);
var actionHeader = soapEnvelope.getHeader().addHeaderElement(actionName);
actionHeader.setTextContent(ActionHeaderContent);
Thanks a lot #Marouane

How to determine the request URI in a JAX-RS client's MessageBodyReader?

I am implementing a custom MessageBodyReader that needs to resolve URIs against the request URI during processing. If the MessageBodyReader is used in a JAX-RS server implementation, which eg. processes incoming POST requests, I can obtain the request URI from a UriInfo object that is injected at runtime using #Context (as described eg. at https://stackoverflow.com/a/3314076):
public class MyProvider implements MessageBodyReader {
#javax.ws.rs.core.Context
javax.ws.rs.core.UriInfo uriInfo;
#Override
public Iterable<URI> readFrom(Class<Iterable<URI>> clazz, Type genericType,
Annotation annotations[], MediaType mediaType,
MultivaluedMap httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
URI requestURI = uriinfo.getAbsolutePath(); // NPE if called in a client
// Parsing entityStream resolving relative URIs using requestURI
}
}
If the MessageBodyReader is called in a JAX-RS client implementation during the reading of an Response's entity, the injection does not happen, hence I get a NullPointerException (see above for the line) when I write a client using JAX-RS like:
Iterable<URI> it = ClientBuilder.newClient().target("http://example.org/").request().get()
.readEntity(new GenericType<Iterable<URI>>(){});
With the aim of resolving URIs, my question is:
How do I find out the request URI in readFrom(...) in the case that the MessageBodyReader is called in a JAX-RS client implementation?
My current workaround: Having read Chapter 10.4 "Filter and interceptor execution order" of the Jersey documentation, I found that in step 16, the ClientResponseFilter, I have access to both the request URI and the response headers. Hence I wrote a ClientResponseFilter that puts the request URI in a custom response header. Then, I can retrieve that custom header from the 5th parameter of readFrom(...).

Spring Integration | HTTP outbound gateway | Multipart

Service A receives a file
Service A performs business logic.
Service B exposed an HTTP inbound to receive the file and post it
to S3.
Service A calling Service B using http outbound gateway.
I am getting an error, unable to find suitable message converter when service A calls Service B using http outbound gateway.
<int:header-enricher input-channel="addHeader" output-channel="s3publishWithHeader">`
<int:header name="Content-Type" value="multipart/form-data" overwrite="true"/> </int:header-enricher>`
<util:list id="converters">
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
</util:list>`
<http:outbound-gateway request-channel="s3publishWithHeader"
http-method="POST" url="http://localhost:8090/com/api/upload"
extract-request-payload="true" message-converters="converters"
></http:outbound-gateway>
Version of Spring Integration: 4.3.12
When using a Resource multipart it seems crucial that this resource has a file name, otherwise I found that the receiving server complains that the part is not present.
The MultiValueMap which will become the multipart request body can contain HttpEntity parts where you can specify the parts more precisely, e.g. you can specify a file name.
Assuming you have two parts, request and file, and file is an InputStreamResource (which never has a file name), you must define the filename by means of the Content-Disposition header. Use the resulting map as outgoing message payload, like so:
MultiValueMap payload = new LinkedMultiValueMap();
// some json part named "request"
HttpHeaders requestPartHeaders = new HttpHeaders();
requestPartHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ConversionRequest> requestEntity =
new HttpEntity<>(requestBean, requestPartHeaders);
// InputStreamResource part named "file"
HttpHeaders fileRequestHeaders = new HttpHeaders();
fileRequestHeaders.setContentDisposition(ContentDisposition.builder("form-data")
.name("file") // name of the part in the map
.filename("my-file-name.pdf") // <-- mandatory file name
.build());
fileRequestHeaders.setContentType(MediaType.parseMediaType(fileMimeType));
HttpEntity<InputStreamResource> fileEntity =
new HttpEntity<>(new InputStreamResource(fileOriginalStream),
fileRequestHeaders);
payload.add("request", requestEntity);
payload.add("file", fileEntity);
You don't need to specify that Content-Type header and you don't need to configure custom converters.
Only what you need is a payload as Map<String, Object>:
else if (content instanceof Map) {
// We need to check separately for MULTIPART as well as URLENCODED simply because
// MultiValueMap<Object, Object> is actually valid content for serialization
if (this.isFormData((Map<Object, ?>) content)) {
if (this.isMultipart((Map<String, ?>) content)) {
contentType = MediaType.MULTIPART_FORM_DATA;
}
else {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
}
}
Nonetheless you have to show the StackTrace for error because I think there is something with response parsing.

Spring MVC with Spring Integration HTTP: how to pass the queryString avoiding the "?" encoding with "%3F" ?

I have two Spring MVC applications interconneted between each others with Spring Integration HTTP.
I have an "application1" and an "application2".
The application1 receive this HTTP GET request:
http://localhost:8080/application1/api/persons/search/findByName?name=Name
The application1 manage the request with this #Controller:
#Controller
public class ApplicationController {
#RequestMapping(value = "/api/{repository}/search/{methodName}", method = RequestMethod.GET)
public void search(#PathVariable(value="repository") String repository,
#PathVariable(value="methodName") String methodName,
ModelMap model,
HttpServletRequest request,
HttpServletResponse response) {
// handling the request ...
}
}
Here is what I can see in the request properties:
getRequestURI=/application1/api/persons/search/findByName
getRequestedSessionId=null
getContextPath=/application1
getPathTranslated=null
getAuthType=null
getMethod=GET
getQueryString=name=Name
getServletPath=/api/persons/search/findByName
getPathInfo=null
getRemoteUser=null
I want to "transfer" this request to the application2 using Spring Integration HTTP with an <int-http:outbound-gateway>.
The message for the channel used by the outbound-gateway is originated in the #Controller in this way:
MessagingChannel messagingChannel = (MessagingChannel)appContext.getBean("requestChannelBean");
String payload = repository+"/search/"+methodName;
Message<String> message = MessageBuilder.withPayload(payload).build();
MessageChannel requestChannel = messagingChannel.getRequestChannel();
MessagingTemplate messagingTemplate = new MessagingTemplate();
Message<?> response = messagingTemplate.sendAndReceive(requestChannel, message);
This is the <int-http:outbound-gateway> configuration:
<int-http:outbound-gateway id="gateway" rest-template="restTemplate"
url="http://localhost:8080/application2/api/service/{pathToCall}"
http-method="POST" header-mapper="headerMapper" extract-request-payload="true"
expected-response-type="java.lang.String">
<int-http:uri-variable name="pathToCall" expression="payload"/>
</int-http:outbound-gateway>
This gateway produces an HTTP POST request towards the url:
http://localhost:8080/application2/api/service/persons/search/findByName
But in this request I lose the original QueryString received by the application1.
I have tried to add the queryString directly to the payload as follows:
String queryString = "";
if (request.getQueryString()!=null)
queryString = request.getQueryString();
String payload = repository+"/search/"+methodName+"?"+queryString;
But this doesn't work: the produced url is:
http://localhost:8080/application2/api/service/persons/search/findByName%3Fname=Name
The "?" symbol is replaced by "%3F", so the called method is "/service/persons/search/findByName%3Fname=Name", instead of "/service/persons/search/findByName"
I suppose that it depends on the http-method="POST"; I want to use the POST method in any case, because I want to use this "service" for general requests.
So what I have to do in order to transfer the queryString of the original request to the other side in the simplest way as possible?
Thanks in advance.
I have found the answer.
The encoding of the "?" into "%3F" was made by the <int-http:outbound-gateway> beacuse the encode-uri="false" attribute was missing.
The outbound gateway encodes the URI by default, because the encode-uri attribute is setted to true by default.

Spring-ws SoapHeader fields access in endpoint method

We are developing a contract-first WebService using spring-ws 2.2.0. We are trying to manage the authentication using a custom tag, named AuthToken, located in the SoapHeader.
The AuthToken has the following structure:
<authToken>
<username>USERNAME</xa:username>
<password>PASSWORD</xa:password>
</authToken>
We are able to generate a WSDL schema containing the specified custom authentication tag inside the SoapHeader.
The problem is that when the client performs the call towards our server we are not able to unmarshal the AuthToken tag (located in the SoapHeader) in our Ws Endpoint implementation.
Using the #RequestPayload annotation in the binding method signature (handleMethodRequest as specified in the example below), we are able to access the unmarshalled payload content (located in the SoapBody).
We tried to make the same thing with the SoapHeader content without success.
In the following code examples we show you what we would like to obtain:
1
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, #SoapHeader(value = "authToken") AuthToken authToken) { }
2
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, AuthToken authToken) { }
3
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, org.springframework.ws.soap.SoapHeader header) { }
4
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, MessageContext messageContext) { }
In case 1, 2 we obtain the following error:
No adapter for endpoint [MethodResponse EndpointImplementation.handleMethodRequest(MethodRequest, AuthToken) throws java.lang.Exception]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
In case 3, 4 we have no errors but we are not able to handle SoapHeader or MessageContext (respectively in case 3 and 4) to reach our purposes, accessing the AuthToken to retrieve username and password sub element.
Looking for a solution in the web we found that many people having the same problem uses Spring Interceptors to handle the authentication.
Following the "Interceptors-way" we should access the AuthToken inside the interceptor. Unfortunately we need to use AuthToken field inside handleMethodRequest method for other purposes, for example loading user specific data, not accessible outside handleMethodRequest.
Therefore we cannot follow this way because we need to refer user specific data inside the handleMethodRequest method.
Does anyone know how can we solve the problem? Thanks in advance.
For that use case, the only supported combination of annotation and parameter type is #SoapHeader and SoapHeaderElement. Spring-WS currently doesn't support unmarshalling headers.
A hacky way of getting the value from interceptor to the handleMethodRequest is using a static ThreadLocal instance. Since the same thread that invokes the interceptor also invokes the handleMethodRequest you can use
ThreadLocal.set(AuthToken); // in interceptor.
ThreadLocal.get();// in handler and then clear it after use.
Also, I noticed that #SoapHeader(value = "{authToken") in your example does not have } is that a typo here or in your code?

Resources