New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HV-1541 Add a contraint for ISBN #893

Merged
merged 6 commits into from Dec 12, 2017
@@ -292,6 +292,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MAX, Duration.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MIN, Duration.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.EMAIL, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.ISBN, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.LENGTH, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.MOD_CHECK, CharSequence.class );
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.LUHN_CHECK, CharSequence.class );
@@ -69,6 +69,7 @@
public static final String CODE_POINT_LENGTH = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".CodePointLength";
public static final String CURRENCY = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Currency";
public static final String EMAIL = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Email";
public static final String ISBN = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ISBN";
public static final String LENGTH = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".Length";
public static final String MOD_CHECK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ModCheck";
public static final String LUHN_CHECK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".LuhnCheck";
@@ -22,6 +22,7 @@
import org.hibernate.validator.ap.testmodel.MethodLevelValidationUsingBuiltInConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithCodePointLengthConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithDateConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithISBNConstraints;
import org.hibernate.validator.ap.testmodel.ModelWithJava8DateTime;
import org.hibernate.validator.ap.testmodel.ModelWithJavaMoneyTypes;
import org.hibernate.validator.ap.testmodel.ModelWithJodaTypes;
@@ -722,4 +723,20 @@ public void codePointLengthConstraints() {
new DiagnosticExpectation( Kind.ERROR, 23 )
);
}
@Test
public void isbnConstraints() {
File[] sourceFiles = new File[] {
compilerHelper.getSourceFile( ModelWithISBNConstraints.class )
};
boolean compilationResult =
compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, false, true, sourceFiles );
assertFalse( compilationResult );
assertThatDiagnosticsMatch(
diagnostics,
new DiagnosticExpectation( Kind.ERROR, 22 )
);
}
}
@@ -0,0 +1,25 @@
/*
* 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.ap.testmodel;
import org.hibernate.validator.constraints.ISBN;
/**
* @author Marko Bekhta
*/
public class ModelWithISBNConstraints {
@ISBN
private String string;
@ISBN
private CharSequence charSequence;
@ISBN
private Integer integer;
}
@@ -683,6 +683,10 @@ With one exception also these constraints apply to the field/property level, onl
Supported data types::: `CharSequence`
Hibernate metadata impact::: None
`@ISBN`:: Checks that the annotated character sequence is a valid https://en.wikipedia.org/wiki/International_Standard_Book_Number[ISBN]. `type` determines the type of ISBN. The default is ISBN-13.
Supported data types::: `CharSequence`
Hibernate metadata impact::: None
`@Length(min=, max=)`:: Validates that the annotated character sequence is between `min` and `max` included
Supported data types::: `CharSequence`
Hibernate metadata impact::: Column length will be set to max
@@ -0,0 +1,25 @@
/*
* 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.cfg.defs;
import org.hibernate.validator.cfg.ConstraintDef;
import org.hibernate.validator.constraints.ISBN;
/**
* @author Marko Bekhta
*/
public class ISBNDef extends ConstraintDef<ISBNDef, ISBN> {
public ISBNDef() {
super( ISBN.class );
}
public ISBNDef type(ISBN.Type type) {
addParameter( "type", type );
return this;
}
}
@@ -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
}
}
@@ -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' );
}
}
@@ -57,6 +57,7 @@
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.Currency;
import org.hibernate.validator.constraints.EAN;
import org.hibernate.validator.constraints.ISBN;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.LuhnCheck;
import org.hibernate.validator.constraints.Mod10Check;
@@ -254,6 +255,7 @@
import org.hibernate.validator.internal.constraintvalidators.bv.time.pastorpresent.PastOrPresentValidatorForZonedDateTime;
import org.hibernate.validator.internal.constraintvalidators.hv.CodePointLengthValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.EANValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.ISBNValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.LengthValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.LuhnCheckValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.Mod10CheckValidator;
@@ -415,6 +417,8 @@ public ConstraintHelper() {
putConstraints( tmpConstraints, FutureOrPresent.class, futureOrPresentValidators );
putConstraint( tmpConstraints, ISBN.class, ISBNValidator.class );
if ( isJavaMoneyInClasspath() ) {
putConstraints( tmpConstraints, Max.class, Arrays.asList(
MaxValidatorForBigDecimal.class,
@@ -25,6 +25,7 @@ org.hibernate.validator.constraints.CreditCardNumber.message = invalid cr
org.hibernate.validator.constraints.Currency.message = invalid currency (must be one of {value})
org.hibernate.validator.constraints.EAN.message = invalid {type} barcode
org.hibernate.validator.constraints.Email.message = not a well-formed email address
org.hibernate.validator.constraints.ISBN.message = invalid ISBN
org.hibernate.validator.constraints.Length.message = length must be between {min} and {max}
org.hibernate.validator.constraints.CodePointLength.message = length must be between {min} and {max}
org.hibernate.validator.constraints.LuhnCheck.message = The check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
@@ -25,6 +25,7 @@ org.hibernate.validator.constraints.CreditCardNumber.message = num\u00E9ro de ca
org.hibernate.validator.constraints.Currency.message = devise invalide (doit faire partie de {value})
org.hibernate.validator.constraints.EAN.message = code barre {type} invalide
org.hibernate.validator.constraints.Email.message = adresse email mal form\u00E9e
org.hibernate.validator.constraints.ISBN.message = ISBN invalide
org.hibernate.validator.constraints.Length.message = la longueur doit \u00EAtre comprise entre {min} et {max} caractères
org.hibernate.validator.constraints.CodePointLength.message = la longueur doit \u00EAtre comprise entre {min} et {max} caractères
org.hibernate.validator.constraints.LuhnCheck.message = le chiffre de contr\u00F4le pour ${validatedValue} est invalide, le contr\u00F4le Luhn Modulo 10 a \u00E9chou\u00E9
@@ -25,6 +25,7 @@ org.hibernate.validator.constraints.CreditCardNumber.message = \u043d\u04
org.hibernate.validator.constraints.Currency.message = \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 \u0432\u0430\u043b\u044e\u0442\u0430 (\u043f\u043e\u0432\u0438\u043d\u043d\u0430 \u0431\u0443\u0442\u0438 \u043e\u0434\u043d\u0456\u0454\u044e \u0456\u0437 {value})
org.hibernate.validator.constraints.EAN.message = \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 \u0448\u0442\u0440\u0438\u0445\u043a\u043e\u0434 {type}
org.hibernate.validator.constraints.Email.message = \u043d\u0435\u043f\u0440\u0432\u0438\u043b\u044c\u043d\u043e \u0441\u0444\u043e\u0440\u043c\u043e\u0432\u0430\u043d\u0430 email \u0430\u0434\u0440\u0435\u0441\u0430
org.hibernate.validator.constraints.ISBN.message = \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0438\u0439 ISBN
org.hibernate.validator.constraints.Length.message = \u0434\u043e\u0432\u0436\u0438\u043d\u0430 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u043c\u0456\u0436 {min} \u0442\u0430 {max}
org.hibernate.validator.constraints.CodePointLength.message = \u0434\u043e\u0432\u0436\u0438\u043d\u0430 \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u043c\u0456\u0436 {min} \u0442\u0430 {max}
org.hibernate.validator.constraints.LuhnCheck.message = \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u0430 \u0446\u0438\u0444\u0440\u0430 \u0434\u043b\u044f ${validatedValue}, \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u0437\u0430 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u043e\u043c \u041b\u0443\u043d\u0430 \u0437\u0430\u043a\u0456\u043d\u0447\u0438\u043b\u0430\u0441\u044c \u0437 \u043f\u043e\u043c\u0438\u043b\u043a\u043e\u044e
@@ -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;
}
}
}
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.