Getting error when trying to perform form validation - java

I am making a registration page for a website, but when I go to the page I get the error: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor'
Controller
#RequestMapping(value = { "/signup" }, method = RequestMethod.POST)
public ModelAndView createUser(#Valid AppUser appUser, BindingResult bindingResult) {
ModelAndView model = new ModelAndView();
AppUser appUserExists = appUserService.findByEmail(appUser.getEmail());
if (appUserExists != null) {
bindingResult.rejectValue("email", "error.user", "This email already exists!");
}
if (bindingResult.hasErrors()) {
model.setViewName("user/signup");
} else {
appUserService.saveUser(appUser);
model.addObject("msg", "User has been registered succesfully!");
model.addObject("appuser", new AppUser());
model.setViewName("user/login");
}
return model;
}
Form
<form class="form-horizontal" role="form" th:action="#{/signup}" th:object="${appUser}" method="post" style="border: 1px solid #ccc">
<label for="name">First Name</label>
<input type="text" th:field="*{firstname}" class="form-control" id="firstname" placeholder="First Name" required autofocus />
<label for="name">Last Name</label>
<input type="text" th:field="*{lastname}" class="form-control" id="lastname" placeholder="Last name" required autofocus />
<label for="email"><b>Email</b></label>
<input type="email" th:field="*{email}" class="form-control" id="email" placeholder="email#domain.com" required autofocus />
<label for="psw"><b>Password</b></label>
<input type="password" th:field="*{password}" class="form-control" id="password" placeholder="Password" required />
</form>
AppUser Class
#Entity
#Table(name = "user")
public class AppUser {
#Id
#Column(name = "id_user", length = 10, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "firstname", length = 30, nullable = false, unique = true)
private String firstName;
#Column(name = "lastname", length = 30, nullable = false, unique = true)
private String lastName;
#Column(name = "email", length = 30, nullable = false, unique = true)
private String email;
#Column(name = "password", length = 500, nullable = false, unique = false)
private String password;
#Column(name = "active", nullable = false)
private int active;
public AppUser() {
super();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
....
}
From what I can see, there's nothing wrong here so I'm stuck.
Any ideas?

The appuser variable in the controller should be appUser. And the lastname and the firstname in the template must be lastName and firstName like in the class.

Related

Invalid property 'projection' of bean class

Hi I need a little help with my code. I tried solutions online but I couldn't fix my bug. I working in java and spring with mysql and tymeleaf. My error is short:
Invalid property 'projection' of bean class [com.bakulic.CinemaTicketShop.model.dto.requests.CreateOrUpdateProjectionDTO]: Bean property 'projection' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
And I found that my problem is in the html file createProjectionForm whitch I will put below as well as the entities and all that is needed. My projection entity has a relation to Hall and Movie and I'm not sure how to get attributes of Movie and Hall in my html. For the fiel I tried to put ${projection.hall.name} and ${projection.movie.movieName}. Zou will find it in the code.
Thank you in advance.
#Data
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "projections")
public class Projection {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int projectionId;
#Column(name = "date")
private String date;
#Column(name = "startTime")
private String startTime;
#ManyToOne
#JoinColumn(name = "idHall")
private Hall hall;
#ManyToOne
#JoinColumn(name = "idMovie")
private Movie movie;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "projection", cascade = CascadeType.ALL)
private List<Seat> seatList;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "projection", cascade = CascadeType.ALL)
private List<Ticket> ticketList;
}
#Entity
#Table(name = "halls")
#Data
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Hall {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int hallId;
#Column(name = "name")
private String name;
#Column(name = "numberofseats")
private Integer numberOfSeats;
#Column(name = "description")
private String description;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "hall", cascade = CascadeType.ALL)
private List<Projection> projectionList;
}
#Entity
#Table(name = "movies")
#Data
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int movieId;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column (name = "length")
private String length;
#Column(name = "picture")
private String picture;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "movie", cascade = CascadeType.ALL)
private List<Projection> projectionList;
}
#Data
public class ProjectionDTO implements Serializable {
private int id;
private String date;
private String startTime;
private List<Seat> seatList;
private List<Ticket> ticketList;
private Hall hall;
private Movie movie;
public ProjectionDTO(Projection projection){
if(projection != null){
this.id = projection.getProjectionId();
this.date = projection.getDate();
this.startTime = projection.getStartTime();
this.seatList = projection.getSeatList();
this.ticketList = projection.getTicketList();
this.hall = projection.getHall();
this.movie = projection.getMovie();
}
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class CreateOrUpdateProjectionDTO implements Serializable {
private String date;
private String startTime;
private List<Seat> seatList;
//aditional info
private String name;
private String movieName;
}
/** create projection*/
public Projection createProjection(CreateOrUpdateProjectionDTO createProjectionDTO){
if(createProjectionDTO == null){
throw new InvalidDataException("Projection cannot be null");
}
timeValidator.checkTime(createProjectionDTO.getStartTime());
dateValidator.checkDate(createProjectionDTO.getDate());
Projection proj = new Projection();
proj.setDate(createProjectionDTO.getDate());
proj.setStartTime(createProjectionDTO.getStartTime());
Hall hall = proj.getHall();
if(hall == null){
hall = new Hall();
}
hall.setName(createProjectionDTO.getName());
Integer numOfSeats = hall.getNumberOfSeats();
Movie movie = proj.getMovie();
if(movie == null){
movie = new Movie();
}
movie.setName(createProjectionDTO.getMovieName());
List<Seat> list = createProjectionDTO.getSeatList();
for(int i=1; i<=numOfSeats; i++ ){
Seat seat = new Seat();
seat.setSeatNumber(i);
seat.setStatus("empty");
list.add(seat);
}
Projection projCreated = projectionRepository.save(proj);
log.info(String.format("Projection %s has been created.", proj.getProjectionId()));
return projCreated;
} The function is similar for update.
<
<!DOCTYPE html>
<html lang="en" xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Create theater</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
<body style="background-color:lightgrey;">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<img src="../pictures/cinemalogo.png" th:src="#{pictures/cinemalogo.png}" class = "center"alt="logo" width="120" height="100"/>
<ul class="navbar-nav">
<li class="nav-item">
<h1>Our cinema!</h1>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
</ul>
</nav>
</nav>
<br>
<br>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>Add projection</h1><br>
<form th:action="#{/projection}" method="post" th:object="${projection}">
<div class="form-group">
<label class="control-label" for="date"> Date </label>
<input id="date" class="form-control" th:field="*{date}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="startTime"> Start time</label> <input
id="startTime" class="form-control" th:field="*{startTime}" required
autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="hallName"> Hall name </label> <input
id="hallName" class="form-control" th:field="*{projection.hall.name}" required
autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="movieName"> Movie name </label> <input
id="movieName" class="form-control" th:field="*{projection.movie.movieName}" required
autofocus="autofocus" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Submit</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
Use th:field="*{hall.name}" instead of th:field="*{projection.hall.name}" and th:field="*{movie.movieName}" instead of th:field="*{projection.movie.movieName}"

Pass two objects with bidirectional One to One relationship into one Thymeleaf form

I'm working on a JPA project in which I have two models: TableDecor and Product Tag, which look like this:
#Entity(name="table_decor")
public class TableDecor {
#Id
#IdConstraint
private String id;
#Positive
#Column(nullable = false)
private int width;
#Positive
#Column(nullable = false)
private int height;
#NotNull
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private Color color;
#NotNull
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private Fabric fabric;
#ManyToMany
#JoinTable(
name = "seller",
joinColumns = #JoinColumn(name = "table_decor_id"),
inverseJoinColumns = #JoinColumn(name = "seller_id"))
private List<Seller> sellers;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="producer_id")
private Producer producer;
#OneToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "product_tag_id", referencedColumnName = "id")
private ProductTag productTag;
// getters, setters
#Entity(name="product_tag")
#Table
public class ProductTag {
#Id
#GeneratedValue
private UUID id;
#NotNull
#Enumerated
#Column(nullable = false)
private ProductState productState;
#Column(nullable = false)
#NotNull
private float price;
#OneToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "table_decor_id", referencedColumnName = "id")
private TableDecor tableDecor;
// getters, setters
As you can see they're connected with each other with bidirectional one to one relationship. Since one can't exist without another, how can I create a Thymeleaf add form that could create both objects at the same time? The controller function currently looks like this:
#GetMapping("/table-decor/add")
public String showAddForm(TableDecor decorToAdd, ProductTag productTag) {
return "table-decor-add";
}
#PostMapping("/table-decor/add-new")
public String addTableDecor(#Valid TableDecor decorToAdd, #Valid ProductTag productTag, BindingResult bindingResult, Model model){
if (bindingResult.hasErrors()) {
System.out.println("Error");
return "table-decor-add";
}
decorToAdd.setProductTag(productTag);
tableDecorService.addTableDecor(decorToAdd);
model.addAttribute("allTableDecor", tableDecorService.getAllTableDecor());
return "redirect:/table-decor";
and the form:
<form action="#" method="post" th:action="#{/table-decor/add-new}">
<div><input name="id" type="text" th:value="${tableDecor.id}"></div>
<span th:if="${#fields.hasErrors('tableDecor.id')}" th:errors="*{tableDecor.id}"></span>
<div><input name="width" type="text" th:value="${tableDecor.width}"></div>
<span th:if="${#fields.hasErrors('tableDecor.width')}" th:errors="*{tableDecor.width}"></span>
<div><input name="height" type="text" th:value="${tableDecor.height}"></div>
<span th:if="${#fields.hasErrors('tableDecor.height')}" th:errors="*{tableDecor.height}"></span>
<select name="color">
<option th:each="color : ${T(jee.labs.lab05.domain.Color).values()}"
th:text="${color}"
th:value="${tableDecor.color}">
</option>
</select>
<select name="fabric">
<option th:each="fabric : ${T(jee.labs.lab05.domain.Fabric).values()}"
th:text="${fabric}"
th:value="${tableDecor.fabric}">
</option>
</select>
<select name="productState">
<option th:each="productState : ${T(jee.labs.lab05.domain.ProductState).values()}"
th:text="${productState}"
th:value="${productTag.productState}">
</option>
</select>
<div><input name="price" type="text" th:value="${productTag.price}"></div>
<span th:if="${#fields.hasErrors('productTag.price')}" th:errors="*{productTag.price}"></span>
<div><input type="submit" th:value="Submit"></div>
</form>

Spring web mvc + Thymeleaf ModelAttribute editing the list in the object

I have a form for editing a user, the user has roles, which are a list of objects of type Authority, I want to be able to use checkboxes (optional) to set the roles that the user will have, but I have no idea how to implement the form in thymeleaf and how to pass the user object with the given roles to the controller.
It's my user
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
#Entity
#Table(name = "users")
#Data
#NoArgsConstructor
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="username", nullable = false, unique = true)
#NotBlank #Size(min=5, message = "Не менeе 5 знаков")
private String username;
#NotBlank #Size(min=5, message = "Не менeе 5 знаков")
#Column(name = "password")
private String password;
#Column(name = "enabled")
private boolean enabled;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "email", nullable = false, unique = true)
private String email;
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
}, fetch = FetchType.EAGER)
#JoinTable(
name = "users_authorities",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "authority_id")
)
private Set<Authority> authorities = new HashSet<>();
public User(String username, String password, boolean enabled,
String name, String surname, String email) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.name = name;
this.surname = surname;
this.email = email;
}
public void addAuthority(Authority authority) {
this.authorities.add(authority);
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
}
It's my edit user form contoller
#GetMapping("/users/{id}/edit")
public String editUser(#PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findById(id));
model.addAttribute("allAuthorities", authorityService.findAll());
return "users/edit-user";
}
It's my edit user form view
<body>
<form th:method="PUT" th:action="#{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<input type="hidden" th:field="*{id}" id="id">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth:${allAuthorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="checkbox" name="authorities" th:checked="${user.authorities.contains(auth)}">
</label>
</div>
<input type="submit" value="Edit">
</form>
</body>
It's put contoller, it getting the data from my form
#PutMapping("/users/{id}")
public String editUser(#PathVariable("id") Long id,
#ModelAttribute("user") User user,
#RequestParam("authorities") List<Authority> authorities) {
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
And it's my Authority class if you need
#Entity
#Table(name = "authorities")
#Data
#NoArgsConstructor
public class Authority implements GrantedAuthority {
#Id
private Long id;
#Column(name = "authority")
private String authority;
#Transient
#ManyToMany(mappedBy = "authorities")
private Set<User> users;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
I'm try to pass the list of roles separately from the user object, but this also doesn't work and gives a bad request error.
To solve the problem, I added a new checked field to Entity authority
#Entity
#Table(name = "authorities")
#Data
#NoArgsConstructor
public class Authority implements GrantedAuthority {
#Id
private Long id;
#Column(name = "authority", nullable = false, unique = true)
private String authority;
#Transient
private boolean checked;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
Before sending the view to it, I changed the authorites field of the user object.
#GetMapping("/users/{id}/edit")
public String editUser(#PathVariable("id") Long id, Model model) {
User user = userService.findById(id);
List<Authority> allAuthorities = authorityService.findAll();
for(Authority auth : allAuthorities) {
if(user.getAuthorities().contains(auth)) {
auth.setChecked(true);
}
}
user.setAuthorities(allAuthorities);
model.addAttribute("user", user);
return "users/edit-user";
}
I also changed the thymeleaf template
<form th:method="PUT" th:action="#{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth, itemStat: ${user.authorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].id}">
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].authority}">
<input type="checkbox" th:checked="${auth.checked}"
th:field="*{authorities[__${itemStat.index}__].checked}">
</label>
</div>
<input type="submit" value="Edit">
</form>
My put controller looks like this
#PutMapping("/users/{id}")
public String editUser(#PathVariable("id") Long id,
#ModelAttribute("user") User user) {
List<Authority> userAuthorities = user.getAuthorities();
userAuthorities.removeIf(auth -> !auth.isChecked());
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
My save and delete methods from UserService look like this(If that would be useful)
#Override
#Transactional
public User save(User user) {
Optional<User> userFromDB = userRepository.findByUsername(user.getUsername());
Optional<Authority> userRole;
if(userFromDB.isPresent()) {
return null;
}
userRole = authorityRepository.findByAuthority("ROLE_USER");
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRole.ifPresent(user::addAuthority);
return userRepository.save(user);
}
#Override
#Transactional
public User update(User user) {
Optional<User> userFromDB = userRepository.findById(user.getId());
if(userFromDB.isPresent()){
//Adding the password for successfully update user in DB
user.setPassword(userFromDB.get().getPassword());
return userRepository.save(user);
}
return null;
}
This may not be the most elegant solution but I haven't found another one yet

Exception evaluating SpringEL expression: "#fields.hasErrors('something')"

I try to make a register page for my application and when I press submit button this error appears:
Exception evaluating SpringEL expression: "#fields.hasErrors('firstName')"
From what I understand, Thymleaf uses POJO's getters for validation and this means that firstName attribute couldn't be found. For registration I use a DTO for user entity, a DTO only for registration. If i use user entity this error never appears. What am I doing wrong?
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Registration Form</title>
<link rel="stylesheet" type="text/css" th:href="#{/css/registration.css}"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form autocomplete="on" action="register.html" th:action="#{/user/register}"
th:object="${user}" method="post" class="form-horizontal"
role="form">
<h2>Registration Form</h2>
<div class="form-group">
<div class="col-sm-9">
<label th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"
class="validation-message"></label>
<input type="text" th:field="*{firstName}" placeholder="Name"
class="form-control"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<label th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"
class="validation-message"></label>
<input type="text" th:field="*{lastName}"
placeholder="Last Name" class="form-control"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<label th:if="${#fields.hasErrors('username')}" th:errors="*{username}"
class="validation-message"></label>
<input type="text" th:field="*{username}"
placeholder="Username" class="form-control"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<input type="text" th:field="*{emailAddress}" placeholder="Email"
class="form-control"/> <label
th:if="${#fields.hasErrors('emailAddress')}" th:errors="*{emailAddress}"
class="validation-message"></label>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<label th:if="${#fields.hasErrors('phoneNumber')}" th:errors="*{phoneNumber}"
class="validation-message"></label>
<input type="text" th:field="*{phoneNumber}"
placeholder="Phone Number" class="form-control"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<input type="password" th:field="*{password}"
placeholder="Password" class="form-control"/> <label
th:if="${#fields.hasErrors('password')}" th:errors="*{password}"
class="validation-message"></label>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<input type="password" th:field="*{confirmPassword}"
placeholder="Confirm Password" class="form-control"/> <label
th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}"
class="validation-message"></label>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<button type="submit" class="btn btn-primary btn-block">Register User</button>
</div>
</div>
<h2><span class="text-success" th:utext="${successMessage}"></span></h2>
</form>
</div>
</div>
</div>
</body>
</html>
RegisterUserDto
#PasswordMatches
public class RegisterUserDto {
private Long id;
#NotNull(message = "This field can't be null")
#NotEmpty(message = "This field can't be empty")
private String firstName;
#NotNull(message = "This field can't be null")
#NotEmpty(message = "This field can't be empty")
private String lastName;
#NotNull(message = "This field can't be null")
#NotEmpty(message = "This field can't be empty")
private String username;
#NotNull(message = "This field can't be null")
#NotEmpty(message = "This field can't be empty")
#ValidEmail
private String emailAddress;
#NotNull(message = "This field can't be null")
#NotEmpty(message = "This field can't be empty")
#ValidPhoneNumber
private String phoneNumber;
#ValidPassword
private String password;
private String confirmPassword;
//getters and setters
Controller
#GetMapping("/register")
public ModelAndView registration(){
ModelAndView modelAndView = new ModelAndView();
RegisterUserDto user = new RegisterUserDto();
modelAndView.addObject("user", user);
modelAndView.setViewName("register");
return modelAndView;
}
#PostMapping("/register")
public ModelAndView createNewUser(#Valid RegisterUserDto user,
BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
User userExists = userService.findUserByEmail(user.getEmailAddress());
if (userExists != null) {
bindingResult
.rejectValue("emailAddress", "error.user",
"There is already a user registered with the email provided");
}
if (bindingResult.hasErrors()) {
modelAndView.setViewName("register");
} else {
userService.createUser(user);
modelAndView.addObject("successMessage", "User has been registered successfully");
modelAndView.setViewName("register");
}
return modelAndView;
}
User entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "username",
unique = true)
private String username;
#Column(name = "email_address",
unique = true)
private String emailAddress;
#Column(name = "phone_number")
private String phoneNumber;
#Column(name = "password")
private String password;
#Column(name = "notification_type",
insertable = false)
private String notificationType = "email";
#Column(name = "date_created")
private Date dateCreated;
#Column(name = "is_active",
insertable = false)
private Boolean active = false;
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<UserGroup> groups;
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Message> messages = new ArrayList<>();
#OneToOne(fetch = FetchType.LAZY,
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST},
mappedBy = "userId",
orphanRemoval = true)
private VerificationToken verificationToken;
#OneToMany(mappedBy = "createdBy",
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST},
orphanRemoval = true)
private List<Group> createdGroups = new ArrayList<>();
//getters and setters

