Spring Ajax #ResponseBody with null returned values - java

I have about fifty controllers that use #ResponseBody annotation.
Like this:
#RequestMapping(value = "/someUrl.controller", method = RequestMethod.GET)
public #ResponseBody Object getObject(#RequestParam("id") Long id) {
Object object = provider.getObject(id);
return object;
}
Some times getObject method return null. The issues is that on client side I get empty response instead of null.
In the initial implementation we have custom JsonView object that works as wrapper without #ResponseBody annotation.
Like this:
#RequestMapping(value = "/someUrl.controller", method = RequestMethod.GET)
public JsonView<Object> getObject(#RequestParam("id") Long id) {
Object object = provider.getObject(id);
return new JsonView(object);
}
So it was working fine.
I have found some solution at How do you override the null serializer in Jackson 2.0? but unfortunatly it works only for fields in the POJOs.
Have you any ideas how in it can be handled?
Thanks in advance!

This is not a trivial problem to solve.
Spring has a common pattern in which if a handler method returns null, it is meant to indicate that the handler has already dealt with producing and writing the appropriate response content and that no further action is necessary on the front.
Spring has applied this pattern in its RequestResponseBodyMethodProcesser (the HandlerMethodReturnValueHandler implementation for #ResponseBody). It checks if the return value is null. It sets the request as handled. If the return value is not null, it attempts to serialize it with an appropriate HttpMessageConverter.
One option is to create your own #ResponseBodyNull annotation and a corresponding HandlerMethodReturnValueHandler which does the same except also handles null. Note that you can't necessarily reuse the code from RequestResponseBodyMethodProcess because some HttpMessageConverters would fail trying to use null.
Another similar option is to override RequestResponseBodyMethodProcessor to accept null (with the limitations stated above) and register it explicitly with your RequestMappingHandlerMapping, overwriting the default HandlerMethodReturnValueHandlers. You have to do this carefully (ie. register the same ones), unless you want to lose functionality.
The better solution, IMO, would be to not deal with null in the response body. If getObject doesn't return anything, that seems like a 404 to me. Set the appropriate response code and voila!
You can always inject the HttpServletResponse into your handler method and do something like
Object object = getObject(..);
if (object == null) {
response.getWriter().print("null");
// also set the content type to application/json
}
return object;
Assuming you knew that this had to be serialized to JSON.

You can return a ResponseEntity and specify the HTTP status to an error when the object is null:
#RequestMapping(value = "/someUrl.controller", method = RequestMethod.GET)
public ResponseEntity<Object> getObject(#RequestParam("id") Long id) {
Object object = provider.getObject(id);
if (object == null ) {
return new ResponseEntity<Object> (HttpStatus.BAD_REQUEST); // Or any other error status
} else {
return new ResponseEntity<Object> (object, HttpStatus.OK);
}
}
In this way your client will be able to know when the object is null checking the response status.
If you actually need the null value returned you can configure Jackson to serialize it (code from tkuty):
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="serializationInclusion">
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
I hope this help you.

Firstly, I advise you should not write like this:
#RequestMapping(value = "/someUrl.controller", method = RequestMethod.GET)
public #ResponseBody Object getObject(#RequestParam("id") Long id) {
/*
*/
}
Make it as standard code. try this:
#RequestMapping(value = "/someUrl.controller", method = RequestMethod.GET)
#ResponseBody
public Object getObject(#RequestParam("id") Long id) {
Object object = provider.getObject(id);
return object;
}
I have experience with quality scanner, this way helps you avoid error found by scanner. About your problem, you can try Transformer or addScala() to return a POJO instead. I faced to this trouble, and made a deal! Good luck.

Related

Java/Spring > Handle Bad Request response for controller method with #RequestBody when no body is sent in request

