From 5258bf730dff1bdc13e06271e33be585fc05f1cd Mon Sep 17 00:00:00 2001 From: Tadhg Pearson Date: Thu, 2 Nov 2017 22:43:03 -0400 Subject: [PATCH 01/10] HV-1466 Add @UniqueElements constraint Checks that a collection doesn't contain elements that are equal. --- .../validator/constraints/UniqueElements.java | 40 +++++ .../hv/UniqueElementsValidator.java | 69 +++++++++ .../validator/ValidationMessages.properties | 1 + .../hv/UniqueElementsValidatorTest.java | 140 ++++++++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java create mode 100644 engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java diff --git a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java new file mode 100644 index 0000000000..cc98b6574a --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java @@ -0,0 +1,40 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.constraints; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +import org.hibernate.validator.internal.constraintvalidators.hv.UniqueElementsValidator; + +/** + * Validates that every object in the provided Collection is unique, that no two elements in the provided collection are equal to one another. + *

+ * This can be useful with JAX-RS, which always deserializes collections to a list. Thus, duplicates would implicitly + * and silently removed when converting it to a set. This annotation will allow you to check for duplicates in the list + * and raise an error instead. + * + * @author Tadhg Pearson + * @since 6.0.5 + */ +@Documented +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = UniqueElementsValidator.class) +public @interface UniqueElements { + + String message() default "{org.hibernate.validator.constraints.UniqueElements.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java new file mode 100644 index 0000000000..083e4629a0 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java @@ -0,0 +1,69 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.constraintvalidators.hv; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +import org.hibernate.validator.constraints.UniqueElements; +import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; +import org.hibernate.validator.internal.engine.messageinterpolation.util.InterpolationHelper; + +import static java.util.stream.Collectors.toList; + +/** + * Validates that the provided collection contains unique elements, that no two elements in the collection are equal. + *

+ * Uniqueness is defined by the equals method of the objects being compared. + * + * @author Tadhg Pearson + */ +public class UniqueElementsValidator implements ConstraintValidator> { + + /** + * @param objects Potentially null collection of input objects to check for duplicates + * @param validatorContext Non-null {@link ConstraintValidatorContext} to accumulate validation messages + * + * @return True if the input collection is null or does not contain duplicate objects + */ + @Override + public boolean isValid(Collection objects, ConstraintValidatorContext validatorContext) { + boolean valid = isValid( objects ); + if ( !valid ) { + List duplicateElements = findDuplicates( objects ); + String duplicates = InterpolationHelper.escapeMessageParameter( duplicateElements.toString() ); + + if ( validatorContext instanceof HibernateConstraintValidatorContext ) { + validatorContext.unwrap( HibernateConstraintValidatorContext.class ) + .addMessageParameter( "duplicates", duplicates ); + } + } + return valid; + } + + private boolean isValid(Collection objects) { + boolean valid; + if ( objects == null ) { + valid = true; + } + else { + Set set = new HashSet<>( objects ); + valid = set.size() == objects.size(); + } + return valid; + } + + + private List findDuplicates(Collection objects) { + Set uniqueElements = new HashSet<>(); + return objects.stream().filter( o -> !uniqueElements.add( o ) ).map( String::valueOf ).collect( toList() ); + } +} diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties index 555a2658ca..1c9cdc6d77 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties @@ -37,6 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = script exp org.hibernate.validator.constraints.Range.message = must be between {min} and {max} org.hibernate.validator.constraints.SafeHtml.message = may have unsafe html content org.hibernate.validator.constraints.ScriptAssert.message = script expression "{script}" didn't evaluate to true +org.hibernate.validator.constraints.UniqueElements.message = Contains the following duplicate elements: {duplicates} org.hibernate.validator.constraints.URL.message = must be a valid URL org.hibernate.validator.constraints.br.CNPJ.message = invalid Brazilian corporate taxpayer registry number (CNPJ) diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java new file mode 100644 index 0000000000..efc5dc2fac --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java @@ -0,0 +1,140 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.internal.constraintvalidators.hv; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import org.hibernate.validator.constraints.UniqueElements; + +import com.google.common.base.Objects; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; + +/** + * Tests the {@link UniqueElements} constraint + * + * @author Tadhg Pearson + */ +public class UniqueElementsValidatorTest { + + private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + private static class AnnotationContainer { + + @UniqueElements + private final List validateMe; + + AnnotationContainer(List validateMe) { + this.validateMe = validateMe; + } + } + + @Test + public void testValidDataPasses() { + List> input = new ArrayList<>(); + input.add( null ); + input.add( Collections.singletonList( null ) ); + input.add( Collections.singletonList( "" ) ); + input.add( Collections.singletonList( "a" ) ); + input.add( Arrays.asList( "a", "aa" ) ); + input.add( Arrays.asList( "^", "ˆ" ) ); + input.add( Arrays.asList( "a", "b", "c", "1", "2", "3", null ) ); + input.add( Arrays.asList( "null", null ) ); + input.add( Arrays.asList( "lorem", "lorem ipsum" ) ); + + input.add( Arrays.asList( + new TestObject( 1 ), + new TestObject( 2 ), + new TestExtendedObject( 2 ), + new TestExtendedObject( 3 ) + ) ); + + for ( List value : input ) { + Set> results = validator.validate( new AnnotationContainer( value ) ); + assertTrue( results.isEmpty(), "Validation should have passed for " + value + ", " + results ); + } + } + + @Test + public void testInvalidDataFails() { + List> input = new ArrayList<>(); + input.add( Arrays.asList( null, null ) ); + input.add( Arrays.asList( "a", "a" ) ); + input.add( Arrays.asList( "ˆ", "ˆ" ) ); + input.add( Arrays.asList( "a", "b", "a" ) ); + input.add( Arrays.asList( "a", "b", "c", "b" ) ); + input.add( Arrays.asList( "*", "*" ) ); + + input.add( Arrays.asList( new TestObject( 1 ), new TestObject( 2 ), new TestObject( 1 ) ) ); + input.add( Arrays.asList( new TestObject( 0 ), new TestObject( 0 ) ) ); + + for ( List value : input ) { + Set> results = validator.validate( new AnnotationContainer( value ) ); + assertTrue( results.size() > 0, "Validation should have failed for " + value + ", " + results ); + } + } + + @Test + public void testMessageContainsDuplicatedValue() { + String duplicate = "seeme"; + List fails = Arrays.asList( duplicate, duplicate ); + Set> results = validator.validate( new AnnotationContainer( fails ) ); + assertTrue( results.stream().anyMatch( cv -> cv.getMessage().contains( duplicate ) ) ); + } + + private static class TestObject { + + final int value; + + TestObject(Integer value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof TestObject ) ) { + return false; + } + TestObject that = (TestObject) o; + return value == that.value; + } + + @Override + public int hashCode() { + return Objects.hashCode( value ); + } + } + + + private static class TestExtendedObject extends TestObject { + + TestExtendedObject(Integer value) { + super( value ); + } + + @Override + public boolean equals(Object o) { + return ( this == o ) || ( o instanceof TestExtendedObject && super.equals( o ) ); + } + + @Override + public int hashCode() { + return java.util.Objects.hash( super.hashCode() ); + } + } +} From d8275bea3012683a8245d68ecee2f96f24c45f0b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Nov 2017 13:30:17 +0100 Subject: [PATCH 02/10] HV-1466 Various adjustments --- .../validator/constraints/UniqueElements.java | 11 ++-- .../hv/UniqueElementsValidator.java | 63 +++++++++---------- .../validator/ValidationMessages.properties | 2 +- .../hv/UniqueElementsValidatorTest.java | 56 ++++++++++++----- .../testutil/ConstraintViolationAssert.java | 14 +++++ 5 files changed, 91 insertions(+), 55 deletions(-) diff --git a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java index cc98b6574a..0e09446471 100644 --- a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java +++ b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java @@ -11,17 +11,20 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Collection; + import javax.validation.Constraint; import javax.validation.Payload; import org.hibernate.validator.internal.constraintvalidators.hv.UniqueElementsValidator; /** - * Validates that every object in the provided Collection is unique, that no two elements in the provided collection are equal to one another. + * Validates that every object in the provided {@link Collection} is unique, i.e. that we can't find 2 equal elements in + * the collection. *

- * This can be useful with JAX-RS, which always deserializes collections to a list. Thus, duplicates would implicitly - * and silently removed when converting it to a set. This annotation will allow you to check for duplicates in the list - * and raise an error instead. + * For instance, this can be useful with JAX-RS, which always deserializes collections to a list. Thus, duplicates would + * implicitly and silently be removed when converting it to a set. This constraint allows you to check for duplicates in + * the list and to raise an error instead. * * @author Tadhg Pearson * @since 6.0.5 diff --git a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java index 083e4629a0..2e8a8cfb33 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java +++ b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java @@ -6,64 +6,61 @@ */ package org.hibernate.validator.internal.constraintvalidators.hv; +import static java.util.stream.Collectors.toList; + import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; + import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.hibernate.validator.constraints.UniqueElements; import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; -import org.hibernate.validator.internal.engine.messageinterpolation.util.InterpolationHelper; - -import static java.util.stream.Collectors.toList; +import org.hibernate.validator.internal.util.CollectionHelper; /** - * Validates that the provided collection contains unique elements, that no two elements in the collection are equal. + * Validates that the provided collection only contains unique elements, i.e. that we can't find 2 equal elements in the + * collection. *

- * Uniqueness is defined by the equals method of the objects being compared. + * Uniqueness is defined by the {@code equals()} method of the objects being compared. * * @author Tadhg Pearson + * @author Guillaume Smet */ public class UniqueElementsValidator implements ConstraintValidator> { /** - * @param objects Potentially null collection of input objects to check for duplicates - * @param validatorContext Non-null {@link ConstraintValidatorContext} to accumulate validation messages + * @param collection the collection to validate + * @param constraintValidatorContext context in which the constraint is evaluated * - * @return True if the input collection is null or does not contain duplicate objects + * @return true if the input collection is null or does not contain duplicate elements */ @Override - public boolean isValid(Collection objects, ConstraintValidatorContext validatorContext) { - boolean valid = isValid( objects ); - if ( !valid ) { - List duplicateElements = findDuplicates( objects ); - String duplicates = InterpolationHelper.escapeMessageParameter( duplicateElements.toString() ); - - if ( validatorContext instanceof HibernateConstraintValidatorContext ) { - validatorContext.unwrap( HibernateConstraintValidatorContext.class ) - .addMessageParameter( "duplicates", duplicates ); - } + public boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) { + if ( collection == null || collection.size() < 2 ) { + return true; } - return valid; - } - private boolean isValid(Collection objects) { - boolean valid; - if ( objects == null ) { - valid = true; + List duplicates = findDuplicates( collection ); + + if ( duplicates.isEmpty() ) { + return true; } - else { - Set set = new HashSet<>( objects ); - valid = set.size() == objects.size(); + + if ( constraintValidatorContext instanceof HibernateConstraintValidatorContext ) { + constraintValidatorContext.unwrap( HibernateConstraintValidatorContext.class ) + .addMessageParameter( "duplicates", duplicates.stream().map( String::valueOf ).collect( Collectors.joining( ", " ) ) ) + .withDynamicPayload( CollectionHelper.toImmutableList( duplicates ) ); } - return valid; - } + return false; + } - private List findDuplicates(Collection objects) { - Set uniqueElements = new HashSet<>(); - return objects.stream().filter( o -> !uniqueElements.add( o ) ).map( String::valueOf ).collect( toList() ); + private List findDuplicates(Collection collection) { + Set uniqueElements = CollectionHelper.newHashSet( collection.size() ); + return collection.stream().filter( o -> !uniqueElements.add( o ) ) + .collect( toList() ); } } diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties index 1c9cdc6d77..46e19e1489 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties @@ -37,7 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = script exp org.hibernate.validator.constraints.Range.message = must be between {min} and {max} org.hibernate.validator.constraints.SafeHtml.message = may have unsafe html content org.hibernate.validator.constraints.ScriptAssert.message = script expression "{script}" didn't evaluate to true -org.hibernate.validator.constraints.UniqueElements.message = Contains the following duplicate elements: {duplicates} +org.hibernate.validator.constraints.UniqueElements.message = must only contain unique elements but contains duplicate elements: {duplicates} org.hibernate.validator.constraints.URL.message = must be a valid URL org.hibernate.validator.constraints.br.CNPJ.message = invalid Brazilian corporate taxpayer registry number (CNPJ) diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java index efc5dc2fac..ef8ee12799 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java @@ -6,21 +6,26 @@ */ package org.hibernate.validator.test.internal.constraintvalidators.hv; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertNoViolations; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf; +import static org.testng.Assert.assertTrue; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; + import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.Validator; +import org.assertj.core.api.Assertions; import org.hibernate.validator.constraints.UniqueElements; - -import com.google.common.base.Objects; +import org.hibernate.validator.engine.HibernateConstraintViolation; +import org.hibernate.validator.testutils.ValidatorUtil; import org.testng.annotations.Test; -import static org.testng.Assert.assertTrue; +import com.google.common.base.Objects; /** * Tests the {@link UniqueElements} constraint @@ -29,14 +34,12 @@ */ public class UniqueElementsValidatorTest { - private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); - private static class AnnotationContainer { @UniqueElements private final List validateMe; - AnnotationContainer(List validateMe) { + private AnnotationContainer(List validateMe) { this.validateMe = validateMe; } } @@ -62,8 +65,8 @@ public void testValidDataPasses() { ) ); for ( List value : input ) { - Set> results = validator.validate( new AnnotationContainer( value ) ); - assertTrue( results.isEmpty(), "Validation should have passed for " + value + ", " + results ); + Set> violations = ValidatorUtil.getValidator().validate( new AnnotationContainer( value ) ); + assertNoViolations( violations, "Validation should have passed for " + value ); } } @@ -81,8 +84,10 @@ public void testInvalidDataFails() { input.add( Arrays.asList( new TestObject( 0 ), new TestObject( 0 ) ) ); for ( List value : input ) { - Set> results = validator.validate( new AnnotationContainer( value ) ); - assertTrue( results.size() > 0, "Validation should have failed for " + value + ", " + results ); + Set> violations = ValidatorUtil.getValidator().validate( new AnnotationContainer( value ) ); + assertThat( violations ) + .describedAs( "Validation should have failed for " + value ) + .containsOnlyViolations( violationOf( UniqueElements.class ) ); } } @@ -90,15 +95,32 @@ public void testInvalidDataFails() { public void testMessageContainsDuplicatedValue() { String duplicate = "seeme"; List fails = Arrays.asList( duplicate, duplicate ); - Set> results = validator.validate( new AnnotationContainer( fails ) ); - assertTrue( results.stream().anyMatch( cv -> cv.getMessage().contains( duplicate ) ) ); + Set> violations = ValidatorUtil.getValidator().validate( new AnnotationContainer( fails ) ); + + assertThat( violations ).containsOnlyViolations( violationOf( UniqueElements.class ) ); + + assertTrue( violations.stream().anyMatch( cv -> cv.getMessage().contains( duplicate ) ) ); + } + + @SuppressWarnings("unchecked") + @Test + public void testDymanicPayloadContainsDuplicatedValue() { + String duplicate = "seeme"; + List fails = Arrays.asList( duplicate, duplicate ); + Set> violations = ValidatorUtil.getValidator().validate( new AnnotationContainer( fails ) ); + + assertThat( violations ).containsOnlyViolations( violationOf( UniqueElements.class ) ); + + ConstraintViolation violation = violations.iterator().next(); + Assertions.assertThat( ((HibernateConstraintViolation) violation.unwrap( HibernateConstraintViolation.class )).getDynamicPayload( List.class ) ) + .containsOnly( duplicate ); } private static class TestObject { - final int value; + private final int value; - TestObject(Integer value) { + private TestObject(Integer value) { this.value = value; } @@ -123,7 +145,7 @@ public int hashCode() { private static class TestExtendedObject extends TestObject { - TestExtendedObject(Integer value) { + private TestExtendedObject(Integer value) { super( value ); } diff --git a/test-utils/src/main/java/org/hibernate/validator/testutil/ConstraintViolationAssert.java b/test-utils/src/main/java/org/hibernate/validator/testutil/ConstraintViolationAssert.java index 32adb11959..ae8830cd19 100644 --- a/test-utils/src/main/java/org/hibernate/validator/testutil/ConstraintViolationAssert.java +++ b/test-utils/src/main/java/org/hibernate/validator/testutil/ConstraintViolationAssert.java @@ -97,6 +97,15 @@ public static void assertNoViolations(Set> viol Assertions.assertThat( violations ).isEmpty(); } + /** + * Asserts that the given violation list has no violations (is empty). + * + * @param violations The violation list to verify. + */ + public static void assertNoViolations(Set> violations, String message) { + Assertions.assertThat( violations ).describedAs( message ).isEmpty(); + } + public static void assertConstraintTypes(Set> descriptors, Class... expectedConstraintTypes) { List> actualConstraintTypes = new ArrayList<>(); @@ -245,6 +254,11 @@ protected ConstraintViolationSetAssert(Set> act super( actualViolations ); } + @Override + public ConstraintViolationSetAssert describedAs(String description, Object... args) { + return (ConstraintViolationSetAssert) super.describedAs( description, args ); + } + public void containsOnlyViolations(ViolationExpectation... expectedViolations) { isNotNull(); From d5b3267514cfc6940351adfb6c403a48da9a11d7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Nov 2017 13:30:30 +0100 Subject: [PATCH 03/10] HV-1466 Add French translation --- .../org/hibernate/validator/ValidationMessages_fr.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties index 4174e77d5f..efe5a9c46d 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties @@ -37,6 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = le script org.hibernate.validator.constraints.Range.message = doit \u00EAtre entre {min} et {max} org.hibernate.validator.constraints.SafeHtml.message = peut contenir du HTML dangereux org.hibernate.validator.constraints.ScriptAssert.message = le script "{script}" n'a pas \u00E9t\u00E9 \u00E9valu\u00E9 \u00E0 vrai +org.hibernate.validator.constraints.UniqueElements.message = ne doit contenir que des \u00E9l\u00E9ments uniques mais contient des \u00E9l\u00E9ments dupliqu\u00E9s : {duplicates} org.hibernate.validator.constraints.URL.message = URL mal form\u00E9e org.hibernate.validator.constraints.br.CNPJ.message = numéro d'enregistrement brésilien de société contribuable (CNPJ) invalide From 6ce3d2fa85267048b229a54504b268c8861a513f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Nov 2017 15:53:07 +0100 Subject: [PATCH 04/10] HV-1466 Add programmatic definition for the @UniqueElements constraint --- .../validator/cfg/defs/UniqueElementsDef.java | 22 +++++++ .../test/cfg/UniqueElementsDefTest.java | 62 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 engine/src/main/java/org/hibernate/validator/cfg/defs/UniqueElementsDef.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/cfg/UniqueElementsDefTest.java diff --git a/engine/src/main/java/org/hibernate/validator/cfg/defs/UniqueElementsDef.java b/engine/src/main/java/org/hibernate/validator/cfg/defs/UniqueElementsDef.java new file mode 100644 index 0000000000..6bb54dfc33 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/defs/UniqueElementsDef.java @@ -0,0 +1,22 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ + +package org.hibernate.validator.cfg.defs; + +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.constraints.UniqueElements; + +/** + * @author Guillaume Smet + * @since 6.0.5 + */ +public class UniqueElementsDef extends ConstraintDef { + + public UniqueElementsDef() { + super( UniqueElements.class ); + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/cfg/UniqueElementsDefTest.java b/engine/src/test/java/org/hibernate/validator/test/cfg/UniqueElementsDefTest.java new file mode 100644 index 0000000000..f8b9d00282 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/cfg/UniqueElementsDefTest.java @@ -0,0 +1,62 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.cfg; + +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf; + +import java.lang.annotation.ElementType; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validator; + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.cfg.defs.UniqueElementsDef; +import org.hibernate.validator.constraints.UniqueElements; +import org.hibernate.validator.testutil.TestForIssue; +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * @author Guillaume Smet + */ +public class UniqueElementsDefTest { + + @Test + @TestForIssue(jiraKey = "HV-1466") + public void testUniqueItemsDef() { + final HibernateValidatorConfiguration configuration = ValidatorUtil.getConfiguration( HibernateValidator.class ); + + final ConstraintMapping programmaticMapping = configuration.createConstraintMapping(); + programmaticMapping.type( Library.class ) + .property( "books", ElementType.FIELD ).constraint( new UniqueElementsDef() ); + configuration.addMapping( programmaticMapping ); + + Validator validator = configuration.buildValidatorFactory().getValidator(); + Set> violations = validator.validate( new Library( + Arrays.asList( "A Prayer for Owen Meany", "The Cider House Rules", "The Cider House Rules" ) ) ); + + assertThat( violations ).containsOnlyViolations( + violationOf( UniqueElements.class ).withProperty( "books" ) + ); + } + + @SuppressWarnings("unused") + private static class Library { + + private final List books; + + public Library(List books) { + this.books = books; + } + } +} From 8316c95da426e57b76caf091dae8155b3d6eb168 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Nov 2017 15:58:47 +0100 Subject: [PATCH 05/10] HV-1466 Add documentation for @UniqueElements --- documentation/src/main/asciidoc/ch02.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/src/main/asciidoc/ch02.asciidoc b/documentation/src/main/asciidoc/ch02.asciidoc index 6e0dffe59e..823a532cb0 100644 --- a/documentation/src/main/asciidoc/ch02.asciidoc +++ b/documentation/src/main/asciidoc/ch02.asciidoc @@ -716,6 +716,10 @@ In addition, `baseURI` allows to specify the base URI used to resolve relative U Supported data types::: Any type Hibernate metadata impact::: None +`@UniqueElements`:: Checks that the annotated collection only contains unique elements. The equality is determined using the `equals()` method. + Supported data types::: `Collection` + Hibernate metadata impact::: None + `@URL(protocol=, host=, port=, regexp=, flags=)`:: Checks if the annotated character sequence is a valid URL according to RFC2396. If any of the optional parameters `protocol`, `host` or `port` are specified, the corresponding URL fragments must match the specified values. The optional parameters `regexp` and `flags` allow to specify an additional regular expression (including regular expression flags) which the URL must match. Per default this constraint used the `java.net.URL` constructor to verify whether a given string represents a valid URL. A regular expression based version is also available - `RegexpURLValidator` - which can be configured via XML (see <>) or the programmatic API (see <>). Supported data types::: `CharSequence` Hibernate metadata impact::: None From ca5dbd083caffbda37d525271e0f67a96d9c6956 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Mon, 13 Nov 2017 23:04:39 +0100 Subject: [PATCH 06/10] HV-1466 Adding Ukrainian translation for the @UniqueElements constraint --- .../org/hibernate/validator/ValidationMessages_uk.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties index 465e676de0..81f15dfdf2 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties @@ -37,6 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = \u0441\u04 org.hibernate.validator.constraints.Range.message = \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u043c\u0456\u0436 {min} \u0442\u0430 {max} org.hibernate.validator.constraints.SafeHtml.message = \u043c\u043e\u0436\u0435 \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043d\u0435\u0431\u0435\u0437\u043f\u0435\u0447\u043d\u0438\u0439 html \u043a\u043e\u043d\u0442\u0435\u043d\u0442 org.hibernate.validator.constraints.ScriptAssert.message = \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432\u0438\u0439 \u0432\u0438\u0440\u0430\u0437 "{script}" \u043d\u0435 \u0454 \u0456\u0441\u0442\u0438\u043d\u043d\u0438\u043c +org.hibernate.validator.constraints.UniqueElements.message = \u043f\u043e\u0432\u0438\u043d\u043d\u043e \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0456 \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0438\u002c \u043d\u0430\u0442\u043e\u043c\u0456\u0441\u0442\u044c \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u044c\u043e\u0432\u0430\u043d\u0456 \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0438: {duplicates} org.hibernate.validator.constraints.URL.message = \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043d\u0438\u043c URL org.hibernate.validator.constraints.br.CNPJ.message = \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0431\u0440\u0430\u0437\u0438\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u043d\u0438\u043a\u0430 \u043f\u043e\u0434\u0430\u0442\u043a\u0456\u0432 (CNPJ) From 6582fee3780ea66e363b4de76307bb87689ccd96 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Nov 2017 15:32:02 +0100 Subject: [PATCH 07/10] HV-1466 Do not include the list of duplicates in the message by default --- documentation/src/main/asciidoc/ch02.asciidoc | 2 +- .../validator/ValidationMessages.properties | 2 +- .../ValidationMessages_de.properties | 1 + .../ValidationMessages_fr.properties | 2 +- .../ValidationMessages_uk.properties | 2 +- .../hv/UniqueElementsValidatorTest.java | 28 +++++++++++++++---- .../hv/UniqueElementsMessages.properties | 1 + 7 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 engine/src/test/resources/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsMessages.properties diff --git a/documentation/src/main/asciidoc/ch02.asciidoc b/documentation/src/main/asciidoc/ch02.asciidoc index 823a532cb0..4f45cc6bb7 100644 --- a/documentation/src/main/asciidoc/ch02.asciidoc +++ b/documentation/src/main/asciidoc/ch02.asciidoc @@ -716,7 +716,7 @@ In addition, `baseURI` allows to specify the base URI used to resolve relative U Supported data types::: Any type Hibernate metadata impact::: None -`@UniqueElements`:: Checks that the annotated collection only contains unique elements. The equality is determined using the `equals()` method. +`@UniqueElements`:: Checks that the annotated collection only contains unique elements. The equality is determined using the `equals()` method. The default message does not include the list of duplicate elements but you can include it by overriding the message and using the `{duplicates}` message parameter. The list of duplicate elements is also included in the dynamic payload of the constraint violation. Supported data types::: `Collection` Hibernate metadata impact::: None diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties index 46e19e1489..47ff74ad7a 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages.properties @@ -37,7 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = script exp org.hibernate.validator.constraints.Range.message = must be between {min} and {max} org.hibernate.validator.constraints.SafeHtml.message = may have unsafe html content org.hibernate.validator.constraints.ScriptAssert.message = script expression "{script}" didn't evaluate to true -org.hibernate.validator.constraints.UniqueElements.message = must only contain unique elements but contains duplicate elements: {duplicates} +org.hibernate.validator.constraints.UniqueElements.message = must only contain unique elements org.hibernate.validator.constraints.URL.message = must be a valid URL org.hibernate.validator.constraints.br.CNPJ.message = invalid Brazilian corporate taxpayer registry number (CNPJ) diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_de.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_de.properties index 0257e09e89..c802fec1a1 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_de.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_de.properties @@ -33,6 +33,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = Skriptausdr org.hibernate.validator.constraints.Range.message = muss zwischen {min} und {max} liegen org.hibernate.validator.constraints.SafeHtml.message = k\u00F6nnte unsicheren HTML-Inhalt haben org.hibernate.validator.constraints.ScriptAssert.message = Skriptausdruck "{script}" gab nicht true zur\u00FCck +org.hibernate.validator.constraints.UniqueElements.message = darf keine Duplikate enthalten org.hibernate.validator.constraints.URL.message = muss eine g\u00FCltige URL sein org.hibernate.validator.constraints.time.DurationMax.message = muss k\u00FCrzer${inclusive == true ? ' oder gleich' : ' als'}${days == 0 ? '' : days == 1 ? ' 1 Tag' : ' ' += days += ' Tage'}${hours == 0 ? '' : hours == 1 ? ' 1 Stunde' : ' ' += hours += ' Stunden'}${minutes == 0 ? '' : minutes == 1 ? ' 1 Minute' : ' ' += minutes += ' Minuten'}${seconds == 0 ? '' : seconds == 1 ? ' 1 Sekunde' : ' ' += seconds += ' Sekunden'}${millis == 0 ? '' : millis == 1 ? ' 1 Millisekunde' : ' ' += millis += ' Millisekunden'}${nanos == 0 ? '' : nanos == 1 ? ' 1 Nanosekunde' : ' ' += nanos += ' Nanosekunden'} sein diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties index efe5a9c46d..7d9dbee9e1 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_fr.properties @@ -37,7 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = le script org.hibernate.validator.constraints.Range.message = doit \u00EAtre entre {min} et {max} org.hibernate.validator.constraints.SafeHtml.message = peut contenir du HTML dangereux org.hibernate.validator.constraints.ScriptAssert.message = le script "{script}" n'a pas \u00E9t\u00E9 \u00E9valu\u00E9 \u00E0 vrai -org.hibernate.validator.constraints.UniqueElements.message = ne doit contenir que des \u00E9l\u00E9ments uniques mais contient des \u00E9l\u00E9ments dupliqu\u00E9s : {duplicates} +org.hibernate.validator.constraints.UniqueElements.message = ne doit contenir que des \u00E9l\u00E9ments uniques org.hibernate.validator.constraints.URL.message = URL mal form\u00E9e org.hibernate.validator.constraints.br.CNPJ.message = numéro d'enregistrement brésilien de société contribuable (CNPJ) invalide diff --git a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties index 81f15dfdf2..a9d2e5d8fc 100644 --- a/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties +++ b/engine/src/main/resources/org/hibernate/validator/ValidationMessages_uk.properties @@ -37,7 +37,7 @@ org.hibernate.validator.constraints.ParametersScriptAssert.message = \u0441\u04 org.hibernate.validator.constraints.Range.message = \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u043c\u0456\u0436 {min} \u0442\u0430 {max} org.hibernate.validator.constraints.SafeHtml.message = \u043c\u043e\u0436\u0435 \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043d\u0435\u0431\u0435\u0437\u043f\u0435\u0447\u043d\u0438\u0439 html \u043a\u043e\u043d\u0442\u0435\u043d\u0442 org.hibernate.validator.constraints.ScriptAssert.message = \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432\u0438\u0439 \u0432\u0438\u0440\u0430\u0437 "{script}" \u043d\u0435 \u0454 \u0456\u0441\u0442\u0438\u043d\u043d\u0438\u043c -org.hibernate.validator.constraints.UniqueElements.message = \u043f\u043e\u0432\u0438\u043d\u043d\u043e \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0456 \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0438\u002c \u043d\u0430\u0442\u043e\u043c\u0456\u0441\u0442\u044c \u043c\u0456\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u043e\u0434\u0443\u0431\u043b\u044c\u043e\u0432\u0430\u043d\u0456 \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0438: {duplicates} +org.hibernate.validator.constraints.UniqueElements.message = \u043f\u043e\u0432\u0438\u043d\u043d\u043e \u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u043b\u0438\u0448\u0435 \u0443\u043d\u0456\u043a\u0430\u043b\u044c\u043d\u0456 \u0435\u043b\u0435\u043c\u0435\u043d\u0442\u0438 org.hibernate.validator.constraints.URL.message = \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0434\u0456\u0439\u0441\u043d\u0438\u043c URL org.hibernate.validator.constraints.br.CNPJ.message = \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u043d\u043e\u043c\u0435\u0440 \u0431\u0440\u0430\u0437\u0438\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u043d\u0438\u043a\u0430 \u043f\u043e\u0434\u0430\u0442\u043a\u0456\u0432 (CNPJ) diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java index ef8ee12799..422bfead80 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsValidatorTest.java @@ -15,18 +15,22 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.validation.ConstraintViolation; +import javax.validation.MessageInterpolator; +import javax.validation.Validator; import org.assertj.core.api.Assertions; +import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.constraints.UniqueElements; import org.hibernate.validator.engine.HibernateConstraintViolation; +import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; +import org.hibernate.validator.resourceloading.AggregateResourceBundleLocator; import org.hibernate.validator.testutils.ValidatorUtil; import org.testng.annotations.Test; -import com.google.common.base.Objects; - /** * Tests the {@link UniqueElements} constraint * @@ -93,9 +97,23 @@ public void testInvalidDataFails() { @Test public void testMessageContainsDuplicatedValue() { + HibernateValidatorConfiguration configuration = ValidatorUtil.getConfiguration(); + + MessageInterpolator messageInterpolator = new ResourceBundleMessageInterpolator( + new AggregateResourceBundleLocator( + Arrays.asList( "org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsMessages" ), + configuration.getDefaultResourceBundleLocator(), + getClass().getClassLoader() + ) + ); + + Validator validator = configuration + .messageInterpolator( messageInterpolator ) + .buildValidatorFactory().getValidator(); + String duplicate = "seeme"; List fails = Arrays.asList( duplicate, duplicate ); - Set> violations = ValidatorUtil.getValidator().validate( new AnnotationContainer( fails ) ); + Set> violations = validator.validate( new AnnotationContainer( fails ) ); assertThat( violations ).containsOnlyViolations( violationOf( UniqueElements.class ) ); @@ -138,7 +156,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hashCode( value ); + return Objects.hash( value ); } } @@ -156,7 +174,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return java.util.Objects.hash( super.hashCode() ); + return Objects.hash( super.hashCode() ); } } } diff --git a/engine/src/test/resources/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsMessages.properties b/engine/src/test/resources/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsMessages.properties new file mode 100644 index 0000000000..e9234d21a8 --- /dev/null +++ b/engine/src/test/resources/org/hibernate/validator/test/internal/constraintvalidators/hv/UniqueElementsMessages.properties @@ -0,0 +1 @@ +org.hibernate.validator.constraints.UniqueElements.message = must only contain unique elements but contains duplicate elements: {duplicates} From ee55769dd7dc265c35d492ab9f271c5e4db8c868 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Nov 2017 15:35:02 +0100 Subject: [PATCH 08/10] HV-1466 Adjust the targets of the @UniqueElements constraint --- .../validator/constraints/UniqueElements.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java index 0e09446471..40b8211c3b 100644 --- a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java +++ b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java @@ -6,8 +6,14 @@ */ package org.hibernate.validator.constraints; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -30,7 +36,7 @@ * @since 6.0.5 */ @Documented -@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE }) +@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UniqueElementsValidator.class) public @interface UniqueElements { From f2a87037261cbc8968de6e01b311202a1879aaeb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Nov 2017 15:56:11 +0100 Subject: [PATCH 09/10] HV-1466 Use ConstraintHelper to declare the constraint --- .../validator/constraints/UniqueElements.java | 21 +++++++++++++++---- .../metadata/core/ConstraintHelper.java | 3 +++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java index 40b8211c3b..2a61ec63e3 100644 --- a/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java +++ b/engine/src/main/java/org/hibernate/validator/constraints/UniqueElements.java @@ -11,18 +11,20 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collection; import javax.validation.Constraint; import javax.validation.Payload; -import org.hibernate.validator.internal.constraintvalidators.hv.UniqueElementsValidator; +import org.hibernate.validator.constraints.UniqueElements.List; /** * Validates that every object in the provided {@link Collection} is unique, i.e. that we can't find 2 equal elements in @@ -36,9 +38,10 @@ * @since 6.0.5 */ @Documented +@Constraint(validatedBy = { }) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = UniqueElementsValidator.class) +@Retention(RUNTIME) +@Repeatable(List.class) public @interface UniqueElements { String message() default "{org.hibernate.validator.constraints.UniqueElements.message}"; @@ -46,4 +49,14 @@ Class[] groups() default {}; Class[] payload() default {}; + + /** + * Defines several {@code @UniqueElements} annotations on the same element. + */ + @Target({ TYPE }) + @Retention(RUNTIME) + @Documented + public @interface List { + UniqueElements[] value(); + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java index f86e55703e..beceb8b01e 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java @@ -66,6 +66,7 @@ import org.hibernate.validator.constraints.SafeHtml; import org.hibernate.validator.constraints.ScriptAssert; import org.hibernate.validator.constraints.URL; +import org.hibernate.validator.constraints.UniqueElements; import org.hibernate.validator.constraints.br.CNPJ; import org.hibernate.validator.constraints.br.CPF; import org.hibernate.validator.constraints.pl.NIP; @@ -262,6 +263,7 @@ import org.hibernate.validator.internal.constraintvalidators.hv.SafeHtmlValidator; import org.hibernate.validator.internal.constraintvalidators.hv.ScriptAssertValidator; import org.hibernate.validator.internal.constraintvalidators.hv.URLValidator; +import org.hibernate.validator.internal.constraintvalidators.hv.UniqueElementsValidator; import org.hibernate.validator.internal.constraintvalidators.hv.br.CNPJValidator; import org.hibernate.validator.internal.constraintvalidators.hv.br.CPFValidator; import org.hibernate.validator.internal.constraintvalidators.hv.pl.NIPValidator; @@ -667,6 +669,7 @@ public ConstraintHelper() { putConstraint( tmpConstraints, ParameterScriptAssert.class, ParameterScriptAssertValidator.class ); putConstraint( tmpConstraints, SafeHtml.class, SafeHtmlValidator.class ); putConstraint( tmpConstraints, ScriptAssert.class, ScriptAssertValidator.class ); + putConstraint( tmpConstraints, UniqueElements.class, UniqueElementsValidator.class ); putConstraint( tmpConstraints, URL.class, URLValidator.class ); this.builtinConstraints = Collections.unmodifiableMap( tmpConstraints ); From efbf8fde10adf9ec22f17444a0b8252f7415625f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Nov 2017 17:19:32 +0100 Subject: [PATCH 10/10] HV-1466 Add support for @UniqueElements in the annotation processor --- .../ap/internal/util/ConstraintHelper.java | 1 + .../validator/ap/internal/util/TypeNames.java | 1 + .../ap/ConstraintValidationProcessorTest.java | 18 ++++++++++++ .../ModelWithUniqueElementsConstraints.java | 28 +++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithUniqueElementsConstraints.java diff --git a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java index f2039d83af..031c771c69 100644 --- a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java +++ b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java @@ -303,6 +303,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_EMPTY, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SAFE_HTML, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SCRIPT_ASSERT, Object.class ); + registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.UNIQUE_ELEMENTS, Collection.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.URL, CharSequence.class ); registerSupportedTypesUnwrappedByDefault( SupportedForUnwrapTypes.OPTIONAL_INT, Integer.class.getName() ); diff --git a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java index d26e94bbf9..f6881af552 100644 --- a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java +++ b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java @@ -80,6 +80,7 @@ public static class HibernateValidatorTypes { public static final String NOT_EMPTY = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".NotEmpty"; public static final String SAFE_HTML = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".SafeHtml"; public static final String SCRIPT_ASSERT = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ScriptAssert"; + public static final String UNIQUE_ELEMENTS = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".UniqueElements"; public static final String URL = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".URL"; public static final String DURATION_MIN = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".time.DurationMin"; public static final String DURATION_MAX = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".time.DurationMax"; diff --git a/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorTest.java b/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorTest.java index 6eeda13e0b..b66fc7310b 100644 --- a/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorTest.java +++ b/annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorTest.java @@ -24,6 +24,7 @@ import org.hibernate.validator.ap.testmodel.ModelWithJava8DateTime; import org.hibernate.validator.ap.testmodel.ModelWithJavaMoneyTypes; import org.hibernate.validator.ap.testmodel.ModelWithJodaTypes; +import org.hibernate.validator.ap.testmodel.ModelWithUniqueElementsConstraints; import org.hibernate.validator.ap.testmodel.ModelWithoutConstraints; import org.hibernate.validator.ap.testmodel.MultipleConstraintsOfSameType; import org.hibernate.validator.ap.testmodel.ValidationUsingAtValidAnnotation; @@ -685,4 +686,21 @@ public void unwrappingConstraints() { new DiagnosticExpectation( Kind.WARNING, 54 ) ); } + + @Test + @TestForIssue(jiraKey = "HV-1466") + public void uniqueElementsConstraints() { + File[] sourceFiles = new File[] { + compilerHelper.getSourceFile( ModelWithUniqueElementsConstraints.class ) + }; + + boolean compilationResult = + compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, false, true, sourceFiles ); + + assertFalse( compilationResult ); + assertThatDiagnosticsMatch( + diagnostics, + new DiagnosticExpectation( Kind.ERROR, 26 ) + ); + } } diff --git a/annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithUniqueElementsConstraints.java b/annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithUniqueElementsConstraints.java new file mode 100644 index 0000000000..ae0bf13c1f --- /dev/null +++ b/annotation-processor/src/test/java/org/hibernate/validator/ap/testmodel/ModelWithUniqueElementsConstraints.java @@ -0,0 +1,28 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.ap.testmodel; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.hibernate.validator.constraints.UniqueElements; + +public class ModelWithUniqueElementsConstraints { + + @UniqueElements + public Collection collection; + + @UniqueElements + public List list; + + @UniqueElements + public Set set; + + @UniqueElements + public String string; +}