Spring - Inconsistent context path - spring

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

Related

spring url mapping when #PathVariable is empty

For the following url mappings (all GET requests):
1. "/book/{book-id}"
2. "/book/publisher/{publisher-id}"
3. "/book/borrower/{borrower-id}"
If 2. or 3. are called with empty string e.g. /book/publisher/ or /book/user/ then the forward slash / is ignored and 1. is invoked with book-id as 'publisher' or 'borrower'. Is this a correct behaviour because a path variable is mandatory rendering 1 as the only valid url in this situation, or is there some config that controls it?
This is a simple rest controller that returns JSON (code simplified):
#RestController
public class BookController {
#Autowired
BookRepository repository; //JPA repository
#RequestMapping(value="/book/{book-id}",method= RequestMethod.GET)
public Book getBook(#PathVariable("book-id") String bookId){
return repository.findOne(bookId);
}
#RequestMapping(value="/book/publisher/{publisher-id}",method=RequestMethod.GET)
public List<Book> getBooksByPublisher(#PathVariable("publisher-id") String publisherId){
return repository.findAll(publisherId);
}
#RequestMapping(value="/book/borrower/{borrower-id}",method= RequestMethod.GET)
public List<Book> getBooksForBorrower(#PathVariable("borrower-id") String borrowerId){
return repository.findAll(borrowerId);
}
}
You can control the flow like this in your getBook() method
if(bookId.equals("publisher") || bookId.equals("borrower")){
model = new ModelAndView("index");
//or do other code to controll the flow
return model;
}

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

Nested Velocity template with Spring formView

I have a Spring app that I'd like to add a login feature to. I'd like to put the login form in the header of the site. This means that it'll be included on several pages. When defining the controller that the form submits to, what do I specify as the formView?
Is it possible to specify the login template that's included in header (that's included in each head :-)) as the formView?
Thanks for the help. If anything is unclear than I'm happy to provide more details or show code.
Nevermind. I realized that it doesn't matter whether the Velocity template is included in another file. I added this to the template:
<form method="POST">
#springBind("credentials.*")
and my controller looks like this:
#Controller
public class SplashController implements Serializable {
protected final Log logger = LogFactory.getLog(getClass());
private static final long serialVersionUID = 7526471155622776147L;
#ModelAttribute("credentials")
public LoginCredentials getFormBean() {
return new LoginCredentials();
}
#RequestMapping(method = RequestMethod.GET)
public String showForm() {
logger.info("In showForm method of SplashController");
return "splash";
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView onSubmit(LoginCredentials credentials, BindingResult bindingResult) {
logger.info("In onSubmit method of SplashController");
logger.info("Username = " + credentials.getUsername());
logger.info("Password = " + credentials.getPassword());
ModelAndView modelAndView = new ModelAndView("home");
return modelAndView;
}
}
and it works.

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

Redirect with GET needs relative path, why?

I am very new to Spring MVC and am seeing a rather trivial behavior I don't understand.
Bellow you can find snippets to my Controller (consider I have feed.jsp and feedList.jsp). What I don't understand is why I need the "../list" in one redirect, when the other works without it
#Controller
#RequestMapping("/feed/*")
public class FeedController {
#RequestMapping(value = "delete/{feedId}", method = RequestMethod.GET)
public String deleteFeed(#PathVariable("feedId") Integer feedId) {
feedService.delete(feedId);
return "redirect:../list";
}
#RequestMapping(value = "save", method = RequestMethod.POST)
public String saveFeed(#ModelAttribute("feed") Feed feed, BindingResult result) {
feedService.create(feed);
return "redirect:list";
}
}
Perhaps the UrlBasedViewResolver is handling view names as relative to the current request mapping url (citation needed).
Anyway, I always use context-relative absolute paths (starting with a slash): redirect:/list. Actually, if your jsp is called "feedList", then your should return redirect:/feedList

Resources