Unable to save entity using thymeleaf and Spring Boot

I've recently started using Spring Boot (I mostly come from a python/flask and node background) with JPA and thymeleaf and I'm trying to create a Project in my database. Everything was going well, I was able to add, delete, etc.. Projects.
I added a variable Set users to my Project entity which looks like this:
#Entity
#Table(name = "project")
public class Project {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "project_id")
private int id;
#Column(name = "title")
#NotEmpty(message = "*Please provide a title")
private String title;
#Column(name = "description")
private String description;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "project_lead", joinColumns = #JoinColumn(name = "project_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
private User projectLead;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_user", joinColumns = #JoinColumn(name = "project_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
private Set<User> users;
...
}
The user class looks like this:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "email")
#Email(message = "*Please provide a valid Email")
#NotEmpty(message = "*Please provide an email")
private String email;
#Column(name = "username")
#NotEmpty(message = "*Please provide a username")
private String username;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#NotEmpty(message = "*Please provide your password")
#Transient
private String password;
#Column(name = "enabled")
private int enabled;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
...
}
I'm able to create new projects when I don't specify the users for the project. But I'm having trouble creating a project when I specify the users using a multiple select. In my form I have:
<form th:action="#{/secure/projects}" th:object="${project}" method="POST">
<div class="form-group">
<label th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="validation-message"></label>
<input type="text" class="form-control" th:field="*{title}" id="title" th:placeholder="Title" />
</div>
<div class="form-group">
<input type="text" class="form-control" th:field="*{description}" id="description" th:placeholder="Description" />
</div>
<div class="form-group">
<select th:field="*{users}" class="users-select form-control" multiple="multiple">
<option th:each="user : ${allUsers}" th:value="${user}" th:text="${user.username}"></option>
</select>
</div>
<button name="Submit" value="Submit" type="Submit" th:text="Create"></button>
</form>
I keep getting 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users'' for the th:field="*{users}". I don't understand why th:value="${user}" is being considered as a String when it's supposed to be of class User. Is there a way for me to simply get the results of the select, loop through it and manually add it in the controller to my object project?
My controller looks like this:
#RequestMapping(value = "/projects", method=RequestMethod.GET)
public ModelAndView showProjectForm() {
ModelAndView modelAndView = new ModelAndView();
// Get authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByEmail(auth.getName());
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("project", new Project());
modelAndView.addObject("allUsers", userService.findAll());
modelAndView.setViewName("project_creation");
return modelAndView;
}
#RequestMapping(value = "/projects", method=RequestMethod.POST)
public ModelAndView processProjectForm(#Valid Project project, BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
// Get authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByEmail(auth.getName());
// Get all projects
List<Project> allProjects = projectService.findAll();
// Check if project already exists
Project projectExist = projectService.findByTitle(project.getTitle());
if(projectExist != null) {
bindingResult
.rejectValue("title", "error.project",
"There is already a project with this title");
}
// Get all users
List<User> allUsers = userService.findAll();
if(bindingResult.hasErrors()) {
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("project", new Project());
modelAndView.addObject("allUsers", allUsers);
modelAndView.setViewName("project_creation");
} else {
// Create project
project.setProjectLead(user);
projectService.saveProject(project);
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("success", "Project successfully created!");
modelAndView.addObject("project", new Project());
modelAndView.addObject("projects", allProjects);
modelAndView.setViewName("redirect:/secure/dashboard");
}
return modelAndView;
}
I was able to fix the 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users''. I simply had to add a converter class telling how to convert from string to User object.
#Component
public class StringToUser implements Converter<String, User> {
#Autowired
private UserService userService;
#Override
public User convert(String arg0) {
Integer id = new Integer(arg0);
return userService.findOne(id);
}
}
And changed the option in my form to have a value of user.id instead of a user object. The converter would take care of the rest:
<option th:each="user : ${allUsers}" th:value="${user.getId()}" th:text="${user.getUsername()}"></option>

Categories

Resources