Spring Integration SpEL for Header evaluation - spring

I am trying to evaluate JMSHeader for Spring Integration message using SpEL but it seems to be evaluating to false always even though message has header stamped as true
<si:router expression="headers.jms_redelivered.equals(T(java.lang.Boolean).FALSE) ? 'channel1' : 'channel2' />
JMSXDeliveryCount=10, jms_redelivered=true
Can some one please comment what I am doing wrong here?
Thanks

Works for me:
#Test
public void testRedeliveredHeaderWithSpEL() throws JMSException {
DefaultJmsHeaderMapper mapper = new DefaultJmsHeaderMapper();
javax.jms.Message jmsMessage = new StubTextMessage() {
#Override
public boolean getJMSRedelivered() throws JMSException {
return true;
}
};
Map<String, Object> headers = mapper.toHeaders(jmsMessage);
assertNotNull(headers.get(JmsHeaders.REDELIVERED));
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new MapAccessor());
Expression expression =
parser.parseExpression("jms_redelivered.equals(T(java.lang.Boolean).FALSE) ? 'channel1' : 'channel2'");
assertEquals("channel2", expression.getValue(context, headers, String.class));
}
You really should be sure that you don't lose headers before your <router>.
Maybe you use some <transformer> before <router> which returns whole Message<?> istead of just payload ?

Related

Camel Routes - How to return the body response as xml

First of all, I am new with Spring Boot.
I am not sure if it is possible, but I would like to return the xml response from the external url.
I have this code:
#GetMapping("/myPage")
public void myPage() {
restConfiguration().host("localhost").port(8080);
from("timer://runOnce?repeatCount=1&delay=0")
.to("rest:get:/external-page")
.to("stream:out");
}
myPage() is returning a XML (that's OK). So, now I would like to return the same XML when I do:
curl http://localhost/myPage
I am not sure if I have to use .to("stream:out"), but the curl is returning an empty result.
Can someone help me?
Thanks in advance.
I found the solution, this is how to get the response.
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
public void configure() {
restConfiguration().host(sHost).port(iPort);
from("direct:start")
.setHeader(Exchange.HTTP_METHOD,simple("GET"))
.to("rest:get:/external-page");
}
});
context.start();
ProducerTemplate template = context.createProducerTemplate();
String headerValue = "application/xml";
Map<String, Object> headers = new HashMap<String,Object>();
headers.put("Content-Type", headerValue);
Object result = template.requestBodyAndHeaders("direct:start", null, headers, String.class);
Exchange exchange = new DefaultExchange(context);
String response = ExchangeHelper.convertToType(exchange, String.class, result);
context.stop();
return response;

Streaming upload via #Bean-provided RestTemplateBuilder buffers full file

