I am new to Spring MVC, I have a controller displaying a single user detail or
all the users as a list
// without passing any parameter the app should display all users
#RequestMapping("/user")
public String listUsers(Model m) {
List<User> users = userService.getUsers();
m.addAttribute(users);
return "userlist";
}
//same url but with userId parameter, the app displays a single user detail
#RequestMapping("/user")
public String userDetail(#RequestParam("userId") String userId, Model m)
throws IOException {
User user = userService.getUserById(userId);
m.addAttribute(user);
return "user_detail";
}
in fact I got a error "spring ambiguous mapping", my mapping syntax is definitely wrong, my question is my desired functionality can be achieved
in Spring or not.
I would say it would be correct to specify list request as
#RequestMapping("/user")
and specific user request as
#RequestMapping("/user/{userId}")
by passing userId as path parameter.
You can correct second method like
#RequestMapping(value={"/user/{userId}"})
public String userDetail(#PathVariable String userId, Model m)
throws IOException {
User user = userService.getUserById(userId);
m.addAttribute(user);
return "user_detail";
}
Related
I am using Spring MVC and Hibernate in my project. Also I have 2 controllers UserController and BookController where BookController redirects to the users page and I am passing a Book object in addition.
I've found I can do this with RedirectAttributes but the problem is that the id of the passed Book object is changed during this transition to user.id.
BookController.java
public class BookController {
#RequestMapping("/users/{user_id}/books/edit/{book_id}")
public String editBook(#PathVariable("user_id") int user_id, #PathVariable("book_id") int book_id, final RedirectAttributes redirectAttrs){
bookDetail = this.bookService.getBookById(book_id)
redirectAttrs.addFlashAttribute("bookDetail", bookDetail);
System.out.println(bookDetail);
return "redirect:/users/"+user_id;
}
}
Prints: id=8, title=Motylek, description=Some description, user_id=2.
UserController.java
public class UserController {
#RequestMapping("/users/{id}")
public String detailUser(#ModelAttribute("bookDetail") Book bookDetail, #PathVariable("id") int id, Model model){
User u = this.userService.getUserById(id);
model.addAttribute("user", u);
model.addAttribute("bookDetail", bookDetail);
System.out.println(bookDetail);
return "user";
}
}
Prints: id=2, title=Motylek, description=Some description, user_id=2.
Do you have and idea why this happens or is it a bug? Thanks.
I'm going to assume that your Book class has a property called id, ie. a getter or setter called getId() and setId(..).
When Spring parses the request URL, it stores path segments as declared in the corresponding #RequestMapping. So for
/your-app/users/2
and
#RequestMapping("/users/{id}")
It will store
id=2
as a request parameter.
Spring will then proceed to generate an argument for
#ModelAttribute("bookDetail") Book bookDetail
It will check the various request, session, servlet attributes for an entry with the name bookDetail. (If it doesn't find one, it will create one and add it to the request attributes.) In your case, it will have found the object in the HttpSession. It will then bind any request parameters to matching object properties. Since the parameter above is called id, it will be bound to the Book property id.
You should be good by changing
#RequestMapping("/users/{id}")
to
#RequestMapping("/users/{user_id}")
along with the corresponding #PathVariable.
I have this Object
public class Deportista implements Serializable {
private static final long serialVersionUID = 6229604242306465153L;
private String id;
...
#NotNull(message="{field.null}")
public String getId() {
return id;
}
...
}
I have the following Controller's methods
#InitBinder(value="deportistaRegistrar")
public void registrarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> registrarInitBinder >>>>>>>>>>>>>");
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.GET)
public String crearRegistrarFormulario(Model model){
logger.info("crearRegistrarFormulario GET");
Deportista deportista = new Deportista();
model.addAttribute("deportistaRegistrar", deportista);
return "deportista.formulario.registro";
}
#RequestMapping(value="/registrar.htm", method=RequestMethod.POST)
public String registrarPerson(#Validated #ModelAttribute("deportistaRegistrar") Deportista deportista,
BindingResult result){
logger.info("registrarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.registro";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.insertDeportista(deportista);
return "redirect:/manolo.htm";
}
Until here the Controller is able to create a form (GET) and submit (POST) a new command object, Validation code works well.
The problem is with the update.
I have the following:
#InitBinder(value="deportistaActualizar")
public void actualizarInitBinder(WebDataBinder binder) {
logger.info(">>>>>>>> actualizarInitBinder >>>>>>>>>>>>>");
binder.setDisallowedFields("id");
}
Observe I have binder.setDisallowedFields("id")
public String crearActualizarFormulario(#PathVariable("id") String id, Model model){
logger.info("crearActualizarFormulario GET");
Deportista deportista = this.fakeMultipleRepository.findDeportista(id);
model.addAttribute("deportistaActualizar", deportista);
return "deportista.formulario.actualizacion";
}
#RequestMapping(value="/{id}/actualizar.htm", method=RequestMethod.POST)
public String actualizarPerson(#Validated #ModelAttribute("deportistaActualizar") Deportista deportista,
BindingResult result){
logger.info("actualizarPerson POST");
logger.info("{}", deportista.toString());
if(result.hasErrors()){
logger.error("There are errors!!!!");
for(ObjectError objectError : result.getAllErrors()){
logger.error("Error {}", objectError);
}
return "deportista.formulario.actualizacion";
}
logger.info("All fine!!!!");
this.fakeMultipleRepository.updateDeportista(deportista);
return "redirect:/manolo.htm";
}
The problem is:
when the form or command has any error, the controller re-render the view and the form appear showing the error messages how is expected, but without the ID value
or
if I try to update the object, of course keeping the id value, and without any error to simply proceed to update, it fails
The following appears in the Console:
- -------- createCollections ---------------
- >>>>>>>> actualizarInitBinder >>>>>>>>>>>>>
- Skipping URI variable 'id' since the request contains a bind value with the same name.
- actualizarPerson POST
- Deportista [id=null, nombre=Manuel, ...]
- There are errors!!!!
- Error Field error in object 'deportistaActualizar' on field 'id': rejected value [null]; codes [NotNull.deportistaActualizar.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [deportistaActualizar.id,id]; arguments []; default message [id]]; default message [The field must be not empty]
The id is null. How I can around this problem keeping the Request Scope?
I have an alternate controller which is working with #SessionAttributes and all works perfect. But since is a huge risk if the user has many tabs open in the same web browser, one for create and other for updating, all is going to be very wrong. According with Spring MVC + Session attributes and multiple tabs, request scope should be used instead of session scope. It has sense.
Sadly seems Spring is not going to fix this:
#SessionAttributes doesn't work with tabbed browsing
Addition
According with your suggestion, I have the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#RequestParam(defaultValue="") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Observe the method uses #RequestParam, my problem is how update that method to work when the URL to update has the following style
http://localhost:8080/spring-utility/deportista/1/actualizar.htm. There is no param in the URL, therefore #RequestParam is useless now.
I already have read the Spring Reference documentation:
Using #ModelAttribute on a method
Second Addition
Yes, you was right, and I did that yesterday, but I forget to share the following:
#ModelAttribute("deportistaActualizar")
public Deportista populateActualizarFormulario(#PathVariable(value="id") String id){
logger.info("populateActualizarFormulario - id: {}", id);
if(id.equals(""))
return null;
else
return this.fakeMultipleRepository.findDeportista(id);
}
Since a #ModelAttribute is called always before by any handler method, the following URL fails http://localhost:8080/spring-utility/deportista/registrar.htm, the following appears on the page
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Of course because the URL does not contains the expected id. Therefore I can't create new records to later edit/see.
I can confirm, that for the following work:
http://localhost:8080/spring-utility/deportista/1/detalle.htm
http://localhost:8080/spring-utility/deportista/1/actualizar.htm
the id (1) is retrieved.
How I could resolve this?
Thank You
Can I add mandatory parameter to all controller?
I develop RESTful api, so I want to require special "apikey" parameter for every route.
[HttpPut]
[PUT("create")]
public PostDto Create(string title, string description, string tag, long photo, float lat, float lon, int error)
{
if (description.Length > DescriptionMaxLength)
throw new ApiException(ErrorList.TooLongDescription, string.Format("Description is more than {0}", DescriptionMaxLength));
throw new NotImplementedException();
}
[HttpPost]
[POST("edit/{id:int}")]
public bool Edit(int id, string title, string description, int? photo)
{
if (description.Length > DescriptionMaxLength)
throw new ApiException(ErrorList.TooLongDescription, string.Format("Description is more than {0}", DescriptionMaxLength));
throw new NotImplementedException();
}
[HttpDelete]
[DELETE("delete/{id:int}")]
public bool Delete(int id)
{
if (id < 0)
throw new ApiException(ErrorList.InvalidValue, "id is smaller than 0");
throw new NotImplementedException();
}
But I don't want to do it manually for every method.
First of all, you need to determine the exact way you are going to retrieve the API key inside the action's body.
Since you don't want to pass it as the method's argument, it can be a property of the controller (not the best way to do this, you have to create a custom base controller class, but it may work for the simple scenarios) or another temporary per-request storage.
Then you need to create a Web API action filter. It's similar to the regular ASP.NET MVC action filters, there are plenty of tutorials over the web, most of them are about the authorization though.
This filter will try to inject the API key from the request into the controller or the temporary storage of your choice - inside the filter's OnActionExecuting method you have an access to both the request info and the controller context.
When it's all done, just register your filter in the Web API config, here is an example on how to do this.
I have a spring MVC based web application. Currently in my web page i am showing the user first name and last name after user logs in. The way i am doing this is, for every HttpServletRequest that comes into #Controller#RequestMapping, i get the Principal object and get the user details from it, then populate the ModelMap with firstname and lastname attribute. For example here is the sample code
#Autowired
private SecurityDetails securityDetails;
#RequestMapping(method = RequestMethod.GET)
public String showWelcomePage(HttpServletRequest request,
HttpServletResponse response, ModelMap model, Principal principal)
{
securityDetails.populateUserName(model, principal);
... lot of code here;
return "home";
}
public boolean populateUserName(ModelMap model, Principal principal) {
if (principal != null) {
Object ob = ((Authentication)principal).getPrincipal();
if(ob instanceof MyUserDetails)
{
MyUserDetails ud = (MyUserDetails)ob;
model.addAttribute("username", ud.getFirstName() + " " + ud.getLastName());
}
return true;
}
else
{
logger.debug("principal is null");
return false;
}
}
My problem is i am having to call the populateUserName method for every RequestMapping. Is there a elegant way, like populating this in Interceptor method, which will result in this method being called just in one place for entire application?
Its good that you want to prevent duplication of code. Here is how you can do it.
Create a custom HandlerInterceptor http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/web/servlet/HandlerInterceptor.html
Post handle is the only method of interest for us, for the others return defaults.
In the post handle method, you have access to the model and view returned from your controller, go ahead and add whatever you want.
The Principal will not be available directly here, you will have to look it up using some code like SecurityContextHolder.getContext().getAuthentication().getPrincipal()
Wire the handler interceptor to intercept all or some of your controllers.
Hope this helps.
You can use either Servlet Filters or Spring Interceptors.
BTW, where do you populate the Principal from?
In any case, thats where you should do this populating stuff.
I have the following method skeleton in a Spring MVC application:
#RequestMapping(value = "/activateMember/{token}", method = RequestMethod.GET, produces = "text/html")
public String activateMember(#PathVariable("token") String token) {
...
}
I am trying to display an error message if the token is invalid for some reason. However I have no ModelAttribute in the method arguments and I don't really want one. But of course I can't use an Errors or BindingResults argument because of the absence of a ModelAttribute and its corresponding form.
So my question is:
what is the recommended way to display an error message given the above method signature and without introducing a ModelAttribute?
If the String you've returned from the method is a viewname (Spring default) then simply create a view for this case and do like:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
if(checkToken(token)){
doProcess();
return "userprofile";
} else {
return "badtoken"
}
}
In more complicated case you may have a hierarchy of exceptions, related to bad tokens. (Token is expired, token is just incorrect and so on). You can register an #ExceptionHandler in the same controller:
#RequestMapping()
public String activateMember(#PathVariable("token") String token) {
return activate(token); // This method may throw TokenException and subclasses.
}
#ExceptionHandler(TokenException.class)
public ModelAndView tokenException(TokenException e){
// some code
return new ModelAndView("badtoken", "exception", e);
}