long story short: I'm creating API that is supposed to be 100% REST.
I'm trying to overwrite default response for the following case:
I've got a method in my #RestController that has #RequestBody as an attribute
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(#RequestBody User user, HttpServletRequest request)
and the method is working just fine if I send a proper request. But there is a problem when I don't. When a request has empty body, I get a generic Tomcat error page for status 400 and I need it to send just a string or a JSON object instead.
So far I tried to add Exception Handlers in my RestControllerAdvice for all Spring exceptions from package org.springframework.web.binding, but it didn't work either.
I'm already aware that for some security-related errors one have to create handlers in configuration, but I don't know if this is the case.
Did anyone face similar issues? Is there something I'm missing?
The solution was to simply put required = false in RequestBody annotation. After that, I could easily add some logic to throw custom exception and handle it in ControllerAdvice.
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(#RequestBody(required = false) User user, HttpServletRequest request){
logger.debug("addClient() requested from {}; registration of user ({})", getClientIp(request), user);
if(user == null){
throw new BadRequestException()
.setErrorCode(ErrorCode.USER_IS_NULL.toString())
.setErrorMessage("Wrong body or no body in reqest");
} (...)
Firstly I suggest you to use BindingResult as a parameter of the POST call and check if it returns an error or not.
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public ResponseEntity<?> registerClient(#RequestBody User user, HttpServletRequest request, BindingResult brs)
if (!brs.hasErrors()) {
// add the new one
return new ResponseEntity<User>(user, HttpStatus.CREATED);
}
return new ResponseEntity<String>(brs.toString(), HttpStatus.BAD_REQUEST);
}
Secondly, the call can throw some of errors, a good practice is to carch them and return them itself or transform them to your own exception object. The advantage is it secures a call of all the update/modify methods (POST, PUT, PATCH)
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return new ResponseEntity<List<MethodArgumentNotValidException>>(e, HttpStatus.BAD_REQUEST);
}
#ExceptionHandler({HttpMessageNotReadableException.class})
#ResponseBody
public ResponseEntity<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return new ResponseEntity<List<HttpMessageNotReadableException>>(e, HttpStatus.BAD_REQUEST);
}
Your control will never reach to your request method under normal circumstances.
If you want a looking good page you can make use of web.xml and configure it to produce your answer.
<error-page>
<error-code>404</error-code>
<location>/pages/resource-not-found.html</location>
</error-page>
Generally, if you want to go past this 400 problem, you will have to add a few annotiations to your User.java to avoid any unknown fields while de-serializing.

How to map REST parameters to complex object?

I want to create a REST service with spring that takes a bunch of parameters. I'd like these parameters to be mapped automatically into a complex transfer object, like:
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public String content(#RequestParam RestDTO restDTO) {
Sysout(restDTO); //always null
}
public class RestDTO {
private boolean param;
//getter+setter
}
But: when I execute a query like localhost:8080/myapp?param=true the restDTO param remains null.
What am I missing?
Try with localhost:8080/myapp?param=true.
Probably a case where another pair of eyes sees the obvious :)
EDIT
Remove #RequestParam from method signature, works for me.
It turned out I have to omit the #RequestParam for complex objects:
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public String content(RestDTO restDTO) {
Sysout(restDTO);
}
So, I see few problems (if it's not mistyping of course):
localhost:8080/myapp&param=true "&" isn't correct, you have to use "?" to split params from URL like localhost:8080/myapp?param=true.
I don't see mapping value in #RequestMapping(method = RequestMethod.GET) (But if you caught the request you've made correct configuration).

Spring MVC: bind request attribute to controller method parameter

In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter

Spring MVC and thymeleaf ModelAttribute either null or not evaluated

I'm developing a web application with Spring MVC and Thymeleaf as my ViewResolver. I have the following controller handler method:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(#ModelAttribute("error") String error /*, other attributes */) {
// find out if there is an error
error = getErrorMessage();
return "someHTMLfile";
}
My view contains this line:
<p><span th:text="${error}">Error Message goes here</span></p>
When executed, the tag does not render to anything. This is probably due to ${error} evaluating to an empty string but I can't understand why. Doesn't Spring's #ModelAttribute annotation add the object to the model map automatically, where Thymeleaf can find it?
If I instead have:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(ModelMap map /*, other attributes */) {
// find out if there is an error
String error;
error = getErrorMessage();
map.addAttribute("error", error);
return "someHTMLfile";
}
The view is rendered perfectly fine with the error message. Does #ModelAttribute not add the object to the request model?
Edit: I've tried doing both:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(#ModelAttribute("error") String error, ModelMap map /*, other attributes */) {
// find out if there is an error
error = getErrorMessage();
map.addAttribute("error", error);
return "someHTMLfile";
}
This also doesn't work.
Actually I don't think your issue is related to Thymeleaf, just SpringMVC :-)
In your first snippet, you don't add anything to the request model but try to get an object called "error" back from the form.
In your second snippet, you do add an object to the model, that's why your view is well rendered.
Take a look at the SpringMVC doc here (16.3.3.8) to have a better understanding of the #ModelAttribute annotation on a method argument.
I feel stupid but whatever, we all make mistakes.
Spring was creating a new String instance for me and injecting it into my method and into the model under the key error. String is an immutable object, so when I do error = getErrorMessage(), I assign another instance to my error object. Now there is my error and the error String in Spring's model with a value of "". That's why Thymeleaf rendering only finds the empty string.