I'm building a reverse-proxy for uploading large files (multiple gigabytes), and therefore want to use a streaming model that does not buffer entire files. Large buffers would introduce latency and, more importantly, they could result in out-of-memory errors.
My client class contains
#Autowired private RestTemplate restTemplate;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
int REST_TEMPLATE_MODE = 1; // 1=streams, 2=streams, 3=buffers
return
REST_TEMPLATE_MODE == 1 ? new RestTemplate() :
REST_TEMPLATE_MODE == 2 ? (new RestTemplateBuilder()).build() :
REST_TEMPLATE_MODE == 3 ? restTemplateBuilder.build() : null;
}
and
public void upload_via_streaming(InputStream inputStream, String originalname) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
#Override public String getFilename() { return originalname; }
#Override public long contentLength() { return -1; }
};
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
body.add("myfile", inputStreamResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
String response = restTemplate.postForObject(UPLOAD_URL, requestEntity, String.class);
System.out.println("response: "+response);
}
This is working, but notice my REST_TEMPLATE_MODE value controls whether or not it meets my streaming requirement.
Question: Why does REST_TEMPLATE_MODE == 3 result in full-file buffering?
References:
How to forward large files with RestTemplate?
How to send Multipart form data with restTemplate Spring-mvc
Spring - How to stream large multipart file uploads to database without storing on local file system -- establishing the InputStream
How to autowire RestTemplate using annotations
Design notes and usage caveats, also: restTemplate does not support streaming downloads
In short, the instance of RestTemplateBuilder provided as an #Bean by Spring Boot includes an interceptor (filter) associated with actuator/metrics -- and the interceptor interface requires buffering of the request body into a simple byte[].
If you instantiate your own RestTemplateBuilder or RestTemplate from scratch, it won't include this by default.
I seem to be the only person visiting this post, but just in case it helps someone before I get around to posting a complete solution, I've found a big clue:
restTemplate.getInterceptors().forEach(item->System.out.println(item));
displays...
org.SF.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
If I clear the interceptor list via setInterceptors, it solves the problem. Furthermore, I found that any interceptor, even if it only performs a NOP, will introduce full-file buffering.
public class SimpleClientHttpRequestFactory { ...
I have explicitly set bufferRequestBody = false, but apparently this code is bypassed if interceptors are used. This would have been nice to know earlier...
#Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
public abstract class InterceptingHttpAccessor extends HttpAccessor { ...
This shows that the InterceptingClientHttpRequestFactory is used if the list of interceptors is not empty.
/**
* Overridden to expose an {#link InterceptingClientHttpRequestFactory}
* if necessary.
* #see #getInterceptors()
*/
#Override
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { ...
The interfaces make it clear that using InterceptingClientHttpRequest requires buffering body to a byte[]. There is not an option to use a streaming interface.
#Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {

Custom json response for internal exception in spring

While implementing a global exception handler in Spring, I noticed that in case of a not recognized Accept header, Spring would throw it's own internal error. What I need is to return a custom JSON error structure instead. Works fine for application specific exceptions and totally fails for Spring HttpMediaTypeNotAcceptableException.
This code tells me "Failed to invoke #ExceptionHandler method: public java.util.Map RestExceptionHandler.springMalformedAcceptHeaderException()" when I try to request a page with incorrect Accept header. Any other way to return custom JSON for spring internal exceptions?
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public Map<String, String> springMalformedAcceptHeaderException() {
Map<String, String> test = new HashMap<String, String>();
test.put("test", "test");
return test;
}
}
Eventually figured that the only way is to do the json mapping manually.
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public String springMalformedAcceptHeaderException(HttpServletResponse response) {
// populate errorObj, set response headers, etc
ObjectWriter jsonWriter = new ObjectMapper().writer();
try {
return jsonWriter.writeValueAsString(errorObj);
} catch(Exception e){}
return "Whatever";
}

Does Spring XwsSecurityInterceptor remove the Security element from Header?

I have implemented Spring XwsSecurityInterceptor and receiving soap message with <wsse:UsernameToken/> inside the <wsse:Security/> tag (OASIS WS-Security). It works fine.
Now I am trying to implement a logging interceptor to log the request/response soap messages in DB.
I can get the Security element in getSource() method of my custom logging interceptor (which extends org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor):
#Override
protected Source getSource(WebServiceMessage message) {
SaajSoapMessage soap = (SaajSoapMessage) message;
logger.info(Utils.getSoapEnvelopeAsString(soap));
// this envelop contains the <wsse:Security/> element as expected
// ...
// ...
}
But My problem is, when I extract the envelop inside my endpoint method, I don't get the <wsse:Security/> element in the header anymore.
public JAXBElement<MyResponseType> getRecepientInfo(#RequestPayload JAXBElement<MyRequestType> request, MessageContext messageContext) {
SaajSoapMessage soapReq = (SaajSoapMessage) messageContext.getRequest();
logger.info(Utils.getSoapEnvelope(soapReq));
// this envelop doesn't contain the <wsse:Security/> element
}
Here is the code for Utils.getSoapEnvelope(soap):
public static String getSoapEnvelope(SaajSoapMessage soapMessage) {
SoapEnvelope envelope = soapMessage.getEnvelope();
String envelopeMessge = "";
try {
envelopeMessge = Utils.getSourceAsString(envelope.getSource());
} catch (Exception e) {
// TODO handle Exception here.
}
return envelopeMessge;
}
public static String getSourceAsString(Source source) throws Exception{
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer xform = tfactory.newTransformer();
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
xform.transform(source, result);
return writer.toString();
}
Does spring remove the <wsse:Security/> element from the header after authentication has been completed? Or, I am doing something wrong here?
How should I get the <wsse:Security/> element from header inside endpoint method?
I know this is a late answer but for whom it may interest I found out how to solve this.
You need to modify your securityPolicy.xml file so that the security header is kept. Simply set the attribute retainSecurityHeader to true. Here is an example of such a file:
<xwss:SecurityConfiguration retainSecurityHeader="true" dumpMessages="false" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:UsernameToken digestPassword="false" useNonce="false" id="someId"/>
</xwss:SecurityConfiguration>

Can't get Spring SOAP Client to work: content type 'text/xml; charset=utf-8' was not the expected type 'application/soap+xml; charset=utf-8'

Hi, I am trying to make a simple soap client work using Spring-ws. The googling I've done on this error says I'm using Soap 1.1 and need to specify Soap 1.2. I've tried to do that. Am I doing it correctly below? If this is not the problem does anybody see what the problem is?
Here's a chunk of the stack trace:
org.springframework.ws.client.WebServiceTransportException: Cannot process the message because the content type 'text/xml; charset=utf-8' was not the expected type 'application/soap+xml; charset=utf-8'. [415]
at org.springframework.ws.client.core.WebServiceTemplate.handleError(WebServiceTemplate.java:663)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:587)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:492)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
at com.jda.fileserver.FujiAuthenticationTest.testLogin(FujiAuthenticationTest.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
Here's my code, thanks for trying to help:
public class AuthTest {
#Test
public void testLogin() throws Exception {
StringBuffer loginXml = new StringBuffer();
loginXml.append("<soapenv:Envelope xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:ns=\"http://abc.com/xyz/2010/08\">");
loginXml.append(" <soapenv:Header>");
loginXml.append(" <ns:loginOperationDetails>");
loginXml.append(" </ns:loginOperationDetails>");
loginXml.append(" </soapenv:Header>");
loginXml.append(" <soapenv:Body>");
loginXml.append(" <ns:LogIn>");
loginXml.append(" <ns:logInInfo>");
loginXml.append(" <ns:CustomerAccountId>customer1</ns:CustomerAccountId>");
loginXml.append(" <ns:Username>jsmith</ns:Username>");
loginXml.append(" <ns:Password>abc123</ns:Password>");
loginXml.append(" </ns:logInInfo>");
loginXml.append(" </ns:LogIn>");
loginXml.append(" </soapenv:Body>");
loginXml.append("</soapenv:Envelope>");
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
SaajSoapMessageFactory defaultMessageFactory = (SaajSoapMessageFactory) webServiceTemplate.getMessageFactory();
defaultMessageFactory.setSoapVersion(SoapVersion.SOAP_12);
webServiceTemplate.setMessageFactory(defaultMessageFactory); // probably not needed
StreamSource source = new StreamSource(new StringReader(loginXml.toString()));
StreamResult result = new StreamResult(System.out);
String uri = "http://xyz.abcstage.com/xyz_1.0/membership.svc/ws";
SoapActionCallback requestCallback = new SoapActionCallback("http://abc.com/xyz/2010/08/MembershipService/LogIn");
try {
webServiceTemplate.sendSourceAndReceiveToResult(uri, source, requestCallback, result);
}
catch (SoapFaultException sfe) {
throw new Exception("SoapFaultException", sfe);
}
catch (WebServiceTransportException wste) {
throw new Exception("WebServiceTransportException", wste);
}
}
}
Ok, I fixed the above problem, which gets me to another problem. First here's how I fixed the above problem. Now I don't set the SOAP version on the SaajSoapMessageFactory, I set it on the wrapped MessageFactory. Now the Content-Type going out in my request is application/soap+xml.
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
MessageFactory msgFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
SaajSoapMessageFactory newSoapMessageFactory = new SaajSoapMessageFactory(msgFactory);
webServiceTemplate.setMessageFactory(newSoapMessageFactory);
Next problem, now I'm getting this:
org.springframework.ws.soap.client.SoapFaultClientException: Unexpected fault in the service.
at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:37)
at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:774)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:600)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:537)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:492)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
and here's the info that was returned in the response:
500 Internal Server Error
The SOAP action specified on the message, '', does not match the HTTP SOAP Action, 'http://abc.com/xyz/2010/08/MembershipService/LogIn'.
I'll try to solve this, but wanted to update anybody reading this so they can stop looking into the previous error. I need to figure out how to correctly set the soap action.
Please use the below code to change the header content type to text/xml;charset=utf-8 in Spring webservice template marshallSendAndReceive method.
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.marshalSendAndReceive(url, request, new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
MimeHeaders headers = soapMessage.getSaajMessage().getMimeHeaders();
headers.addHeader(TransportConstants.HEADER_CONTENT_TYPE, "text/xml;charset=utf-8");
For adding soapheader with action and to tags, below code is working fine for me.
public void doWithMessage(WebServiceMessage message) throws IOException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
SoapEnvelope soapEnvelope = soapMessage.getEnvelope();
SoapHeader soapHeader = soapEnvelope.getHeader();
//Initialize QName for Action and To
QName action = new QName("{uri}", "Action", "{actionname}");
QName to = new QName("{uri}", "To", "{actionname}");
soapHeader.addNamespaceDeclaration("{actionname}", "{uri}");
SoapHeaderElement soapHeaderElementAction = soapHeader.addHeaderElement(action);
SoapHeaderElement soapHeaderElementTo = soapHeader.addHeaderElement(to);
soapHeaderElementAction.setText("{text inside the tags}");
soapHeaderElementTo.setText("{text inside the tags}");
soapMessage.setSoapAction("{add soap action uri}");
soapMessage.writeTo(out);
}
Use setHeader since you want probably want to replace the contents of an existing header or add it if it doesn't exist. Though Httpheaders are supposed to be case insensitive using addHeader with do just that. Debugging through the source code when addHeader() is called even though it ignores case you'll see that it inserts the new header after the other header.
So , at least in Java, will end up with
content-type:
Content-Type:
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
SaajSoapMessage soapMessage = (SaajSoapMessage) message;
MimeHeaders headers = soapMessage.getSaajMessage().getMimeHeaders();
headers.setHeader(TransportConstants.HEADER_CONTENT_TYPE, "text/xml;charset=utf-8");

Resources