5

I would like to create a custom constraint @LengthIfNotBlank(min=,max=) based on Hibernates @Length constraint:

@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = LengthIfNotBlankValidator.class)
@Documented
public @interface LengthIfNotBlank {
    String message() default "{com.mycompany.LengthIfNotBlank}";
    Class<? extends Payload>[] payload() default {};
    int max() default Integer.MAX_VALUE;
    Class<?>[] groups() default {};
    int min() default 0;
}

public class LengthIfNotBlankValidator extends LengthValidator {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        return StringUtils.isBlank(value) || super.isValid(value, constraintValidatorContext);
    }
}

Exception:

[java] javax.validation.ValidationException: Unable to initialize com.example.constraints.LengthIfNotBlankValidator
[java] Caused by: java.lang.ClassCastException: $Proxy32 cannot be cast to org.hibernate.validator.constraints.Length

UPDATE

Still getting this exception :-(

public class LengthIfNotBlankValidator extends LengthValidator {
    public void initialize(LengthIfNotBlank parameters) {
        Map<String,Object> elements = new HashMap<String,Object>();
        elements.put("message", parameters.message());
        elements.put("payload", parameters.payload());
        elements.put("groups", parameters.groups());
        elements.put("min", parameters.min());
        elements.put("max", parameters.max());

        AnnotationDescriptor<Length> descriptor = AnnotationDescriptor.getInstance(Length.class, elements);
        Length length = AnnotationFactory.create(descriptor);
        super.initialize(length);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        return StringUtils.isBlank(value) || super.isValid(value, constraintValidatorContext);
    }
}

2 Answers 2

2

Since annotation types cannot be inherited, you should override initialize() and pass an actual instance of type Length to super.initialize().

Note that you cannot create an instance of annotation type directly, therefore you have to use something like AnnotationFactory.

Also I'm not sure that compiler would allow you to do it due to type safety constraints. If it doesn't, use composition instead of inheritance (i.e. create a field of type LengthValidator).

Sign up to request clarification or add additional context in comments.

Comments

1

I think you should not bother with that: Hibernate's LengthValidator is really simple and whatever you do, you will end up with more code that if you had just reimplemented the code yourself like this:

public class LengthIfNotBlankValidator implements ConstraintValidator<LengthIfNotBlank, String> {
    private int min;
    private int max;

    public void initialize(LengthIfNotBlank parameters) {
        min = parameters.min();
        max = parameters.max();
        validateParameters();
    }

    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if ( StringUtils.isBlank(value) ) {
            return true;
        }
        int length = value.length();
        return length >= min && length <= max;
    }

    private void validateParameters() {
        if ( min < 0 ) {
            throw new IllegalArgumentException( "The min parameter cannot be negative." );
        }
        if ( max < 0 ) {
            throw new IllegalArgumentException( "The max parameter cannot be negative." );
        }
        if ( max < min ) {
            throw new IllegalArgumentException( "The max cannot be less than the min." );
        }
    }
}

(This is just Hibernate's code adapted for your particular case).

Also, I don't think it would be possible to extend LengthValidator while implementing ConstraintValidator<LengthIfNotBlank, String>, which is mandatory for your validator.

Notice however that if you want to re-use unparameterized validators (or validators with constant parameters), you could simply annotate your validator with the re-used one (and maybe add @ReportAsSingleViolation)

2 Comments

I agree but I am not sure using reflection to avoid it is really better when the duplicated code is so simple. The reflection code is probably more difficult to understand and maintain…
+1 Really surprised to see that this answer which is >6 years old and on a question with 6K views didn't have a single upvote, it's a much simpler and better approach. Just because something is possible doesn't mean it should be done.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.