I am using Spring Roo
to create a simple Web application with user management. I have an AppUser
entity with (among others) a password
field which when updating other attributes of the entity is being set to null even though I haven't changed it at all.
To implement the use case of updating an AppUser
's instance, Roo
generated two methods updateForm
and update
.
The former method prepares the form for editing the entity:
@RequestMapping(value = "/{id}", params = "form", produces = "text/html")
public String updateForm(@PathVariable("id") Long id, Model uiModel) {
AppUser appUser = AppUser.findAppUser(id);
populateEditForm(uiModel, appUser);
System.out.println("On Update Form - " + appUser);
return "users/update";
}
The second method checks for validation errors and merges the entity into the JPA context:
@RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String update(@Valid AppUser appUser, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
System.out.println("On Update - " + appUser);
if (bindingResult.hasErrors()) {
populateEditForm(uiModel, appUser);
return "users/update";
}
uiModel.asMap().clear();
appUser.merge();
return "redirect:/users/" + encodeUrlPathSegment(appUser.getId().toString(), httpServletRequest);
}
The default value of the password
field in the edit form is blank, but on it's mutator method setPassword
, I am not updating password when null or blank is sent.
public void setPassword(String password) {
// Don't update password if null or blank is sent
if (password != null && !password.isEmpty()) {
String encodedPassword = passwordEncoder.encodePassword(password, null);
this.password = encodedPassword;
}
}
The outcome in the standard output is the following:
On Update Form - AppUser[username=user,password=04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb,enabled=true,appRole=AppRole[rolename=ROLE_USER,id=2,version=1],customer=Customer[code=<null>,description=default,address=<null>,cap=<null>,locality=<null>,province=<null>,telephone=<null>,fax=<null>,vat=<null>,fiscalCode=<null>,sex=<null>,customerType=<null>,insertionDate=<null>,lastVisit=<null>,id=0,version=<null>],id=2,version=0]
On Update - AppUser[username=user,password=<null>,enabled=true,appRole=AppRole[rolename=ROLE_USER,id=2,version=1],customer=Customer[code=<null>,description=default,address=<null>,cap=<null>,locality=<null>,province=<null>,telephone=<null>,fax=<null>,vat=<null>,fiscalCode=<null>,sex=<null>,customerType=<null>,insertionDate=<null>,lastVisit=<null>,id=0,version=<null>],id=2,version=0]
Evidently the password is being changed, but I can't understand where.
Edit:
The jspx view I'm using to update the AppUser information is the following:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
<form:update id="fu_it_digitelematica_leovince_domain_AppUser" modelAttribute="appUser" path="/users" versionField="Version" z="9/aOTZ+wTF1ijLMgiWkx1JcbpG8=">
<field:input field="username" id="c_it_digitelematica_leovince_domain_AppUser_username" required="true" z="H3JOKuXes6/RLOnAd4x1v0j3Njo="/>
<field:input field="password" id="c_it_digitelematica_leovince_domain_AppUser_password" required="false" type="password" z="user-managed"/>
<field:checkbox field="enabled" id="c_it_digitelematica_leovince_domain_AppUser_enabled" z="4WIlYpFXs46FLp4l8koJ2DXmWz8="/>
<field:select field="appRole" id="c_it_digitelematica_leovince_domain_AppUser_appRole" itemValue="id" items="${approles}" path="/approles" required="true" z="KDeZRlxip/VWLtb/kN58hn2zZdg="/>
<field:select field="customer" id="c_it_digitelematica_leovince_domain_AppUser_customer" itemValue="id" items="${customers}" path="/customers" required="true" z="rEMR1CzjRLuSkoIKTHmq4N+qZoI="/>
</form:update>
</div>
This is the standard way: When you merge a entity you override all its values. Let me explain:
On point #2 entityManager doesn't know anything about Request and it's values, so, when you merge the user, if password
field is null
that will be the new value for field on DB. This is why Spring Roo generated views includes all fields in update form.
To solve this problem you must load an attached instance of entity and fill the missing values of binding instance (warning, not tested code):
@RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String update(@Valid AppUser appUser, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
System.out.println("On Update - " + appUser);
if (bindingResult.hasErrors()) {
populateEditForm(uiModel, appUser);
return "users/update";
}
uiModel.asMap().clear();
User attached = User.findUserById(user.getId());
appUser.setPassword_encoded(attached..getPassword());
appUser.merge();
return "redirect:/users/" + encodeUrlPathSegment(appUser.getId().toString(), httpServletRequest);
}
You need a mutator which set password without encode it.
Good luck!