accessing Apache camel header in spring expression language - spring

I want to access camel exchange header value while parsing spring expression.
1.
Is it possible ? I am looking some thing like below.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("request.headers['myHeader'].concat('!')");
String message = (String) exp.getValue();
System.out.println(message);
or can we use #Header and access the same ?
Is there any other way than adding the value to StandardEvaluationContext as variable and then parsing the same ?
Appreciate some quick help on the same.
2.
One more question is is there a way i can register all my custom functions at only one place ? I mean i want to avoid registering functions for every call to evaluate the expression. Currently i am doing it below way.
public static String evalExpr(String expr,
Map<String, Object> variables) throws NoSuchMethodException,
SecurityException {
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("concat", CusExprs.class
.getDeclaredMethod("concat", String[].class));
context.registerFunction("substr", CustExprs.class
.getDeclaredMethod("substr", new Class[] { String.class,
Integer.class, Integer.class }));
context.setVariables(variables);
return parser.parseExpression(expr).getValue(context, String.class);
}

Related

WebClient URI template variables from bodyValue?

I recently encountered some unexpected behaviour and was wondering if this was in fact intended functionality when using WebClient. With a client config as shown below, the uri variables in the template are being overridden with the fields from a POJO used as an arg to bodyValue.
class ExampleRequest {
private String namespace = "jar";
private String service = "zoo";
...
}
...
this.client = WebClient.builder()
.baseUrl("http://{service}.{namespace}.svc.cluster.local")
.defaultUriVariables(Map.of("service", "foo", "namespace", "bar"))
.build();
this.client.post()
.uri("/baz")
.bodyValue(new ExampleRequest())
.retrieve()
...
The above ends up calling out to http://zoo.jar.svc.cluster.local/baz, not http://foo.bar.svc.cluster.local/baz. I wasn't expecting the body of the message in this instance to be picked up as uri variables. :confused:
This seems strange to me, but if this is expected could point me to the source/docs of where this expansion is taking place..?
Could you please use standard HashMap implementation and recheck? I tested locally for me it is working as expected, I mean foo and bar chosen:
Map<String, String > params = new HashMap<>();
params.put("service", "foo");
params.put("namespace", "bar");
WebClient client = WebClient.builder()
.baseUrl("http://{service}.{namespace}.svc.cluster.local")
.defaultUriVariables(params)
.build();

Debugging AJAX to Spring Controller (count of variables and spelling)

