Spring 3.2 using cross parameter constraint - java

I'm trying to use a cross parameter constraint validator targeted on method parameters. The problem is the validation is not triggered for the method parameters instead it is triggered for the method return value.
I'm using Spring's LocalValidatorFactoryBean.
The JSR-303 provider is hibernate-validator 4.2.0.Final.
Spring version is 3.2.0.
Spring configuration excerpt :
<!-- JSR 303 validation -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
<bean>
Custom Constraint Validator:
#SupportedValidationTarget( ValidationTarget.PARAMETERS )
public class InstanceValidator implements
ConstraintValidator<InstanceCheck, Object[]> {
#Override
public void initialize(InstanceCheck constraintAnnotation) {}
#Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
...
}
}
Annotation:
#Target( { METHOD, ANNOTATION_TYPE, CONSTRUCTOR } )
#Retention(RUNTIME)
#Constraint(validatedBy = InstanceValidator.class)
#Documented
public #interface InstanceCheck {
String message() default "{constraint.instance.not.allowed}";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
}
SomeService:
#Validated
public interface SomeService {
...
#InstanceCheck
public String addContent(String content) throws SomeException;
...
}
SomeServiceImpl :
#Service
public class SomeServiceImpl implements SomeService {
...
public String addContent(String content) throws SomeException {
// do something
}
...
}
: UPDATE :
I have tried adding the "validationAppliesTo" method for the annotation type (see below)
#Target( { METHOD, ANNOTATION_TYPE, CONSTRUCTOR } )
#Retention(RUNTIME)
#Constraint(validatedBy = InstanceValidator.class)
#Documented
public #interface InstanceCheck {
String message() default "{constraint.instance.not.allowed}";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
ConstraintTarget validationAppliesTo() default ConstraintTarget.PARAMETERS;
}
, and now I get the following error:
"Parameters starting with 'valid' are not allowed in a constraint."
Thanks in advance for any type of advice.

Cross-parameter constraints are only supported in Hibernate Validator 5.x (Bean Validation 1.1). Hibernate Validator 4.2 doesn't know how to deal with such constraints.

This is coding issue or temporary solution in Hibernate Validator.
Remove any field or method starting with 'valid' in custom Annotation.
In your example rename method -
ConstraintTarget validationAppliesTo() default ConstraintTarget.PARAMETERS;
to
ConstraintTarget xxxxxationAppliesTo() default ConstraintTarget.PARAMETERS;
Issue code ConstraintHelper-
private void assertNoParameterStartsWithValid(Class<? extends Annotation> annotationType) {
final Method[] methods = run( GetDeclaredMethods.action( annotationType ) );
for ( Method m : methods ) {
if ( m.getName().startsWith( "valid" ) && !m.getName().equals( VALIDATION_APPLIES_TO ) ) {
throw log.getConstraintParametersCannotStartWithValidException();
}
}
}

Related

Spring boot custom annotation for pathVariable

There is an annotation that should ideally throw an exception if the entity by id is not found for different controllers.
Annotation:
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = CheckExistHandler.class)
public #interface CheckExist {
Class<?> entityClass();
String message() default "Entity with specified id does not exist!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validator sketches:
#Component
public class CheckExistHandler implements ConstraintValidator<CheckExist, Long> {
#PersistenceContext
private EntityManager entityManager;
#Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
if (value != 0 && entityManager.find(Topic.class, value) != null) {
return true;
} else {
return false;
}
}
}
Method for tests from one controller:
#GetMapping("/{topicId}")
public ResponseEntity<TopicDto> getTopicById(#CheckExist(entityClass = Topic.class) #PathVariable("topicId") Long topicId) {
if (!topicService.isExistByKey(topicId)) {
throw new NotFoundException("topic not found");
}
return new ResponseEntity<>(topicMapper.toDto(topicService.getByKey(topicId)), HttpStatus.OK);
}
In this regard, questions:
How to isolate a class from an annotation in a validator using reflection in order to correctly use the EntityManager?
How to throw an exception without getting 500?

How to get parameter of my Annotation as a validator?

