I am trying to edit the Roles of an user using html and thymeleaf. The point is that everything works ok, I can edit the username, the password, etc but when it comes to the roles, I get null when saving the List. I can't find anything useful on the internet and I'm struggling with this for a couple of weeks now, I need a way to edit those roles, so the page will return a List containing those roles that are checked.
Here I have the UserDetails:
@Getter
@Setter
public class MyUserDetails implements UserDetails {
private final User user;
public MyUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
.map(role->new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
public int getId(){return this.user.getId();}
public User getUser(){
return this.user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.isEnabled();
}
}
Here is the User:
@Builder
@Getter
@Setter
@Entity
@Table(name="user",schema = "public")
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@Column(name="user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotEmpty(message = "You must provide a username!")
@Size(min=5, max=30)
private String username;
@NotEmpty(message = "You must provide a password!")
@ValidPassword
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private boolean enabled;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name="user_roles",
joinColumns = @JoinColumn(name="user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<Role> roles;
@JoinColumn(name="last_played")
private LocalDate lastPlayed;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public List<String> getRoleNames(){
return roles.stream()
.map(Role::getName)
.collect(Collectors.toList());
}
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Reward> rewards;
@Override
public String toString() {
return "User{"
"id=" id
", username='" username '\''
", password='" password '\''
", enabled=" enabled
", roles=" roles
", lastPlayed=" lastPlayed
", rewards=" rewards
'}';
}
}
Here is the Role class:
@Getter
@Setter
@Entity
@Table(name="role",schema = "public")
public class Role {
@Id
@Column(name="role_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
}
The controller:
@Controller
@RequiredArgsConstructor
public class EditUserController {
private final IUserDetailsService userService;
private final RoleService roleService;
@PostMapping(USERS_SAVE)
public String saveEdit( @ModelAttribute(AttributeNames.USER) User user,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
return EDIT_USER;
}
userService.updateUser(user);
return REDIRECT USERS;
}
@GetMapping("/users/{id}")
public String editUser(@PathVariable int id, Model model){
List<Role> roleList = roleService.findAll();
UserDetails user = userService.findUserById(id);
model.addAttribute(AttributeNames.USER, user);
model.addAttribute(AttributeNames.ROLE_LIST, roleList);
return EDIT_USER;
}
@DeleteMapping(Mappings.USERS_DELETE_ID)
public String deleteUser(@PathVariable int id){
userService.deleteUser(id);
return REDIRECT USERS;
}
}
And finally the html part for the roles :
<div >
<label >Roles</label>
<div >
<th:block th:each="role:${roleList}">
<input type="checkbox"
name="roles"
th:field="*{user.roles}"
th:value="${role.id}"
th:text="${role.name}" />
</th:block>
</div>
</div>
Here is a picture with the form, this user has both roles of user and admin, and they are both checked when entering the page, but if I go for the update, the roles will be modified to null. Can you help me figure this out?
https://i.stack.imgur.com/DWpRw.png
CodePudding user response:
The mapping does not need to be many to many, Use a uni directional as this:
@OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = @JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private List<Role> roles;
Because a role does not need to have references for corresponding users it's being used by. Then have the table created 'user_role'. After that if you change roles and save the user it should be updated in user_role table.
UPDATE Could you try this when returning user:
@GetMapping(path = "/test")
@ResponseStatus(HttpStatus.OK)
public User test() {
User user = // get user here by id
//set roles there
return user;
}
CodePudding user response:
Apparently my problem was that I was adding the MyUserInterface class to the Model insted Of User. So in the Controller I added this:
MyUserDetails userDetails=userService.findUserById(id);
User user = userDetails.getUser();
And now thymeleaf can get access to the list of the User directly.
<th:block th:each="role:${roleList}">
<input type="checkbox"
name="roles"
th:field="*{roles}"
th:value="${role.id}"
th:text="${role.name}" />
</th:block>