I am spending a lot of effort debugging ajax calls. The common issues are
number of parameters dont match from the ajax to the controller
spelling of the #RequestMapping parameter does not match
If the type does not match the call happens and it can be debugged. But the bigger issue is the above two. I have 88 parameters that I am passing and have a hell of a time trying to figure out what is missing or spelt incorrectly.
example
#RequestMapping("/saveClient")
public #ResponseBody String saveClientAJAXMethodView(#RequestParam(value = "clientName") String clientName,
.... 88 parameters more
$
.ajax({
type : "Post",
url : "saveClient",
data : {
clientName : clientName,
... 88 parameters more
I got this error
The request sent by the client was syntactically incorrect.
So I changed the signature of my controller to add , method = RequestMethod.POST).
Now I am getting
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource.
Its clearly a "POST" and still it get a request method GET not supported.
The question is NOT how to solve this problem. The question is how to debug such issues easily. What errors map to what issues, how to debug the 88 parameter spellings and count ? There must be a easier way to do this debugging.
I use the following
Debugging Mode of the controller
Inspect on Chrome.
Since you are sending huge amount of parameter in URL for POST request. I would suggest you to send your data in body.
For example if you are sending parameters like clientId, clientName, clientEmail etc.. you have used #RequestParam annotation to get individual parameter data in your controller:
String saveClientAJAXMethodView( #RequestParam String clientId,
#RequestParam String clientName,
#RequestParam String clientEmail
.... more parameters)
Instead of using #RequestParam I would suggest you to use #RequestBody, For this you need to create a Data transfer object (DTO) like this:
class ClientInfo{
String clientId,
String clientName,
String clientEmail,
....
.... other variables
.... getters and setters of variables
}
And then use this DTO in your controller method like this:
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
}
Using this approach you will not get any exception regarding spelling mistake or parameter missing .The value will be assigned to a DTO variable if you are sending value with right key as specified in DTO.
To count variables in ClientInfo object you will need to cast ClientInfo to JSONObject and use its size() method to get count of variables
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
JSONObject json = new JSONObject(clientInfo);
System.out.println(json.keySet().size());
}
Your ajax call will look like this:
var clientInfo = {
'clientName': 'tom',
'clientId': '23AZ1',
'clientEmail': 'xyz#gmail.com',
...
};
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(clientInfo),
contentType: "application/json",
complete: callback
});
I hope following steps would help you debug:
1- Use a filter to intercept request.
2- Create a custom annotation which would indicate that you want to debug this method.
3- Use the method defined in this post Can I get all of requestMapping URL with GET method in the Spring? and your custom annotation to store list of all methods which you want to debug in a singleton bean.
4- Now write some logic in filter which would print mismatch between the method parameters and request parameters.
CustomFilter:
public class CustomFilter extends GenericFilterBean {
#Autowired
#Qualifier("printMismatchMethods")
HashMap<String,Method> methodsToCheck;
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
HttpServletRequest r = (HttpServletRequest) request;
String url = r.getRequestURI().substring(r.getContextPath().length());
//Remove extensions if present any
int index = url.indexOf('.');
if(index > 0)
url = url.substring(0,url.indexOf('.'));
/*Matching string this should be replaced by url pattern matching of spring.*/
if (methodsToCheck.containsKey(url)){
Method method = methodsToCheck.get(url);
Map<String, String[]> requestParameterMap = r.getParameterMap();
Map<String,Boolean> isParamPresent = new HashMap<String,Boolean>();
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
if (!requestParam.name().isEmpty())
isParamPresent.put(requestParam.name(), false);
else
isParamPresent.put(requestParam.value(), false);
}
}
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
String name = null;
if (!requestParam.name().isEmpty())
name=requestParam.name();
else
name=requestParam.value();
if (requestParameterMap.containsKey(name)){
isParamPresent.put(name, true);
}
}
}
for (Map.Entry<String, Boolean> entry : isParamPresent.entrySet()){
if (!entry.getValue()){
System.out.println(entry.getKey() + " is either missing or mis-spelled");
}
}
}
chain.doFilter(request, response);
}
}
configured as follows:
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
Declare following custom annotation.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PrintParamMismatch {
}
Define following bean which would populate on startup
#Bean(name="printMismatchMethods")
#Autowired
public HashMap<String,Method> printParamMismatchMethods(BeanFactory beanFactory){
HashMap<String,Method> methods = new HashMap<String,Method>();
Map<String, RequestMappingHandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
(ListableBeanFactory)beanFactory,
RequestMappingHandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
ArrayList<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(handlerMappings);
RequestMappingHandlerMapping mappings = matchingBeans.get("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mappings.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethod : handlerMethods.entrySet()){
RequestMappingInfo info = handlerMethod.getKey();
HandlerMethod hMethod = handlerMethod.getValue();
Method method = hMethod.getMethod();
if (method.getAnnotation(PrintParamMismatch.class) != null){
String path = info.getPatternsCondition().toString();
path = path.substring(1,path.length());
path = path.substring(0,path.length()-1);
methods.put(path, method);
}
}
}
return methods;
}
This, I think is generic enough to show debug information for now. However we need to store and match Patterns instead of url string.
So I used divide and rule and solved my issue. I commented top half of the parameters and ran, to check if my controller gets called. It did. then I added 1/4th, then 1/8th and found that I missed a parameter.
That along with #ArsianAnjum's answer is good for debugging. #Aji's answer is the long term solution. I should be using that.

Freemarker - compose language switch url

