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

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 !!

Related

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

Map #CookieValue, #RequestHeader etc. to POJO in Spring Controller

I have a bunch of params in my controller and want to map all of them to a separate POJO to keep readability. There is also a #CookieValue, #RequestHeader I need to evaluate and aim for a solution to also map them to that POJO. But how?
I saw a possible solution on a blog but it doesn't work, the variable stays null.
Controller:
#RequestMapping(path = MAPPING_LANGUAGE + "/category", produces = MediaType.TEXT_HTML_VALUE)
#ResponseBody
public String category(CategoryViewResolveModel model) {
doSomething();
}
And my POJO is this:
public class CategoryViewResolveModel {
private String pageLayoutCookieValue;
public CategoryViewResolveModel() {
}
public CategoryViewResolveModel(
#CookieValue(value = "SOME_COOKIE", required = false) String pageLayoutCookieValue) {
this.pageLayoutCookieValue = pageLayoutCookieValue;
}
... some other RequestParams, PathVariables etc.
}
According to the documentation it's not possible for #CookieValue and #RequestHeader.
This annotation is supported for annotated handler methods in Servlet
and Portlet environments.
Take a look at:
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-a-custom-handlermethodargumentresolver/
instead of using getParameter to access request parameters you can use getHeader to retrieve the header value and so define your CategoryViewResolveModel just as you were requesting

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,

RequestParam value in spring MVC to be case insensitive

Am sending data from JSP to controller using query string.
My controller is annotation driven.
The value of the the request parameter should be case-insensitive.
The method which i use for welcome page is
public String welcome(#RequestParam("orgID") String orgID, ModelMap model)
The request parameter "orgID" should be case insensitive. How to do this ?.
I should be able to give the query-string as "orgid" or "orgId". The parameter should be completely case-insensitive. Looking for your help friends.
Thanks in Advance :-)
Another approach would be to have two parameters "orgId" and "orgid" and have the optional.
public String welcome(#RequestParam(value="orgID", required = false) String org_ID, #RequestParam(value="orgid", required=false, String orgid, ModelMap model) {
final String orgId = orgid == null ? org_ID : orgid;
...
}
But if you have control over the parameters I would strongly prefer to just have one consistent way, say org-id and follow it both in the client and the server side.
It might be nice for Spring to support this, but I can also understand why they wouldn't since it could result in a weakening of an interface contract between a supplier and consumer. In the meantime, here's a fairly straightforward way to take the first value using the parameter name regardless of its case.
String myVal = null;
for (String pname : request.getParameterMap().keySet() ) {
if ( pname != null && pname.toLowerCase().equals("your_lc_pname") ) {
for (String v : request.getParameterValues(pname) ) {
// do something with your value(s)
}
}
}
in Spring 4.2+
#Configuration
#EnableWebMvc
#ComponentScan(basePackages="org.saat")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
AntPathMatcher matcher = new AntPathMatcher();
matcher.setCaseSensitive(false);
configurer.setPathMatcher(matcher);
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new
InternalResourceViewResolver();
viewResolver.setPrefix("/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
You'll have to try changing the way Spring matches your urls . You could for one, create a filter (probably a DelegatingFilterProxyBean) to lower case your parameter before you pass it on to Spring or try to change the way the paths are matched .
An explanation to the second options is given at How can I have case insensitive URLS in Spring MVC with annotated mappings .
The simplest way I found was to change the controller method to take a Map with a single key value pair as a parameter and then convert the key to lowercase.
public String welcome(#RequestParam(required = true) Map<String, String> params, ModelMap model) {
String caseInsensitiveOrgId = params.keySet().toArray()[0].toString().toLowerCase();
String OrgValue = params.values().toArray()[0];
// Do whatever you want here
}
There is a simple workaround.You can operate directly on the HttpServletRequest and use method getParameter() and check all versions of parameter.
public String welcome(HttpServletRequest request, ModelMap model){
String orgID = extractOrgId(request);
//rest of your code
}
private String extractOrgId(HttpServletRequest request){
if(request.getParameter("orgId") != null){
return request.getParameter("orgId");
}
// and so on
}

Negating params in a Spring controller's RequestMapping

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);
}
}

Resources