ModelAndView Model objects not emptied on re-creation - spring

My question is related to
Spring mvc interceptor addObject
At some point my application needs to know what the previousUrl is that has been visited, so in some occasions the previousUrl is stored in the ModelAndView and 'previous' can be called.
In another case I want to do a redirect and I don't want a previousUrl showing up in the URL bar of my browser. But when I try to initialize a new ModelAndView that old previousUrl object is still there. How is this possible?
The code
if (requestEmployee == null) {
LOGGER.warn("User [" + requestEmployeeName + "] not found.");
model = new ModelAndView(AbstractController.VIEW_REDIRECT_OVERVIEW, null);
return model;
}
should create a new ModelAndView without model objects so why is the previousUrl object still added to the URL as path variable in the browser?

This might depend on what your handler method signature is.
Spring's RequestMappingHandlerAdapter is actually adding model attributes from the returned ModelAndView to the existing model attributes. If you have Model as one of your handler method argument and you are adding some attributes there, they will get merged.

Related

Command object automatically added to model?

I have a controller method like this:
#RequestMapping("/hello")
public String hello(UserForm user) {
return "hello";
}
It receives some request parameters in the UserForm command object. But I have not written any code to add the object to the Model. Still, in the view hello.jsp, I'm able to access the data, like this:
Hello, ${userForm.name}!
Does it mean that Spring MVC adds command objects to the Model automatically?
You don't need #ModelAttribute just to use a Bean as a parameter.
You'll need to use #ModelAttribute or model.addAttribute() to load default data into your model - for example from a database.
Most of the Spring controllers in the real world accept a lot of different types of parameters - Path variables, URL parameters, request headers, request body and sometimes even the entire HTTP Request object. This provides a flexible mechanism to create APIs. Spring is really good at parsing these parameters in to Java types as long as there is an ObjectMapper (like Jackson) configured to take care of the de-serialization.
The RequestMappingHandlerAdapter makes sure the arguments of the method are resolved from the HttpServletRequest.
Spring model data created prior to (or during) the handler method
execution gets copied to the HttpServletRequest before the next view
is rendered.
By now, Spring has processed the HTTP request and it creates the ModelAndView object from the method’s return value. Also, note that you are not required to return a ModelAndView instance from a controller method. You may return a view name, or a ResponseEntity or a POJO that will be converted to a JSON response etc.
ServletInvocableHandlerMethod invocableMethod
= createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(
this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(
this.returnValueHandlers);
}
The returnValueHandlers object is a composite of HandlerMethodReturnValueHandler objects. There are also a lot of different value handlers that can process the result of your method to create ModelAndViewobject expected by the adapter.
Then, it has to render the HTML page that the user will see in the browser. It does that based on the model and the selected view encapsulated in the ModelAndView object.
Now, at this stage, the view gets access to the userForm (as in your example above) from the request scope.

Why I can't redirect from a Spring MVC controller method to another controller method?