I am using freemarker template engine with Spring and I want to add or replace lang=xxx parameter in the href attribute of link element when rendering page.
The closest solution I have found is following:
<a href="${springMacroRequestContext.getRequestUri()}?lang=en'/>English</a>
But this is not sufficient when I have URL with parameters and fragments because I will miss them. For example http://localhost/sports/search?query=Volleyball&lang=cs#some-fragment results in http://localhost/sports/search?lang=en
How to compose the URL with added or changed lang parameter in freemarker and do not miss any part of requested URL?
I preferred to do it in Java, this simple implementation does not take into account hashes (#) or the presence of the given parameter in the url..
public static String addParamToUrl(String url, String paramName, String paramValue){
StringBuffer buffer = new StringBuffer(url);
//adding separator
if(url.indexOf('?') == -1){
buffer.append('?');
}else if(!url.endsWith("?") && !url.endsWith("&")){
buffer.append('&');
}
buffer.append(paramName);
if(paramValue != null){
buffer.append("=");
buffer.append(URLEncoder.encode(paramValue, "UTF-8"));
}
return buffer.toString();
}
Put this method in a Class (i.e:Utils.java) that can be staticly accessed by Freemarker engine and than:
<#assign url = Utils.addParamToUrl(springMacroRequestContext.getRequestUri(), "lang", "en") />
English
To expose your Utils class you have to customize FreemarkerManager
public class MyFreemarkerManager extends FreemarkerManager {
public MyFreemarkerManager(){
super();
}
#Override
protected void populateContext(ScopesHashModel model, ValueStack stack, Object action, HttpServletRequest request, HttpServletResponse response) {
super.populateContext(model, stack, action, request, response);
try {
BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.VERSION_2_3_24).build();
TemplateHashModel staticModels = beansWrapper.getStaticModels();
TemplateHashModel utils = (TemplateHashModel)staticModels.get("com.package.Utils");
model.put("Utils", utils);
} catch (TemplateModelException e) {
//...
}
}
}
Finally you have to tell Spring you are using MyFreemarkerManager as manager for Freemarker.

Spring Integration SpEL for Header evaluation

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 ?

Spring RestRemplate postforobject with request parameter having integer value

I have a method in Spring rest service.
#RequestMapping(value = "test/process", method = RequestMethod.POST)
public #ResponseBody MyResponse processRequest(String RequestId, int count)
I am using Spring RestTemplate to call this service like this.
RestTemplate restTemplate = this.getRestTemplate();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("RequestId", RequestId);
map.add("count", count);
restTemplate.postForObject(url, map,MyResponse.class);
When I try to invoke the client method I get the exception that no suitable HttpMessageConverter found for request type [java.lang.Integer]
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [java.lang.Integer]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:310)
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:270)
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:260)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:200)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:1)
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:596)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:444)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:287)
I know one of the ways is to pass all the parameters as String. But I might need to pass complex data types as parameters later.
What is the ways to achieve this.
I have googled and some option seem to be writing my own converters. How should I start about solving this problem.
The root cause of this error is that by specifying an Integer in the LinkedMultiValueMap, the RestTemplate will take that to mean that your request is a multipart request. There is no HttpMessageConverter registered by default that can handle writing values of type Integer to a request body.
As you said, you can handle this situation by changing the count to be a String. After all, there is no Integer type in HTTP request parameters. However, you were worried
But I might need to pass complex data types as parameters later.
Assume something like this
public #ResponseBody MyResponse processRequest(String RequestId, int count, Complex complex) {
with
public class Complex {
private String someValue;
private int intValue;
public String getSomeValue() {
return someValue;
}
public void setSomeValue(String someValue) {
this.someValue = someValue;
}
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
public String toString() {
return someValue + " " + intValue;
}
}
The the following will work just fine
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("RequestId", "asd");
map.add("count", "42");
map.add("someValue", "complex");
map.add("intValue", "69");
restTemplate.postForObject(url, map,MyResponse.class);
Remember that the request parameters are used to populate the fields of model attributes by their names.
An even better solution would have you using a serialization standard like JSON or XML.

Resources