Change model (#ModelAttribute) depending on a request parameter - java

help me please.
I have the code of controller like this:
#RequestMapping(method = RequestMethod.GET)
public String showOrders(#RequestParam(value = "status", required = false) String status, Model model) {
if(status != null) {
Order.Status orderStatus = Order.Status.valueOf(status);
if (orderStatus != null) model.addAttribute("currentStatus", orderStatus);
}
return "admin/orders";
}
#ModelAttribute("currentStatus")
public Order.Status populateCurrentStatus() {
return Order.Status.PAYMENT;
}
#ModelAttribute("orders")
public List<Order> populateOrders(#ModelAttribute("currentStatus") Order.Status status) {
return orderBo.getByStatus(status);
}
I want the default currentStatus to be equal to Order.Status.PAYMENT, but if the controller receives a GET request with the argument status (on method showOrders), then replace currentStatus in the model to the status transmitted in the request.
And the populateOrders should return a different list of orders, in accordance with the new status. But unfortunately, this is not happening. Method populateOrders always gets currentStatus equal Order.Status.PAYMENT and it never changes.

You can add #RequestParam to your #ModelAttribute definition in order to populate #ModelAttribute differently, depending on a request parameter.
This should work:
#RequestMapping(method = RequestMethod.GET)
public String showOrders() {
//no need to manually modify currentStatus in the model anymore
return "admin/orders";
}
#ModelAttribute("currentStatus")
public Order.Status populateCurrentStatus(#RequestParam(value = "status", defaultValue = "PAYMENT") String status) {
return Order.Status.valueOf(status);
}
#ModelAttribute("orders")
public List<Order> populateOrders(#ModelAttribute("currentStatus") Order.Status status) {
return orderBo.getByStatus(status);
}
Personal note:
I personally dislike Spring's #ModelAttribute system for defining default model attributes, especially in case of more complex models or in more complex controllers. It becomes hard to maintain because it's hard to control what exactly goes into the model (for example, for some requests, maybe you don't want the whole model populated, e.g. you only need currentStatus but not the list of orders. I prefer populating the model manually (Model.addAttribute(...)) - it's not much extra work and everything is explicit and easy to debug if a problem pops out later on.

Related

How do i differentiate between two endpoints, each with one PathVariable?

I'm working on a Spring Boot application. I have the following REST endpoint(s):
#GetMapping(value = { "/person/{name}", "/person/{age}" })
public PersonData getPersonData(#PathVariable(required = false) String name,
#PathVariable(required = false) Integer age) {
}
This endpoint can be called with either a name variable or an age variable, however it looks like it can't differentiate between them. If I was to call '.../person/20', it would not call "/person/{age}", but it always calls "/person/{name}".
I know I could make something like:
#GetMapping(value = { "/person/name/{name}", "/person/age/{age}" })
However are there any other way to solve it without adding anything to the path?
A path variable is something like a primary key usually.
Like:
/person/{id}
What you try is to search for data and this should be done with query parameters.
Example:
#GetMapping(value = { "/person/{name}", "/person/{age}" })
public PersonData getPersonData(#RequestParam String name,
#RequestParam Integer age) {
}
Then you can call it
/person?age=40
/person?name=Peter
age and name are logically not the same thing; the chosen best answer correctly suggests to keep them as distinguished parameters.
However you can check if the value is numeric and treat it like an age,
#GetMapping("/person/{value}")
public String getPerson(#PathVariable String value) {
if (value.matches("[0-9]|[0-9][0-9]"))
return "Age";
else
return "Name";
}
but this is ambiguous and error prone (e.g. how'll you distinguish when adding other numerical params like shoeSize or numberOfPartners?).
In your case I would make 2 different endpoints to be more clear, each one requiring it's own query parameter to be served.
#GetMapping(value = "/person")
public PersonData getPersonDataByName(#RequestParam(required = true) String name) {
....
}
#GetMapping(value = "/person")
public PersonData getPersonDataByAge(#RequestParam(required = true) Integer age) {
....
}
required = true can be omitted from the annotation as this is the default value, I used it just to point that each endpoint will be fulfilled only for that specific query parameter

Spring ResponseEntity best practice

I am new to RESTful web services in general, and am learning the Spring implementation of web services.
I am particularly interested in learning how to properly use ResponseEntity return types for most of my use cases.
I have one endpoint:
/myapp/user/{id}
This endpoint supports a GET request, and will return a JSON formatted string of the User object whose ID is {id}. I plan to annotate the controller method as producing JSON.
In the case that a user with ID {id} exists, I set a status of 200, and set the JSON string of the user in the body.
In the event that no user exists with that ID, I was going to return some flavor of a 400 error, but I am not sure what to set in the body of the ResponseEntity. Since I annotate the endpoint method as producing JSON, should I come up with a generic POJO that represents an error, and return that as JSON?
You donĀ“t need to use a generic Pojo, using RequestMapping you can create different responses for every Http code. In this example I show how to control errors and give a response accordingly.
This is the RestController with the service specification
#RestController
public class User {
#RequestMapping(value="/myapp/user/{id}", method = RequestMethod.GET)
public ResponseEntity<String> getId(#PathVariable int id){
if(id>10)
throw new UserNotFoundException("User not found");
return ResponseEntity.ok("" + id);
}
#ExceptionHandler({UserNotFoundException.class})
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException ex){
return new ResponseEntity<ErrorResponse>(
new ErrorResponse(ex.getMessage(), 404, "The user was not found") , HttpStatus.NOT_FOUND);
}
}
Within the getId method there is a little logic, if the customerId < 10 It should response the Customer Id as part of the body message but an Exception should be thrown when the customer is bigger than 10 in this case the service should response with an ErrorResponse.
public class ErrorResponse {
private String message;
private int code;
private String moreInfo;
public ErrorResponse(String message, int code, String moreInfo) {
super();
this.message = message;
this.code = code;
this.moreInfo = moreInfo;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public String getMoreInfo() {
return moreInfo;
}
}
And finally I'm using an specific Exception for a "Not Found" error
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
In the event that no user exists with that ID, I was going to return
some flavor of a 400 error, but I am not sure what to set in the body
of the ResponseEntity. Since I annotate the endpoint method as
producing JSON, should I come up with a generic POJO that represents
an error, and return that as JSON?
This is definitely a possible solution, if you want to add e.g. a more specific reason why the request failed or if you want to add a specific I18N message or just want to generify your API to provide some abstract structure.
I myself prefer the solution #Herr Derb suggested, if there is nothing to return, don't return anything. The returned data may be completely unnecessary/unused and the receiver may just discard it if the return code is anything else than 2XX.
This may be related:
http://www.bbenson.co/post/spring-validations-with-examples/
The author describes how to validate incoming models and builds a generic error response. Maybe this is something you want to do...

Spring : Configure xml to make a controller return a view depending on a parameter

I have a spring MVC based application and I want to add a functionality in which some of my controllers will return the same view depending on the value of a parameter.
#RequestMapping("/someView")
public String returnView(Model model, HttpServletRequest request, String param){
if(param.equals("condition")){
return "commonView";
}
// do stuff
return "methodSpecificView";
}
Is there a way in which the first if condition can be configured in an xml? Since similar functionality needs to implemented in many controllers and I don't want to write boilerplate code an xml configuration can make things simpler.
Furthermore, if the first one is possible, can it be extended to eliminate the parameter param from request mapping method signature and put that in xml too?
You can use #RequestMapping:
#RequestMapping(value = {"/someView", "/anotherView", ...}, params = "name=condition")
public String returnCommonView(){
return "commonView";
}
In Spring 3.2 which is annotation based the below code snippet will give you an idea for your problem:
#RequestMapping("formSubmit.htm")
public String onformSubmit(#ModelAttribute("TestBean") TestBean testBean,BindingResult result, ModelMap model, HttpServletRequest request) {
String _result = null;
if (!result.hasErrors()) {
_result = performAction(request, dataStoreBean);//Method to perform action based on parameters recieved
}
if(testBean.getCondition()){
_result = "commonView";
}else{
_result = "methodSpecificView";
}
return _result;
}
TestBean//Class to hold all the required setters and getters
Explanation:
As the request from your view comes to this method the ModelAttribute reference will hold all the values from view if the condition is obtained from the view than you can directly obtain it from model attribute and return the corresponding view.
If your condition is obtained after applying certain logic than you can set the condition in the testBean and again get it to return the corresponding view.
You should consider implementing this via AOP - Around advice something like below.
#Around("#annotation(RequestMapping)") // modify the condition to include / exclude specific methods
public Object aroundAdvice(ProceedingJoinPoint joinpoint) throws Throwable {
Object args[] = joinpoint.getArgs();
String param = args[2]; // change the index as per convenience
if(param.equals("condition")){
return "commonView";
} else {
return joinpoint.proceed(); // this will execute the annotated method
}
}

Overloading a spring controller method with the same request mapping

I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.

How to inherit RequestMappings in a Spring 3 MVC REST API

I'm trying to build a RESTful API using Spring MVC. I'm shooting for clean and manageable code where the package structure follows the url structure.
So here is what I've got:
// com.test.api.library
#RequestMapping("/library/{libraryId}")
public Library getLibrary(#PathVariable long libraryId) {
return service.getLibraryById(libraryId);
}
// com.test.api.library.book
#RequestMapping("/library/{libraryId}/book/{bookId}")
public Book getBook(#PathVariable long libraryId, #PathVariable long bookId) {
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
While this works, I find it messy and error-prone to have to repeat "/library/{libraryId}" in all inherited #RequestMappings, /library is likely to be to root of a big part of the API and it should be written once and reused instead of written everywhere.
I would like to rewrite the book-class to something like this:
// com.test.api.library.book
#RequestMapping("/book/{bookId}")
public Book getBook(#PathVariable long bookId) {
// long libraryId magically given to me from the library-class's getLibrary()
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
Is there any way Spring can help me here? It is acceptable for me to use normal java inheritance, spring annotation or anything else that helps me to not write "/library/{libraryId}" as a part of every url I ever write.
I believe this question has been asked & answered before: Spring MVC #RequestMapping Inheritance
That said, here is one way to reduce the amount of duplicate information. I don't actually do this in my own code because I think having the URI right next to the code is more maintainable, even if it means a little duplication.
#RequestMapping(URI_LIBRARY)
public interface LibraryNamespace {
public static String URI_LIBRARY = "/library/{libraryId}";
}
#RequestMapping(URI_BOOK)
public interface BookNamespace {
public static String URI_BOOK = LibraryNamespace.URI_LIBRARY + "/book/{bookId}";
}
#Controller
public class LibraryController implements LibraryNamespace {
#RequestMapping("")
public Library get(#PathVariable long libraryId) {
return service.getLibraryById(libraryId);
}
}
#Controller
public class BookController implements BookNamespace {
#RequestMapping("")
public Book get(#PathVariable long libraryId, #PathVariable long bookId) {
Library library service.getLibraryById(libraryId);
return library.getBookById(bookId);
}
}
Since I wouldn't take this approach myself, I haven't actually tried this solution! Based on my understanding of Spring, I think it should work though...
Use a polymorphic parent approach.
#Controller
public class CommentsController {
#RequestMapping(value="/comments", method = RequestMethod.GET)
public #ResponseBody String index() {
/* kludge to allow optional path parameters */
return index(null, null);
}
#RequestMapping(value="/{parent_collection}/{parent_id}/comments", method = RequestMethod.GET)
public #ResponseBody String index(#PathVariable("parent_collection") String parentCollection, #PathVariable("parent_id") String parentId) {
if (parentCollection == null) {
return "all comments";
}
else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
/* get parent, then get comments for parent */
return "comments for single post";
}
else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
/* get parent, then get comments for parent */
return "comments for single customer";
}
else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
/* get parent, then get comments for parent */
return "comments for single movie";
}
}
#RequestMapping(value = "/comments/{id}", method = RequestMethod.GET)
public #ResponseBody String show(#PathVariable Integer id) {
/* kludge to allow optional path parameters */
return show(null, null, id);
}
#RequestMapping(value = "/{parent_collection}/{parent_id}/comments/{id}", method = RequestMethod.GET)
public #ResponseBody String show(#PathVariable("parent_collection") String parentCollection, #PathVariable("parent_id") String parentId, #PathVariable Integer id) {
/* get comment, then get parent from foreign key */
if (parentCollection == null) {
return "single comment";
}
else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
return "single comment for single post";
}
else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
return "single comment for single customer";
}
else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
return "single comment for single movie";
}
}
}
Additionally, you could use a base controller to route the URI prefix to parent resources (/libraries/{library_id}/../..), add the parent models to the request scope, and then let the regular request mappings handle the rest of the URI to child resources (/../../books/1). I don't have an example of this off-hand.
Side note. Singular nested resources are generally regarded as an antipattern for URI design. A controller should handle its own resources. The most common implementations make the key for the singular nested resource unique, i.e., not dependent on its parent resource. For instance, a database record primary key. However, there are situations where the key might not be unique, such as an ordinal or position value (e.g., book 1, chapter 1, chapter 2), or maybe even a natural key (e.g., book ISBN, person SSN, email address, username, filename).
Example of canonical URIs for nested resources:
/articles => ArticlesController#index
/articles/1 => ArticlesController#show
/articles/1/comments => CommentsController#index
/articles/1/comments/2 => CommentsController#show (okay, but not preferred)
/comments/2 => CommentsController#show (preferred)
I don't think it's possible. But you can have the #RequestMapping annotation on the class itself, so it will save you at least some typing.
#Controller
#RequestMapping("/library/{libraryId}")
public class HelloWorldController {
#RequestMapping(value="/book/{bookId}")
public ModelAndView helloWorld() {
....
}
}

Categories

Resources