Use of getFlashAttributes() in Spring's RedirectAttributes - java

In order to access the redirect attributes in the redirected method, we utilize the model's map, like this :
#Controller
#RequestMapping("/foo")
public class FooController {
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(Model map) {
String some = (String) map.asMap().get("some");
}
#RequestMapping(value = "/bar", method = RequestMethod.POST)
public ModelAndView handlePost(RedirectAttributes redirectAttrs) {
redirectAttrs.addFlashAttributes("some", "thing");
return new ModelAndView().setViewName("redirect:/foo/bar");
}
}
But, why can't we access them in this way :
#RequestMapping(value = "/bar", method = RequestMethod.GET)
public ModelAndView handleGet(RedirectAttributes redAttr) {
String some = redAttr.getFlashAttributes().get("some");
}
If the only purpose of adding flashAttributes is that they become available to the model in the redirected method, what's the purpose of getFlashAttributes() ?

RedirectAttributes are for setting flash attributes before redirection. They are merged into model after the redirection so there is no reason to access them again via RedirectAttributes again as you have suggested.
Being able to work with the attributes just like with a map might be useful. You can check what have you set (containsKey, isEmpty, ...). However the use of the wildcard generic parameter Map<String, ?> getFlashAttributes() prevents writing into map and it is strange why they have used it instead of a plain Object parameter.

Related

Differentiation of #ModelAttribute, ModelAndView mode, HttpServletRequest request

I'm new to mvc spring and I have some code from the internet like the following:
#RequestMapping(value = "/newContact", method = RequestMethod.GET)
public ModelAndView newContact(ModelAndView model) {
Contact newContact = new Contact();
model.addObject("contact", newContact);
model.setViewName("ContactForm");
return model;
}
#RequestMapping(value = "/saveContact", method = RequestMethod.POST)
public ModelAndView saveContact(#ModelAttribute Contact contact) {
contactDAO.saveOrUpdate(contact);
return new ModelAndView("redirect:/");
}
#RequestMapping(value = "/deleteContact", method = RequestMethod.GET)
public ModelAndView deleteContact(HttpServletRequest request) {
int contactId = Integer.parseInt(request.getParameter("id"));
contactDAO.delete(contactId);
return new ModelAndView("redirect:/");
}
My question is,
what is the purpose of using and using in any case with # ModelAttribute, ModelAndView mode and HttpServletRequest request?
Thanks you.
Please check https://stackoverflow.com/a/33422321/3530898. I my opinion ModelAttribute is used usually with form transport objects (i.e. bean classes whose fields contain the form data), ModelAndView is used when you want to set view name and model object in a single method of a controller. Both ModelAndView and Model uses HttpServletRequest internally, Spring has added wrapper classes over HttpServletRequest to make development easier for developer. But sometime you need HttpServletRequest class for instance, when you want to capture a query parameter in an Ajax call etc

Spring REST service to consume and produce both HTML form POST and AJAX/JSON in a single method

