Spring class level validation and Thymeleaf - spring

I am learning Spring Framework and Thymeleaf. I have known how to display field error by using something like ${#fields.errors("xx")}. However, I get stuck about how to display object error message in Thymeleaf.
Here is my UserForm class:
#PasswordMatches
public class UserForm {
#NotNull
#NotEmpty
private String username;
#NotNull
#NotEmpty
private String password;
#NotNull
#NotEmpty
private String matchingPassword;
#NotNull
#NotEmpty
#ValidEmail
private String email;
/* setter and getter methods */
Here is my PasswordMatches annotation:
#Target({ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PasswordMatchesValidator.class)
#Documented
public #interface PasswordMatches {
String message() default "Passwords don't match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {
#Override
public void initialize(PasswordMatches constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext context){
UserDto user = (UserDto) obj;
return user.getPassword().equals(user.getMatchingPassword());
}
}
Here is my Controller method:
#RequestMapping(value="/registration", method=RequestMethod.POST)
public ModelAndView registerUserAccount(#ModelAttribute("user") #Valid UserForm userForm,
BindingResult result, WebRequest request, Errors errors) {
if (!result.hasErrors()) {
return new ModelAndView("registerSuccess");
}
else {
return new ModelAndView("registration", "user", userForm);
}
}
Now here is my problem: If the password field and confirmPass field doesn't match, how can I get the default error message returned by the class level annotation in Thymeleaf?

I know this is old post but I also encountered this problem and here is the soulution (maybe it will also help someone else):
Modify PasswordMatchesValidator to this:
class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {
#Override
public void initialize(PasswordMatches constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext context){
UserDto user = (UserDto) obj;
boolean isValid = user.getPassword().equals(user.getMatchingPassword());
if(!isValid){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode( "matchingPassword" ).addConstraintViolation();
}
return isValid;
}
it will bind the validation result to your 'matchingPassword' attribute. So in your thymeleaf template us it like this:
${#fields.errors("matchingPassword")}

Add this inside the form tag:
<p data-th-each="err : ${#fields.allErrors()}" data-th-text="${err}" class="error">
Invalid input.
</p>

<p th:if="${#fields.hasErrors('${yourObject}')}" th:errors="${yourObject}"></p>

Related

Spring MVC #ExceptionHandler does not catch MethodArgumentNotValidException

I wanted to create a registration form where the passed data is observed whether it is valid or not.
Here is my code:
#Controller
public class RegistrationController {
#Autowired
private UserService userService;
#GetMapping("/register")
public String registration(Model model) {
model.addAttribute("user", new RegistrationRequest());
return "register";
}
#PostMapping("/register")
public String register(#Valid #ModelAttribute("user") RegistrationRequest user,
HttpServletResponse response) {
String token = userService.registerUser(user);
response.addHeader("Set-Cookie", token + "; HttpOnly; Secure; SameSite=Lax;");
return "index";
}
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleIncorrectRequests(MethodArgumentNotValidException ex, Model model) {
String errorFields = ex.getBindingResult()
.getAllErrors().stream()
.map(err -> ((FieldError) err)
.getField())
.distinct()
.collect(Collectors.joining(", "));
String message = String.format("The next fields are incorrectly filled: %s!",
errorFields);
model.addAttribute("error", "Registration failed");
model.addAttribute("cause", message);
return "error";
}
#ExceptionHandler(EmailAlreadyInUseException.class)
public String handleEmailAlreadyInUse(Model model) {
model.addAttribute("error", "Registration failed");
model.addAttribute("cause", "The given email is already being used! Please try with a different one!");
return "error";
}
}
Here is the RegistrationRequest's implementation:
#Data
#ValidateConfirmPassword
public class RegistrationRequest {
#NotBlank
#Size(min = 4 ,max = 100)
private String username;
#NotBlank
private String email;
#NotBlank
#Size(min = 6)
private String password;
#NotBlank
#Size(min = 6)
private String confirmPassword;
}
The additional validator classes:
#Documented
#Constraint(validatedBy = ConfirmPasswordValidator.class)
#Target( { ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateConfirmPassword {
String message() default "Confirm password not matches the password!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class ConfirmPasswordValidator implements ConstraintValidator<ValidateConfirmPassword, RegistrationRequest>{
#Override
public boolean isValid(RegistrationRequest value, ConstraintValidatorContext context) {
return value.getPassword().equals(value.getConfirmPassword());
}
}
When the data is valid the system does its job, but when any constraint violation happen I get the following log and bad request screen:
[36m.w.s.m.s.DefaultHandlerExceptionResolver[0;39m [2m:[0;39m Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: ... (list of incorrectly filled fields)
I have no clue why the ExceptionHandler does not catch it, while the other one for EmailAlreadyInUseException works properly. Any help would be a blessing.

How to server validate each entry in list using custom validator

I have a Springboot Rest application having a server custom validator for one of the model. There are 2 api endpoints, one receives single object which other receives list of same object. My custom validator works fine on first endpoint. How can i use same validator for other endpoint.
Model class
#Entity
#Table(name=TABLE_MESSAGE, schema = SCHEMA)
public class Message implements java.io.Serializable {
#Id #GeneratedValue(strategy=IDENTITY)
#Column(name=COLUMN_ID, unique=true)
private Long id;
#Basic(optional = false)
#Column(name = COLUMN_CREATETIMESTAMP, insertable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
#Column(name=COLUMN_MESSAGE_SENDERNAME)
private String senderName;
#Column(name=COLUMN_MESSAGE_SENDEREMAIL)
private String senderEmail;
#Column(name=COLUMN_MESSAGE_SUBJECT)
private String subject;
#Column(name=COLUMN_MESSAGE_BODY)
private String body;
}
DTO class
public class MessageForm {
private List<Message> messageList;
public List<Message> getMessageList() {
return messageList;
}
public void setMessageList(List<Message> messageList) {
this.messageList = messageList;
}
}
Custom validator
#Component
public class MessageValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Message.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "senderName", ERRORCODE_MESSAGE_SENDERNAME_EMPTY);
ValidationUtils.rejectIfEmpty(errors, "senderEmail", ERRORCODE_MESSAGE_SENDEREMAIL_EMPTY);
ValidationUtils.rejectIfEmpty(errors, "subject", ERRORCODE_MESSAGE_SUBJECT_EMPTY);
ValidationUtils.rejectIfEmpty(errors, "body", ERRORCODE_MESSAGE_BODY_EMPTY);
Message m = (Message) target;
if (!m.getSenderName().trim().equalsIgnoreCase(EMPTY_STRING) && m.getSenderName().matches(REGEX_CONTAINS_NUMBER)) {
errors.rejectValue("senderName", ERRORCODE_MESSAGE_SENDERNAME_INVALID);
}
if (!m.getSenderEmail().trim().equalsIgnoreCase(EMPTY_STRING) && !m.getSenderEmail().matches( REGEX_EMAIL)) {
errors.rejectValue("senderEmail", ERRORCODE_MESSAGE_SENDEREMAIL_INVALID);
}
}
}
Controller
#RestController
public class MainSiteRestController
{
#Autowired
private MessageValidator messageValidator;
#InitBinder("message")
protected void initMessageBinder(WebDataBinder binder) {
binder.addValidators(messageValidator);
}
// this works fine
public ResponseForm saveMessage(#Valid #RequestBody Message message, BindingResult bindingResult) throws APIException {
if (bindingResult.hasErrors()){
throw new APIException(getErrorMesage(bindingResult.getAllErrors()));
}
return apiService.saveMessage(message);
}
// this is not working
public ResponseForm saveAllMessage(#RequestBody MessageForm messageForm, Errors errors) throws APIException {
// need to validate the complete list or particular indexed object here, tried below code but not working
// messageValidator.validate(messageForm.getMessageList().get(0), errors);
if(errors.hasErrors()) {
throw new APIException(createErrorString(errors));
}
return apiService.saveAllMessage(messageForm);
}
}
Spring validators work on a single form, therefore you will have to create a validator for list dto.

Jackson deserialization errorhandling in spring-framework

I'm looking for a clean way to handle Jackson Deserialization errors for REST web requests.
More precisely: I have an Enum in a incoming DTO object, mapped from JSON. But if the user sends a wrong value, a 400 Bad Request is returned. I would like to return a 422 Unprocessable Entity with a correct message.
One option would be to accept a String, and use bean validation. However, it's not possible to pass all enum values as a list to the annotation (not a constant), so I would need to pass all enum values separately and keep them up to date. This will be very error prone over the whole application. I'm looking for a more structural way to handle this.
I solved this by using a String in the DTO and using a public #interface EnumValueas annotation.
The EnumValue:
#ReportAsSingleViolation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EnumValueValidator.class)
#Target(ElementType.FIELD)
public #interface EnumValue {
Class<? extends Enum> value();
String message() default "The input contains validation errors.";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The validator:
public class EnumValueValidator implements ConstraintValidator<EnumValue, String> {
private Class<? extends Enum> enumClass;
private String message;
#Override
public void initialize(final EnumValue constraintAnnotation) {
this.enumClass = constraintAnnotation.value();
this.message = constraintAnnotation.message();
}
#Override
public boolean isValid(final String value, final ConstraintValidatorContext context) {
boolean valid = false;
for (final Enum enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
valid = true;
}
}
if (!valid) {
context.buildConstraintViolationWithTemplate(message) //
.addConstraintViolation();
}
return valid;
}
}

Validating #RequestParam using #Annotations

I'm trying to validate #RequestParam of my parameter. Here's my Controller:
#RequestMapping(value = "/addCategory", method = RequestMethod.POST)
public ModelAndView addCategory(#ValidCategoryName #RequestParam(value = "categoryName") String categoryName) {
ModelAndView modelAndView = new ModelAndView();
boolean addedSuccessfully = sideViewControllerDelegate.addMenuCategory(categoryName);
modelAndView.setViewName("home_partial/side_view/category_completed");
if (addedSuccessfully) {
modelAndView.addObject("responseMessage", "ADDED");
}
return modelAndView;
}
And The #ValidCaegoryName is defined like this:
#Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER})
#Retention(RUNTIME)
#Documented
#NotNull
#Constraint(validatedBy = ValidCategoryImpl.class)
public #interface ValidCategoryName {
String message() default "This Category Does not Seem to Allowed";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int min() default 30;
}
And my Impl class is this:
public class ValidCategoryImpl implements ConstraintValidator<ValidCategoryName, String> {
#Autowired
MenuCategoriesService menuCategoriesService;
private int min;
#Override
public void initialize(ValidCategoryName constraintAnnotation){
min = constraintAnnotation.min();
}
#Override
public boolean isValid(String categoryName, ConstraintValidatorContext context){
return categoryName.length() < min && menuCategoriesService.containsCategoryName(categoryName);
}
}
Am i Missing anything?
Thanks!

Custom JSR 303 validation is not invoked

My custom JSR 303 validation is not getting invoked. Here is my code
my spring config has
<mvc:annotation-driven />
My controller's handler method:
#RequestMapping(value="update", method = RequestMethod.POST ,
consumes="application/json" ,
produces="application/json"))
#ResponseBody
public String update(#Valid #RequestBody MyBean myBean){
return process(myBean);
}
MyBean (annotated with ValidMyBeanRequest):
#ValidMyBeanRequest
public class MyBean {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
ValidMyBeanRequest annotaion:
#Target({ TYPE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = {MyBeanValidator.class})
public #interface ValidMyBeanRequest {
String message() default "{validMyBeanRequest.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
MyBeanValidator class:
public class MyBeanValidator implements
ConstraintValidator<ValidMyBeanRequest, MyBean> {
#Override
public void initialize(ValidMyBeanRequest constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(MyBean myBean, ConstraintValidatorContext context) {
boolean isValid = true;
int id = myBean.getId();
if(id == 0){
isValid = false;
}
return isValid;
}
}
My http POST request has below JSON data:
{id:100}
The problem is MyBeanValidator's isValid is not getting invoked. I am using Spring 3.1.0 and HibernateValidator is in classpath.
Please see what I am missing??
Update: Updated handler method to include POST request type and consumes, produces values. Also included my http request with JSON data.
Assuming that you do get model correctly, in this case you are doing everything right, except one thing: you need to handle your validation's result manually.
For achieving this you need to add BindingResult object into list of your handler parameters, and then process validation constraints in the way you would like:
#RequestMapping(value="update")
#ResponseBody
public String update(#Valid #ModelAttribute #RequestBody MyBean myBean, BindingResult result) {
if (result.hasErrors()){
return processErrors(myBean);
}
return process(myBean);
}

Resources