diff --git a/build.gradle b/build.gradle index 93d00d681d..9962dbd1c4 100644 --- a/build.gradle +++ b/build.gradle @@ -39,9 +39,9 @@ configure(allprojects) { project -> ext.activationApiVersion = "1.1.1" ext.annotationApiVersion = "1.3" ext.aspectjVersion = "1.9.0.BETA-6" - ext.beanvalVersion = "1.1.0.Final" + ext.beanvalVersion = "2.0.0.Final" ext.cacheApiVersion = "1.0.0" - ext.caffeineVersion = "2.5.3" + ext.caffeineVersion = "2.5.4" ext.eclipselinkVersion = "2.6.5-RC2" ext.ehcacheVersion = "2.10.4" ext.ehcachejcacheVersion = "1.0.1" @@ -54,7 +54,7 @@ configure(allprojects) { project -> ext.gsonVersion = "2.8.1" ext.hamcrestVersion = "1.3" ext.hibernate5Version = "5.2.10.Final" - ext.hibvalVersion = "5.4.1.Final" + ext.hibvalVersion = "6.0.1.Final" ext.hsqldbVersion = "2.4.0" ext.httpasyncVersion = "4.1.3" ext.httpclientVersion = "4.5.3" @@ -77,7 +77,7 @@ configure(allprojects) { project -> ext.junitJupiterVersion = '5.0.0-RC2' ext.junitPlatformVersion = '1.0.0-RC2' ext.log4jVersion = '2.8.2' - ext.nettyVersion = "4.1.13.Final" + ext.nettyVersion = "4.1.14.Final" ext.niomultipartVersion = "1.1.0" ext.okhttp3Version = "3.8.1" ext.poiVersion = "3.16" @@ -94,7 +94,7 @@ configure(allprojects) { project -> ext.snakeyamlVersion = "1.18" ext.testngVersion = "6.11" ext.tiles3Version = "3.0.7" - ext.tomcatVersion = "8.5.16" + ext.tomcatVersion = "8.5.19" ext.tyrusVersion = "1.13.1" ext.undertowVersion = "1.4.18.Final" ext.websocketVersion = "1.1" @@ -512,9 +512,8 @@ project("spring-context") { optional("javax.interceptor:javax.interceptor-api:${interceptorApiVersion}") optional("javax.enterprise.concurrent:javax.enterprise.concurrent-api:1.0") optional("javax.money:money-api:1.0.1") - optional("org.eclipse.persistence:javax.persistence:${jpaVersion}") - optional("javax.validation:validation-api:${beanvalVersion}") - optional("org.hibernate:hibernate-validator:${hibvalVersion}") + optional("javax.validation:validation-api:1.1.0.Final") + optional("org.hibernate:hibernate-validator:5.4.1.Final") optional("joda-time:joda-time:${jodaVersion}") optional("org.aspectj:aspectjweaver:${aspectjVersion}") optional("org.codehaus.groovy:groovy-all:${groovyVersion}") @@ -660,6 +659,17 @@ project("spring-jdbc") { } } +project("spring-context-indexer") { + description = "Spring Context Indexer" + + dependencies { + testCompile(project(":spring-context")) + testCompile("javax.inject:javax.inject:1") + testCompile("javax.annotation:javax.annotation-api:${annotationApiVersion}") + testCompile("org.eclipse.persistence:javax.persistence:${jpaVersion}") + } +} + project("spring-context-support") { description = "Spring Context Support" @@ -682,22 +692,16 @@ project("spring-context-support") { testCompile("org.apache.poi:poi:${poiVersion}") testCompile("org.hsqldb:hsqldb:${hsqldbVersion}") testCompile("org.slf4j:slf4j-api:${slf4jVersion}") + testCompile("javax.validation:validation-api:${beanvalVersion}") + testCompile("org.hibernate:hibernate-validator:${hibvalVersion}") + testRuntime("javax.el:javax.el-api:${elApiVersion}") + testRuntime("org.glassfish:javax.el:3.0.1-b08") + testRuntime("javax.annotation:javax.annotation-api:${annotationApiVersion}") testRuntime("com.sun.mail:javax.mail:${javamailVersion}") testRuntime("org.ehcache:jcache:${ehcachejcacheVersion}") } } -project("spring-context-indexer") { - description = "Spring Context Indexer" - - dependencies { - testCompile(project(":spring-context")) - testCompile("javax.inject:javax.inject:1") - testCompile("javax.annotation:javax.annotation-api:${annotationApiVersion}") - testCompile("org.eclipse.persistence:javax.persistence:${jpaVersion}") - } -} - project("spring-web") { description = "Spring Web" @@ -782,7 +786,7 @@ project("spring-web") { testRuntime("com.sun.xml.bind:jaxb-core:${jaxbVersion}") testRuntime("com.sun.xml.bind:jaxb-impl:${jaxbVersion}") testRuntime("javax.json:javax.json-api:1.1") - testRuntime("org.apache.johnzon:johnzon-jsonb:1.1.1") + testRuntime("org.apache.johnzon:johnzon-jsonb:1.1.2") } } @@ -891,7 +895,7 @@ project("spring-webmvc") { testRuntime("org.jetbrains.kotlin:kotlin-script-util:${kotlinVersion}") testRuntime("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}") testRuntime("org.jruby:jruby:9.1.12.0") - testRuntime("org.python:jython-standalone:2.5.3") + testRuntime("org.python:jython-standalone:2.7.1") testRuntime("org.webjars:underscorejs:1.8.3") testRuntime("org.glassfish:javax.el:3.0.1-b08") testRuntime("com.sun.xml.bind:jaxb-core:${jaxbVersion}") @@ -999,7 +1003,7 @@ project("spring-webflux") { testRuntime("org.jetbrains.kotlin:kotlin-script-util:${kotlinVersion}") testRuntime("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}") testRuntime("org.jruby:jruby:9.1.12.0") - testRuntime("org.python:jython-standalone:2.5.3") + testRuntime("org.python:jython-standalone:2.7.1") testRuntime("org.synchronoss.cloud:nio-multipart-parser:${niomultipartVersion}") testRuntime("org.webjars:underscorejs:1.8.3") testRuntime("org.glassfish:javax.el:3.0.1-b08") @@ -1050,7 +1054,7 @@ project("spring-test") { exclude group: "io.netty", module: "netty" } optional("org.skyscreamer:jsonassert:${jsonassertVersion}") - optional("com.jayway.jsonpath:json-path:2.3.0") + optional("com.jayway.jsonpath:json-path:2.4.0") optional("org.reactivestreams:reactive-streams") optional("io.projectreactor:reactor-core") optional("io.projectreactor:reactor-test") @@ -1139,7 +1143,6 @@ project("spring-aspects") { ajc("org.aspectj:aspectjtools:${aspectjVersion}") rt("org.aspectj:aspectjrt:${aspectjVersion}") compile("org.aspectj:aspectjweaver:${aspectjVersion}") - provided("org.eclipse.persistence:javax.persistence:${jpaVersion}") optional(project(":spring-aop")) // for @Async support optional(project(":spring-beans")) // for @Configurable support optional(project(":spring-context")) // for @Enable* support @@ -1269,7 +1272,7 @@ configure(rootProject) { task wrapper(type: Wrapper) { description = "Generates gradlew[.bat] scripts" - gradleVersion = '3.5.1' + gradleVersion = '4.1' doLast() { def gradleOpts = "-XX:MaxMetaspaceSize=1024m -Xmx1024m" diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/BeanValidationPostProcessorTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/BeanValidationPostProcessorTests.java new file mode 100644 index 0000000000..8d0d3c1577 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/BeanValidationPostProcessorTests.java @@ -0,0 +1,162 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.validation.beanvalidation2; + +import javax.annotation.PostConstruct; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.junit.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.tests.sample.beans.TestBean; +import org.springframework.validation.beanvalidation.BeanValidationPostProcessor; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + */ +public class BeanValidationPostProcessorTests { + + @Test + public void testNotNullConstraint() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class)); + ac.registerBeanDefinition("bean", new RootBeanDefinition(NotNullConstrainedBean.class)); + try { + ac.refresh(); + fail("Should have thrown BeanCreationException"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getRootCause().getMessage().contains("testBean")); + assertTrue(ex.getRootCause().getMessage().contains("invalid")); + } + ac.close(); + } + + @Test + public void testNotNullConstraintSatisfied() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class)); + RootBeanDefinition bd = new RootBeanDefinition(NotNullConstrainedBean.class); + bd.getPropertyValues().add("testBean", new TestBean()); + ac.registerBeanDefinition("bean", bd); + ac.refresh(); + ac.close(); + } + + @Test + public void testNotNullConstraintAfterInitialization() { + GenericApplicationContext ac = new GenericApplicationContext(); + RootBeanDefinition bvpp = new RootBeanDefinition(BeanValidationPostProcessor.class); + bvpp.getPropertyValues().add("afterInitialization", true); + ac.registerBeanDefinition("bvpp", bvpp); + ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class)); + ac.registerBeanDefinition("bean", new RootBeanDefinition(AfterInitConstraintBean.class)); + ac.refresh(); + ac.close(); + } + + @Test + public void testSizeConstraint() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + RootBeanDefinition bd = new RootBeanDefinition(NotNullConstrainedBean.class); + bd.getPropertyValues().add("testBean", new TestBean()); + bd.getPropertyValues().add("stringValue", "s"); + ac.registerBeanDefinition("bean", bd); + try { + ac.refresh(); + fail("Should have thrown BeanCreationException"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getRootCause().getMessage().contains("stringValue")); + assertTrue(ex.getRootCause().getMessage().contains("invalid")); + } + ac.close(); + } + + @Test + public void testSizeConstraintSatisfied() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + RootBeanDefinition bd = new RootBeanDefinition(NotNullConstrainedBean.class); + bd.getPropertyValues().add("testBean", new TestBean()); + bd.getPropertyValues().add("stringValue", "ss"); + ac.registerBeanDefinition("bean", bd); + ac.refresh(); + ac.close(); + } + + + public static class NotNullConstrainedBean { + + @NotNull + private TestBean testBean; + + @Size(min = 2) + private String stringValue; + + public TestBean getTestBean() { + return testBean; + } + + public void setTestBean(TestBean testBean) { + this.testBean = testBean; + } + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + @PostConstruct + public void init() { + assertNotNull("Shouldn't be here after constraint checking", this.testBean); + } + } + + + public static class AfterInitConstraintBean { + + @NotNull + private TestBean testBean; + + public TestBean getTestBean() { + return testBean; + } + + public void setTestBean(TestBean testBean) { + this.testBean = testBean; + } + + @PostConstruct + public void init() { + this.testBean = new TestBean(); + } + } + +} diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/MethodValidationTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/MethodValidationTests.java new file mode 100644 index 0000000000..0b6ed0dbf6 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/MethodValidationTests.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.validation.beanvalidation2; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.validation.constraints.Max; +import javax.validation.constraints.NotNull; +import javax.validation.groups.Default; + +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor; +import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor; +import org.springframework.validation.annotation.Validated; +import org.springframework.validation.beanvalidation.MethodValidationInterceptor; +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + */ +@SuppressWarnings("rawtypes") +public class MethodValidationTests { + + @Test + public void testMethodValidationInterceptor() { + MyValidBean bean = new MyValidBean(); + ProxyFactory proxyFactory = new ProxyFactory(bean); + proxyFactory.addAdvice(new MethodValidationInterceptor()); + proxyFactory.addAdvisor(new AsyncAnnotationAdvisor()); + doTestProxyValidation((MyValidInterface) proxyFactory.getProxy()); + } + + @Test + public void testMethodValidationPostProcessor() { + StaticApplicationContext ac = new StaticApplicationContext(); + ac.registerSingleton("mvpp", MethodValidationPostProcessor.class); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("beforeExistingAdvisors", false); + ac.registerSingleton("aapp", AsyncAnnotationBeanPostProcessor.class, pvs); + ac.registerSingleton("bean", MyValidBean.class); + ac.refresh(); + doTestProxyValidation(ac.getBean("bean", MyValidInterface.class)); + ac.close(); + } + + + @SuppressWarnings("unchecked") + private void doTestProxyValidation(MyValidInterface proxy) { + assertNotNull(proxy.myValidMethod("value", 5)); + try { + assertNotNull(proxy.myValidMethod("value", 15)); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + try { + assertNotNull(proxy.myValidMethod(null, 5)); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + try { + assertNotNull(proxy.myValidMethod("value", 0)); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + + proxy.myValidAsyncMethod("value", 5); + try { + proxy.myValidAsyncMethod("value", 15); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + try { + proxy.myValidAsyncMethod(null, 5); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + + assertEquals("myValue", proxy.myGenericMethod("myValue")); + try { + proxy.myGenericMethod(null); + fail("Should have thrown ValidationException"); + } + catch (javax.validation.ValidationException ex) { + // expected + } + } + + + @MyStereotype + public static class MyValidBean implements MyValidInterface { + + @Override + public Object myValidMethod(String arg1, int arg2) { + return (arg2 == 0 ? null : "value"); + } + + @Override + public void myValidAsyncMethod(String arg1, int arg2) { + } + + @Override + public String myGenericMethod(String value) { + return value; + } + } + + + public interface MyValidInterface { + + @NotNull Object myValidMethod(@NotNull(groups = MyGroup.class) String arg1, @Max(10) int arg2); + + @MyValid + @Async void myValidAsyncMethod(@NotNull(groups = OtherGroup.class) String arg1, @Max(10) int arg2); + + T myGenericMethod(@NotNull T value); + } + + + public interface MyGroup { + } + + + public interface OtherGroup { + } + + + @Validated({MyGroup.class, Default.class}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MyStereotype { + } + + + @Validated({OtherGroup.class, Default.class}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MyValid { + } + +} diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java new file mode 100644 index 0000000000..9011665a0b --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java @@ -0,0 +1,291 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.validation.beanvalidation2; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Payload; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.context.support.StaticMessageSource; +import org.springframework.util.ObjectUtils; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.beanvalidation.SpringValidatorAdapter; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; +import static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; + +/** + * @author Kazuki Shimizu + * @author Juergen Hoeller + */ +public class SpringValidatorAdapterTests { + + private final Validator nativeValidator = Validation.buildDefaultValidatorFactory().getValidator(); + + private final SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(nativeValidator); + + private final StaticMessageSource messageSource = new StaticMessageSource(); + + + @Before + public void setupSpringValidatorAdapter() { + messageSource.addMessage("Size", Locale.ENGLISH, "Size of {0} is must be between {2} and {1}"); + messageSource.addMessage("Same", Locale.ENGLISH, "{2} must be same value with {1}"); + messageSource.addMessage("password", Locale.ENGLISH, "Password"); + messageSource.addMessage("confirmPassword", Locale.ENGLISH, "Password(Confirm)"); + } + + + @Test + public void testUnwrap() { + Validator nativeValidator = validatorAdapter.unwrap(Validator.class); + assertSame(this.nativeValidator, nativeValidator); + } + + @Test // SPR-13406 + public void testNoStringArgumentValue() { + TestBean testBean = new TestBean(); + testBean.setPassword("pass"); + testBean.setConfirmPassword("pass"); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean"); + validatorAdapter.validate(testBean, errors); + + assertThat(errors.getFieldErrorCount("password"), is(1)); + assertThat(errors.getFieldValue("password"), is("pass")); + assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), + is("Size of Password is must be between 8 and 128")); + } + + @Test // SPR-13406 + public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { + TestBean testBean = new TestBean(); + testBean.setPassword("password"); + testBean.setConfirmPassword("PASSWORD"); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean"); + validatorAdapter.validate(testBean, errors); + + assertThat(errors.getFieldErrorCount("password"), is(1)); + assertThat(errors.getFieldValue("password"), is("password")); + assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), + is("Password must be same value with Password(Confirm)")); + } + + @Test // SPR-13406 + public void testApplyMessageSourceResolvableToStringArgumentValueWithUnresolvedLogicalFieldName() { + TestBean testBean = new TestBean(); + testBean.setEmail("test@example.com"); + testBean.setConfirmEmail("TEST@EXAMPLE.IO"); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean"); + validatorAdapter.validate(testBean, errors); + + assertThat(errors.getFieldErrorCount("email"), is(1)); + assertThat(errors.getFieldValue("email"), is("test@example.com")); + assertThat(errors.getFieldErrorCount("confirmEmail"), is(1)); + assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH), + is("email must be same value with confirmEmail")); + assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH), + is("Email required")); + } + + @Test // SPR-15123 + public void testApplyMessageSourceResolvableToStringArgumentValueWithAlwaysUseMessageFormat() { + messageSource.setAlwaysUseMessageFormat(true); + + TestBean testBean = new TestBean(); + testBean.setEmail("test@example.com"); + testBean.setConfirmEmail("TEST@EXAMPLE.IO"); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(testBean, "testBean"); + validatorAdapter.validate(testBean, errors); + + assertThat(errors.getFieldErrorCount("email"), is(1)); + assertThat(errors.getFieldValue("email"), is("test@example.com")); + assertThat(errors.getFieldErrorCount("confirmEmail"), is(1)); + assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH), + is("email must be same value with confirmEmail")); + assertThat(messageSource.getMessage(errors.getFieldError("confirmEmail"), Locale.ENGLISH), + is("Email required")); + } + + @Test // SPR-15839 + public void testListElementConstraint() { + BeanWithListElementConstraint bean = new BeanWithListElementConstraint(); + bean.setProperty(Arrays.asList("no", "element", "can", "be", null)); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(bean, "bean"); + validatorAdapter.validate(bean, errors); + + assertThat(errors.getFieldErrorCount("property[4]"), is(1)); + assertNull(errors.getFieldValue("property[4]")); + } + + + + @Same(field = "password", comparingField = "confirmPassword") + @Same(field = "email", comparingField = "confirmEmail") + static class TestBean { + + @Size(min = 8, max = 128) + private String password; + + private String confirmPassword; + + private String email; + + @Pattern(regexp = "[\\p{L} -]*", message = "Email required") + private String confirmEmail; + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConfirmPassword() { + return confirmPassword; + } + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getConfirmEmail() { + return confirmEmail; + } + + public void setConfirmEmail(String confirmEmail) { + this.confirmEmail = confirmEmail; + } + } + + + @Documented + @Constraint(validatedBy = {SameValidator.class}) + @Target({TYPE, ANNOTATION_TYPE}) + @Retention(RUNTIME) + @Repeatable(SameGroup.class) + @interface Same { + + String message() default "{org.springframework.validation.beanvalidation.Same.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + String field(); + + String comparingField(); + + @Target({TYPE, ANNOTATION_TYPE}) + @Retention(RUNTIME) + @Documented + @interface List { + Same[] value(); + } + } + + + @Documented + @Inherited + @Retention(RUNTIME) + @Target({TYPE, ANNOTATION_TYPE}) + @interface SameGroup { + + Same[] value(); + } + + + public static class SameValidator implements ConstraintValidator { + + private String field; + + private String comparingField; + + private String message; + + public void initialize(Same constraintAnnotation) { + field = constraintAnnotation.field(); + comparingField = constraintAnnotation.comparingField(); + message = constraintAnnotation.message(); + } + + public boolean isValid(Object value, ConstraintValidatorContext context) { + BeanWrapper beanWrapper = new BeanWrapperImpl(value); + Object fieldValue = beanWrapper.getPropertyValue(field); + Object comparingFieldValue = beanWrapper.getPropertyValue(comparingField); + boolean matched = ObjectUtils.nullSafeEquals(fieldValue, comparingFieldValue); + if (matched) { + return true; + } + else { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(message) + .addPropertyNode(field) + .addConstraintViolation(); + return false; + } + } + } + + + public class BeanWithListElementConstraint { + + private List<@NotNull String> property; + + public List getProperty() { + return property; + } + public void setProperty(List property) { + this.property = property; + } + } + +} diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java new file mode 100644 index 0000000000..c14b6b1b95 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/ValidatorFactoryTests.java @@ -0,0 +1,508 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.validation.beanvalidation2; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintViolation; +import javax.validation.Payload; +import javax.validation.Valid; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorFactory; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.Environment; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + */ +public class ValidatorFactoryTests { + + @Test + public void testSimpleValidation() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + Set> result = validator.validate(person); + assertEquals(2, result.size()); + for (ConstraintViolation cv : result) { + String path = cv.getPropertyPath().toString(); + if ("name".equals(path) || "address.street".equals(path)) { + assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull); + } + else { + fail("Invalid constraint violation with path '" + path + "'"); + } + } + + Validator nativeValidator = validator.unwrap(Validator.class); + assertTrue(nativeValidator.getClass().getName().startsWith("org.hibernate")); + assertTrue(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory); + assertTrue(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory); + + validator.destroy(); + } + + @Test + public void testSimpleValidationWithCustomProvider() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.setProviderClass(HibernateValidator.class); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + Set> result = validator.validate(person); + assertEquals(2, result.size()); + for (ConstraintViolation cv : result) { + String path = cv.getPropertyPath().toString(); + if ("name".equals(path) || "address.street".equals(path)) { + assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull); + } + else { + fail("Invalid constraint violation with path '" + path + "'"); + } + } + + Validator nativeValidator = validator.unwrap(Validator.class); + assertTrue(nativeValidator.getClass().getName().startsWith("org.hibernate")); + assertTrue(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory); + assertTrue(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory); + + validator.destroy(); + } + + @Test + public void testSimpleValidationWithClassLevel() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + ValidPerson person = new ValidPerson(); + person.setName("Juergen"); + person.getAddress().setStreet("Juergen's Street"); + Set> result = validator.validate(person); + assertEquals(1, result.size()); + Iterator> iterator = result.iterator(); + ConstraintViolation cv = iterator.next(); + assertEquals("", cv.getPropertyPath().toString()); + assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid); + } + + @Test + public void testSpringValidationFieldType() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + person.setName("Phil"); + person.getAddress().setStreet("Phil's Street"); + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, errors); + assertEquals(1, errors.getErrorCount()); + assertThat("Field/Value type mismatch", errors.getFieldError("address").getRejectedValue(), + instanceOf(ValidAddress.class)); + } + + @Test + public void testSpringValidation() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, result); + assertEquals(2, result.getErrorCount()); + FieldError fieldError = result.getFieldError("name"); + assertEquals("name", fieldError.getField()); + List errorCodes = Arrays.asList(fieldError.getCodes()); + assertEquals(4, errorCodes.size()); + assertTrue(errorCodes.contains("NotNull.person.name")); + assertTrue(errorCodes.contains("NotNull.name")); + assertTrue(errorCodes.contains("NotNull.java.lang.String")); + assertTrue(errorCodes.contains("NotNull")); + fieldError = result.getFieldError("address.street"); + assertEquals("address.street", fieldError.getField()); + errorCodes = Arrays.asList(fieldError.getCodes()); + assertEquals(5, errorCodes.size()); + assertTrue(errorCodes.contains("NotNull.person.address.street")); + assertTrue(errorCodes.contains("NotNull.address.street")); + assertTrue(errorCodes.contains("NotNull.street")); + assertTrue(errorCodes.contains("NotNull.java.lang.String")); + assertTrue(errorCodes.contains("NotNull")); + } + + @Test + public void testSpringValidationWithClassLevel() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + person.setName("Juergen"); + person.getAddress().setStreet("Juergen's Street"); + BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, result); + assertEquals(1, result.getErrorCount()); + ObjectError globalError = result.getGlobalError(); + List errorCodes = Arrays.asList(globalError.getCodes()); + assertEquals(2, errorCodes.size()); + assertTrue(errorCodes.contains("NameAddressValid.person")); + assertTrue(errorCodes.contains("NameAddressValid")); + } + + @Test + public void testSpringValidationWithAutowiredValidator() throws Exception { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext( + LocalValidatorFactoryBean.class); + LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class); + + ValidPerson person = new ValidPerson(); + person.expectsAutowiredValidator = true; + person.setName("Juergen"); + person.getAddress().setStreet("Juergen's Street"); + BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, result); + assertEquals(1, result.getErrorCount()); + ObjectError globalError = result.getGlobalError(); + List errorCodes = Arrays.asList(globalError.getCodes()); + assertEquals(2, errorCodes.size()); + assertTrue(errorCodes.contains("NameAddressValid.person")); + assertTrue(errorCodes.contains("NameAddressValid")); + ctx.close(); + } + + @Test + public void testSpringValidationWithErrorInListElement() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + person.getAddressList().add(new ValidAddress()); + BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, result); + assertEquals(3, result.getErrorCount()); + FieldError fieldError = result.getFieldError("name"); + assertEquals("name", fieldError.getField()); + fieldError = result.getFieldError("address.street"); + assertEquals("address.street", fieldError.getField()); + fieldError = result.getFieldError("addressList[0].street"); + assertEquals("addressList[0].street", fieldError.getField()); + } + + @Test + public void testSpringValidationWithErrorInSetElement() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + ValidPerson person = new ValidPerson(); + person.getAddressSet().add(new ValidAddress()); + BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, result); + assertEquals(3, result.getErrorCount()); + FieldError fieldError = result.getFieldError("name"); + assertEquals("name", fieldError.getField()); + fieldError = result.getFieldError("address.street"); + assertEquals("address.street", fieldError.getField()); + fieldError = result.getFieldError("addressSet[].street"); + assertEquals("addressSet[].street", fieldError.getField()); + } + + @Test + public void testInnerBeanValidation() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + MainBean mainBean = new MainBean(); + Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean"); + validator.validate(mainBean, errors); + Object rejected = errors.getFieldValue("inner.value"); + assertNull(rejected); + } + + @Test + public void testValidationWithOptionalField() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + MainBeanWithOptional mainBean = new MainBeanWithOptional(); + Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean"); + validator.validate(mainBean, errors); + Object rejected = errors.getFieldValue("inner.value"); + assertNull(rejected); + } + + @Test + public void testListValidation() 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]")); + } + + + @NameAddressValid + public static class ValidPerson { + + @NotNull + private String name; + + @Valid + private ValidAddress address = new ValidAddress(); + + @Valid + private List addressList = new LinkedList<>(); + + @Valid + private Set addressSet = new LinkedHashSet<>(); + + public boolean expectsAutowiredValidator = false; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ValidAddress getAddress() { + return address; + } + + public void setAddress(ValidAddress address) { + this.address = address; + } + + public List getAddressList() { + return addressList; + } + + public void setAddressList(List addressList) { + this.addressList = addressList; + } + + public Set getAddressSet() { + return addressSet; + } + + public void setAddressSet(Set addressSet) { + this.addressSet = addressSet; + } + } + + + public static class ValidAddress { + + @NotNull + private String street; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + } + + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Constraint(validatedBy = NameAddressValidator.class) + public @interface NameAddressValid { + + String message() default "Street must not contain name"; + + Class[] groups() default {}; + + Class[] payload() default {}; + } + + + public static class NameAddressValidator implements ConstraintValidator { + + @Autowired + private Environment environment; + + @Override + public void initialize(NameAddressValid constraintAnnotation) { + } + + @Override + public boolean isValid(ValidPerson value, ConstraintValidatorContext context) { + if (value.expectsAutowiredValidator) { + assertNotNull(this.environment); + } + boolean valid = (value.name == null || !value.address.street.contains(value.name)); + if (!valid && "Phil".equals(value.name)) { + context.buildConstraintViolationWithTemplate( + context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation(); + } + return valid; + } + } + + + public static class MainBean { + + @InnerValid + private InnerBean inner = new InnerBean(); + + public InnerBean getInner() { + return inner; + } + } + + + public static class MainBeanWithOptional { + + @InnerValid + private InnerBean inner = new InnerBean(); + + public Optional getInner() { + return Optional.ofNullable(inner); + } + } + + + public static class InnerBean { + + private String value; + + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } + } + + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @Constraint(validatedBy=InnerValidator.class) + public static @interface InnerValid { + + String message() default "NOT VALID"; + + Class[] groups() default { }; + + Class[] payload() default {}; + } + + + public static class InnerValidator implements ConstraintValidator { + + @Override + public void initialize(InnerValid constraintAnnotation) { + } + + @Override + public boolean isValid(InnerBean bean, ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + if (bean.getValue() == null) { + context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation(); + return false; + } + return true; + } + } + + + public static class ListContainer { + + @NotXList + private List list = new LinkedList<>(); + + public void addString(String value) { + list.add(value); + } + + public List getList() { + return list; + } + } + + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @Constraint(validatedBy = NotXListValidator.class) + public @interface NotXList { + + String message() default "Should not be X"; + + Class[] groups() default {}; + + Class[] payload() default {}; + } + + + public static class NotXListValidator implements ConstraintValidator> { + + @Override + public void initialize(NotXList constraintAnnotation) { + } + + @Override + public boolean isValid(List 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; + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index 7840edb841..68cb263a14 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.validation.ConstraintViolation; +import javax.validation.ElementKind; +import javax.validation.Path; import javax.validation.ValidationException; import javax.validation.executable.ExecutableValidator; import javax.validation.metadata.BeanDescriptor; @@ -57,7 +59,7 @@ import org.springframework.validation.SmartValidator; */ public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator { - private static final Set internalAnnotationAttributes = new HashSet<>(3); + private static final Set internalAnnotationAttributes = new HashSet<>(4); static { internalAnnotationAttributes.add("message"); @@ -176,7 +178,31 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. * @see org.springframework.validation.FieldError#getField() */ protected String determineField(ConstraintViolation violation) { - return violation.getPropertyPath().toString(); + Path path = violation.getPropertyPath(); + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Path.Node node : path) { + if (node.isInIterable()) { + sb.append('['); + Object index = node.getIndex(); + if (index == null) { + index = node.getKey(); + } + if (index != null) { + sb.append(index); + } + sb.append(']'); + } + String name = node.getName(); + if (name != null && node.getKind() == ElementKind.PROPERTY) { + if (!first) { + sb.append('.'); + } + first = false; + sb.append(name); + } + } + return sb.toString(); } /** diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java index 6ebc2a0474..3cadae83d5 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,6 @@ import org.springframework.tests.sample.beans.TestBean; import static org.junit.Assert.*; /** - * Tested against Hibernate Validator 5.x. - * * @author Juergen Hoeller */ public class BeanValidationPostProcessorTests { diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java index a383d81b16..b79903d784 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,8 +35,6 @@ import org.springframework.validation.annotation.Validated; import static org.junit.Assert.*; /** - * Tests against Hibernate Validator 5.x. - * * @author Juergen Hoeller */ @SuppressWarnings("rawtypes") diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java index e63a8fe224..a4044dedd2 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java @@ -48,7 +48,6 @@ import static org.junit.Assert.*; /** * @author Kazuki Shimizu * @author Juergen Hoeller - * @since 4.3 */ public class SpringValidatorAdapterTests { @@ -84,6 +83,7 @@ public class SpringValidatorAdapterTests { validatorAdapter.validate(testBean, errors); assertThat(errors.getFieldErrorCount("password"), is(1)); + assertThat(errors.getFieldValue("password"), is("pass")); assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), is("Size of Password is must be between 8 and 128")); } @@ -98,6 +98,7 @@ public class SpringValidatorAdapterTests { validatorAdapter.validate(testBean, errors); assertThat(errors.getFieldErrorCount("password"), is(1)); + assertThat(errors.getFieldValue("password"), is("password")); assertThat(messageSource.getMessage(errors.getFieldError("password"), Locale.ENGLISH), is("Password must be same value with Password(Confirm)")); } @@ -112,6 +113,7 @@ public class SpringValidatorAdapterTests { validatorAdapter.validate(testBean, errors); assertThat(errors.getFieldErrorCount("email"), is(1)); + assertThat(errors.getFieldValue("email"), is("test@example.com")); assertThat(errors.getFieldErrorCount("confirmEmail"), is(1)); assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH), is("email must be same value with confirmEmail")); @@ -131,6 +133,7 @@ public class SpringValidatorAdapterTests { validatorAdapter.validate(testBean, errors); assertThat(errors.getFieldErrorCount("email"), is(1)); + assertThat(errors.getFieldValue("email"), is("test@example.com")); assertThat(errors.getFieldErrorCount("confirmEmail"), is(1)); assertThat(messageSource.getMessage(errors.getFieldError("email"), Locale.ENGLISH), is("email must be same value with confirmEmail")); @@ -139,6 +142,7 @@ public class SpringValidatorAdapterTests { } + @Same(field = "password", comparingField = "confirmPassword") @Same(field = "email", comparingField = "confirmEmail") static class TestBean { diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java index 56aaee72e2..c9a183e01a 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java @@ -55,8 +55,6 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** - * Tests against Hibernate Validator 5.x. - * * @author Juergen Hoeller */ public class ValidatorFactoryTests {