diff --git a/spring-context/src/main/java/org/springframework/validation/Validator.java b/spring-context/src/main/java/org/springframework/validation/Validator.java index b67b6d5d8b77..3ad6ade1b550 100644 --- a/spring-context/src/main/java/org/springframework/validation/Validator.java +++ b/spring-context/src/main/java/org/springframework/validation/Validator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -16,6 +16,10 @@ package org.springframework.validation; +import java.util.function.BiConsumer; + +import org.springframework.util.Assert; + /** * A validator for application-specific objects. * @@ -59,6 +63,7 @@ * application. * * @author Rod Johnson + * @author Toshiaki Maki * @see SmartValidator * @see Errors * @see ValidationUtils @@ -92,4 +97,38 @@ public interface Validator { */ void validate(Object target, Errors errors); + /** + * Takes the {@link BiConsumer} containing the validation logic for the specific type + * <T> and returns the {@link Validator} instance.
+ * This validator implements the typical {@link #supports(Class)} method + * for the given <T>.
+ * + * By using this method, a {@link Validator} can be implemented as follows: + * + *
Validator passwordEqualsValidator = Validator.of(PasswordResetForm.class, (form, errors) -> {
+	 *       if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
+	 *         errors.rejectValue("confirmPassword",
+	 *             "PasswordEqualsValidator.passwordResetForm.password",
+	 *             "password and confirm password must be same.");
+	 *       }
+	 *     });
+ * @param targetClass the class of the object that is to be validated + * @param delegate the validation logic to delegate for the specific type <T> + * @param the type of the object that is to be validated + * @return the {@link Validator} instance + */ + static Validator of(Class targetClass, BiConsumer delegate) { + Assert.notNull(targetClass, "'targetClass' must not be null."); + return new Validator() { + @Override + public boolean supports(Class clazz) { + return targetClass.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + delegate.accept(targetClass.cast(target), errors); + } + }; + } } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java index 05dd886da828..ccf887c87a45 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -82,6 +82,16 @@ */ class DataBinderTests { + Validator spouseValidator = Validator.of(TestBean.class, (tb, errors) -> { + if (tb == null || "XXX".equals(tb.getName())) { + errors.rejectValue("", "SPOUSE_NOT_AVAILABLE"); + return; + } + if (tb.getAge() < 32) { + errors.rejectValue("age", "TOO_YOUNG", "simply too young"); + } + }); + @Test void bindingNoErrors() throws BindException { TestBean rod = new TestBean(); @@ -1144,7 +1154,6 @@ void validatorNoErrors() throws Exception { errors.setNestedPath("spouse"); assertThat(errors.getNestedPath()).isEqualTo("spouse."); assertThat(errors.getFieldValue("age")).isEqualTo("argh"); - Validator spouseValidator = new SpouseValidator(); spouseValidator.validate(tb.getSpouse(), errors); errors.setNestedPath(""); @@ -1195,7 +1204,6 @@ void validatorWithErrors() { errors.setNestedPath("spouse."); assertThat(errors.getNestedPath()).isEqualTo("spouse."); - Validator spouseValidator = new SpouseValidator(); spouseValidator.validate(tb.getSpouse(), errors); errors.setNestedPath(""); @@ -1267,7 +1275,6 @@ void validatorWithErrorsAndCodesPrefix() { errors.setNestedPath("spouse."); assertThat(errors.getNestedPath()).isEqualTo("spouse."); - Validator spouseValidator = new SpouseValidator(); spouseValidator.validate(tb.getSpouse(), errors); errors.setNestedPath(""); @@ -1332,7 +1339,6 @@ void validatorWithNestedObjectNull() { testValidator.validate(tb, errors); errors.setNestedPath("spouse."); assertThat(errors.getNestedPath()).isEqualTo("spouse."); - Validator spouseValidator = new SpouseValidator(); spouseValidator.validate(tb.getSpouse(), errors); errors.setNestedPath(""); @@ -1348,7 +1354,6 @@ void nestedValidatorWithoutNestedPath() { TestBean tb = new TestBean(); tb.setName("XXX"); Errors errors = new BeanPropertyBindingResult(tb, "tb"); - Validator spouseValidator = new SpouseValidator(); spouseValidator.validate(tb, errors); assertThat(errors.hasGlobalErrors()).isTrue(); @@ -2160,28 +2165,6 @@ public void validate(@Nullable Object obj, Errors errors) { } } - - private static class SpouseValidator implements Validator { - - @Override - public boolean supports(Class clazz) { - return TestBean.class.isAssignableFrom(clazz); - } - - @Override - public void validate(@Nullable Object obj, Errors errors) { - TestBean tb = (TestBean) obj; - if (tb == null || "XXX".equals(tb.getName())) { - errors.rejectValue("", "SPOUSE_NOT_AVAILABLE"); - return; - } - if (tb.getAge() < 32) { - errors.rejectValue("age", "TOO_YOUNG", "simply too young"); - } - } - } - - @SuppressWarnings("unused") private static class GrowingList extends AbstractList { diff --git a/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java b/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java index 0a027b95df43..bec3ff28ef27 100644 --- a/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java +++ b/spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -19,7 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.testfixture.beans.TestBean; -import org.springframework.lang.Nullable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -34,6 +33,10 @@ */ public class ValidationUtilsTests { + Validator emptyValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!")); + + Validator emptyOrWhitespaceValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!")); + @Test public void testInvokeValidatorWithNullValidator() throws Exception { TestBean tb = new TestBean(); @@ -46,14 +49,14 @@ public void testInvokeValidatorWithNullValidator() throws Exception { public void testInvokeValidatorWithNullErrors() throws Exception { TestBean tb = new TestBean(); assertThatIllegalArgumentException().isThrownBy(() -> - ValidationUtils.invokeValidator(new EmptyValidator(), tb, null)); + ValidationUtils.invokeValidator(emptyValidator, tb, null)); } @Test public void testInvokeValidatorSunnyDay() throws Exception { TestBean tb = new TestBean(); Errors errors = new BeanPropertyBindingResult(tb, "tb"); - ValidationUtils.invokeValidator(new EmptyValidator(), tb, errors); + ValidationUtils.invokeValidator(emptyValidator, tb, errors); assertThat(errors.hasFieldErrors("name")).isTrue(); assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY"); } @@ -62,15 +65,14 @@ public void testInvokeValidatorSunnyDay() throws Exception { public void testValidationUtilsSunnyDay() throws Exception { TestBean tb = new TestBean(""); - Validator testValidator = new EmptyValidator(); tb.setName(" "); Errors errors = new BeanPropertyBindingResult(tb, "tb"); - testValidator.validate(tb, errors); + emptyValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isFalse(); tb.setName("Roddy"); errors = new BeanPropertyBindingResult(tb, "tb"); - testValidator.validate(tb, errors); + emptyValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isFalse(); } @@ -78,8 +80,7 @@ public void testValidationUtilsSunnyDay() throws Exception { public void testValidationUtilsNull() throws Exception { TestBean tb = new TestBean(); Errors errors = new BeanPropertyBindingResult(tb, "tb"); - Validator testValidator = new EmptyValidator(); - testValidator.validate(tb, errors); + emptyValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isTrue(); assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY"); } @@ -88,8 +89,7 @@ public void testValidationUtilsNull() throws Exception { public void testValidationUtilsEmpty() throws Exception { TestBean tb = new TestBean(""); Errors errors = new BeanPropertyBindingResult(tb, "tb"); - Validator testValidator = new EmptyValidator(); - testValidator.validate(tb, errors); + emptyValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isTrue(); assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY"); } @@ -115,32 +115,31 @@ public void testValidationUtilsEmptyVariants() { @Test public void testValidationUtilsEmptyOrWhitespace() throws Exception { TestBean tb = new TestBean(); - Validator testValidator = new EmptyOrWhitespaceValidator(); // Test null Errors errors = new BeanPropertyBindingResult(tb, "tb"); - testValidator.validate(tb, errors); + emptyOrWhitespaceValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isTrue(); assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE"); // Test empty String tb.setName(""); errors = new BeanPropertyBindingResult(tb, "tb"); - testValidator.validate(tb, errors); + emptyOrWhitespaceValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isTrue(); assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE"); // Test whitespace String tb.setName(" "); errors = new BeanPropertyBindingResult(tb, "tb"); - testValidator.validate(tb, errors); + emptyOrWhitespaceValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isTrue(); assertThat(errors.getFieldError("name").getCode()).isEqualTo("EMPTY_OR_WHITESPACE"); // Test OK tb.setName("Roddy"); errors = new BeanPropertyBindingResult(tb, "tb"); - testValidator.validate(tb, errors); + emptyOrWhitespaceValidator.validate(tb, errors); assertThat(errors.hasFieldErrors("name")).isFalse(); } @@ -163,32 +162,4 @@ public void testValidationUtilsEmptyOrWhitespaceVariants() { assertThat(errors.getFieldError("name").getDefaultMessage()).isEqualTo("msg"); } - - private static class EmptyValidator implements Validator { - - @Override - public boolean supports(Class clazz) { - return TestBean.class.isAssignableFrom(clazz); - } - - @Override - public void validate(@Nullable Object obj, Errors errors) { - ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!"); - } - } - - - private static class EmptyOrWhitespaceValidator implements Validator { - - @Override - public boolean supports(Class clazz) { - return TestBean.class.isAssignableFrom(clazz); - } - - @Override - public void validate(@Nullable Object obj, Errors errors) { - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!"); - } - } - }