Spring 3.2 Validate Request Param, ControllerAdvice not formatting response - java

I'm using JSR 303 #Valid to validate multiple request params in a controller, the params posted along with a MultipartFile.
The validation part of this seems to be working,
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody
Response upload(#RequestParam(value = "file", required = true) MultipartFile file,
#Valid ValidBean bean) {
//method
}
ValidBean is a collection of Strings, Longs and a List<String>.
public class ValidBean{
#NotNull
String someString;
#Size(min = 1, max=10)
String anotherString;
//getters, setters, random been goodness
}
It seems that the client is getting rejected if the posted params do not match what is defined in ValidBean.
Where I am having an issue is with my global #ControllerAdvice ValidationHandler.
#ControllerAdvice
public class ValidationHandler {
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public #ResponseBody
StatusContext handleArgumentNotValid( MethodArgumentNotValidException error ){
//format and return response
}
My #ExceptionHandler only seems to be used in methods that use #RequestBody #Valid
So this method returns a formatted response,
#RequestMapping(value = "/works", method = RequestMethod.POST)
public #ResponseBody
Formatted addUser(#RequestBody #Valid ValidBean user)
And this one does not,
#RequestMapping(value = "/noGood", method = RequestMethod.POST)
public #ResponseBody
NotFormatted addUser(#Valid ValidBean user)
Though both do seem to actually perform validation.

The documentation for #Valid #RequestBody has this to say:
Just like with #ModelAttribute parameters, an Errors argument can be
used to examine the errors. If such an argument is not declared, a
MethodArgumentNotValidException will be raised
that is the reason why your #ExceptionHandler with a method signature of MethodArgumentNotValidException gets called for #Valid #RequestBody.
On the other hand without #RequestBody but with #Valid, without an additional BindingResult parameter, a BindException gets generated, this will not be handled by the current specific signature of your #ExceptionHandler. The fix may be to make your #ExceptionHandler a little broader or add another #ExceptionHandler for BindException. Even better may be to just add BindingResult as an additional parameter.

Related

GET & POST in RequestMapping [duplicate]

I have a resource that supports both GET and POST requests. Here a sample code for a sample resource:
#RequestMapping(value = "/books", method = RequestMethod.GET)
public ModelAndView listBooks(#ModelAttribute("booksFilter") BooksFilter filter, two #RequestParam parameters, HttpServletRequest request)
throws ParseException {
LONG CODE
}
#RequestMapping(value = "/books", method = RequestMethod.POST)
public ModelAndView listBooksPOST(#ModelAttribute("booksFilter") BooksFilter filter, BindingResult result)
throws ParseException {
SAME LONG CODE with a minor difference
}
The code in the two methods is practically the same, except for lets say a variable definition. The two methods can be easily combined using method = {RequestMethod.POST, RequestMethod.GET}, and a simple if inside. I tried, but it doesn't work, because the two methods have a different parameter at the end, i.e. HttpServletRequest and BindingResult (the #RequestParam's are not required and therefore not needed in the POST request). Any ideas how to combine the two methods?
#RequestMapping(value = "/testonly", method = { RequestMethod.GET, RequestMethod.POST })
public ModelAndView listBooksPOST(#ModelAttribute("booksFilter") BooksFilter filter,
#RequestParam(required = false) String parameter1,
#RequestParam(required = false) String parameter2,
BindingResult result, HttpServletRequest request)
throws ParseException {
LONG CODE and SAME LONG CODE with a minor difference
}
if #RequestParam(required = true) then you must pass parameter1,parameter2
Use BindingResult and request them based on your conditions.
The Other way
#RequestMapping(value = "/books", method = RequestMethod.GET)
public ModelAndView listBooks(#ModelAttribute("booksFilter") BooksFilter filter,
two #RequestParam parameters, HttpServletRequest request) throws ParseException {
myMethod();
}
#RequestMapping(value = "/books", method = RequestMethod.POST)
public ModelAndView listBooksPOST(#ModelAttribute("booksFilter") BooksFilter filter,
BindingResult result) throws ParseException {
myMethod();
do here your minor difference
}
private returntype myMethod(){
LONG CODE
}
Below is one of the way by which you can achieve that, may not be an ideal way to do.
Have one method accepting both types of request, then check what type of request you received, is it of type "GET" or "POST", once you come to know that, do respective actions and the call one method which does common task for both request Methods ie GET and POST.
#RequestMapping(value = "/books")
public ModelAndView listBooks(HttpServletRequest request){
//handle both get and post request here
// first check request type and do respective actions needed for get and post.
if(GET REQUEST){
//WORK RELATED TO GET
}else if(POST REQUEST){
//WORK RELATED TO POST
}
commonMethod(param1, param2....);
}
#RequestMapping(value = "/books", method = { RequestMethod.GET,
RequestMethod.POST })
public ModelAndView listBooks(#ModelAttribute("booksFilter") BooksFilter filter,
HttpServletRequest request)
throws ParseException {
//your code
}
This will works for both GET and POST.
For GET if your pojo(BooksFilter) have to contain the attribute which you're using in request parameter
like below
public class BooksFilter{
private String parameter1;
private String parameter2;
//getters and setters
URl should be like below
/books?parameter1=blah
Like this way u can use it for both GET and POST

Multiple Spring MVC validator for the same Controller

I am having a Spring controller with a Validator defined as:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new MyValidator(myService));
}
And calling it:
public ResponseEntity<?> executeSomething(
#ApiParam(name = "monitorRequest", required = true, value = "") #Valid #RequestBody MonitorRequest monitorRequest,
HttpServletRequest request, HttpServletResponse response) throws RESTException
I need to add one more Validator for this controller that could be called from some specific methods of this controller. Is there any way to achieve this?
EDIT: I am handling the Error by:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
ValidationErrorObj obj = processFieldErrors(fieldErrors);
ResponseEntity r = new ResponseEntity(obj, HttpStatus.BAD_REQUEST);
return r;
}
You can have more than one InitBinder method in a controller. It is controlled by the optional value parameter . For the javadoc of InitBinder : String[] value : The names of command/form attributes and/or request parameters that this init-binder method is supposed to apply to ... Specifying model attribute names or request parameter names here restricts the init-binder method to those specific attributes/parameters, with different init-binder methods typically applying to different groups of attributes or parameters.
Another way would be to explicely call a complementary Validator in specific methods.
BTW : I can't see any Errors or BindingResult in your controller method signature : where do you find whether errors occured ?
For those who are still trying to figure out how to solve this in 2017. I was facing similar issues while trying to implement 2 validators in my RestController. I followed the approach mentioned above by #Serge Ballasta.
I ended up making 2 Model each of linked to their specific Validators. The Controller methods look something like
#RequestMapping(value = "register", method = RequestMethod.POST)
public ResponseEntity<User> register(#Valid #RequestBody UserRegisterRequest userRegisterRequest) {
return null;
}
#RequestMapping(value = "test", method = RequestMethod.POST)
public ResponseEntity<?> test(#Valid #RequestBody TestRequest testRequest) {
return null;
}
and I created 2 initBinders to wire these validators in the controller like
#InitBinder("testRequest")
public void setupBinder(WebDataBinder binder) {
binder.addValidators(testValidator);
}
#InitBinder("userRegisterRequest")
public void setupBinder1(WebDataBinder binder) {
binder.addValidators(userRegistrationRequestValidator);
}
Please note that the #RequestBody attributes (userRegisterRequest , testRequest) had to be provided as values in the #InitBinder() annotations.
By the way the in my code I handle the bindingResult in a custom ExceptionHandler class which extends ResponseEntityExceptionHandler which gives me freedom to do custom handling of the response.

#ResponseBody annotated method do not return Model as JSON Spring 3.2.10

My method is annotated as
#RequestMapping(value = "/keepAlive", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ModelMap test(HttpServletRequest req, final ModelMap model) {
model.addAttribute("keepAlive", true);
return model;
}
when I call is using ajax using JQuery
it returns 500 server error and at server log I can see that it is looking for KeepAlive.jsp, I am using spring 3.2.10 and have jackson 2 at class path. When I debugged source code request is passed to ModelAndViewMethodReturnValueHandler rather than RequestResponseBodyMethodProcessor , It seems Model and view handler is registered before req res handler. How to solve this. Same code worked for spring 3.1.2.
Thanks

Can #PathVariable return null if it's not found?

Is it possible to make the #PathVariable to return null if the path variable is not in the url? Otherwise I need to make two handlers. One for /simple and another for /simple/{game}, but both do the same just if there is no game defined i pick first one from a list however if there is a game param defined then i use it.
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable("example") String example,
HttpServletRequest request) {
And this is what I get when trying to open page /simple:
Caused by: java.lang.IllegalStateException: Could not find #PathVariable [example] in #RequestMapping
They cannot be optional, no. If you need that, you need two methods to handle them.
This reflects the nature of path variables - it doesn't really make sense for them to be null. REST-style URLs always need the full URL path. If you have an optional component, consider making it a request parameter instead (i.e. using #RequestParam). This is much better suited to optional arguments.
As others have already mentioned No you cannot expect them to be null when you have explicitly mentioned the path parameters. However you can do something like below as a workaround -
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable Map<String, String> pathVariablesMap,
HttpServletRequest request) {
if (pathVariablesMap.containsKey("game")) {
//corresponds to path "/simple/{game}"
} else {
//corresponds to path "/simple"
}
}
If you are using Spring 4.1 and Java 8 you can use java.util.Optional which is supported in #RequestParam, #PathVariable, #RequestHeader and #MatrixVariable in Spring MVC
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable Optional<String> game,
HttpServletRequest request) {
if (game.isPresent()) {
//game.get()
//corresponds to path "/simple/{game}"
} else {
//corresponds to path "/simple"
}
}
You could always just do this:
#RequestMapping(value = "/simple", method = RequestMethod.GET)
public ModelAndView gameHandler(HttpServletRequest request) {
gameHandler2(null, request)
}
#RequestMapping(value = "/simple/{game}", method = RequestMethod.GET)
public ModelAndView gameHandler2(#PathVariable("game") String game,
HttpServletRequest request) {
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable(value="example",required = false) final String example)
Try this approach, it worked for me.
I just tested this just now, but by combining the above solution i got this:
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable(value = "game", required = false) String example,
HttpServletRequest request) {
if (example != null) {
//...
} else {
//pick first, ...
}
}
Now when you use "/simple", String example will be null instead of throwing Exception.
Short solution, no fancy Optional<> or Map<>
We can write multiple methods in controllers with explicit mapping with the path variable combination to exclude the optional variables (if using old version of Spring)
In my scenario wanted to develop an API to get recycle value for old device where parameters could be brand, model and network however network is an option one.
One option to handle this was use network as a request parameter instead of pathVariable.
for e.g. /value/LG/g3?network=vodafone however I didn't like this approach.
for me the more cleaner one was to use below
/refurbValue/LG/g3
/refurbValue/LG/g3/vodafone
#RequestMapping(value = "/refurbValue/{make}/{model}/{network}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
def getRefurbValueByMakeAndModelAndNetwork(#PathVariable String make, #PathVariable String model, #PathVariable String network ) throws Exception {
//logic here
}
#RequestMapping(value = "/refurbValue/{make}/{model}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
def getRefurbValueByMakeAndModel(#PathVariable String make, #PathVariable String model) throws Exception {
//logic here
}
In the above example, both controller can use the same service method and handling of the parameter can be done. In my case I was using Groovy so it was easy to use with optional parameter like
Map getRefurbValue(String brand, String model, String network="")

