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

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.

Related

How to send JSON response from controller?

I want to pass a boolean value from my controller to javascript using json but couldnot find a way as I am new to spring mvc.
While using servlet we wrote:
response.getWriter().println(somevalue)
and the somevalue can be received using ajax.
Here my controller method is:
#RequestMapping(value = REGISTERACTION , method = RequestMethod.POST)
#ResponseBody
public boolean RegisterUser(#ModelAttribute("register") Register register,HttpServletRequest request, HttpServletResponse response)
{
boolean Registrationsuccess = userService.RegisterUser(register);
return Registrationsuccess;
}
So, here the boolean variable is Registrationsuccess which I want to send to js file and receive it using ajax.
And in my javascipt function which is called using onsubmit event-->
function AccountExists()
{
$.ajax({
type: 'POST',
url: 'registerProcess',
success: function(data){
let detail = JSON.parse(data);
if( data == true)
alert("Success");
else
alert("Not ");
}
});
}
Getting error --
The target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request, and the server is unwilling to supply a default representation.
You need to use ResponseEntity and #RestController for JSON Response.
Note : #RestController includes both annotations #Controller and #ResponseBody.
Try with this :
#RestController
#RequestMapping("controller")
public class Controller {
#PostMapping("REGISTERACTION")
public ResponseEntity<Boolean> RegisterUser(#ModelAttribute("register") Register register)
{
Boolean registrationSuccess = userService.RegisterUser(register);
return new ResponseEntity<Boolean>(registrationSuccess , HttpStatus.OK);
}
}
Try to use #ResponseBody annotation in your controller's method. And change the return type of the method to Boolean, then return Registrationsuccess instead of ModelAndView.
You can achieve this using 2 approach
Approach 1: Set model attribute and using expression language you can find on jsp
model.addAttribute("test",true);
in Jsp page
${test}
Approach 2: If you are sending ajax request instead of ModelAndView create a object
set any attribute boolean and return object from method #ResponseBody annotation you will get json in Ajax Response
#RequestMapping(value = REGISTERACTION , method = RequestMethod.POST)
public #ResponseBody MyCustomObject RegisterUser(#ModelAttribute("register") Register register,HttpServletRequest request, HttpServletResponse response)
{
boolean Registrationsuccess = userService.RegisterUser(register);
MyCustomObject cusobj=new MyCustomObject();
cusobj.setStatus(true);
return cusobj;
}
Whatever code you have written it will not return json(It is basically form submission approach) so you have to go with first approach.

How to update header parameter and sent the same to Controller using AOP in Spring/Spring boot

How to update header parameter and sent the same to Controller using AOP in Spring/Spring boot? I am able to add but not able to send it to the controller. I am getting null value in the controller. I don't want to use #Around.
#Before("PointcutDefinition.controllerLayer()")
public Object beforeAdvice(JoinPoint joinPoint)
{
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.setAttribute("traceId", ServiceUtil.getTraceId());
return request;
}
Update:
I was able to update traceId using the following code.
#Around("execution(* com.test.api.*.*(..)) && " + "args(traceId,..)")
public Object setTraceId(ProceedingJoinPoint joinPoint, String traceId) throws Throwable
{
String newTraceId = ServiceUtil.getTraceId();
Object result = joinPoint.proceed(new Object[]
{ newTraceId, "", "", "", "", "", "", "", "", "" });
return result;
}
In my Controller, I have multiple methods with a different number of arguments. But the first argument in all the method is traceId. I would like to update traceId alone and leave the other parameter as it is. But in the above approach, I am compelled to pass all the arguments. Is there a way I can update the first param alone and send the remaining parameters untouched.
I was able to do that using #Around. I believe it cannot be achieved using #Before.
#Around("execution(* com.test.api.*.*(..)) && " + "args(traceId,..)")
public Object setTraceId(ProceedingJoinPoint joinPoint, String traceId) throws Throwable
{
String newTraceId = ServiceUtil.getTraceId();
Object[] obj = joinPoint.getArgs();
obj[0] = newTraceId;
return joinPoint.proceed(obj);
}
Your trouble lies not in the #Before annotation, but in mistakenly setting an attribute on the HttpServletRequest instead of a header. Attribute != Header. So, of course, that parameter is going to be null in your controller.
The headers on HttpServletRequest are read only. You'll need to wrap the request in a HttpServletRequestWrapper and do all sorts of work in order to manage the original headers and your custom ones. There are many exampless, including a complete one here: https://wilddiary.com/adding-custom-headers-java-httpservletrequest/
It introduces a MutableHttpServletRequest that extends HttpServletRequestWrapper. You'll have keep state on your custom headers and mere them with the original ones. You'll have to override getHeader and getHeaderNames!
Your #Before code would look something like this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
MutableHttpServletRequest wrappedRequest = new MutableHttpServletRequest(request);
request.putHeader("traceId", ServiceUtil.getTraceId());
return request;
However, this seems like such a long way to go in order to get a tiny bit of functionality (unless you need to do it again and again). For a one-off I'd just use the #Around technique.
All this ceremony to add a value to a map!

Calling #PatchMapping annotated method using TestRestController

I have a method annotated with #PatchMapping.
#PatchMapping(path= "/api/transaction/{transaction-id}/return")
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId){
Transaction transaction = transactionRepository.findById(transactionId).get();
transaction.setDateOfReturn(LocalDateTime.now());
return ResponseEntity.ok().body(transaction);
}
I need to test this method. In test method, I need to use TestRestController.patchForObject();
#Test
public void testReturnBookTransaction() throws Exception {
ResponseEntity<Transaction> response = testRestTemplate.patchForObject("/api/transaction/{transaction-id}/return",
, Transaction.class, 1);
Assert.assertEquals(200, response.getStatusCode().value());
}
The code above shows compiler error in template.patchForObject(), as it needs 4 parameters. I don't know, what to pass in 4th parameter.
I just have to pass 1 for {transaction-id}, URL String and return type class, which I have already passed. But this method requires one more parameter as Object.
I don't know, what to pass in Object.
From Spring's documentation:
public T patchForObject(String url,
Object request,
Class responseType,
Map uriVariables)
throws RestClientException
The second argument is for request object. Since you don't have any request-related info in your controller method, you can set it to null.
Try the following:
Transaction response = testRestTemplate.patchForObject("/api/transaction/{transaction-id}/return", null
, Transaction.class, 1);
UPDATE:
If you want to have access to the response entity, try the following:
ResponseEntity<Transaction> response = testRestTemplate.exchange("/api/transaction/{transaction-id}/return", HttpMethod.PATCH, null,Transaction.class, 1);

Spring MVC Controller method mapping using form body

I'm building a small application to serve as a client for some third party library here at work. The API states that a Webhookis needed to respond some asynchronous events, but all their methods have the very same signature, apart from a changing _method field between the calls. For example, I have a _method = ping, media, etc.
I'd like to have separate methods on my controller to respond for each one of these methods. If the app allowed me to specify different URLs for each method it would be easy to use Spring MVC's #RequestMapping for each one of them. But I have to specify a single endpoint to receive all calls.
Is there a way (for example using Spring's HttpMessageConverter or something like that) to map different controller methods based on what the Request Body is? I've already tried with #RequestBody, #RequestParam but didn't seem to find anything.
I really, really didn't want to use a bunch of case, switch methods on a front controller to dispatch actions based on my _method field that comes with my POST data, so I happen to believe someone had this problem before and solved it intelligently.
Thanks a lot!
Edit 1: Providing source code
#Controller
#RequestMapping("/webhooks")
public class WebhookController {
#RequestMapping(method = RequestMethod.POST, params = {"_method=ping"})
#ResponseBody
public String ping(){
return "pong";
}
#RequestMapping(method = RequestMethod.POST, params = {"_method=media"})
#ResponseBody
public String media(){
return "media";
}
}
This is the answer:
{
"timestamp": 1440875190389,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.UnsatisfiedServletRequestParameterException",
"message": "Parameter conditions \"_method=ping\" not met for actual request parameters: ",
"path": "/webhooks"
}
Right, I got it working. The answer is a bit tricky so I wanted to register it here should anyone have such problem.
#Neil McGuigan pointed me on the right direction on his comment but I didn't pay attention at first. The main culprit here is a very, very, very bad API design on our remote application's side.
_method is a field used to specify non-standard HTTP verbs such as PUT, PATCH, DELETE, TRACE and so on. This field is filtered by HiddenHttpMethodFilter and the HttpServletRequest is wrapped with this 'new' method. You can see at the file's source how it works.
As I wanted this _method field to get thru the filter without modifying the whole request (and causing the errors because there's no such verb as pingor message on `RequestMethod) I firstly had to deactivate the filter. This could be done by two ways:
I could stop Spring Boot from automagically configuring Spring MVC, skipping WebMvcAutoConfiguration from being loaded when the ApplicationContext was loaded. As you can imagine this is a BIG, BIG, BIIIIG NO because, well, things could happen.
I could use a FilterRegistrationBean to disable the bad filter. Pretty simple and straightforward, this was the method I chose to use:
#Bean
public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Last but not least, I decided to give HiddenHttpMethodFilter a little extension to somehow improve how the requests were getting thru. The Java EE Spec is pretty clear on the Servlet Spec Commandments where it states:
Thou should not alter your request on your side. You must respect the sender (something like that)
Though I agree with this, for the sake of my mental stability I decided to alter it anyway. To achieve this, we can use a simple HttpServletRequestWrapper, override the chosen methods and filter the original request with the wrapped part. I ended up doing something like this:
public class WhatoolsHiddenHttpMethodFilter extends OrderedHiddenHttpMethodFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(OrderedHiddenHttpMethodFilter.DEFAULT_METHOD_PARAM);
if("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
List<String> whatoolsMethods = Arrays.asList("ping", "message", "carbon", "media", "media_carbon", "ack");
if(whatoolsMethods.contains(paramValue)){
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, "POST", paramValue);
filterChain.doFilter(wrapper, response);
} else {
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, method, null);
filterChain.doFilter(wrapper, response);
}
} else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
private final String whatoolsMethod;
public HttpMethodRequestWrapper(HttpServletRequest request, String method, String whatoolsMethod) {
super(request);
this.method = method;
this.whatoolsMethod = whatoolsMethod;
}
#Override
public String getMethod() {
return this.method;
}
#Override
public String getHeader(String name) {
if("x-whatools-method".equals(name)){
return this.whatoolsMethod;
}
return super.getHeader(name);
}
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if(this.whatoolsMethod != null){
names.add("x-whatools-method");
}
return Collections.enumeration(names);
}
}
}
So, what this does is to wrap the request with a new x-whatools-method header when the header is in my whatoolsMethods list. With this, I can easily use #RequestMapping's headers property and map the requests to the correct controller methdods.
Back to the initial question, I'm almost sure (well, 99,95% should be completely sure but let's not risk it) the params property on #RequestMapping works only for request parameters on GET URIs, e.g http://foo.bar/?baz=42. It won't work filtering parameters sent on the request's body.
Thanks Neil for your guidance, even if small! I hope this helps someone.
You can use params in a request mapping:
#RequestMapping(value="/foo", params={"_method=ping"})
Assuming these are post parameters that is
params DOES work for POST, I promise you
Here's my controller:
#Controller
#RequestMapping("/test1")
public class ParamTestController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody String getA(){
return "A";
}
#RequestMapping(method = RequestMethod.POST, params = {"b"})
#ResponseBody String getB(){
return "B";
}
}
Here's my test:

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