How to create a #RequestMapping that accepts no request parameters Spring - java

#RequestMapping(method = RequestMethod.GET)
public HttpEntity<Object> list(WebRequest req) {
I have this code in my application, I want to subclass the class that has this method and create a new method like this:
#RequestMapping(method = RequestMethod.GET)
public HttpEntity<Object> list(WebRequest req, #RequestParam(defaultValue = "false", required= true) String includehardcoded) {
Now I am getting: java.lang.IllegalStateException: Ambiguous mapping found when my beans are created. I get that the declaration is ambigous but is there any way to make the method in the parent class accept no request parameters at all?
This works:
#RequestMapping(method = RequestMethod.GET, params="!includehardcoded")
public HttpEntity<Object> list(WebRequest req) {
but then it is an ugly hack that uses information from the subclass in the parent class (and I inherit this parent class multiple times).

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

Spring REST ambigious method

I need a spring rest controller for inserting data. This is what i already got:
#RestController
#Transactional
public abstract class AbstractRESTController<E extends Identifiable<P>, P extends Serializable> {
#RequestMapping(method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody final E entity) {
service.create(entity);
}
}
So i am able to insert a entity at http://mycontext/
What i need now is a method which accepts a list of entitys at the same path. Basiclly this:
#RequestMapping(method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#RequestBody final List<E> entities) {
for (E entity : entities) {
service.create(entity);
}
}
So how can i make spring aware of that im sending a array and not a single entity and then use the other function?
My error:
Caused by: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'sfusersRESTController' bean method
public void AbstractRESTController.createAll(java.util.List<E>)
to {[/sfusers],methods=[POST],params=[],headers=[],consumes=[application/json],produces=[],custom=[]}: There is already 'sfusersRESTController' bean method
public void AbstractRESTController.create(E) mapped.
If you want to map more than one request to a given path, you will have to use different HTTP methods; eg. POST, PUT.
In your situation, I would make the URLs different; i.e. one /mycontext/as-entity and /mycontext/as-list.
Or you MUST have the same URL - it must be able to handle all kinds of request bodies. So you could have one RequestMapping() which expects an Object - and then handle that Object - either as an Entity or a List.
Personally, I would still prefer different RequestMapping paths.
define different request mapping paths so that it can make corresponding callback using
#RequestMapping(value = "")
Now your rest controller will look like this:
#RestController
#Transactional
public abstract class AbstractRESTController<E extends Identifiable<P>, P extends Serializable> {
#RequestMapping(value = "create", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody final E entity) {
service.create(entity);
}
#RequestMapping(value = "createAll", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#RequestBody final List<E> entities) {
for (E entity : entities) {
service.create(entity);
}
}
}

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.

Use of getFlashAttributes() in Spring's RedirectAttributes

In order to access the redirect attributes in the redirected method, we utilize the model's map, like this :
#Controller
#RequestMapping("/foo")
public class FooController {
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(Model map) {
String some = (String) map.asMap().get("some");
}
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public ModelAndView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new ModelAndView().setViewName("redirect:/foo/bar");
}
}
But, why can't we access them in this way :
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(RedirectAttributes redAttr) {
String some = redAttr.getFlashAttributes().get("some");
}
If the only purpose of adding flashAttributes is that they become available to the model in the redirected method, what's the purpose of getFlashAttributes() ?
RedirectAttributes are for setting flash attributes before redirection. They are merged into model after the redirection so there is no reason to access them again via RedirectAttributes again as you have suggested.
Being able to work with the attributes just like with a map might be useful. You can check what have you set (containsKey, isEmpty, ...). However the use of the wildcard generic parameter Map<String, ?> getFlashAttributes() prevents writing into map and it is strange why they have used it instead of a plain Object parameter.

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="")

Categories