learning Spring's #RequestBody and #RequestParam

I'm editing a web project that uses Spring and I need to adding some of Spring's annotations. Two of the ones I'm adding are #RequestBody and #RequestParam. I've been poking around a little and found this, but I still don't completely understand how to use these annotations. Could anyone provide an example?
Controller example:
#Controller
class FooController {
#RequestMapping("...")
void bar(#RequestBody String body, #RequestParam("baz") baz) {
//method body
}
}
#RequestBody: variable body will contain the body of the HTTP request
#RequestParam: variable baz will hold the value of request parameter baz
#RequestParam annotated parameters get linked to specific Servlet request parameters. Parameter values are converted to the declared method argument type.
This annotation indicates that a method parameter should be bound to a web request parameter.
For example Angular request for Spring RequestParam(s) would look like that:
$http.post('http://localhost:7777/scan/l/register', {params: {"username": $scope.username, "password": $scope.password, "auth": true}}).
success(function (data, status, headers, config) {
...
})
#RequestMapping(method = RequestMethod.POST, produces = "application/json", value = "/register")
public Map<String, String> register(Model uiModel,
#RequestParam String username, #RequestParam String password, boolean auth,
HttpServletRequest httpServletRequest) {...
#RequestBody annotated parameters get linked to the HTTP request body. Parameter values are converted to the declared method argument type using HttpMessageConverters.
This annotation indicates a method parameter should be bound to the body of the web request.
For example Angular request for Spring RequestBody would look like that:
$scope.user = {
username: "foo",
auth: true,
password: "bar"
};
$http.post('http://localhost:7777/scan/l/register', $scope.user).
success(function (data, status, headers, config) {
...
})
#RequestMapping(method = RequestMethod.POST, produces = "application/json", value = "/register")
public Map<String, String> register(Model uiModel,
#RequestBody User user,
HttpServletRequest httpServletRequest) {...
Hope this helps.

Categories