Skip to content

SpringValidatorAdapter is incorrectly resolving rejected value for bean based field level constraints [SPR-9332] #13970

@spring-projects-issues

Description

@spring-projects-issues

Pavel Horal opened SPR-9332 and commented

SpringValidatorAdapter is converting JSR-303 ConstraintViolations to Spring's FieldErrors. This conversion contains a flaw, when the violation occurs on FIELD level constraint, which is validated as bean (e.g. subform).

Consider following example:

public class Address {

    private String street;
    private String number;

    // Getters and Setters

}
public class UserForm {

    @NotEmpty
    private String name;
    @AddressValid(/* some parameters */)
    private Address contactAddress;
    @AddressValid(/* some parameters */)
    private Address billingAddress;


    // Getters and Setters

}
public class AddressValidator implements ConstraintValidator<AddressValid, Address> {

    // initialize ...

    public boolean isValid(Address address, ConstraintValidationContext context) {
        // check null, disableDefaultConstraintViolation ...

        if (street == null || street.length() == 0) {
            context.buildConstraintViolationWithTemplate("{validation.noStreet}")
                    .addNode(street).addConstraintViolation();
            return false;
        );
        return true;
    }

}

When submitting empty street the validator above will produce ConstraintValidation with Address instance as the invalidValue and UserForm instance as the leafBean. Now consider current SpringValidatorAdapter code:

protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
	for (ConstraintViolation<Object> violation : violations) {
		// ... irrelevant part of code
				if (errors instanceof BindingResult) {
					BindingResult bindingResult = (BindingResult) errors;
					String nestedField = bindingResult.getNestedPath() + field;
					if ("".equals(nestedField)) {
						String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
						bindingResult.addError(new ObjectError(
								errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()));
					}
					else {
						Object invalidValue = violation.getInvalidValue();
						// ... THIS IS WHAT IS WRONG - the rejected value is the whole bean Address!!!
						if (!"".equals(field) && invalidValue == violation.getLeafBean()) {
							// bean constraint with property path: retrieve the actual property value
							invalidValue = bindingResult.getRawFieldValue(field);
						}
						String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
						bindingResult.addError(new FieldError(
								errors.getObjectName(), nestedField, invalidValue, false,
								errorCodes, errorArgs, violation.getMessage()));
					}
				}
		// ... irrelevant part of code
	}
}

The impact of this behavior is critical - Spring's FORM tags are using rejected values as default values in forms. This means, that the user instead of the rejected value will see result of toString() method of the parent bean.

Correct behavior would be to check if the field contains path separator '*.*' instead of just checking invalidValue == violation.getLeafBean().


Affects: 3.1.1, 3.2 M1

Attachments:

Issue Links:

Referenced from: commits 8e75eee

1 votes, 2 watchers

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions