RedirectAttributes changes object id between controllers - spring-mvc

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.

Related

How to accept GET argument in Spring boot Controller and return appropriate object

I am very new to spring boot and I can't figure out the #Controller class. What should I pass if I can't find a particular object in my DB in spring boot? Is it better if I declare my return type as Response Entity and send a null User object?
//Get single user
#GetMapping("/users/{id}")
public User getUser(#PathVariable String id){
try {
Long i = Long.parseLong(id);
} catch (NumberFormatException ex) {
return ???? //Can't figure out what to return here.
}
return userService.getUser(id);
}
I want the consumer to know that they've sent an invalid string.
2) Also, User's variable id is of Long type. So should I take the argument as Long in the getUser function or take a String and parse it? Taking a Long would crash my server if a string was sent in the link.
This is my typical code of a REST controller for 'get user by id' request:
#RestController
#RequestMapping("/users") // 1
public class UserController {
private final UserRepo userRepo;
public UserController(UserRepo userRepo) {
this.userRepo = userRepo;
}
#GetMapping("/{id}") // 2
public ResponseEntity getById(#PathVariable("id") Long id) { // 3
return userRepo.findById(id) // 4
.map(UserResource::new) // 5
.map(ResponseEntity::ok) // 6
.orElse(ResponseEntity.notFound().build()); // 7
}
}
Where:
1 - is a common starting path for all requests handled by this controller
2 - a path variable pattern of GET request (/users/{id}).
3 - provide the name of path variable which name is correspond to the parameter in GetMapping. The type of the parameter in getById method is corresponds to the type of User ID.
4 - I use findById method of my UserRepo that return Optional
5 - Here I transform User to some type of DTO - UserResource (it's optional step)
6 - return OK response if User was found
7 - or return Not Found response otherwise.
I'm also using the controller-service-repository pattern in a couple of projects, and this is how I lay it out:
Controller.java
#RestController // 1
#RequestMapping(value = "/users") // 2
public class UserController {
private final UserService userService;
#Autowired // 3
public UserController(UserService userService) {
this.userService = userService;
}
#RequestMapping(value = "/{user_id}", method = RequestMethod.GET) //4
#ResponseStatus(HttpStatus.OK) //5
public UserModel getUser(#PathVariable(value="user_id") long user_id) { //6
return userService.getUserById(user_id);
}
#RequestMapping(method = RequestMethod.POST) // 7
#ResponseStatus(HttpStatus.CREATED) // 8
public UserModel getUser(#ResponseBody UserModel userModel) { // 9
return userService.createUser(usermodel);
}
}
1) #RestController is a combination of #Controller and #ResponseBody, which essentially means every method in your class will have a response body.
2) Prefix #RequestMapping values in this class with /users
3) Autowiring in the Constructor is the safest approach to injecting beans.
4) This method will be accessible via a GET request to /users/{user_id}
5) This method will return HttpStatus.OK status code on success (200)
6) Extracts the path variable "user_id" from the Request. Use the same numeric type as your user id's here (i.e. int or long).
7) This method will be accessible via a POST request to /users
8) This method will return HttpStatus.CREATED status code on success (201)
9) Extracts a UserModel from the request body (should have the same structure as the json given later).
There are no real differences to Cepr0 and my approach, it's purely a style preference.
UserModel can be a class like this:
UserModel.java
public class UserModel {
private String username;
// Constructor, Getter, Setter...
}
And this will return a JSON object in the body of the response like this:
{
"username":"username"
}
If you would like to handle Exceptions within your controller (and even control the data returned by an exception, you can use #ExceptionHandler like so:
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponseWrapper> handleGenericException(Exception ex){
return ResponseEntity
.status(HttpStatus.I_AM_A_TEAPOT)
.body(new CustomExceptionWrapper(ex)));
}
Where CustomExceptionHandler converts exceptions thrown by your application into a format you decide (This can also be a POJO and Spring Boot will transform it into JSON for you!)
To answer your questions more specifically:
1) You should throw an exception if the user isn't found that will include the response status 404 (NOT FOUND). Returning null is typically a bad idea as it could mean a lot of things.
1.1?) If your user sends an invalid string, you can look up which exception it causes in your server, and use the exception handler to deal with it and return an appropriate response (BAD_REQUEST maybe?)
2) Yes use long if your use id's are longs.
Check out the baeldung site, would really recommend them for learning Spring Boot.

Spring MVC #Validation with Marker Interface in Generic Controller Method

