I have a Car class and I want it to be validated by two different custom validators in order. I am setting the first validator on top of the class and the other one from validation-constraints-car.xml file.
@Validator1
public class Car {
private static final long serialVersionUID = 5535968331666441498L;
...
}
<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd"
xmlns="http://jboss.org/xml/ns/javax/validation/mapping">
<bean class="com.galery.Car" ignore-annotations="false">
<class ignore-annotations="false">
<constraint annotation="com.galery.validation.specific.Validator2"></constraint>
</class>
</bean>
</constraint-mappings>
When the first validator fails, I don't want to execute the second validator. Right now, even if the first one fails it executes the second one and returns the messages for both of the validators. Here is my annotation interfaces and the controller method.
@RequestMapping(value = "....", method = RequestMethod.POST)
public void validateCar(@Valid @RequestBody Car car) {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(
validatedBy = {Validator1Impl.class}
)
public @interface Validator1{
String message() default "{validator1.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(
validatedBy = {Validator2Impl.class}
)
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public @interface Validator2{
String message() default "{validator1.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
How can I achieve what I want? Is there any way that I can lookup the message of previous validator in ConstraintValidatorContext?
@Override
public boolean isValid(Car value, ConstraintValidatorContext context) {
...
}
============================== right code
My code use dto "student" to verify.
student dto
@Getter
@Setter
public class Student implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private Integer grade;
}
StudentValidator
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
@Component
public class StudentValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
boolean result = Student.class.equals(clazz);
return result;
}
@Override
public void validate(Object target, Errors errors) {
Student student = (Student) target;
ValidationUtils.rejectIfEmpty(errors, "name","name must not be empty");
}
}
StudentValidator2. if having error, not to validate.
@Component
public class StudentValidator2 implements Validator {
@Autowired
private MessageSource messageSource;
@Override
public boolean supports(Class<?> clazz) {
boolean result = Student.class.equals(clazz);
return result;
}
@Override
public void validate(Object target, Errors errors) {
if(errors.hasErrors()){ // if having error, not go ahead
return ;
}
Student student = (Student) target;
if (student.getGrade() <= 0) {
errors.rejectValue("grade", "grade must be more than 0");
}
}
}
StudentController
@RestController("/")
public class StudentController {
@Autowired
private StudentValidator studentValidator;
@Autowired
private StudentValidator2 studentValidator2;
@InitBinder(value = "student")
void initStudentValidator(WebDataBinder binder) {
binder.addValidators(studentValidator, studentValidator2);
}
@PostMapping("/student")
public ResponseEntity<Student> saveStudent(@RequestBody @Valid Student student) {
// Other logic here(Calling the service layer,etc.)
return new ResponseEntity<>(student, HttpStatus.CREATED);
}
}
use postMan to do post to "/student" and no params. Its response as below
{
"status": "BAD_REQUEST",
"error": "Validation failed",
"count": 1,
"errors": [
"name must not be empty"
]
}
======================================= the below is not right
I think you should use WebDataBinder and Override validate() method to implement it. Please ref here. The below I give some pseudocode
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
@Component
public class MyWebDataBinder extends WebDataBinder {
public MyWebDataBinder(Object target) {
super(target);
}
public MyWebDataBinder(Object target, String objectName) {
super(target, objectName);
}
@Override
public void validate() {
Object target = getTarget();
Assert.state(target != null, "No target to validate");
BindingResult bindingResult = getBindingResult();
// Call each validator with the same binding result
for (Validator validator : getValidators()) {
validator.validate(target, bindingResult);
if(bindingResult.hasErrors()) // jump out when if having error
break;
}
}
}
@Controller
public class CustomerController {
@Autowired
Validator1 validator1
@Autowired
Validator2 validator2
@InitBinder(value = "car")
void initCarValidator(@Qualifier("myWebDataBinder")WebDataBinder binder) {
binder.addValidators(validator1);
binder.addValidators(validator2);
}
@RequestMapping(value = "....", method = RequestMethod.POST)
public void validateCar(@Valid @RequestBody Car car) {
// .... your code
}