I am pretty new in Spring MVC and I have some problem trying to redirect to a controller method after that another controller method terminate its execution.
So I have the following situation. Into a controller class I have this method that correctly handle POST request toward the validaProgetti resource:
#RequestMapping(value = "validaProgetti", method=RequestMethod.POST)
public #ResponseBody String validaProgetti(#RequestBody List<Integer> checkedRowList) {
System.out.println("ID progetti da aggiornare: " + checkedRowList);
List<Twp1007Progetto> progettiDaValidare = new ArrayList<Twp1007Progetto>();
for (int i=0; i<checkedRowList.size(); i++) {
System.out.println("ID PROGETTO: " + checkedRowList.get(i));
progettiDaValidare.add(progettoService.getProgetto(checkedRowList.get(i)));
}
progettoService.validaProgetti(progettiDaValidare);
return "redirect:ricercaValidazione";
}
So this method is correctly mapped and when the validaProgetti resource is called it is executed.
At the end of this method I don't return a view name that render a JSP page but I have to redirect to another method (that do something and render a JSP page). So, instead to return a view name, I redirect toward another resource:
return "redirect:ricercaValidazione";
Then in the same controller class I have declared this method that handle request toward this ricercaValidazione resource:
#RequestMapping(value = "ricercaValidazione", method=RequestMethod.POST)
public String ricercaValidazione(#ModelAttribute ConsultazioneFilter consultazioneFilter, Model model, HttpServletRequest request) {
RicercaConsultazioneViewObject filtro = null;
try {
filtro = new ObjectMapper().readValue(request.getParameter("filtro"), RicercaConsultazioneViewObject.class);
filtro.setSelStatoProgetto(3); // Progetti da validare
} catch (IOException e) {
logger.error(e);
}
consultazioneFilter = new ConsultazioneFilter(filtro);
model.addAttribute("consultazioneFilter", consultazioneFilter);
model.addAttribute("listaProgetti", new ListViewObject<Twp1007Progetto>(progettoService.getListaProgettiConsultazione(consultazioneFilter)) );
return "validazione/tabellaRisultati";
}
The problem is that it can't work and after the redirection can't enter into the ricercaValidazione() method.
I think that maybe the problem is that this ricercaValidazione() method handle POST request toward the ricercaValidazione resource and the return "redirect:ricercaValidazione"; maybe generate a GET request.
But I am not sure about it.
Why? What am I missing? How can I solve this issue?
Tnx
the redirect and fordward prefix are for resolving views; you are tring to redirect from one controller to another one. This can be done but redirect works in the following way
A response is sent to the browser with the redirect http status code and and url
The browser loads via GET the request URL
Your Spring controller (and the corresponding ammping method) is invocated if it matches the annotation params
From what you write I'm not sure this is what you really want; as you already noted there is a mismatch between HTTP methods (GET vs POST).
Your second method ricercaValidazione expects a filtro param in order to filter some data, but in the validaProgetti there is nothing similar, so it seems that the two controllers are not directly chainable. If what you want is to display a page after validaProgetti that shows a form and the the user can submit it you must add a method annotated with a method GET and url ricercaValidazione; the new method must return the view containing the form; which points via POST to url of validaProgetti. In this way you can redirect from ricercaValidazione to validaProgetti
Give mapping name of your controller with redirect like
redirect:/controll-mapping_name/ricercaValidazione
have a look on this question
Unable to redirect from one controller to another controller-Spring MVC

In Spring 3.2, does RedirectAttributes really pass the attributes themselves? Losing elements

