diff --git a/src/main/java/run/halo/app/utils/ValidationUtils.java b/src/main/java/run/halo/app/utils/ValidationUtils.java index a029527ef..2368eb567 100644 --- a/src/main/java/run/halo/app/utils/ValidationUtils.java +++ b/src/main/java/run/halo/app/utils/ValidationUtils.java @@ -1,15 +1,14 @@ package run.halo.app.utils; +import org.hibernate.validator.internal.engine.path.PathImpl; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.validation.FieldError; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.Validator; +import javax.validation.*; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; /** * Object validation utilities. @@ -30,7 +29,7 @@ public class ValidationUtils { * @return validator */ @NonNull - public static Validator getValidatorOrCreate() { + public static Validator getValidator() { if (VALIDATOR == null) { synchronized (ValidationUtils.class) { if (VALIDATOR == null) { @@ -52,17 +51,55 @@ public class ValidationUtils { */ public static void validate(Object obj, Class... groups) { - Validator validator = getValidatorOrCreate(); + Validator validator = getValidator(); - // Validate the object - Set> constraintViolations = validator.validate(obj, groups); + if (obj instanceof Iterable) { + // validate for iterable + validate((Iterable) obj, groups); + } else { + // validate the non-iterable object + Set> constraintViolations = validator.validate(obj, groups); - if (!CollectionUtils.isEmpty(constraintViolations)) { - // If contain some errors then throw constraint violation exception - throw new ConstraintViolationException(constraintViolations); + if (!CollectionUtils.isEmpty(constraintViolations)) { + // If contain some errors then throw constraint violation exception + throw new ConstraintViolationException(constraintViolations); + } } } + /** + * Validates iterable objects. + * + * @param objs iterable objects could be null + * @param groups validation groups + */ + public static void validate(@Nullable Iterable objs, @Nullable Class... groups) { + if (objs == null) { + return; + } + + // get validator + Validator validator = getValidator(); + + // wrap index + AtomicInteger i = new AtomicInteger(0); + final Set> allViolations = new LinkedHashSet<>(); + objs.forEach(obj -> { + int index = i.getAndIncrement(); + Set> violations = validator.validate(obj, groups); + violations.forEach(violation -> { + Path path = violation.getPropertyPath(); + if (path instanceof PathImpl) { + PathImpl pathImpl = (PathImpl) path; + pathImpl.makeLeafNodeIterableAndSetIndex(index); + } + allViolations.add(violation); + }); + }); + if (!CollectionUtils.isEmpty(allViolations)) { + throw new ConstraintViolationException(allViolations); + } + } /** * 将字段验证错误转换为标准的map型,key:value = field:message diff --git a/src/test/java/run/halo/app/utils/ValidationUtilsTest.java b/src/test/java/run/halo/app/utils/ValidationUtilsTest.java new file mode 100644 index 000000000..7a9a1438a --- /dev/null +++ b/src/test/java/run/halo/app/utils/ValidationUtilsTest.java @@ -0,0 +1,84 @@ +package run.halo.app.utils; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.jupiter.api.Test; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import javax.validation.constraints.NotBlank; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Validation utils test. + * + * @author johnniang + */ +class ValidationUtilsTest { + + Validator validator = ValidationUtils.getValidator(); + + @Test + void validateObjectTest() { + Car car = new Car(null); + Set> violations = validator.validate(car); + validateObjectAssert(violations); + + ConstraintViolationException exception = assertThrows(ConstraintViolationException.class, + () -> ValidationUtils.validate(car)); + validateObjectAssert(exception.getConstraintViolations()); + } + + void validateObjectAssert(Set> violations) { + assertEquals(1, violations.size()); + ConstraintViolation violation = violations.iterator().next(); + assertEquals("name", violation.getPropertyPath().toString()); + assertEquals("Name must not be blank", violation.getMessage()); + } + + @Test + void validateListTest() { + List cars = Arrays.asList(new Car(""), + new Car("car name"), + new Car(null)); + + ConstraintViolationException exception = assertThrows(ConstraintViolationException.class, + () -> ValidationUtils.validate(cars)); + + validateIteratorTest(exception.getConstraintViolations()); + } + + void validateIteratorTest(Set> violations) { + assertEquals(2, violations.size()); + + LinkedList> violationList = new LinkedList<>(violations); + violationList.sort(Comparator.comparing(v -> v.getPropertyPath().toString())); + + // get first violation + ConstraintViolation firstViolation = violationList.get(0); + // get second violation + ConstraintViolation secondViolation = violationList.get(1); + + assertEquals("name[0]", firstViolation.getPropertyPath().toString()); + assertEquals("name[2]", secondViolation.getPropertyPath().toString()); + } + + /** + * Car entity. + * + * @author johnniang + */ + @Data + @AllArgsConstructor + static class Car { + + @NotBlank(message = "Name must not be blank") + private String name; + + } + +} \ No newline at end of file