Search code examples
javahibernate-validatorbean-validation

JSR-303: Yet Another Cross-field Validation Problem


I have a @Money constraint that indicates the annotated Double property is, well, money. I wrote a ConstraintValidator that checks if a @Money property has a valid value, which depends on a Currency instance. For example, USD has both dollar and cent values whereas Japanese Yen doesn't, so while 19.99 is a valid value for USD, it isn't for JPY. The Currency value is stored in another property of the same bean.

The problem is looking up the Currency property of a given bean within the ConstraintValidator. I thought about making it class-level validation, but it would be quite cumbersome and redundant to write at the class level which fields are money, and more importantly, you can only generate one error message, even if there is more than one money property that is invalid.

Any suggestions, even Hibernate Validator specific ones are welcome.

Thanks!


Solution

  • IMO, the simplest solution is to create a separate java class say Money that holds both information, the type of money (i.e. Currency) and the value of money.

    public class Money {
    
        private Currency currency;
        private Double value;
    
        public Currency getCurrency() { return currency; }
        public void setCurrency(Currency currency) { this.currency = currency; }
    
        public Double getValue() { return value; }
        public void setValue(Double value) { this.value = value; }
    
        public boolean isValid() {
            if(getCurrency() == null || getValue() == null) {
                return false;
            }
    
            // critical logic goes here
    
            // sample code
            if("JPY".equalsIgnoreCase(currency.getCurrencyCode())) {
                int intValue = getValue().intValue();
                double diff = getValue() - intValue;
                if(diff > 0) {
                    return false;
                }
            }
    
            /*double fractionValue = value - (value % (currency.getDefaultFractionDigits() * 10));
            if(fractionValue > currency.getDefaultFractionDigits() * 10) {
                return false;
            }*/
    
            return true;
        }
    
    }
    

    After this, create a constraint say @ValidMoney and MoneyValidator.

    public class MoneyValidator implements ConstraintValidator<ValidMoney, Money> {
    
        @Override
        public void initialize(ValidMoney constraintAnnotation) {
            // TODO Auto-generated method stub
        }
    
        @Override
        public boolean isValid(Money value, ConstraintValidatorContext context) {
            return value.isValid();
        }
    
    }
    

    Example:-

    public class Bid {
        @ValidMoney
        private Money bidAmount;    
    }