Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring validation crashes with Hibernate Validation 5 style list constraint violations [SPR-15082] #19648

Closed
spring-issuemaster opened this issue Jan 2, 2017 · 1 comment

Comments

@spring-issuemaster
Copy link
Collaborator

commented Jan 2, 2017

Sander van Schouwenburg opened SPR-15082 and commented

In Hibernate Validation 5 it is possible to let a validation error refer to a list element. However, the SpringValidatorAdapter does not cope with this situation.

Take the following code example:

public class ValidatorFactoryTests {
	
	public static class ListContainer {
		@NotXList
		private List<String> list = new LinkedList<>();
		
		public void addString(String value) {
			list.add(value);
		}
		
		public List<String> getList() {
			return list;
		}
	}

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.FIELD)
	@Constraint(validatedBy=NotXListValidator.class)
	public static @interface NotXList {

		String message() default "Should not be X";

		Class<?>[] groups() default { };

		Class<? extends Payload>[] payload() default {};
	}


	public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {

		@Override
		public void initialize(NotXList constraintAnnotation) {
		}

		@Override
		public boolean isValid(List<String> list, ConstraintValidatorContext context) {
			context.disableDefaultConstraintViolation();
			boolean valid = true;
			for (int i = 0; i < list.size(); i++) {
				if ("X".equals(list.get(i))) {
					context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
					valid = false;
				}
			}
			return valid;
		}
	}

	@Test
	public void testSpringValidatorWithListValidation() throws Exception {
		LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
		validator.afterPropertiesSet();

		ListContainer listContainer = new ListContainer();
		listContainer.addString("A");
		listContainer.addString("X");
		BeanPropertyBindingResult errors = new BeanPropertyBindingResult(listContainer, "listContainer");
		errors.initConversion(new DefaultConversionService());
		validator.validate(listContainer, errors);
		
		FieldError fieldError = errors.getFieldError("list[1]");
		assertEquals("X", errors.getFieldValue("list[1]"));
	}
}

This test will fail on the last line with the following IllegalArgumentException:

java.lang.IllegalArgumentException: Source to convert from must be an instance of [@org.springframework.validation.beanvalidation.ValidatorFactoryTests$NotXList java.lang.String]; instead it was a [java.util.LinkedList]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:184)
	at org.springframework.validation.AbstractPropertyBindingResult.formatFieldValue(AbstractPropertyBindingResult.java:125)
	at org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:232)
	at org.springframework.validation.beanvalidation.ValidatorFactoryTests.testSpringValidatorWithListValidation(ValidatorFactoryTests.java:273)

This IllegalArgumentException mainly comes from the ConversionService, but without it the test will still fail, because errors.getFieldValue("list\[1\]") will result in the entire list, not just the failed element.

The problem (or potential fix) lies in SpringValidatorAdapter.getRejectedValue. The ConstraintViolation.getInvalidValue results in the value on which the annotation was defined (in this case the list itself). It seem to try to detect here whether it should it should re-obtain the value in case a custom path was supplied. However, it only checks for the existance of a "." in the fieldname or whether the validation annotation was on the entire command object. In this case the field was list\[1\] which does not pass the test.

A possible fix is to include the "[" character in the field test.

Affected Versions
I believe this affects all versions which have Hibernate Validation 5 support. Hibernate Validation 4 did not support a constraint violation of this type.


Affects: 4.3 GA

Issue Links:

  • #13970 SpringValidatorAdapter is incorrectly resolving rejected value for bean based field level constraints
  • #14876 GenericConversionService.convert() throws IllegalArgumentException after updating to Spring 3.2.1
  • #20725 NumberFormatException caused by property paths from JSR-303 based validation with no index into a collection

Referenced from: commits d0e9328, de611cb

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 10, 2017

Juergen Hoeller commented

Good catch! Fixed for 4.3.6 now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.