Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally invoke spring validators

I have a model like below. Existing code already has validations on individual properties. Now I have a requirement to ignore existing validations on billing_country and address if buyer.method is foo. I was thinking I could have a custom validator at buyer level, check for method and invoke validations only when buyer.method!=foo. Is this a valid approach? Are there any better alternatives?

"buyer": {
    "method": "foo",
    "instruments": [
      {
        "card": {
          "type": "MASTERCARD",
          "number": "234234234234234",
          "expire_month": "12",
          "expire_year": "2017",
          "billing_country" : "US"
          "Address" : {
             "line1": "summer st",
             "city": "Boston"
             "country": "US"
           }
        }
      }
    ]
}
like image 475
nilesh Avatar asked Jun 16 '15 19:06

nilesh


1 Answers

There's two ways to do this.

You can either

  1. create a custom validation annotation and validator which checks the value of method and then applies to appropriate validations to the other fields

  2. use validation groups, where you manually check the value of method, then choose which group to use; your annotated fields will then need to be changed to only be applied when that group is active.

Option #1

Define a class-level annotation:

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { MethodAndInstrumentsValidator.class })
@Documented
public @interface ValidMethodAndInstruments {
    String message() default "{my.package.MethodAndInstruments.message}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Define a validator:

public class MethodAndInstrumentsValidator 
             implements ConstraintValidator<ValidMethodAndInstruments, Buyer> {
    public final void initialize(final ValidMethodAndInstruments annotation) {
        // setup
    }

    public final boolean isValid(final Buyer value, 
                                 final ConstraintValidatorContext context) {
        // validation logic
        if(!"foo".equals(value.getMethod())) {
            // do whatever conditional validation you want
        }
    }

}

Annotate the buyer class:

@ValidMethodAndInstruments
public class Buyer { }

Option #2

Here, you'll have to invoke the validation manually.

First, define the validation groups:

public interface FooValidation {}
public interface NotFooValidation {}

Configure a validator:

@Bean
public LocalValidatorFactoryBean validatorFactory() {
    return new LocalValidatorFactoryBean();
}

In your controller(?), check the value of method and do the validation:

@Autowired
private Validator validator;

// ...

public void validate(Object a, Class<?>... groups) {
    Set<ConstraintViolation<Object>> violations = validator.validate(a, groups);
    if(!violations.isEmpty()) {
        throw new ConstraintViolationException(violations);
    }
}

// ...

validate(buyer, "foo".equals(buyer.getMethod()) 
                    ? FooValidation.class 
                    : NotFooValidation.class);

Finally, modify the groups in your model/dto class:

public class Buyer {
    // ...

    @Null(groups = FooValidation.class)
    @NotNull(groups = NotFooValidation.class)
    protected String billingCountry;
}
like image 82
beerbajay Avatar answered Sep 22 '22 02:09

beerbajay