How can I retrieve current validation context's groups in a Validator?


#1

Hi guys,

As part of a project, I would like to trigger a @Valid conditionally on an object if a value is not null. To do that i have created a custom validator named ValidateFieldIfAnotherIsNotNull which I’m using as annotation on my object :

MyObject.java

@ValidateFieldIfAnotherIsNotNull(fieldToValidateName = "mySubObject", dependFieldName = "myValue")
public class MyObject {

    private MySubObject mySubObject;
    private String myValue;

...

MySubObject.java

public class MySubObject {

    @NotNull
    private String stringA;
    @NotNull(groups = {FormModes.Creation.class})
    private String stringB;

...

So, when myValue is not set there is no validation done (as i wanted) but, when i set a value and try to validate MySubObject my validator doesn’t take into account groups given :

Example.java

MySubObject sub = new MySubObject("StringA");
MyObject object = new MyObject(sub, "myValue");
validator.validate(object, FormModes.Creation.class);

In this example my @NotNull(groups = {FormModes.Creation.class}) is totally ignored.
By following you will find my custom validator where I would like to find the used groups dynamically :

ValidateFieldIfAnotherIsNotNullValidator.java

public class ValidateFieldIfAnotherIsNotNullValidator
        implements ConstraintValidator<ValidateFieldIfAnotherIsNotNull, Object> {

    private String fieldToValidateName;
    private String dependFieldName;

    @Override
    public void initialize(final ValidateFieldIfAnotherIsNotNull annotation) {
        fieldToValidateName = annotation.fieldToValidateName();
        dependFieldName = annotation.dependFieldName();
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext ctx) {
        
        if (value == null) {
            return true;
        }

        try {
            final Object dependFieldValue = PropertyUtils.getProperty(value, dependFieldName);
            final Object fieldToValidateValue = PropertyUtils.getProperty(value, fieldToValidateName);

            if(dependFieldValue != null) {

                    ctx.disableDefaultConstraintViolation();
                    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
                    Validator validator = factory.getValidator();

                    Class[] applicableGroups = ?????; // How can i retrieved my current validation context groups ??

                    Set<ConstraintViolation<Object>> errorList = validator.validate(fieldToValidateValue, applicableGroups);

                    for(ConstraintViolation<Object> error : errorList) {
                        ctx.buildConstraintViolationWithTemplate(error.getMessageTemplate())
                                .addNode(fieldToValidateName+"."+error.getPropertyPath())
                                .addConstraintViolation();
                    }

                    return errorList.isEmpty();

            }

        } catch (final NoSuchMethodException ex) {
            throw new RuntimeException(ex);

        } catch (final InvocationTargetException ex) {
            throw new RuntimeException(ex);

        } catch (final IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }

        return true;
    }

}

Would it be possible to initialize applicableGroups variable with current context’s validation groups (in code above, retrieve FormModes.Creation.class) ?

Thanks in advance,


#2

Idea:

@ValidateFieldIfAnotherIsNotNull(fieldToValidateName = "mySubObject", dependFieldName = "myValue") will be triggered by validation of the javax.validation.groups.Default, by calling validator.validate(object, FormModes.Creation.class) instead of validator.validate(object, Default.class, FormModes.Creation.class); the validation of root object of class MyObject is not triggered at all, so - a fortiori - none of the field of MySubObject will be checked either.

Also the annotation’s groups are available in the initialize() method, so you can test there, and store the result, and if the validation groups of the annotation allow the validation asked by your call to validate(), then isValid() will be later called, and you can then test if the group of interest to you was present.


#3

Hi,

Sorry for the delay, forgot about this one.

So what you’re trying to do is currently not feasible. Instantiating a new ValidatorFactory inside a ConstraintValidator is a no no. It will perform awfully.

I’m wondering if maybe we should add a feature allowing to trigger the cascading conditionally. It’s not the first time I see this requirement.

Will think about it.


#4

Hi @yanicks,

Could you explain us more in detail the business case you’re trying to fulfill?

We understood your technical explanation but it’s not very graphic and we would like to better understand what are your exact business requirements.

Thanks!