Spring 3.0 making JSON response using jackson message converter

i configure my messageconverter as Jackson's then
class Foo{int x; int y}
and in controller
#ResponseBody
public Foo method(){
return new Foo(3,4)
}
from that i m expecting to return a JSON string {x:'3',y:'4'} from server without any other configuration. but getting 404 error response to my ajax request
If the method is annotated with #ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters.
Am I wrong ? or should I convert my response Object to Json string myself using serializer and then returning that string as response.(I could make string responses correctly) or should I make some other configurations ? like adding annotations for class Foo
here is my conf.xml
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
</list>
</property>
You need the following:
Set annotation-driven programming model: put <mvc:annotation-driven /> in spring.xml
Place jaskson jar (Maven artifactId is org.codehaus.jackson:jackson-mapper-asl) in classpath.
Use as the following:
#RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
public #ResponseBody Foo method(#Valid Request request, BindingResult result){
return new Foo(3,4)
}
This works for me.
Please note, that
#ResponseBody is applied to return type, not to the method definition.
You need #RequestMapping annotation, so that Spring will detect it.
This worked for me:
#RequestMapping(value = "{p_LocationId}.json", method = RequestMethod.GET)
protected void getLocationAsJson(#PathVariable("p_LocationId") Integer p_LocationId,
#RequestParam("cid") Integer p_CustomerId, HttpServletResponse response) {
MappingJacksonHttpMessageConverter jsonConverter =
new MappingJacksonHttpMessageConverter();
Location requestedLocation = new Location(p_LocationId);
MediaType jsonMimeType = MediaType.APPLICATION_JSON;
if (jsonConverter.canWrite(requestedLocation.getClass(), jsonMimeType)) {
try {
jsonConverter.write(requestedLocation, jsonMimeType,
new ServletServerHttpResponse(response));
} catch (IOException m_Ioe) {
// TODO: announce this exception somehow
} catch (HttpMessageNotWritableException p_Nwe) {
// TODO: announce this exception somehow
}
}
}
Note that the method doesn't return anything: MappingJacksonHttpMessageConverter#write() does the magic.
The MessageConverter interface http://static.springsource.org/spring/docs/3.0.x/javadoc-api/ defines a getSupportedMediaTypes() method, which in case of the MappingJacksonMessageCoverter returns application/json
public MappingJacksonHttpMessageConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET));
}
I assume a Accept: application/json request header is missing.
A HTTP 404 error just means that the resource cannot be found. That can have 2 causes:
Request URL is wrong (client side error or wrong URL in given link/button).
Resource is not there where you expect it is (server side error).
To fix 1, ensure you're using or providing the correct request URL (casesensitive!). To fix 2, check the server startup logs for any startup errors and fix them accordingly.
This all goes beyond the as far posted code and information.
I found that I need jackson-core-asl.jar too, not only jackson-mapper-asl.jar
This is just a guess, but by default Jackson only auto-detects public fields (and public getters; but all setters regardless of visibility). It is possible to configure this (with version 1.5) to also auto-detect private fields if that is desired (see here for details).
I guess that 404 is not related to your HttpMessageConverter. I had same 404-issue and the reason was that I forgot that only requests matching <url-pattern> are sent to DispatcherServlet (I changed request mapping from *.do to *.json). Maybe this is your case also.
In addition to the answers here..
if you are using jquery on the client side, this worked for me:
Java:
#RequestMapping(value = "/ajax/search/sync")
public ModelAndView sync(#RequestBody Foo json) {
Jquery (you need to include Douglas Crockford's json2.js to have the JSON.stringify function):
$.ajax({
type: "post",
url: "sync", //your valid url
contentType: "application/json", //this is required for spring 3 - ajax to work (at least for me)
data: JSON.stringify(jsonobject), //json object or array of json objects
success: function(result) {
//do nothing
},
error: function(){
alert('failure');
}
});

Categories