I am confused about how to use #SessionAttributes - spring

I am trying to understand architecture of Spring MVC. However, I am completely confused by behavior of #SessionAttributes.
Please look at SampleController below , it is handling post method by SuperForm class. In fact, just field of SuperForm class is only binding as I expected.
However, After I put #SessionAttributes in Controller, handling method is binding as SubAForm. Can anybody explain me what happened in this binding.
-------------------------------------------------------
#Controller
#SessionAttributes("form")
#RequestMapping(value = "/sample")
public class SampleController {
#RequestMapping(method = RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute("form", new SubAForm());
return "sample/input";
}
#RequestMapping(method = RequestMethod.POST)
public String register(#ModelAttribute("form") SuperForm form, Model model) {
return "sample/input";
}
}
-------------------------------------------------------
public class SuperForm {
private Long superId;
public Long getSuperId() {
return superId;
}
public void setSuperId(Long superId) {
this.superId = superId;
}
}
-------------------------------------------------------
public class SubAForm extends SuperForm {
private Long subAId;
public Long getSubAId() {
return subAId;
}
public void setSubAId(Long subAId) {
this.subAId = subAId;
}
}
-------------------------------------------------------
<form:form modelAttribute="form" method="post">
<fieldset>
<legend>SUPER FIELD</legend>
<p>
SUPER ID:<form:input path="superId" />
</p>
</fieldset>
<fieldset>
<legend>SUB A FIELD</legend>
<p>
SUB A ID:<form:input path="subAId" />
</p>
</fieldset>
<p>
<input type="submit" value="register" />
</p>
</form:form>

When processing POST request, Spring does the following:
Without #SessionAttributes: Spring instantiates a new instance of SuperForm (type is inferred from the signature of register()), populates its properties by values from the form fields and passes it to the register() method.
With #SessionAttributes: Spring obtains an instance of model attribute from the session (where it was placed when processing GET due to presence of #SessionAttributes), updates its properties by values from the from fields and passes it to the register() method.
That is, with #SessionAttributes , register() gets the same instance of the model attribute object that was placed into the Model by getCreateForm().

Adding on to what #axtavt said: Suppose, in getCreateForm you are putting some values for a drop-down (list or map), or you are putting some values in form that you want in register method but you don't want them to show in form (not even in hidden fields). Now suppose that an error occurred in register method and you need to show the form again. To populate drop down values and other values that you would need in next post, you would have to repopulate them in form. The #SessionAttribute helps here as #axtavt very well described above.

#Controller
#SessionAttributes("test")
public class Controller{
Customer customer;
public Controller() {
super();
customer = new Customer();
}
#ModelAttribute("test")
public Customer getCustomer() {
customer.setName("Savac");
return customer;
}
#RequestMapping({"/index"})
public ModelAndView showMainPage (#ModelAttribute("test") Customer customer, ModelMap model, method = RequestMethod.GET) {
//in the view you set the name
return new ModelAndView("index");
}
#RequestMapping(value = "customer/{customerID}", method = RequestMethod.GET)
public ModelAndView viewAdvice(#PathVariable("customerID") int customerID, #ModelAttribute("test") Customer customer, ModelMap model) {
customer.setName("AnotherName");
model.addAttribute("test", customer);
return new ModelAndView("customer");
}
}