I have a Spring MVC survey application where the Controller method called by each form POST is virtually identical:
#PostMapping("/1")
public String processGroupOne (
Model model,
#ModelAttribute("pageNum") int pageNum,
#ModelAttribute(GlobalControllerAdvice.SESSION_ATTRIBUTE_NAME) #Validated(SurveyGroupOne.class) SurveyCommand surveyCommand,
BindingResult result) {
if (result.hasErrors()) {
LOG.debug(result.getAllErrors().toString());
model.addAttribute("pageNum", pageNum);
return "survey/page".concat(Integer.toString(pageNum));
}
pageNum++;
model.addAttribute("pageNum", pageNum);
return "redirect:/survey/".concat(Integer.toString(pageNum));
}
The only difference is what part of the SurveyCommand object is validated at each stop along the way. This is designated by the marker interface passed to the #Validated() annotation. The marker interfaces (SurveyGroupOne, SurveyGroupTwo, etc) are just that, markers:
public interface SurveyGroupOne {}
public interface SurveyGroupTwo {}
...
and they are applied to properties of objects in the SurveyCommand object:
public class Person {
#NotBlank(groups = {
SurveyGroupTwo.class,
SurveyGroupThree.class})
private String firstName;
#NotBlank(groups = {
SurveyGroupTwo.class,
SurveyGroupThree.class})
private String lastName;
...
}
My question: how can I make the method generic and still use the marker interface specific to the page being processed? Something like this:
#PostMapping("/{pageNum}")
public String processGroupOne (
Model model,
#PathVariable("pageNum") int pageNum,
#ModelAttribute(GlobalControllerAdvice.SESSION_ATTRIBUTE_NAME)
#Validated(__what goes here??__) SurveyCommand surveyCommand,
BindingResult result) {
if (result.hasErrors()) {
LOG.debug(result.getAllErrors().toString());
model.addAttribute("pageNum", pageNum);
return "survey/page".concat(Integer.toString(pageNum));
}
pageNum++;
model.addAttribute("pageNum", pageNum);
return "redirect:/survey/".concat(Integer.toString(pageNum));
}
How can I pass the proper marker interface to #Validated based solely on the pageNum #PathVariable (or any other parameter)?
Because #Validated is an annotation, it requires its arguments to be available during compilation and hence static. You can still use it but in this case you will have N methods, where N is a number of steps. To distinguish one step from another you can use params argument of #PostMapping annotation.
There is also another way where you need to inject Validator to the controller and invoke it directly with an appropriate group that you need.

MediaFormatter or ModelBinder for web api PUT method

I have a PUT method in web api which accepts a JSON data and a route data as follows.
[Route("api/v1/Orders/{orderId}/active")]
public HttpResponseMessage Put(Guid? orderId,List<ActiveRequest> activeRequests)
{
}
public class ActiveRequest
{
public int Id { get; set; }
public bool IsActive { get; set; }
}
Now is it possible to simplify the method signature as:
[Route("api/v1/Orders/{orderId}/active")]
public HttpResponseMessage Put(ActiveRequestModel model)
{
}
public class ActiveRequestModel
{
public Guid OrderId { get; set; }
public List<ActiveRequest> ActiveRequests {get; set;}
}
I tried writing a custom ModelBinder by implementing the System.Web.Http.ModelBinding.IModelBinder interface but could'nt find a way to read the JSON data that is coming inside the Request object.
I doubt that is there a way by which I can bind my model with data coming from three different places i.e. from route data, json & form.
You cannot simplify the parameter as described.
Unlike MVC model binding, beacuse of how the Web API formatter works, in Web API you only can have a single parameter that is deserialized from the payload, and a number of simple type parameters coming from route parameters or url query string. The reason is that the creation of the parameter coming from the payload is done in a single pass deserialization of the payload.
So, for your example you need the two parameters in your original version, i.e.:
public HttpResponseMessage Put(Guid? orderId, List<ActiveRequest> activeRequests)
If you want to use the ActiveRequestModel you need to include a payload which has exactly the same structure, so you should include the orderId in the payload, because it will not be recovered from the url (even if the name matches).
Please, read this article which explains how parameter binding works in Web API:
Parameter Binding in ASP.NET Web API
If you read it thoroughly you'll see that you can create and register your own model binder to make it work the same way that an MVC controller, but I think it's not worth the effort (so I include it only in this last paragraph), and it's not the standard way of working.

Use a viewmodel with web api action