I'm trying to teach myself Spring by creating a very simple web application. I have a class to create "Note" objects:
#Controller
#RequestMapping(value = "/notes")
public class NoteRestController {
#Autowired
private MappingJackson2JsonView jsonView;
[...]
#ResponseStatus(HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST, consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ModelAndView create(final Model model,
#Valid #ModelAttribute final Note note, final BindingResult result) {
ModelAndView mav;
// how can I test the request source?
if (<requesting from HTML FORM>) {
// return jsonView
mav = new ModelAndView(jsonView);
} else {
// return JSP view
mav = new ModelAndView("note", "model", model);
}
model.addAttribute("note", note);
if (result.hasErrors()) {
model.addAttribute("errors", result.getAllErrors());
// on error, redirect back to note page with form
// return new ModelAndView("note/note", "model", model);
return mav;
}
note.setId(daoService.createNote(note));
return mav;
}
}
I would like to be able to use a single method (like the above) to handle requests from both an AJAX post AND a HTML form post. If triggered by AJAX I would like to return JSON (with validation errors if present), and if it is triggered by a HTML form, I would like to return to the JSP using the form taglib
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
and show validation errors next to input fields using e.g.
<form:errors path="title" cssClass="errorMessage"></form:errors>
Is this possible, or should I be creating two controllers; one for the REST/JSON, and one for HTML/form? Maybe there is something I can pass into the method that can be interrogated to determibne the request source, but I can't see it right now.
What would be the "best practice" in this case?
EDIT 1:
Trying answer from #ring-bearer first as it allows for the same URL pattern, but having issues.
Using methods:
// used to handle JSON/XML
#RequestMapping(method = RequestMethod.POST, produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public #ResponseBody Note create(
#Valid #ModelAttribute final Note note, final BindingResult result) {
[...]
}
// used to handle form view
#RequestMapping(method = RequestMethod.POST)
public ModelAndView createForView(final Model model,
#Valid #ModelAttribute final Note note, final BindingResult result) {
[...]
}
Interestingly, the HTML form submission, still gets handled by create() and not createForView(). After looking at the form submission request headers, I see that this Accept header:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
By adding produces = "text/html" to #RequestMapping on createForView(), all 3 scenarios work (form, AJAX/JSON, AJAX/XML).
Is this normal, or am I still missing something?
This can be achieved using "content negotiation". Spring MVC needs to be enabled for content negotiation using a "contentNegotiationManager" definition. It can be set up using Java or XML configuration. The configuration will centrally manage media type mappings(json, xml etc). Once that is set up, a controller class can be built to cater to both JSON and View(HTML). Below is a generic example(uncompiled), it should be easy to refactor your class to similar structure to avoid violation of DRY.
#Controller
class ReportController{
//1- Method for JSON/marshalling types(XML)
#RequestMapping(value="/report", produces={"application/xml", "application/json"})
#ResponseStatus(HttpStatus.OK)
public #ResponseBody List<ReportPara> generateReport(Principal principal) {
return reportService.generateReport(principal);
}
//2- For View technologies ( Eg:JSP HTML)
#RequestMapping("/report")
public String generateReportForView(Model model, Principal principal) {
model.addAttribute( generateReport(principal) );
// Return the view to use for rendering the response
return ¨reports/main¨;
}
}
Which of the two #RequestMapping methods will execute? It is determined by content negotiation definition. Eg: URLs such as report.xml or report.json map to the first method, any other URLs ending in report.anything map to the second.
The following will be easier to maintain:
#Controller
class NoteController {
#Autowired NoteService service;
#RequestMapping(method = RequestMethod.POST, value = "/note")
public ModelAndView createFromForm(#ModelAttribute #Valid Note note, BindingResult result) {
return new ModelAndView("note", create(note));
}
#RequestMapping(method = RequestMethod.POST, value = "/api/note")
#ResponseBody
public Note createFromApi(#RequestBody Note note) {
return create(note);
}
private Note create(Note note) {
return service.create(note);
}
}

How to provide a list of objects for a REST query in spring-mvc?

I want to create a REST-GET controller in spring-mvc that takes a list of objects, eg 10 ids as follows:
#RestController
public class MyRest {
#RequestMapping(method = RequestMethod.GET)
public Object test(#RequestParam value="id" required=false) List<Integer> ids) {
Sysout(ids);
}
}
Anyway when I call it, I have to repeat the id param multiple times:
localhost:8080/app?id=1&id=2&id=3&...
It is possible to change the param to some kind of list? Eg
app?id=1,2,3,4,5
And if yes, is this advisable? What's better from the client point of view?
Its better to use POST message with JSON or XML as request body.
As you never know how many id's will be passed.
#RestController
public class MyRest {
#RequestMapping(method = RequestMethod.POST)
public Object test(#RequestBody IDRequest request) {
Sysout(ids);
}
public static final class IDRequest {
List<Integer> ids;
<!-- getter/setters--->
}
}
where the request will be some kind of a JSON or XML like this
{"ids":[1,2,3,4,5,6,7,8,9]}
You can provide list of objects to rest service as request param.Here is the example
#RequestMapping(value = "/animals, method = RequestMethod.GET)
public void test(#RequestParam(value="animalsNames[]") String[] animalsNames) {
Sysout(animalsNames);
}
And your request looks like
http://localhost:8080/appname/animals?animalsNames[]=dog,horse
HTTP Method type : GET
Controller :
public #ResponseBody String getInfos(HttpServletRequest request,
#RequestParam #DateTimeFormat( #RequestParam List<Long> ids) {...}
Request :
http://localhost:8080/test/api?ids=1,2,3

What is the difference between #ModelAttribute, model.addAttribute in spring?