According to Spring reference documentation #ModelAttribute annotated method argument is resolved as follows:
Retrieve from model object if it is present (normally added via #ModelAttribute annotated methods)
Retrieve from HTTP session by using #SessionAttributes.
Create using URI path variable that matches the #ModelAttribute name through a converter
Create using default constructor and add it to Model.
A handler class can be annotated with #SessionAttributes with a list of names as its arguments. This is to instruct Spring to persist (in session) those data items present in the model data which match the names specified in #SessionAttributes annotation.
Thus in the SampleController, the post method's #ModelAttribute argument is resolved with #SessionAttributes field due to the resolution method mentioned above.

Related

How to pass onchange value from form:select into controller

I am using Spring MVC for developing the Java Web Application.
In my current scenario, i am stuck in one situation.
In my current project, I have a dropdown and i need to pass the selected value on onChange to controller for processing the result.
For showing dropdown i am using form:select (Spring Form).
Can you please suggest me how to pass selected value to controller on OnChange
<body>
<form:form method="post" modelAttribute="ad" action="/save">
<form:select cssClass="select" cssStyle="width:100%;margin-left:10%;"
path="work_type" items="${allworktype}" itemValue="id"
itemLabel="work_type" />
</form:form>
#Controller
public class WebController {
#Autowired
public AdminBuildingRepo adminBuildingRepo;
#ModelAttribute("allworktype")
public List<AdminBuilding> getblocks(Model model){
return adminBuildingRepo.findAll();
}
#GetMapping("/")
public String home(Model model) {
AdminBuilding ad= new AdminBuilding();
model.addAttribute("ad", ad);
return "index";
}
#RequestMapping(value = "/save", method = RequestMethod.GET)
public String transferForDevice( Model model) throws Exception {
System.out.println("*********");
//so now I can use "user" from #ModelAttribute
return "redirect:/admin";
}
}
Please help me how to pass value to controller on selection.
function formSubmit(){
$('form#myForm').attr({action: 'save'});
$('form#myForm').attr({modelAttribute: 'ad'});
$('form#myForm').attr({method: 'post'});
$('form#myForm').submit();
}
declare form with id myForm
call this function on select onchange

Add attribute to wildcard Requestmapping path?

Is there a way to add an attribute to all paths of a certain user?
I.e I am trying to reach the current logged in administrator on all pages the administrator can reach, but I don't want to add this attribute to every single controller.
Something like this, where I don't need to return anything:
#RequestMapping(value = {"admin/**"}, method = RequestMethod.GET)
public void adminPaths(ModelMap model) {
model.addAttribute("user", getPrincipal());
}
You can use #ModelAttributes on a method in a controller. An #ModelAttribute on a method indicates the purpose of that method is to add one or more model attributes to all controller methods:
#Controller
#RequestMapping("/admin")
public class AdminController {
...
#ModelAttribute
public void populateModel(Model model) {
model.addAttribute("user", getPrincipal());
// add more ...
}
...
}
#ModelAttribute methods in a controller are invoked before #RequestMapping methods, within the same controller.
For truly wildcard matching, you can use ControllerAdvice and ModelAttributes on methods together. Something like following:
#ControllerAdvice(annotations = Controller.class)
public class AdminPopulatorAdvice {
#ModelAttribute
public void populateModel(HttpServletRequest request, Model model) {
// examine the request
// if its path contains /admin, then add attribute
model.addAttribute("user", getPrincipal());
// add more ...
}
}
I forgot to mention that I was using Spring security.
Bohuslav pointed me into the right direction and ended up here: https://docs.spring.io/spring-security/site/docs/current/reference/html/taglibs.html

How to view data in Jsp when added in a model using spring mvc

I have the following flows for an application when a submit button is clicked:
1)The viewActivity method is called from ActivityController.java
ActivityController.java
#ActionMapping(params = "ActivityController=showActivity")
public void viewActivity(#RequestParam Integer index, ActionResponse response, Model model, #ModelAttribute Header header,
....
model.addAttribute("recoveryForm", new RecoveryForm(detailsResult.getDetails()));
response.setRenderParameter("ServiceController", "showService");
}
2) Then showRecovery method is called from serviceConroller as show below:
ServiceController.JAVA
#RenderMapping(params = "ServiceController=showService")
public String showRecovery(#ModelAttribute recoveryForm form, #ModelAttribute header header) {
.....
return service;
}
Then my service.jsp is displayed
Basically i have to display the value of a variable which is detailName found in DetailsResult.getDetails() object which i have added to my model as
it can be seen in viewActivity method found in ActivityController.java showed ealier.
I know when we add model.addAttribute it should be able to be displayed on this jsp using the following tag :
<form:input path="..." />
But in this case it is added to as a constructor argument as shown below:
model.addAttribute("recoveryForm", new RecoveryForm(detailsResult.getDetails()));
I have the following variable on my RecoveryForm:
public class RecoveryForm implements Serializable {
private CDetails Cdlaim;
private Action addAction;
private String addRemark;
private String remarks;
public RecoveryForm(CDetails Cdlaim) {
...
}
...
}
However i don't have the detailsResult in my RecoveryForm.
Any idea how i can get a value which is in DetailsResult.getDetails() in my service.jsp?
I believe you are looking at this the wrong way. The value of DetailsResult.getDetails() is obviously stored in RecoveryForm as a property somehow. So, I'm going to assume your RecoveryForm looks something like:
public class RecoveryForm {
private String details;
public RecoveryForm(String details) {
this.details = details;
}
public String getDetails() {
return details;
}
}
When you bind to a form in your jsp, you need to nest your <form:input ...> tag in a <form:form ...> tag:
<form:form commandName="recoveryForm" ...>
<form:input path="details" ... />
</form:form>
The commandName is key to telling the form the model object from which you will be pulling form values. In this case you are getting the details property from the RecoveryForm instance named recoveryForm. Make sense?

