9

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;
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
David B
  • 3,269
  • 12
  • 48
  • 80

3 Answers3

9

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).

  • Even though the difference among the concerned annotation is the data it's going to be validate (i.e., value is within the enum or iteration)? – David B Feb 10 '15 at 05:39
  • Also would it be possible to set the value of `@CustomAnnotation.message()` to `GenericValidatorBootstrapperValidator.initialize()`? – David B Feb 10 '15 at 11:22
  • Hi @KaidoShugo, I don't think I understand your questions :) which enum or iteration are you talking about? Also CustomAnnotation.message() will be available to GenericValidatorBootstrapperValidator.initialize() as the annotation itself is provided. – Emmanuel Bernard Feb 11 '15 at 08:15
  • BTW, annotations cannot be subclassed. Is that what you wanted to do in the first place? That's not doable in Java. – Emmanuel Bernard Feb 11 '15 at 08:18
  • I'll try to create a Bitbucket repository to explain my question. But basically, the question is creating a single `@CustomAnnotation` in which the validator could be set as a parameter following the format in the question. Cause I have almost several validators in which the only difference is the data source (ie., enum, list, table, etc.) its going to validate from. – David B Feb 11 '15 at 10:03
  • Thanks for updating your answer. You got the taught on having multiple validators for different field types. But the specification of this question also handles similar data types wherein the source of the data are fetched from different entities. For example, `@CustomAnnotation() String clientId` would validate if the value of `clientId` already exists on `client` table/entity. Same goes for `@CustomAnnotation() String staffId` to validate for `staff` table/entity and so on and so forth. – David B Feb 13 '15 at 03:05
2

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;
    }

}
Vassilis Blazos
  • 1,560
  • 1
  • 14
  • 20
1

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.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • That's what my current implementation is. Though the concerned annotations has almost similar validations with the difference on the value of data to be validated? I was hoping this could be done by the means of Abstraction on Annotations (if such exists). – David B Feb 10 '15 at 05:41
  • Try moving the validation logic to a separate CommonValidator, and each individual Validator will inherit the CommonValidator logic while defining its specific logic too. – Vlad Mihalcea Feb 10 '15 at 05:48
  • So you're saying to follow the implementation of "Emmanuel Bernard"? – David B Feb 10 '15 at 05:53
  • Emmanuel is the Hibernate Validation project lead, so he knows best. – Vlad Mihalcea Feb 10 '15 at 07:58