NOTE: Ultimately my goal is simply to change the resulting URL from "/public/academy/register?param=blah" to a customized SEO-ified URL, as shown in the code. If I'm on the wrong path by trying to change from returning a "success view" JSP in the POST mapping to instead using post-redirect-get (which is good practice anyway), I'm open to suggestions.
Below are two methods: the POST request mapping to retrieve a registration form and process it, and the mapping method for the success page. I'm adding a flash attribute to redirect, which holds the form POSTed to the first method.
The form has a property hierarchy of Form -> Schedule -> Course -> Content -> Vendors, where each is its own class object except that Vendors is a SortedSet<Vendor>. When I load the success page, I get a Hibernate exception stating that the Vendors could not be lazily initialized. Why is it so far down the chain that it stops loading, or more basically, why is it losing this property value in the first place? When I set a breakpoint before the return, the RedirectAttributes object has the Vendors populated in the form I passed to it. What gives?
#RequestMapping(value = "/public/academy/register", method = RequestMethod.POST)
public String processSubmit(Site site, Section section, User user,
#ModelAttribute #Valid AcademyRegistrationForm form,
BindingResult result, Model model, RedirectAttributes redirectAttributes) {
validator.validate(form, result);
if (site.isUseStates()
&& StringUtils.isBlank(form.getBooker().getState())) {
result.rejectValue("booker.state",
"gui.page.academy.attendee.state");
}
if (result.hasErrors()) {
LOG.debug("Form has errors: {}", result.getAllErrors());
return "common/academy-registration";
}
// Form is valid when no errors are present. Complete the registration.
AcademyRegistration registration = form.toAcademyRegistration();
academyService.performRegistration(registration, site);
redirectAttributes.addFlashAttribute(form);
String redirectUrl = "redirect:/public/academy/register/"
+ registration.getSchedule().getCourse().getContent().getSeoNavTitle()
+ "-completed";
return redirectUrl;
}
#RequestMapping(value="/public/academy/register/**-completed", method=RequestMethod.GET)
public String displayRegistrationSuccess(#ModelAttribute("academyRegistrationForm") final AcademyRegistrationForm form)
{
SortedSet<Vendor> dummy = form.getSchedule().getCourse().getContent().getVendors();
return "common/academy-registration-success";
}
Here's the exception:
Oct 2, 2013 2:11:31 PM org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet jsp threw exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.horn.cms.domain.Content.vendors, could not initialize proxy - no Session
Here's what I assume happens (until you update with the details):
AcademyRegistration registration = form.toAcademyRegistration();
academyService.performRegistration(registration, site);
does some Hibernate queries and retrieves some persisten entities lazily, ie. they haven't been initialized. The loading that did happen probably occurred in some Hibernate Session (do you have a #Transactional somewhere?). The Session is closed and dis-associated from the lazily loaded object.
You then add the form object, which has some nested reference to the lazily loaded entity (it'll be a hibernate proxy), to the RedirectAttributes. This in itself is not a problem because all you're doing is passing a reference.
The request handling completes by sending a 302 response. Your client will then make the new request that is handled by displayRegistrationSuccess() and hits this line
SortedSet<Vendor> dummy = form.getSchedule().getCourse().getContent().getVendors();
Here, the form object is the same as was added in the previous request. One of the objects in this reference chain is your Hibernate proxy that was lazily initialized. Because the object is no longer associated with a Session, Hibernate complains and you get the exception you get.
It's not a good idea to pass around (across request boundaries) objects that depend on persistent state. Instead, you should pass around an ID that you use to retrieve the entity. The alternative is to fully initialize your object inside your academyService method.

Spring MVC PRG pattern with multiple tabs session workaround

I have the following sequence.
View1 (POST form) -> PostController (create model and redirect) -> GetController -> View2
I am using RedirectAttributes to pass model between PostController and GetController, I have
class PostController {
public String mypost(..., final RedirectAttributes redirectAttrs){
//create model
redirectAttrs.addFlashAttribute("model", model);
return "redirect:myget";
}
}
and
#SessionAttributes("model")
class GetController {
public ModelAndView myget(#ModelAttribute("model") final Model model){
ModelAndView mav = new ModelAndView("view2");
mav.addObject("model", model);
return mav;
}
}
When a user opens multiple tabs on a browser, then refresh the earlier tab, it will be overwritten by the latter opened tab.
I would like each tab to be independent, hope someone point me to the right direction.
Thanks.
Edit
The problem is at #SessionAttributes("model"). I use it because "Flash attributes are saved temporarily before the redirect (typically in the session) to be made available to the request after the redirect and removed immediately.". Thus, tabs are overwritten each other because the model in session is updated.
Typically when I use PRG I try to put all the relevant attributes in the redirect url. Something like this...
public String myPost(ThingBean thingBean){
Thing t = myService.updateThing(thingBean);
return "redirect:thingView?id="+t.getId();
}
That way when you intercept the redirected get request you don't have to rely on any previously stored session data.
#RequestMapping(value="thingView",method=RequestMethod.Get)
public String thingView(Map<String,Object> model, #RequestParam(value="id") Integer id){
model.put("thing",myService.getThing(id));
return "thing/viewTemplate";
}
Keeping your model as a session attribute is kind of like storing your page in a global variable. It's not a good idea. And when you hit refresh on a page the get request is only going to send what's in the url (and maybe some cookie data if you're using that).

passing data in querystring when using tiles

I am using tiles2 and spring in my project. When i am redirecting from spring controller to a jsp(the jsp page is mapped in tiles.xml file) page using query string like:
return "showRes.jsp?subSucc=ok";
it shows me:
javax.servlet.ServletException: Could not resolve view with name 'showRes.jsp?subSucc=ok'
I think this is wrong way to passing data using query string.
Please tell me how can i do this.
Thanks
Shams
The Problem is that return "showRes.jsp?subSucc=ok"; statment should return the name of a jsp and it is NOT a URL.
The normal Spring way to pass values is a jsp is to use a Model Map (of course there are some other ways, but this is the easysest to describe one).
Have a look at the ModelAndView and Model class. Create an instance of it, set the view name and add your parameter, and then return it instead of the String.
Model model = new Model();
model.addAttribute("subSucc","ok");
ModelAndView modelAndView = new ModelAndView("showRes.jsp", model);
//may without ".jsp" postfix - this depends on your configuration
return modelAndView;

Resources