Strange behavior : Spring Session Attributes vs ModelAttribute - spring

I got a very strange issue with Spring MVC 4.0.6, hosted on JBoss 7.2. When updating an existing user, the submitted information sometimes get transferred to the POST RequestMapping controller method (validateUpdate method below).
UserController.java
#Controller
#RequestMapping(value = "/security/user")
#SessionAttributes(value = {"userForm", "user"})
public class UserController {
private final String CREATE_VIEW = "user/createOrUpdate";
private final String READ_VIEW = "user/readOrDelete";
private final String UPDATE_VIEW = CREATE_VIEW;
private final String DELETE_VIEW = READ_VIEW;
#Autowired
private LocationService locationService;
#Autowired
private SecurityService securityService;
#Autowired
private UserValidator userValidator;
#InitBinder("userForm")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(userValidator);
binder.setDisallowedFields("strId");
}
#ModelAttribute("regions")
public List<Region> populateRegions() {
Locale locale = LocaleContextHolder.getLocale();
List<Region> regions = this.locationService.lstRegions(locale.getLanguage());
return regions;
}
#RequestMapping(value = "/update/{intUserId}", method = RequestMethod.GET)
public String updateUser(#PathVariable Integer intUserId, Model model) {
User user = this.securityService.findUserByPk(intUserId);
if (user != null) {
UserForm userForm = new UserForm();
userForm.setUser(user);
model.addAttribute(userForm);
}
return UPDATE_VIEW;
}
#RequestMapping(value = "/update/validate", method = RequestMethod.POST)
public String validateUpdate(#Valid #ModelAttribute("userForm") UserForm userForm,
BindingResult result,
Model model,
RedirectAttributes redirectAttributes,
SessionStatus status) {
return this.performCreateOrUpdateOperation(userForm, result, model, redirectAttributes, status);
}
private String performCreateOrUpdateOperation(
UserForm userForm,
BindingResult result,
Model model,
SessionStatus status) {
if(result.hasErrors()) {
return UPDATE_VIEW;
} else {
User user = userForm.getUser();
this.securityService.validateCreateOrUpdateUser(result, user);
if (result.hasErrors() == false) {
if (userForm.isNew()) {
this.securityService.addUser(user);
} else {
this.securityService.updateUser(user);
}
model.addAttribute(user);
status.setComplete();
return "user/success";
} else {
return UPDATE_VIEW;
}
}
}
}
Form Bean
public class UserForm {
private String strUserIdToSearch = "";
private String strId = "0";
private String strUserId = "";
private String strFirstName = "";
private String strLastName = "";
private String strEmail = "";
private String strRegionId = "0";
private boolean booArchived = false;
public User getUser() {
User user = new User();
user.setIntId(Integer.valueOf(this.strId));
user.setStrUserId(this.strUserId);
user.setStrFirstName(this.strFirstName);
user.setStrLastName(this.strLastName);
user.setStrEmail(this.strEmail);
Region region = new Region(Integer.valueOf(this.strRegionId));
user.setRegion(region);
user.setBooArchived(this.booArchived);
return user;
}
public void setUser(User user) {
this.strUserIdToSearch = user.getStrUserId();
this.strId = String.valueOf(user.getIntId());
this.strUserId = user.getStrUserId();
this.strFirstName = user.getStrFirstName();
this.strLastName = user.getStrLastName();
this.strEmail = user.getStrEmail();
this.strRegionId = String.valueOf(user.getRegion().getIntId());
this.booArchived = user.getBooArchived();
}
...getters and setters...
}
JSP (removed styling for clarity)
crudMethod is a JSP tag returning "create", "read", "update" or "delete" depending on ${requestScope['javax.servlet.forward.request_uri']}
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<spring:url var="userFormAction" value="/rest/security/user/${crudMethod}/validate" />
<form:form id="userForm" modelAttribute="userForm" action="${userFormAction}" method="POST">
<form:hidden path="strId" />
<form:hidden path="strUserId" id="strUserId" />
<form:hidden path="strLastName" id="strLastName" />
<form:hidden path="strFirstName" id="strFirstName" />
<form:hidden path="strEmail" id="strEmail" />
<form:select id="strRegionId" path="strRegionId">
<form:option value="0" label="${strSelectRegionLabel}" />
<form:options items="${regions}" itemValue="intId" itemLabel="${strRegionLabel}" />
</form:select>
</form:form>
So, when I submit the form, and for instance, change the region to another ID in the list (let's say from 1 to 6). Sometimes it works, sometimes not. By "works", I mean I hit the success page, then I go back to see the user again. Sometimes it stays at 1, sometimes it's changed to 6.
I have found a pattern/workaround that works all the time to reproduce the issue:
Load the update form (UserController > updateUser)
Change the region from 1 to 6
Click Save. Form submits so the UserController.validateUser method is being invoked.
Got the success page. On that success page I got a link to the read operation for the user. Clicking that link, I realize that the region did not change (the main problem). There is a link to update a user on the read page.
Re-do the exact same change that I did at step #2.
Click Save. Form submits and I got the success page view.
Click the read hyperlink again, and now I see that the change worked.
Question is: WHY? Am I missing something??
Note: It's not related to the business layer. I have tested it and it's stable. That is certainly related to my use of the Spring MVC Framework.
Browser is IE11.
Thank you for help.
* UPDATE 2015-06-29 *
After a few more searches, I found that:
when it does NOT work, the request Content-Length header value is 0.
when it works, there is a value (eg: 146).
the request message body is always correct, like so:
strId=THE_ID&strUserId=THE_USERID&strLastName=THE_LASTNAME&strFirstName=THE_FIRSTNAME&strEmail=THE_EMAILADDRESS&strRegionId=THE_REGIONID&booArchived=false
Please note that "THE_REGIONID" is good every single time.
Related resources
Challenge-Response Authentication and Zero-Length Posts
Form Submit Not working on IE intermittently with Spring MVC in backend

<form:select id="intRegionId" path="strRegionId">
I think the id in the form:select is the culprit . It doesn't match the attribute name in UserForm, which is "strRegionId". And therefore it doesn't bind. Change the id value to strRegionId

Related

how we give error message when user enters string value in text box which accepts only integers

I have jsp page which contains one text box which is accepting only integers. That means i have command object which is having integer property. when user enter string in the box.how spring reacts.
Use Spring form tag library. When Spring binds a property and there is a type mismatch it adds a field error to the model with error code typeMismatch
Example
//form object
public class Person{
private int age;
//getters and setters
}
#Controller
#RequestMapping("/person")
public class PersonController{
#RequestMapping(method = RequestMethod.GET)
public String showPersonForm(Model model){
Person person = new Person();
model.addAttribute("person" , person";
return "person";
}
#RequestMapping(method = RequestMethod.POST)
public String savePerson(Person person , BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "person";
}
//return success url
}
}
//person.jsp
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<form:form method="post" modelAttribute="person" >
<form:input path="age" type="text" />
<form:errors path="age" />
<input type="submit"/>
</form:form>
You will need to define a MessageSource bean in your application context to allow for resolution of your error messages for example using Java Config
#Bean
public MessageSource messageSource(){
ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
resourceBundleMessageSource.setBasenames("errors");
return resourceBundleMessageSource;
}
The errors.properties file should be in the root of the application context
//errors.properties
typeMismatch=The value you entered is invalid #for all type mismatches
typeMismatch.age=The age you have entered is invalid #for field specific type mismatched

Accessing Session Object in JSP

I am setting a Session using Spring HttpSession.
Here session is set as a object containing four/five fields.
I can access this session at my jsp but when i want to access the individual records within the Sessionobject. it gives null.
Help please
// This is session data model
public class UserServiceModel implements Serializable
{
public String MobileNo;
public String CityName;
public String UserName;
public String UserPwd;
public String UserRole;
public String Device;
public UUID SessionID;
// getters,setters
}
// code for setting session
StaffModel record1 = (StaffModel) data.get("records");
UserServiceModel sessionData = new UserServiceModel();
sessionData.setMobileNo(record1.getMobileNo());
sessionData.setCityName(record1.getCity());
sessionData.setUserName(record1.getFirstName());
sessionData.setUserRole(record1.getRole());
sessionData.SessionID = UUID.randomUUID();
System.out.println("SessionID=" + sessionData.MobileNo);
sessionObj.setAttribute("SessionData" , sessionData); // setting session Data
// this is jsp code
<% String ses = (String)session.getAttribute("SessionData.MobileNo");
out.print("Hello User:"+ses);
%>
Output gives: null
You have update the JSP code like below. As session.getAttribute() method return object as value.
<%
UserServiceModel obj= (UserServiceModel)session.getAttribute("SessionData");
String ses = obj.getMobileNo();
out.print("Hello User:"+ses);
%>
I have solved this
Here is the code:
sUserName = <% UserServiceModel obj = (UserServiceModel)session.getAttribute("SessionData");
out.print(obj.getUserName());
// at top of the jsp page i have to import the model UserServiceModel
<%# page import="org.kmsg.listobject.UserServiceModel"%>
and now userName which exist within the object SessionData is accessible in variable "sUserName"
Thanks...

jsr-303 validation in spring mvc application doesn't validate

I'm having some trouble setting up validation for a form in spring.
The bean I would like to validate look like this:
public class RegistrationForm extends ProjectXUser {
#NotEmpty
private String password2;
#NotBlank
#AssertTrue
private Boolean agreedToConditions;
...
ProjectXUser inherits from BaseUser which has some more properties which are also annotated.
My controller looks like this:
#Controller
public class RegistrationController {
private static final String REGISTRATION_JSP = "registration";
#ModelAttribute("registrationForm")
public RegistrationForm getRegistrationForm() {
return new RegistrationForm();
}
#RequestMapping(value = { "/registratie/jaar", "registratie/proef" }, method = RequestMethod.GET)
public String year() {
return "registration";
}
#RequestMapping(value = { "/registratie/jaar", "registratie/proef" }, method = RequestMethod.POST)
public ModelAndView register(#Valid RegistrationForm registrationForm, BindingResult result) {
if (result.hasErrors()) {
return new ModelAndView(REGISTRATION_JSP);
} else {
return new ModelAndView("redirect:/registratie/success");
}
}
}
My spring configuration file contains:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<mvc:annotation-driven />
I've read in the spring documentation that if a jsr-303 validator is present in the class path spring will detect it automatically and use it. So i've added hibernate-validator to my pom.
But when I debug my controller I can see the registrationForm contains the values I've filled in. But results always has 0 errors. Even if I enter some explicit wrong input in my form fields.
You need to return the result in the ModelAndView in the case where there are errors. All you are returning is an empty mav.
See: http://forum.springsource.org/showthread.php?117436-Spring-MVC-3-and-returning-validation-errors-to-page-from-Valid&highlight=bindingresult for an example.
If this bit of your code is firing and redirecting back to your registration page
if (result.hasErrors()) {
return new ModelAndView(REGISTRATION_JSP);
}
Then your JSR-303 validation is being picked up. You need to display your errors on your JSP page like this
<form:errors path="password2" cssClass="error" />
where cssClass="error" is the CSS you want to display the error with. It's automatically put into a <div> for you.

Nested Velocity template with Spring formView

I have a Spring app that I'd like to add a login feature to. I'd like to put the login form in the header of the site. This means that it'll be included on several pages. When defining the controller that the form submits to, what do I specify as the formView?
Is it possible to specify the login template that's included in header (that's included in each head :-)) as the formView?
Thanks for the help. If anything is unclear than I'm happy to provide more details or show code.
Nevermind. I realized that it doesn't matter whether the Velocity template is included in another file. I added this to the template:
<form method="POST">
#springBind("credentials.*")
and my controller looks like this:
#Controller
public class SplashController implements Serializable {
protected final Log logger = LogFactory.getLog(getClass());
private static final long serialVersionUID = 7526471155622776147L;
#ModelAttribute("credentials")
public LoginCredentials getFormBean() {
return new LoginCredentials();
}
#RequestMapping(method = RequestMethod.GET)
public String showForm() {
logger.info("In showForm method of SplashController");
return "splash";
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView onSubmit(LoginCredentials credentials, BindingResult bindingResult) {
logger.info("In onSubmit method of SplashController");
logger.info("Username = " + credentials.getUsername());
logger.info("Password = " + credentials.getPassword());
ModelAndView modelAndView = new ModelAndView("home");
return modelAndView;
}
}
and it works.

Spring MVC form:errors not showing up

Apologies if this question has been asked before. I'm hoping that someone can step in and help me figure out why my form validation errors aren't showing up.
I'm using Spring 3.0.3 and Hibernate, and I'm using jsr-303 validation to validate my form inputs. I have a Spring controller that handles GETting a page that contains a form that is created with the help of Spring's form taglib. In this form a user is able to change their name and have it saved to the database. If any of the input is empty then the page with the form should be displayed again with error messages. The same controller handles the page's submission. It seems that the controller is functioning properly in most respects, but when there is an error in the user submitted form, no errors are showing up on the page.
Here is what form looks like:
<form:form commandName="changeNameCommand">
<form:errors path="*" cssClass="errorBox" />
<table width="100%" border="0" cellspacing="5" cellpadding="5" align="left">
<tr>
<td><b>First Name:</b></td>
<td><form:input path="firstName" value="${user.firstName}" /></td>
</tr>
<tr>
<td><b>Last Name:</b></td>
<td> <form:input path="lastName" value="${user.lastName}" /></td>
</tr>
</table>
</form:form>
Note that there is a user object in the view that is used to populate the form with the user's current first and last name. This is working properly.
The controller looks something like this:
#Controller
#RequestMapping(value = "/account/settings/change-name")
#SessionAttributes("changeNameCommand")
public class ChangeNameController {
#ModelAttribute("changeNameCommand")
public ChangeNameCommand getCommand() {
return new ChangeNameCommand();
}
#RequestMapping(method = RequestMethod.GET)
public ModelAndView getChangeNamePage(HttpServletRequest req) {
ModelAndView mav = new ModelAndView("Account.ChangeName");
mav.addObject("page_title", "Change Name");
return mav;
}
#RequestMapping(method = RequestMethod.POST)
public String doChangeName(
#ModelAttribute("changeNameCommand")
#Valid ChangeNameCommand command,
BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
return "redirect:/account/settings/change-name";
}
// Code here to persist updated user first and last name to database...
status.setComplete();
return "redirect:/home";
}
}
I'm using Tiles 2.1.2 to compose pages and Urlrewrite 3.1.0 to help form friendly urls.
The ChangeNameCommand class looks something like this:
import org.hibernate.validator.constraints.NotEmpty;
public class ChangeNameCommand {
#NotEmpty(message = "You must provide a first name.")
private String firstName;
#NotEmpty(message = "You must provide a last name.")
private String lastName;
#NotEmpty(message = "Your password is required to make changes.")
private String currentPassword;
// Getters and setters here...
}
When debugging, I see that when there is not input for either the first or last name the BindingResult instance does contain errors. My concern is the redirect when there is an error. I've seen other questions here that just return the view name instead of using the redirect: prefix. I tried that but (I think) because of the way I'm using Urlrewrite and the way my servlet mapping is set up Spring returns an error. I've tried returning
/account/settings/change-name
/web/account/settings/change-name
/mywebsite/web/account/settings/change-name
but to no avail. FWIW, here is my servlet-mapping:
<servlet-mapping>
<servlet-name>mywebsite</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
Any help much appreciated!
using redirect makes all request attributes (including errors and the whole model) disappear. So, don't use redirect, or use the session to temporarily store the data, or use conversations. Or figure out how to use something like a flash-scope (I'm about to in a while)
Another thing - using UrlRewriteFilter with spring-mvc is uncalled for. You have full control over your beatuful REST-like URLs with spring-mvc only.
Here is how I solved my problem. To start off with, I didn't want to drop my use of UrlRewriteFilter and Tiles. However, the problem with this was that, in the case of errors, I couldn't just return the path, as indicated in the controllers RequestMapping annotation. Below is my solution, with the redirect removed in the case of errors, in doChangeName().
#Controller
#RequestMapping(value = "/account/settings/change-name")
#SessionAttributes("changeNameCommand")
public class ChangeNameController {
#ModelAttribute("changeNameCommand")
public ChangeNameCommand getCommand() {
return new ChangeNameCommand();
}
#RequestMapping(method = RequestMethod.GET)
public ModelAndView getChangeNamePage() {
ModelAndView mav = new ModelAndView("Account.ChangeName");
mav.addObject("page_title", "Change Name");
return mav;
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView doChangeName(#ModelAttribute("changeNameCommand") #Valid ChangeNameCommand command,
BindingResult result, SessionStatus status) {
if (result.hasErrors
ModelAndView mav = new ModelAndView("Account.ChangeName");
mav.addObject("page_title", "Change Name");
return mav;
}
// Code here to persist updated user first and last name to database...
status.setComplete();
RedirectView view = new RedirectView("/home");
return new ModelAndView(view);
}
}
Thanks to everyone who helped me out on this!

Resources