I have validation that email is unique. It's work during registration. But when I try to change email in user profile I have an error.
I check email to run userRepository.findByEmail. If the user was found then email isn't unique. But when the user changes his email in profile findByEmail returns this user. Validation fails.
I need to check that the user was returned by findByUser is not the same user that changes email. For it, I need to pass on the user that changes email in validator.
It's my code.
entity:
@Data
@Table(name = "users")
@NoArgsConstructor
@CheckPasswordConfirm(message = "Password not equal!")
@Entity
public class User implements UserDetails{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotBlank(message = "Username is empty!")
@UniqueUsername(message = "Username isn't unique")
private String username;
@NotBlank(message = "Password is empty!")
private String password;
@Transient
@NotBlank(message = "Password confirm is empty!")
private String passwordconfirm;
private boolean active;
@Email(message = "E-mail!")
@NotBlank(message = "E-mail is empty!")
@UniqueEmail(message = "Email isn't unique!")
private String email;
private String activationCode;
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
@Enumerated(EnumType.STRING)
private Set<Role> roles;
public boolean isAdmin()
{
return roles.contains(Role.ADMIN);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Annotation UniqueEmail:
@Constraint(validatedBy = UniqueEmailValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface UniqueEmail {
public String message();
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default{};
}
Validator:
package ru.watchlist.domain.validation.validators;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import ru.watchlist.domain.validation.annotations.UniqueEmail;
import ru.watchlist.service.UserService;
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
@Autowired
private UserService userService;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && !userService.isEmailAlredyUse(value);
}
}
userService.isEmailAlredyUse:
public boolean isEmailAlredyUse(String value) {
User user = userRepository.findByEmail(value);
if(user != null) {
return true;
}
return false;
}
Here I need check:
User userFromDB = userRepository.findByEmail(value);
if(userFromDB != null && user != userFromDB) {
return true;
}
How can I solve this problem?
P.S. If I will do validator to class with cross fields, I can't show errors with their fields in Thymeleaf. Therefore I need validator for fields.
P.S.S. My controller:
@Controller
@RequestMapping("/profile")
public class ProfileController {
@Autowired
UserService userService;
@GetMapping
public String profile(@AuthenticationPrincipal User user, Model model) {
model.addAttribute(user);
return "profile";
}
@PostMapping("/update")
public String saveChanges(@Valid User user, Errors errors) {
if(errors.hasErrors()) {
return "profile";
}
userService.addUser(user);
return "redirect:/profile";
}
}
UserRepository:
public interface UserRepository extends JpaRepository<User, Long>{
User findByUsername(String username);
User findByActivationCode(String code);
User findByEmail(String email);
}