Negating params in a Spring controller's RequestMapping - spring

Hi,
I've got two methods in my controller and I'm trying to get one method to fire if a parameter is a certain value and the other method if the parameter is not that value. My "not" method is firing when the param equals the certain value.
According to (my understanding of) this I'm writing the expression correctly. Here are the signatures of the two methods:
#RequestMapping(value = "/doit", params= {"output=grouped", "display!=1"})
public #ResponseBody HashMap<String, Object> doSomething(
#RequestParam("text") String text,
#RequestParam("display") String display)
{
// this method runs if display=1 but why?
}
#RequestMapping(value = "/doit", params= {"output=grouped", "display=1"})
public #ResponseBody HashMap<String, Object> doSomethingElse(
#RequestParam("text") String text)
{
// this method is not being called when display=1...why not?
}
I have Spring debug logging enabled and I see where Spring converts the parameter to a RequestParam String with value '1'. In the very next line, though, it decides to map it to the wrong method.
What am I doing wrong?
Thanks!

It is not your fault: it is a Bug SPR-8059. At the moment it is fixed in Version 3.1M2 and svn Trunk rev 4408.
A workaround would be not to use != in #RequestMapping. Instead you have to do it with an if by hand:
#RequestMapping(value = "/doit", params= {"output=grouped")
public #ResponseBody HashMap<String, Object> acceptAll(
#RequestParam("text") String text,
#RequestParam("display") String display) {
if("1".equals(display) {
return doSomethingElse(text);
} else {
return doSomething(text,display);
}
}

Related

Spring - Inconsistent context path