I just read this post by Dave Ward (http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/), and I'm trying to throw together a simple web api controller that will accept a viewmodel, and something just isn't clicking for me.
I want my viewmodel to be an object with a couple DateTime properties:
public class DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
Without changing anything in the stock web api project, I edit my values controller to this:
public IEnumerable<float> Get()
{
DateRange range = new DateRange()
{
Start = DateTime.Now.AddDays(-1),
End = DateTime.Now
};
return Repo.Get(range);
}
// GET api/values/5
public IEnumerable<float> Get(DateRange id)
{
return Repo.Get(range);
}
However, when I try to use this controller, I get this error:
Multiple actions were found that match the request:
System.Collections.Generic.IEnumerable1[System.Single] Get() on type FEPIWebService.Controllers.ValuesController
System.Collections.Generic.IEnumerable1[System.Single] Get(FEPIWebService.Models.DateRange) on type FEPIWebService.Controllers.ValuesController
This message appears when I hit
/api/values
or
/api/values?start=01/01/2013&end=02/02/2013
How can I solve the ambiguity between the first and second get actions?
For further credit, if I had this action
public void Post(DateRange value)
{
}
how could I post the Start and End properties to that object using jQuery so that modelbinding would build up the DateRange parameter?
Thanks!
Chris
The answer is in detail described here: Routing and Action Selection. The Extract
With that background, here is the action selection algorithm.
Create a list of all actions on the controller that match the HTTP request method.
If the route dictionary has an "action" entry, remove actions whose name does not match this value.
Try to match action parameters to the URI, as follows:
For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude
optional parameters.
From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are
case insensitive and do not depend on the parameter order.
Select an action where every parameter in the list has a match in the URI.
If more that one action meets these criteria, pick the one with the most parameter matches.
4.Ignore actions with the [NonAction] attribute.
Other words, The ID parameter you are using, is not SimpleType, so it does not help to decide which of your Get methods to use. Usually the Id is integer or guid..., then both methods could live side by side
If both of them would return IList<float>, solution could be to omit one of them:
public IEnumerable<float> Get([FromUri]DateRange id)
{
range = range ?? new DateRange()
{
Start = DateTime.Now.AddDays(-1),
End = DateTime.Now
};
return Repo.Get(range);
}
And now both will work
/api/values
or
/api/values?Start=2011-01-01&End=2014-01-01

What does it mean when Spring MVC #Controller returns null view name?

I downloaded the code for the Spring MVC 3 Showcase. One thing puzzles me (well, more than one), why does this (edited for concision) sample return null?
#Controller
#RequestMapping("/form")
public class FormController {
#RequestMapping(method=RequestMethod.POST)
public String processSubmit(#Valid FormBean form,
BindingResult result,
WebRequest webRequest,
HttpSession session, Model model) {
if (result.hasErrors()) {
return null;
} else {
session.setAttribute("form", form);
return "redirect:/form";
}
}
}
If a controller returns a null view name, or declares a void return type, Spring will attempt to infer the view name from the request URL.
In your case, it will assume the view name is form, and proceed on that assumption.
It does this using an implementation of RequestToViewNameTranslator, the default implementation of which is DefaultRequestToViewNameTranslator, the javadoc for which explains the exact rules it applies.
AnnotationMethodHandlerAdapter.invokeHandlerMethod() takes care of invoking handler methods. Here, a ModelAndView will be retrieved via ServletHandlerMethodInvoker.getModelAndView().
In your case, getModelAndView() gets provided the handler method's null return value. The getModelAndView() method checks for the return value's type, but as in Java null is never an instanceof any class, that method's logic will create a new ModelAndView. A new ModelAndView has initially its view property set to null.
Then later back up the call stack, in DispatcherServlet.doDispatch(), there is a test if the ModelAndView object has a View associated with it ( mv.hasView() ). Because view == null, doDispatch()'s logic calls mv.setViewName(getDefaultViewName(request)). It delegates to the registered RequestToViewNameTranslator, whose default implementation is DefaultRequestToViewNameTranslator. This subclass translates the request URI into a view name, in your case form.
Later in doDispatch(), via render() -> resolveViewName(), this sample's ViewResolvers are provided with the view name form. Only one ViewResolver, InternalResourceViewResolver is used in this sample. Also, this InternalResourceViewResolver was configured in src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml to add the prefix /WEB-INF/views/ and the suffix .jsp to the view name. So in total, it will create a View using the JSP file /WEB-INF/views/form.jsp. Luckily, a JSP file exists at exactly this location.

Resources