i am new Spring learner.i'm really confused about what is the difference between two concept:
#ModelAttribute
model.addAttribute
in below there are two "user" value.Are these same thing?Why should I use like this?
Thank you all
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
model.addAttribute("user", new User());
return "editUser";
}
#RequestMapping(method = RequestMethod.POST)
public String processSubmit( #ModelAttribute("user") User user, BindingResult result, SessionStatus status) {
userStorageDao.save(user);
status.setComplete();
return "redirect:users.htm";
}
When used on an argument, #ModelAttribute behaves as follows:
An #ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument’s fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.
http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-method-args
That's a very powerful feature. In your example, the User object is populated from the POST request automatically by Spring.
The first method, however, simply creates an instance of Userand adds it to the Model. It could be written like that to benefit from #ModelAttribute:
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#ModelAttribute User user) {
// user.set...
return "editUser";
}

Can #PathVariable return null if it's not found?

Is it possible to make the #PathVariable to return null if the path variable is not in the url? Otherwise I need to make two handlers. One for /simple and another for /simple/{game}, but both do the same just if there is no game defined i pick first one from a list however if there is a game param defined then i use it.
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable("example") String example,
HttpServletRequest request) {
And this is what I get when trying to open page /simple:
Caused by: java.lang.IllegalStateException: Could not find #PathVariable [example] in #RequestMapping
They cannot be optional, no. If you need that, you need two methods to handle them.
This reflects the nature of path variables - it doesn't really make sense for them to be null. REST-style URLs always need the full URL path. If you have an optional component, consider making it a request parameter instead (i.e. using #RequestParam). This is much better suited to optional arguments.
As others have already mentioned No you cannot expect them to be null when you have explicitly mentioned the path parameters. However you can do something like below as a workaround -
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable Map<String, String> pathVariablesMap,
HttpServletRequest request) {
if (pathVariablesMap.containsKey("game")) {
//corresponds to path "/simple/{game}"
} else {
//corresponds to path "/simple"
}
}
If you are using Spring 4.1 and Java 8 you can use java.util.Optional which is supported in #RequestParam, #PathVariable, #RequestHeader and #MatrixVariable in Spring MVC
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable Optional<String> game,
HttpServletRequest request) {
if (game.isPresent()) {
//game.get()
//corresponds to path "/simple/{game}"
} else {
//corresponds to path "/simple"
}
}
You could always just do this:
#RequestMapping(value = "/simple", method = RequestMethod.GET)
public ModelAndView gameHandler(HttpServletRequest request) {
gameHandler2(null, request)
}
#RequestMapping(value = "/simple/{game}", method = RequestMethod.GET)
public ModelAndView gameHandler2(#PathVariable("game") String game,
HttpServletRequest request) {
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable(value="example",required = false) final String example)
Try this approach, it worked for me.
I just tested this just now, but by combining the above solution i got this:
#RequestMapping(value = {"/simple", "/simple/{game}"}, method = RequestMethod.GET)
public ModelAndView gameHandler(#PathVariable(value = "game", required = false) String example,
HttpServletRequest request) {
if (example != null) {
//...
} else {
//pick first, ...
}
}
Now when you use "/simple", String example will be null instead of throwing Exception.
Short solution, no fancy Optional<> or Map<>
We can write multiple methods in controllers with explicit mapping with the path variable combination to exclude the optional variables (if using old version of Spring)
In my scenario wanted to develop an API to get recycle value for old device where parameters could be brand, model and network however network is an option one.
One option to handle this was use network as a request parameter instead of pathVariable.
for e.g. /value/LG/g3?network=vodafone however I didn't like this approach.
for me the more cleaner one was to use below
/refurbValue/LG/g3
/refurbValue/LG/g3/vodafone
#RequestMapping(value = "/refurbValue/{make}/{model}/{network}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
def getRefurbValueByMakeAndModelAndNetwork(#PathVariable String make, #PathVariable String model, #PathVariable String network ) throws Exception {
//logic here
}
#RequestMapping(value = "/refurbValue/{make}/{model}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
def getRefurbValueByMakeAndModel(#PathVariable String make, #PathVariable String model) throws Exception {
//logic here
}
In the above example, both controller can use the same service method and handling of the parameter can be done. In my case I was using Groovy so it was easy to use with optional parameter like
Map getRefurbValue(String brand, String model, String network="")

Categories