Spring seems to resolve links in an inconsistent way.
For example, if the current URL is
http://localhost/roles
the following anchor
roleA1
goes to http://localhost/roleA1 instead of http://localhost/roles/roleA1
On the other hand, if the current URL is
http://localhost/roles/ (note the '/' at the end)
the previous link will resolve to http://localhost/roles/roleA1
Both http://localhost/roles and http://localhost/roles/ go to the same page, so Spring treats them equally. Now I would like to avoid using absolute paths but if I leave it as it is now, users navigating to http://localhost/roles will get the wrong behaviour.
Is there a way to fix it?
This is my Controller's configuration:
#RequestMapping("/roles")
public class RoleController {
#RequestMapping(method = RequestMethod.GET)
public String roles(final Map<String, Object> model) {
...
return "roles";
}
#RequestMapping(path = "/{roleId}", method = RequestMethod.GET)
public String role(#PathVariable String roleId, final Map<String, Object> model) {
...
return "role";
}

Spring REST: DTO as parameter - values are not urldecoded

I have a REST controller with a RequestMapping that looks like this:
#RequestMapping(method = RequestMethod.GET)
public List<MyDTO> search(SearchParameters searchParameters) {
// ...
}
and call it like that: /data/search?name=some%20value&....
searchParameters is populated, but the values are not being urldecoded. So instead of setting searchParameter's attribute name to "some value" it is "some%20value". How can I instruct Spring to urldecode these values?
One possible solution is to use a Map and have their names stored statically in a class, like :
#RequestMapping(method = RequestMethod.GET)
public List<MyDTO> search(#RequestParam Map<String,String> parameters) {
String name = parameters.get(SearchParameters.NAME);
// ...
}
or use the Map to build the Object SearchParameters:
#RequestMapping(method = RequestMethod.GET)
public List<MyDTO> search(#RequestParam Map<String,String> parameters) {
SeachParameters searchParameters = new SearchParameters(parameters);
// ...
}
Okay, the problem was not actually the decoding, but the encoding. It was encoded in a test case, although it wouldn't have needed to be. So the URL looking like /data/search?name=some%20value should really have been /data/search?name=some value with actual spaces.

Spring MVC parse web url Object

I have a GET request in the format below
http://www.example.com/companies?filters=%7B%22q%22%3A%22aaa%22%7D
After decode it is
filters={"q":"aaa"}
I have created an Object named Filters as below
public class Filters {
private String q;
//getter setter....
}
and in my controller
#RequestMapping(method = RequestMethod.GET)
public List<CompanyDTO> getCompanies(Filters filters) {
filters.getQ();
//do things
}
However, the filters.getQ() is null.
Am I doing something incorrect here?
You need to associate the request parameter to the method argument. Add #RequestParam to your method i.e.
#RequestMapping(method = RequestMethod.GET)
public List<CompanyDTO> getCompanies(#RequestParam(value="filters") Filters filters) {
filters.getQ();
//do things
}
Instead of #RequestParam, use #RequestBody
Instead of String filters=%7B%22q%22%3A%22aaa%22%7D, pass JSON object as parameter http://www.example.com/companies?filters={"q":"aaa"}

how to capture multiple parameters using #RequestParam using spring mvc?

Suppose a hyperlink is clicked and an url is fired with the following parameter list myparam=myValue1&myparam=myValue2&myparam=myValue3 . Now how can I capture all the parameters using #RequestParam in spring mvc?
My requirement is I have to capture all the params and put them in a map.
Please help!
#RequestMapping(value = "users/newuser", method = RequestMethod.POST)
public String saveUser(#RequestParam Map<String,String> requestParams) throws Exception{
String userName=requestParams.get("email");
String password=requestParams.get("password");
//perform DB operations
return "profile";
}
You could use RequestParam in the above mentioned manner.
It seems you can't get
Map<String,String>
because all your params have same name "myparam"
Try this instead:
public ModelAndView method(#RequestParam("myparam") List<String> params) { }
To get all parameters at once try this:
public ModelAndView postResultPage(#RequestParam MultiValueMap<String, String> params)
This feature is described in the #RequestParam java doc (3. Paragraph):
Annotation which indicates that a method parameter should be bound to a web request parameter. Supported for annotated handler methods in Servlet and Portlet environments.
If the method parameter type is Map and a request parameter name is specified, then the request parameter value is converted to a Map assuming an appropriate conversion strategy is available.
If the method parameter is Map<String, String> or MultiValueMap<String, String> and a parameter name is not specified, then the map parameter is populated with all request parameter names and values.
As of Spring 3.0, you can also use MultiValueMap to achieve this:
A rudimentary example would be:
public String someMethod(#RequestParam MultiValueMap<String,String> params) {
final Iterator<Entry<String, List<String>>> it = params.entrySet().iterator();
while(it.hasNext()) {
final String k = it.next().getKey();
final List<String> values = it.next().getValue();
}
return "dummy_response";
}
If anyone is trying to do the same in Spring Boot, use RequestBody in place of RequestParam
Spring mvc can support List<Object>, Set<Object> and Map<Object> param, but without #RequestParam.
Take List<Object> as example, if your object is User.java, and it like this:
public class User {
private String name;
private int age;
// getter and setter
}
And you want pass a param of List<User>, you can use url like this
http://127.0.0.1:8080/list?users[0].name=Alice&users[0].age=26&users[1].name=Bob&users[1].age=16
Remember to encode the url, the url after encoded is like this:
http://127.0.0.1:8080/list?users%5B0%5D.name=Alice&users%5B0%5D.age=26&users%5B1%5D.name=Bob&users%5B1%5D.age=16
Example of List<Object>, Set<Object> and Map<Object> is displayed in my github.
You can use for multiple Params as such
public String saveUser(#RequestParam("email") String userName, #RequestParam("password") String password) throws Exception{
//your code
//perform DB operations
return "profile";
}
For params with same name, you can use MultiValueMap<String ,String>. Then all the values would be present as List
You can use multiple #RequestParam annotations as shown below.
#RequestParam(value="myparam1", required = true) <Datatype> myparam1,
#RequestParam(value = "myparam2", required = false) <Datatype> myparam2,

Spring MVC: how to indicate whether a path variable is required or not?

I am doing a Spring web. For a controller method, I am able to use RequestParam to indicate whether a parameter it is required or not. For example:
#RequestMapping({"customer"})
public String surveys(HttpServletRequest request,
#RequestParam(value="id", required = false) Long id,
Map<String, Object> map)
I would like to use PathVariable such as the following:
#RequestMapping({"customer/{id}"})
public String surveys(HttpServletRequest request,
#PathVariable("id") Long id,
Map<String, Object> map)
How can I indicate whether a path variable is required or not? I need to make it optional because when creating a new object, there is no associated ID available until it is saved.
Thanks for help!
VTTom`s solution is right, just change "value" variable to array and list all url possibilities: value={"/", "/{id}"}
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
There's no way to make it optional, but you can create two methods with one having the #RequestMapping({"customer"}) annotation and the other having #RequestMapping({"customer/{id}"}) and then act accordingly in each.
I know this is an old question, but searching for "optional path variable" puts this answer high so i thought it would be worth pointing out that since Spring 4.1 using Java 1.8 this is possible using the java.util.Optional class.
an example would be (note the value must list all the potential routes that needs to match, ie. with the id path variable and without. Props to #martin-cmarko for pointing that out)
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
VTToms answer will not work as without id in path it will not be matched (i.e will not find corresponding HandlerMapping) and consequently controller will not be hit. Rather you can do -
#RequestMapping({"customer/{id}","customer"})
public String surveys(HttpServletRequest request, #PathVariable Map<String, String> pathVariablesMap, Map<String, Object> map) {
if (pathVariablesMap.containsKey("id")) {
//corresponds to path "customer/{id}"
}
else {
//corresponds to path "customer"
}
}
You can also use java.util.Optional which others have mentioned but it requires requires Spring 4.1+ and Java 1.8..
There is a problem with using 'Optional'(#PathVariable Optional id) or Map (#PathVariable Map pathVariables) in that if you then try to create a HATEOAS link by calling the controller method it will fail because Spring-hateoas seems to be pre java8 and has no support for 'Optional'. It also fails to call any method with #PathVariable Map annotation.
Here is an example that demonstrates the failure of Map
#RequestMapping(value={"/subs","/masterclient/{masterclient}/subs"}, method = RequestMethod.GET)
public List<Jobs> getJobListTest(
#PathVariable Map<String, String> pathVariables,
#RequestParam(value="count", required = false, defaultValue = defaultCount) int count)
{
if (pathVariables.containsKey("masterclient"))
{
System.out.println("Master Client = " + pathVariables.get("masterclient"));
}
else
{
System.out.println("No Master Client");
}
//Add a Link to the self here.
List list = new ArrayList<Jobs>();
list.add(linkTo(methodOn(ControllerJobs.class).getJobListTest(pathVariables, count)).withSelfRel());
return list;
}
I know this is an old question, but as none of the answers provide some updated information and as I was passing by this, I would like to add my contribution:
Since Spring MVC 4.3.3 introduced Web Improvements,
#PathVariable(required = false) //true is default value
is legal and possible.
#RequestMapping(path = {"/customer", "/customer/{id}"})
public String getCustomerById(#PathVariable("id") Optional<Long> id)
throws RecordNotFoundException
{
if(id.isPresent()) {
//get specific customer
} else {
//get all customer or any thing you want
}
}
Now all URLs are mapped and will work.
/customer/123
/customer/1000
/customer - WORKS NOW !!

Resources