I have controller with method:
void save(#Validated(BookingForm.ValidationSave.class) #RequestBody BookingForm form, Errors result) {...}
and BookingForm with a field and my custom validator:
public class BookingForm {
public interface ValidationSave {}
public interface ValidationEstimate {}
....
#Future
private LocalDateTime when;
....
}
Future.java
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FutureValidator.class)
#Documented
public #interface Future {
String message() default "{javax.validation.constraints.Future.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I've expected that validator will valid my 'when' field but it does not.
When I removed the group from #Validated it works. Any ideas?
Works fine with #NotNull and others javax validators.
Updated:
FutureValidator.java
public class FutureValidator implements ConstraintValidator<Future, Temporal> {
#Override
public void initialize(Future constraintAnnotation) {
}
#Override
public boolean isValid(Temporal value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
LocalDate ld = LocalDate.from(value);
return ld.isAfter(LocalDate.now());
}
}
Related
Getting this error HV000030: No validator could be found for constraint EnumChecker validating type MyEnum Check configuration for ‘myenum’.
Using the annotation on this class to be validated, I can't do private final String myenum because I need to use it as an enum in order to use the function inside the enum
public class Request {
#EnumChecker(enumclass = MyEnum.class)
private final MyEnum myenum;
}
annotation
#Documented
#Constraint(validatedBy = MyEnumValidator.class)
#Target({FIELD, PARAMETER, TYPE, TYPE_USE, TYPE_PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
#Retention(RUNTIME)
public #interface EnumChecker {
String message() default "must be one of these values";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum> enumClass();
}
validator
public class MyEnumValidator implements ConstraintValidator<EnumChecker, String> {
private List<String> validEnumList;
#Override
public void initialize(EnumChecker constraintAnnotation) {
validEnumList = Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return validEnumList.contains(value);
}
}
enum
public enum MyEnum {
YES, NO;
private static final Map<MyEnum, String> MyEnumConverter =
Map.of(YES, "yes");
public String toMyString() {
return MyEnumConverter.get(this);
}
}
First delete final modifier in myenum and create setter and getter for that field, because Jackson is not able to bind values from http request to your Request class for example.
public class Request {
#EnumChecker(enumClass = MyEnum.class)
private MyEnum myenum;
public MyEnum getMyenum() {
return myenum;
}
public void setMyenum(MyEnum myenum) {
this.myenum = myenum;
}
}
Then change ConstraintValidator target type to match MyEnum instead of String
public class MyEnumValidator implements ConstraintValidator<EnumChecker, MyEnum> {
private List<MyEnum> validEnumList;
#Override
public void initialize(EnumChecker constraintAnnotation) {
validEnumList = Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
.collect(Collectors.toList());
}
#Override
public boolean isValid(MyEnum value, ConstraintValidatorContext context) {
return validEnumList.contains(value);
}
}
Test: I did the following test
#RestController
public class MainController {
#PostMapping("/annot")
public String filter(#RequestBody #Valid Request req) {
return req.getMyenum().name();
}
}
Also I changed the implementation of MyEnumValidator to only see validation effect and prove that it takes place. (I admit that currently this validator does not make sense. Also using only #RequestBody guarantees that myenum can only Have YES or NO values otherwise throws exception)
public class MyEnumValidator implements ConstraintValidator<EnumChecker, MyEnum> {
private List<MyEnum> validEnumList;
#Override
public void initialize(EnumChecker constraintAnnotation) {
validEnumList = Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
.collect(Collectors.toList());
}
#Override
public boolean isValid(MyEnum value, ConstraintValidatorContext context) {
return validEnumList.contains(value) && value.name().length() == 3;
}
}
For a invalid case:
stacktrace:
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String api.sweater.controller.MainController.filter(api.sweater.request.Request): [Field error in object 'request' on field 'myenum': rejected value [NO]; codes [EnumChecker.request.myenum,EnumChecker.myenum,EnumChecker.api.sweater.request.MyEnum,EnumChecker]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.myenum,myenum]; arguments []; default message [myenum],class api.sweater.request.MyEnum]; default message [must be one of these values]] ]
My purpose is to use a specific ConstraintValidator in 2 scenarios below
use annotations on POJO to validate the whole object (the popular way)
validate a single value with specific validator's isValid function (for some configurable dynamic validation request)
The validator must support services injection, so I must get it from spring but not create a new validator instance manually.
followed my test codes:
annotation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { IdNumberValidator.class })
public #interface IdNumber {
String message() default "id number is not available";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
validator
public class IdNumberValidator implements ConstraintValidator<IdNumber, String>, BeanNameAware {
#Autowired
private IUserService usrService;
private String bn;
#Override
public boolean isValid(String value, ConstraintValidatorContext context){
System.out.println("my name is:" + bn);
return usrService.getUserByAccount(value).isPresent();
}
#Override
public void setBeanName(String name) {
this.bn = name;
}
}
pojo
#Getter
#Setter
public class TestPOJO {
private long id;
#IdNumber
private String idn;
}
service
#Service
public class TestValidatorService {
#Autowired
private Validator validator;
#Autowired
private ApplicationContext context;
public void validatePojo(TestPOJO pojo){
BeanPropertyBindingResult e = new BeanPropertyBindingResult(pojo, "TestPOJO");
validator.validate(pojo, e);
if(e.hasErrors()){
for(ObjectError oe : e.getAllErrors()){
System.out.println(oe.toString());
}
}
}
public void validatePojoByDynamicValidator(TestPOJO pojo){
IdNumberValidator validator = context.getBean("com.test.IdNumberValidator", IdNumberValidator.class); // got the name via BeanNameAware but seems not working
System.out.println(validator.isValid(pojo.getIdn(), null));
}
}
In the test case for service, function "validatePojo" passed but "validatePojoByDynamicValidator" did not.
Any solution for this problem? Thanks!
The following code yields null on the teamResource auto-wired object inside isValid() function:
#Component //I am not sure it is required he
public class TeamIdValidator implements ConstraintValidator<TeamIdConstraint, Integer> {
#Autowired private TeamResource teamResource;
public TeamIdValidator() {
}
#Override
public void initialize(TeamIdConstraint teamIdConstraint) {
// this.teamIdConstraint = teamIdConstraint;
}
#Override
public boolean isValid(Integer teamId, ConstraintValidatorContext cxt) {
int numOfAvailableTeams = teamResource.retrieveAllTeams().size(); //teamResource is null :(
return teamId < 0 || teamId >= numOfAvailableTeams;
}
}
TeamResource class:
#RestController
#CrossOrigin
public class TeamResource {
#Autowired private TeamRepository teamRepository;
public TeamResource() {
}
...
//mapping methods
}
And if it relevant...
#Documented
#Constraint(validatedBy = TeamIdValidator.class)
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface TeamIdConstraint {
String message() default "Invalid team id!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Why on earth teamResource is null?
It is not being explicitly initialized with new anywhere else.
Already mentioned in the comments but declare a a bean like this
#Configuration
public class ValidationConfig{
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator);
return methodValidationPostProcessor;
}
}
Remove all the #autowired, prefer constructor injection. (check the internet why).
And then, it makes no sense to inject a RestContoller to validator.
I am using JSR 303 for validating my request object of rest controller. Validation is working fine but i am not getting the response. below is the code am i missing something ?
Pojo class:
#ValInfo(message ="empty filed", groups=ExtendedValidation.class)
public class Request {
Validator classes:
#Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = { ValInfoValidator.class })
#Documented
public #interface ValInfo {
String message() default "{Client information invalid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class ValInfoValidator implements ConstraintValidator<ValInfo, TvxOnlineRequest> {
#Override
public void initialize(ValInfo arg0) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(TvxOnlineRequest arg0, ConstraintValidatorContext arg1) {
// TODO Auto-generated method stub
return false;
}
I've created a custom constraint validator that works on a list of objects. Unfortunately it doesn't seem to be getting invoked, it worked when I had a wrapper class containing the list with the annotation on the list.
This is the code that worked fine
public class wrapper {
#ValidMyObjectList
List<MyObject> myObjects;
...
}
But now I've got rid of the wrapper class and added the annotation to the parameter in the controller method.
Here's the controller
#RequestMapping(value = "", method = RequestMethod.POST)
public List<MyObject> stopCheque(
#ValidMyObjectList #RequestBody final List<MyObject> myObjects,
final HttpServletResponse httpServletResponse) {
....
}
Here's the constraint annotation
#Constraint(validatedBy = MyObjectListValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface MyObjectList {
Class<?>[] groups() default {};
String message() default "";
Class<? extends Payload>[] payload() default {};
}
And part of the validator itself
public class MyObjectListValidator implements
ConstraintValidator<MyObjectList, List<MyObject>> {
#Override
public void initialize(final MyObjectList myObjectList) {
}
#Override
public boolean isValid(final List<MyObjectList> myObjectLists, final ConstraintValidatorContext cxt) {
...
}
Would greatly appreciate any help. Thanks
Add to your Spring config class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
and add #Validated to controller class.