/* * 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.beanvalidation; 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.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.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 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 * @since 4.3 */ 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(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(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.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.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")); } @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<? extends Payload>[] 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<Same, Object> { 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; } } } }