Monday, May 19, 2014

Spring Validation rejectValue for inherited fields

Recently I've encountered a problem at Spring Validation. While trying to rejectValue for array I got  exception

org.springframework.beans.NotReadablePropertyException: Invalid property 'entries[0].reason' of bean class [my.company.data.SDROrder]: Bean property 'entries[0].reason' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

this was thrown with following code snippet

Errors errors = new BeanPropertyBindingResult(new SDROrder(), "sdr");
orderValidator.validate(order, errors);

and this is my OrderValidatior implementation

public class OrderValidator implements Validator
{

    @Override
    public boolean supports(Class<?> clazz)
    {
        return Order.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(final Object target, final Errors errors)
    {
        errors.rejectValue("entries[0].reason", "Wrong Reason");
    }

}

the problem was at

errors.rejectValue("entries[0].reason", "Wrong Reason");

apparently Spring can't understand that entries[0] is of type SDROrder but not Order that hasn't reason attribute in it. Let's see data hierarchy

public class Order
{
    private List<AbstractOrderEntry> entries;

    public List<AbstractOrderEntry> getEntries()
    {
        return entries;
    }

    public void setEntries(List<AbstractOrderEntry> entries)
    {
        this.entries = entries;
    }
}
public class SDROrder extends Order {}
public class AbstractOrderEntry {}

So how to explain Spring to use child but not parent here? Does Spring understand hierarchies of objects? Why Spring doesn't uses late binding here?!

All these questions were in my mind for a long time. Finally I found the solution...
The trick is at
  org.springframework.validation.Errors.pushNestedPath(String)
 and
org.springframework.validation.Errors.popNestedPath()
methods. The correct validation should be done as follow:

    errors.pushNestedPath("entries[0]");
    errors.rejectValue("reason", "Wrong Reason");
    errors.popNestedPath();

and that's it!

You may find sources for wrong approach mentioned here at link
and of course correct approach is here

Hope it will save time to someone!