javavalidationstruts2struts-validationstruts2-convention-plugin

How to short-circuit a @CustomValidator?


Consider below sample, which checks if fromDate and toDate are valid dates and if fromDate is less than toDate:

@CustomValidator(type = "DateValidator", 
            fieldName = "fromDate",
         shortCircuit = true),

@CustomValidator(type = "DateValidator", 
            fieldName = "toDate",
         shortCircuit = true),

@CustomValidator(type = "CompareDatesValidator", 
              message = "validate.date.jalali.same.or.before",
         shortCircuit = true, 
           parameters = {
        @ValidationParameter(name = "fromDateParam", value = "${fromDate}"),
        @ValidationParameter(name = "toDateParam", value = "${toDate}") 
               })

The DateValidator extends the FieldValidatorSupport and the CompareDatesValidator extends the ValidatorSupport

Although I have shortCircuit the DateValidators, but the CompareDatesValidator always run, which is not correct. Can I fix this ?!


Solution

  • As explained in the documentation.

    Plain validator takes precedence over field-validator. They get validated first in the order they are defined and then the field-validator in the order they are defined. Failure of a particular validator marked as short-circuit will prevent the evaluation of subsequent validators and an error (action error or field error depending on the type of validator) will be added to the ValidationContext of the object being validated.

    Then your actual execution order is:

    1. CompareDatesValidator (plain)
    2. DateValidator (field fromDate)
    3. DateValidator (field toDate)

    The problem is that it will be executed first, but since its check is a composite check based on two fields, the atomic check on the fields itself should be performed first.

    But this is how the framework works, so you need to workaround it.

    If your plain validator is still this one (even if with some modification), you could avoid the check and ignore the error in case the input is not valid, letting this validation happen where it belongs, in the Field Validators:

    public final class CompareDatesValidator extends ValidatorSupport {
        private String fromDate; // getter and setter
        private String toDate;   // getter and setter    
    
        @Override
        public void validate(Object o) throws ValidationException {
            Date d1 = (Date)parse(fromDate, Date.class);
            Date d2 = (Date)parse(toDate, Date.class);
    
            if (d1==null || d2==null){
                LOG.debug("Silently disabling Plain Validator. Check performed by Field ones");
            } else if (d2.before(d1)){
                addActionError(getDefaultMessage());
            }
        }
    }
    

    You only need to remember to always put the Field Validators in the same validation stack of the CompareDatesValidator, or the "Date not valid" errors will be silently swallowed.