I created my own Annotation to validate my REST parameter like this:
#PostMapping("/users")
#ResponseStatus(HttpStatus.CREATED)
public User createUser(#UserConstraint("CREATE_MODE") #RequestBody User user)
{ //code }
I got everything working where my ConstraintValidator is called to validate the User input, but I can't figure out how to get the parameter of my own annotation. I want to read the value CREATE_MODE.
#Constraint(validatedBy = UserValidator.class)
#Target(ElementType.PARAMETER )
#Retention(RetentionPolicy.RUNTIME)
public #interface UserConstraint {
String message() default "";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
public String value();
}
How to access??
public class UserValidator implements ConstraintValidator<UserConstraint, User> {
#Override
public boolean isValid(User user,
ConstraintValidatorContext context) {
???
}
#Override
public void initialize(UserConstraint annotation) {
// initialization, probably not needed
mode = annotation.value();
}

Custom annotation for method parameter

I am trying to validate a method parameter using custom annotation, but annotation validator is not getting invoked.
The annotation
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FieldValidator.class)
public #interface ValidField {
String message() default "Incorrect field.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The FieldValidator
public class FieldValidator implements ConstraintValidator<ValidField, String> {
#Override
public void initialize(final ValidField arg0) {
}
#Override
public boolean isValid(final String field, final ConstraintValidatorContext context) {
System.out.println("Called annotations successfully - "+ field);
return false;
}
}
Testing through the main method
public void testAnnotation(#ValidField String q){
System.out.println("inside testAnnotation..");
}
/************************* TESTING ****************************/
public static void main(String[] args) {
Test test= new Test();
test.testAnnotation("sample");
Expected: Called annotations successfully - sample should be displayed in the console
Okay, It was a mistake testing it by the main method.
#Retention(RetentionPolicy.RUNTIME) says annotation will be available at runtime.
It was tested successfully by the service call when the server is running.

How to validate #PathVariable with custom validator annotation containing repository bean

I know how to validate #PathVariable from https://stackoverflow.com/a/35404423/4800811
and it worked as expected with standard annotations but not with the customized one using a Repository bean. Maybe the bean is not initialized and I end up with NullPointerException when accessing the end point has #PathVariable validated. So how to get that work?
My Controller:
#RestController
#Validated
public class CustomerGroupController {
#PutMapping(value = "/deactive/{id}")
public HttpEntity<UpdateResult> deactive(#PathVariable #CustomerGroupEmpty String id) {
}
}
My custom validator:
public class CustomerGroupEmptyValidator implements ConstraintValidator<CustomerGroupEmpty, String>{
#Autowired
private CustomerRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// NullPointerException here (repository == null)
if (value!=null && !repository.existsByCustomerGroup(value)) {
return false;
}
return true;
}
}
My Custom Annotation:
#Documented
#Constraint(validatedBy = CustomerGroupEmptyValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomerGroupEmpty {
String message() default "The customer group is not empty.";
Class<?>[] groups() default {};
Class<? extends Payload> [] payload() default {};
}
code in this post is correct, only mistake is that validator need to override initialize method as well. Probably user123 incorrect configure repository bean, the simply way to check this is define it manually in configuration class

Hibernate Validator - Add a Dynamic ConstraintValidator

After learning about Hibernate Custom Validators, it has given me an interest in one topic, could I possibly create one base annotation wherein I could set which Validator to use?
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = validator().class)
public #interface CustomAnnotation {
public String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends ConstraintValidator<? extends CustomAnnotation, Serializable>> validator();
}
So that I could use #CustomAnnotation in this manner
#CustomAnnotation(validator = CustomConstraintValidator.class, message = "validationMessage")
private Object fieldName;
I would not recommend it but you can do it roughly this way:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = GenericValidatorBootstrapperValidator.class)
public #interface CustomAnnotation {
public String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends ConstraintValidator<? extends CustomAnnotation, Serializable>> validator();
}
public class GenericValidatorBootstrapperValidator implements ConstraintValidator<CustomAnnotation, Object> {
private final ConstraintValidator validator;
#Override
public void initialize(CustomAnnotation constraintAnnotation) {
Class<? extends ConstraintValidator> validatorClass = constraintAnnotation.validator();
validator = validatorClass.newInstance();
validator.initialize( ... ); //TODO with what?
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return validator.isValid(value, context);
}
}
But again, prefer specific annotations, they are more expressive.
Edit
After your comment, I think what you want is to be able to set different validators based on the return type of the property
#CustomAnnotation
List<String> foo;
#CustomAnnotation
Table bar;
If that's the case, add several validators implementations in the #Constraint annotation.
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {ListValidatorImpl.class, TableValidatorImpl.class, ...})
public #interface CustomAnnotation {
public String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class ListValidatorImpl implements ConstraintValidator<CustomAnnotation, List> {
#Override
public boolean isValid(List value, ConstraintValidatorContext context) {
}
}
public class TableValidatorImpl implements ConstraintValidator<CustomAnnotation, Table> {
#Override
public boolean isValid(Table value, ConstraintValidatorContext context) {
}
}
You can even link a contraint annotation with an implementation via the META/validation.xml file
<constraint-mappings
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.1.xsd"
xmlns="http://jboss.org/xml/ns/javax/validation/mapping" version="1.1">
<constraint-definition annotation="org.mycompany.CustomAnnotation">
<validated-by include-existing-validators="true">
<value>org.mycompany.EnumCustomValidatorImpl</value>
</validated-by>
</constraint-definition>
</constraint-mappings>
If you need something more flexible, I think my initial proposal would work. In the GenericValidatorBootstrapperValidator isValid method, you could call the right validator instance based on the object type of the value parameter (via instanceof for example).
Hibernate Validator also offers now a annotation #ScriptAssert which makes the implementation of custom validations easier and helps to avoid plenty lines of code.
Example of use:
#ScriptAssert(lang = "javascript",
script = "_this.capital.equals(_this.capital.toUpperCase)",
message = "capital has not Capital letters")
public class BigLetters {
private String capital;
public String getCapital() {
return capital;
}
public void setCapital(String capital) {
this.capital = capital;
}
}
I don't think you can implement a dynamic validator resolver on top of Hibernate Validator support. It's much better to have a dedicated set of annotation-validator pairs so when you annotate a field with a specific Validation annotation, it's clear what Validator will be used.

Categories

Resources