-
Notifications
You must be signed in to change notification settings - Fork 569
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Adding new ISBN constraint annotation. - Adding new ConstraintValidator for ISBN constraint (ISBNValidator). - Registering new constraint in ConstraintHelper so it is known to HV engine. - Adding default message. - Adding tests for isbn validation to make sure that validator logic is correct. - Adding simple test with @ISBN to make sure that new constraint is recognised by engine and is validated.
- Loading branch information
1 parent
6512e03
commit 5ba6668
Showing
6 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
engine/src/main/java/org/hibernate/validator/constraints/ISBN.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Hibernate Validator, declare and validate application constraints | ||
* | ||
* License: Apache License, Version 2.0 | ||
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. | ||
*/ | ||
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 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.Target; | ||
|
||
import javax.validation.Constraint; | ||
import javax.validation.Payload; | ||
|
||
import org.hibernate.validator.constraints.ISBN.List; | ||
|
||
/** | ||
* Checks that the annotated character sequence is a valid | ||
* <a href="https://en.wikipedia.org/wiki/International_Standard_Book_Number">ISBN</a>. | ||
* The length of the number and the check digit are both verified. | ||
* <p> | ||
* The supported type is {@code CharSequence}. {@code null} is considered valid. | ||
* <p> | ||
* During validation all non ISBN characters are ignored. All digits and 'X' are considered | ||
* to be valid ISBN characters. This is useful when validating ISBN with dashes separating | ||
* parts of the number (ex. {@code 978-161-729-045-9}). | ||
* | ||
* @author Marko Bekhta | ||
*/ | ||
@Documented | ||
@Constraint(validatedBy = { }) | ||
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) | ||
@Retention(RUNTIME) | ||
@Repeatable(List.class) | ||
public @interface ISBN { | ||
|
||
String message() default "{org.hibernate.validator.constraints.ISBN.message}"; | ||
|
||
Class<?>[] groups() default { }; | ||
|
||
Class<? extends Payload>[] payload() default { }; | ||
|
||
Type type() default Type.ISBN13; | ||
|
||
/** | ||
* Defines several {@code @ISBN} annotations on the same element. | ||
*/ | ||
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) | ||
@Retention(RUNTIME) | ||
@Documented | ||
public @interface List { | ||
|
||
ISBN[] value(); | ||
} | ||
|
||
/** | ||
* Defines the ISBN length. Valid lengths of ISBNs are {@code 10} and {@code 13} | ||
* which are represented as {@link Type#ISBN10} and {@link Type#ISBN13} respectively. | ||
*/ | ||
enum Type { | ||
ISBN10, | ||
ISBN13 | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
...src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/ISBNValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* Hibernate Validator, declare and validate application constraints | ||
* | ||
* License: Apache License, Version 2.0 | ||
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. | ||
*/ | ||
package org.hibernate.validator.internal.constraintvalidators.hv; | ||
|
||
import java.util.function.Function; | ||
import java.util.regex.Pattern; | ||
|
||
import javax.validation.ConstraintValidator; | ||
import javax.validation.ConstraintValidatorContext; | ||
|
||
import org.hibernate.validator.constraints.ISBN; | ||
|
||
/** | ||
* Checks that a given character sequence (e.g. string) is a valid ISBN. | ||
* | ||
* @author Marko Bekhta | ||
*/ | ||
public class ISBNValidator implements ConstraintValidator<ISBN, CharSequence> { | ||
|
||
/** | ||
* Pattern to replace all non ISBN characters. ISBN can have digits or 'X'. | ||
*/ | ||
private static Pattern NOT_DIGITS_OR_NOT_X = Pattern.compile( "[^\\dX]" ); | ||
|
||
private int length; | ||
|
||
private Function<String, Boolean> checkChecksumFunction; | ||
|
||
@Override | ||
public void initialize(ISBN constraintAnnotation) { | ||
switch ( constraintAnnotation.type() ) { | ||
case ISBN10: | ||
length = 10; | ||
checkChecksumFunction = this::checkChecksumISBN10; | ||
break; | ||
case ISBN13: | ||
length = 13; | ||
checkChecksumFunction = this::checkChecksumISBN13; | ||
break; | ||
} | ||
} | ||
|
||
@Override | ||
public boolean isValid(CharSequence isbn, ConstraintValidatorContext context) { | ||
if ( isbn == null ) { | ||
return true; | ||
} | ||
|
||
// Replace all non-digit (or !=X) chars | ||
String digits = NOT_DIGITS_OR_NOT_X.matcher( isbn ).replaceAll( "" ); | ||
|
||
// Check if the length of resulting string matches the expecting one | ||
if ( digits.length() != length ) { | ||
return false; | ||
} | ||
|
||
return checkChecksumFunction.apply( digits ); | ||
} | ||
|
||
/** | ||
* Check the digits for ISBN 10 using algorithm from | ||
* <a href="https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digits">Wikipedia</a>. | ||
*/ | ||
private boolean checkChecksumISBN10(String isbn) { | ||
int sum = 0; | ||
for ( int i = 0; i < isbn.length() - 1; i++ ) { | ||
sum += ( isbn.charAt( i ) - '0' ) * ( i + 1 ); | ||
} | ||
char checkSum = isbn.charAt( 9 ); | ||
return sum % 11 == ( checkSum == 'X' ? 10 : checkSum - '0' ); | ||
} | ||
|
||
/** | ||
* Check the digits for ISBN 13 using algorithm from | ||
* <a href="https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-13_check_digit_calculation">Wikipedia</a>. | ||
*/ | ||
private boolean checkChecksumISBN13(String isbn) { | ||
int sum = 0; | ||
for ( int i = 0; i < isbn.length() - 1; i++ ) { | ||
sum += ( isbn.charAt( i ) - '0' ) * ( i % 2 == 0 ? 1 : 3 ); | ||
} | ||
char checkSum = isbn.charAt( 12 ); | ||
return 10 - sum % 10 == ( checkSum - '0' ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
...est/java/org/hibernate/validator/test/constraints/annotations/hv/ISBNConstrainedTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Hibernate Validator, declare and validate application constraints | ||
* | ||
* License: Apache License, Version 2.0 | ||
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. | ||
*/ | ||
package org.hibernate.validator.test.constraints.annotations.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 java.util.Set; | ||
|
||
import javax.validation.ConstraintViolation; | ||
|
||
import org.hibernate.validator.constraints.ISBN; | ||
import org.hibernate.validator.test.constraints.annotations.AbstractConstrainedTest; | ||
|
||
import org.testng.annotations.Test; | ||
|
||
/** | ||
* Test to make sure that elements annotated with {@link ISBN} are validated. | ||
* | ||
* @author Marko Bekhta | ||
*/ | ||
public class ISBNConstrainedTest extends AbstractConstrainedTest { | ||
|
||
@Test | ||
public void testISBN() { | ||
Foo foo = new Foo( "978-1-56619-909-4" ); | ||
Set<ConstraintViolation<Foo>> violations = validator.validate( foo ); | ||
assertNoViolations( violations ); | ||
} | ||
|
||
@Test | ||
public void testISBNInvalid() { | ||
Foo foo = new Foo( "5412-3456-7890" ); | ||
Set<ConstraintViolation<Foo>> violations = validator.validate( foo ); | ||
assertThat( violations ).containsOnlyViolations( | ||
violationOf( ISBN.class ).withMessage( "invalid ISBN" ) | ||
); | ||
} | ||
|
||
private static class Foo { | ||
|
||
@ISBN | ||
private final String number; | ||
|
||
public Foo(String number) { | ||
this.number = number; | ||
} | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
...java/org/hibernate/validator/test/internal/constraintvalidators/hv/ISBNValidatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* Hibernate Validator, declare and validate application constraints | ||
* | ||
* License: Apache License, Version 2.0 | ||
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. | ||
*/ | ||
package org.hibernate.validator.test.internal.constraintvalidators.hv; | ||
|
||
import static org.testng.Assert.assertFalse; | ||
import static org.testng.Assert.assertTrue; | ||
|
||
import org.hibernate.validator.constraints.ISBN; | ||
import org.hibernate.validator.internal.constraintvalidators.hv.ISBNValidator; | ||
import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor; | ||
|
||
import org.testng.annotations.BeforeMethod; | ||
import org.testng.annotations.Test; | ||
|
||
/** | ||
* A set of tests for {@link ISBN} constraint validator ({@link ISBNValidator}), which | ||
* make sure that validation is performed correctly. | ||
* | ||
* @author Marko Bekhta | ||
*/ | ||
public class ISBNValidatorTest { | ||
|
||
private ISBNValidator validator; | ||
|
||
@BeforeMethod | ||
public void setUp() throws Exception { | ||
validator = new ISBNValidator(); | ||
} | ||
|
||
@Test | ||
public void validISBN10() throws Exception { | ||
validator.initialize( initializeAnnotation( ISBN.Type.ISBN10 ) ); | ||
|
||
assertValidISBN( null ); | ||
assertValidISBN( "99921-58-10-7" ); | ||
assertValidISBN( "9971-5-0210-0" ); | ||
assertValidISBN( "960-425-059-0" ); | ||
assertValidISBN( "80-902734-1-6" ); | ||
assertValidISBN( "0-9752298-0-X" ); | ||
assertValidISBN( "0-85131-041-9" ); | ||
assertValidISBN( "0-684-84328-5" ); | ||
assertValidISBN( "1-84356-028-3" ); | ||
} | ||
|
||
@Test | ||
public void invalidISBN10() throws Exception { | ||
validator.initialize( initializeAnnotation( ISBN.Type.ISBN10 ) ); | ||
|
||
// invalid check-digit | ||
assertInvalidISBN( "99921-58-10-8" ); | ||
assertInvalidISBN( "9971-5-0210-1" ); | ||
assertInvalidISBN( "960-425-059-2" ); | ||
assertInvalidISBN( "80-902734-1-8" ); | ||
assertInvalidISBN( "0-9752298-0-3" ); | ||
assertInvalidISBN( "0-85131-041-X" ); | ||
assertInvalidISBN( "0-684-84328-7" ); | ||
assertInvalidISBN( "1-84356-028-1" ); | ||
|
||
// invalid length | ||
assertInvalidISBN( "" ); | ||
assertInvalidISBN( "978-0-5" ); | ||
assertInvalidISBN( "978-0-55555555555555" ); | ||
} | ||
|
||
@Test | ||
public void validISBN13() throws Exception { | ||
validator.initialize( initializeAnnotation( ISBN.Type.ISBN13 ) ); | ||
|
||
assertValidISBN( null ); | ||
assertValidISBN( "978-123-456-789-7" ); | ||
assertValidISBN( "978-91-983989-1-5" ); | ||
assertValidISBN( "978-988-785-411-1" ); | ||
assertValidISBN( "978-1-56619-909-4" ); | ||
assertValidISBN( "978-1-4028-9462-6" ); | ||
assertValidISBN( "978-0-85131-041-1" ); | ||
assertValidISBN( "978-0-684-84328-5" ); | ||
assertValidISBN( "978-1-84356-028-9" ); | ||
assertValidISBN( "978-0-54560-495-6" ); | ||
} | ||
|
||
@Test | ||
public void invalidISBN13() throws Exception { | ||
validator.initialize( initializeAnnotation( ISBN.Type.ISBN13 ) ); | ||
|
||
// invalid check-digit | ||
assertInvalidISBN( "978-123-456-789-6" ); | ||
assertInvalidISBN( "978-91-983989-1-4" ); | ||
assertInvalidISBN( "978-988-785-411-2" ); | ||
assertInvalidISBN( "978-1-56619-909-1" ); | ||
assertInvalidISBN( "978-1-4028-9462-0" ); | ||
assertInvalidISBN( "978-0-85131-041-5" ); | ||
assertInvalidISBN( "978-0-684-84328-1" ); | ||
assertInvalidISBN( "978-1-84356-028-1" ); | ||
assertInvalidISBN( "978-0-54560-495-4" ); | ||
|
||
// invalid length | ||
assertInvalidISBN( "" ); | ||
assertInvalidISBN( "978-0-54560-4" ); | ||
assertInvalidISBN( "978-0-55555555555555" ); | ||
} | ||
|
||
private void assertValidISBN(String isbn) { | ||
assertTrue( validator.isValid( isbn, null ), isbn + " should be a valid ISBN" ); | ||
} | ||
|
||
private void assertInvalidISBN(String isbn) { | ||
assertFalse( validator.isValid( isbn, null ), isbn + " should be an invalid ISBN" ); | ||
} | ||
|
||
private ISBN initializeAnnotation(ISBN.Type type) { | ||
ConstraintAnnotationDescriptor.Builder<ISBN> descriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>( ISBN.class ); | ||
descriptorBuilder.setAttribute( "type", type ); | ||
return descriptorBuilder.build().getAnnotation(); | ||
} | ||
} |