How to pass model attributes between method inside controller Spring MVC

Good day folks.
I have couple methods inside controller I want to pass model attributes between them,
First method gets data from a database:
#RequestMapping(value="/result", method=RequestMethod.GET)
public String resultHTML(#RequestParam String name, #ModelAttribute("fbosAttributes") FormBackingObjectSearch fbos,BindingResult bindingResult, Model model) throws Exception {
model.addAttribute("findAttributes", educationWebService.fetchByNam(fbos.getName()));
return "search";
And another method have to get attributes witch was created from method above:
#RequestMapping(value="/result.xls", method=RequestMethod.GET)
public String resultXLS(#ModelAttribute("findAttributes") ArrayList<FormDate> mylists, Model model) throws Exception {
model.addAttribute("findAttributesNew", mylists);
return "xlspage";
}
when I check for mylists.size() it shows/returns 0
Please help.
You can add session attributes to your controller class by this annotation:
#SessionAttributes({"findAttributes"})

Spring MVC binding extra objects

I'm getting some weird binding issue with Spring MVC 3.
My Controller request mapping looks like this:
#RequestMapping
public String save(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditForm form,
BindingResult formBindingResult,
ModelMap model,
#ModelAttribute("session") AdminSession session) {
// some validation etc
}
The UserEditForm:
public class UserEditForm {
private User user;
public User getUser() { ... }
public void setUser(User user) { ... }
}
The AdminSession:
public class AdminSession {
private User user;
public User getUser() { ... }
public void setUser() { ...}
}
What's happening is that when I submit my form, Spring is binding the User as I expect in my UserEditForm object, however, the AdminSession is also having it's User bound by Spring, in so far as it's property values are also updated.
I'm going to assume it's due to having a user property in both #ModelAttribute objects.
I thought that having the BindingResult after the UserEditForm form in the method signature would stop this? The objects are separate instances, and my form elements reference the UserEditForm object:
<#spring.bind "userEditForm.user.name" />
<input name="${spring.status.expression}" />
I've noticed that in the generated HTML it's outputting:
<input name="user.name" />
Hardcoding the name as userEditForm.user.name gives me errors, so that's not the way forward.
Is there anyway to stop this from happening?
That's the default behavior when you annotate a handler method parameter with the #ModelAttribute. Spring takes the request properties and matches them to properties of the objects annotated with #ModelAttribute. That's what Spring looks at when deciding what to do: your annotations.
Since both UserEditForm and AdminSession are annotated with #ModelAttribute and both have a User property, a request property named user.name will get bound to both User properties.
You tried to include the command name in the input name and got an error. That's because when binding occurs it occurs on your command object and Spring looks for properties on it (the bindinf path is relative to the command object) and off course the expression does not find any property with that name. If you want to use a full name you could wrap the form in another object and use that for your command instead, something like this:
public class UserEditFormWrapper {
private UserEditForm form;
public UserEditForm getForm() {
return form;
}
public void setForm(UserEditForm form) {
this.form = form;
}
}
Now you can use an expression like this in your inputs: form.user.name and when you submit to your handler method that now looks like this:
#RequestMapping
public String save(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditFormWrapper formWrapper,
BindingResult formBindingResult,
ModelMap model,
#ModelAttribute("session") AdminSession session) {
UserEditForm form = formWrapper.getForm();
// some validation etc
}
the binding won't be triggered since AdminSession does not have a form property.
That's one way to solve this but it's kind of a hack. You don't want to have the request parameters bound to AdminSession but that's part of your model so you must have created it somewhere and placed it on the model, right? If so, then remove it from the method's parameters and just get it from the model, something like:
#RequestMapping(value = "/test", method = { RequestMethod.POST })
public String handlePost(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditForm form,
BindingResult formBindingResult, ModelMap model) {
AdminSession session = (AdminSession) model.get("session");
// some validation etc
}

Resources