Skip to content
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-1831] filters @Valid annotations from jvm and native types #1334

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion documentation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<!-- Skip artifact deployment -->
<maven.deploy.skip>true</maven.deploy.skip>
<gpg.skip>true</gpg.skip>
<surefire.jvm.args.additional>-Duser.language=en</surefire.jvm.args.additional>
<surefire.jvm.args.additional>-Duser.language=en -Duser.country=EN</surefire.jvm.args.additional>

<forbiddenapis-junit.path>forbidden-allow-junit.txt</forbiddenapis-junit.path>
<hibernate-validator-parent.path>..</hibernate-validator-parent.path>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
Expand All @@ -78,6 +80,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.validation.Valid;
import org.hibernate.validator.constraints.CodePointLength;
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.CreditCardNumber;
Expand Down Expand Up @@ -324,6 +327,7 @@
import org.hibernate.validator.internal.constraintvalidators.hv.time.DurationMaxValidator;
import org.hibernate.validator.internal.constraintvalidators.hv.time.DurationMinValidator;
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorDescriptor;
import org.hibernate.validator.internal.properties.Constrainable;
import org.hibernate.validator.internal.util.CollectionHelper;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.logging.Log;
Expand Down Expand Up @@ -381,6 +385,14 @@ public class ConstraintHelper {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
private static final String JODA_TIME_CLASS_NAME = "org.joda.time.ReadableInstant";
private static final String JAVA_MONEY_CLASS_NAME = "javax.money.MonetaryAmount";
private static final java.util.regex.Pattern BUILTIN_TYPE_NAMES = java.util.regex.Pattern.compile( "" +
// primitives
"(boolean|byte|char|int|short|float|double|long" +
// boxed primitives
"|java.lang.Boolean|java.lang.Byte|java.lang.Character|java.lang.Integer|java.lang.Short|java.lang.Float|java.lang.Double|java.lang.Long" +
// selected final types from java.* hierarchy
"|java.lang.String" +
")" );

@Immutable
private final Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> enabledBuiltinConstraints;
Expand Down Expand Up @@ -757,7 +769,8 @@ private ConstraintHelper(Set<BuiltinConstraint> enabledBuiltinConstraints) {
putBuiltinConstraint( tmpConstraints, EAN.class, EANValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_EMAIL ) ) {
putBuiltinConstraint( tmpConstraints, org.hibernate.validator.constraints.Email.class, org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator.class );
putBuiltinConstraint( tmpConstraints, org.hibernate.validator.constraints.Email.class,
org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_ISBN ) ) {
putBuiltinConstraint( tmpConstraints, ISBN.class, ISBNValidator.class );
Expand Down Expand Up @@ -787,7 +800,8 @@ private ConstraintHelper(Set<BuiltinConstraint> enabledBuiltinConstraints) {
putBuiltinConstraint( tmpConstraints, NIP.class, NIPValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NOT_BLANK ) ) {
putBuiltinConstraint( tmpConstraints, org.hibernate.validator.constraints.NotBlank.class, org.hibernate.validator.internal.constraintvalidators.hv.NotBlankValidator.class );
putBuiltinConstraint( tmpConstraints, org.hibernate.validator.constraints.NotBlank.class,
org.hibernate.validator.internal.constraintvalidators.hv.NotBlankValidator.class );
}
if ( enabledBuiltinConstraints.contains( ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NOT_EMPTY ) ) {
putBuiltinConstraint( tmpConstraints, org.hibernate.validator.constraints.NotEmpty.class );
Expand Down Expand Up @@ -856,22 +870,18 @@ public static Set<String> getBuiltinConstraints() {
}

/**
* Returns the constraint validator classes for the given constraint
* annotation type, as retrieved from
*
* Returns the constraint validator classes for the given constraint annotation type, as retrieved from
* <ul>
* <li>{@link Constraint#validatedBy()},
* <li>internally registered validators for built-in constraints</li>
* <li>XML configuration and</li>
* <li>programmatically registered validators (see
* {@link org.hibernate.validator.cfg.ConstraintMapping#constraintDefinition(Class)}).</li>
* </ul>
*
* The result is cached internally.
*
* @param annotationType The constraint annotation type.
* @param <A> the type of the annotation
*
* @param <A> the type of the annotation
* @return The validator classes for the given type.
*/
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValidatorDescriptors(Class<A> annotationType) {
Expand All @@ -880,37 +890,34 @@ public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValid
}

/**
* Returns those validator descriptors for the given constraint annotation
* matching the given target.
* Returns those validator descriptors for the given constraint annotation matching the given target.
*
* @param annotationType The annotation of interest.
* @param annotationType The annotation of interest.
* @param validationTarget The target, either annotated element or parameters.
* @param <A> the type of the annotation
*
* @param <A> the type of the annotation
* @return A list with matching validator descriptors.
*/
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> findValidatorDescriptors(Class<A> annotationType, ValidationTarget validationTarget) {
return getAllValidatorDescriptors( annotationType ).stream()
.filter( d -> supportsValidationTarget( d, validationTarget ) )
.collect( Collectors.toList() );
.filter( d -> supportsValidationTarget( d, validationTarget ) )
.collect( Collectors.toList() );
}

private boolean supportsValidationTarget(ConstraintValidatorDescriptor<?> validatorDescriptor, ValidationTarget target) {
return validatorDescriptor.getValidationTargets().contains( target );
}

/**
* Registers the given validator descriptors with the given constraint
* annotation type.
* Registers the given validator descriptors with the given constraint annotation type.
*
* @param annotationType The constraint annotation type
* @param annotationType The constraint annotation type
* @param validatorDescriptors The validator descriptors to register
* @param keepExistingClasses Whether already-registered validators should be kept or not
* @param <A> the type of the annotation
* @param keepExistingClasses Whether already-registered validators should be kept or not
* @param <A> the type of the annotation
*/
public <A extends Annotation> void putValidatorDescriptors(Class<A> annotationType,
List<ConstraintValidatorDescriptor<A>> validatorDescriptors,
boolean keepExistingClasses) {
List<ConstraintValidatorDescriptor<A>> validatorDescriptors,
boolean keepExistingClasses) {

List<ConstraintValidatorDescriptor<A>> validatorDescriptorsToAdd = new ArrayList<>();

Expand All @@ -930,9 +937,7 @@ public <A extends Annotation> void putValidatorDescriptors(Class<A> annotationTy
* Checks whether a given annotation is a multi value constraint or not.
*
* @param annotationType the annotation type to check.
*
* @return {@code true} if the specified annotation is a multi value constraints, {@code false}
* otherwise.
* @return {@code true} if the specified annotation is a multi value constraints, {@code false} otherwise.
*/
public boolean isMultiValueConstraint(Class<? extends Annotation> annotationType) {
if ( isJdkAnnotation( annotationType ) ) {
Expand Down Expand Up @@ -962,12 +967,10 @@ public boolean isMultiValueConstraint(Class<? extends Annotation> annotationType
/**
* Returns the constraints which are part of the given multi-value constraint.
* <p>
* Invoke {@link #isMultiValueConstraint(Class)} prior to calling this method to check whether a given constraint
* actually is a multi-value constraint.
* Invoke {@link #isMultiValueConstraint(Class)} prior to calling this method to check whether a given constraint actually is a multi-value constraint.
*
* @param multiValueConstraint the multi-value constraint annotation from which to retrieve the contained constraints
* @param <A> the type of the annotation
*
* @param <A> the type of the annotation
* @return A list of constraint annotations, may be empty but never {@code null}.
*/
public <A extends Annotation> List<Annotation> getConstraintsFromMultiValueConstraint(A multiValueConstraint) {
Expand All @@ -982,8 +985,7 @@ public <A extends Annotation> List<Annotation> getConstraintsFromMultiValueConst
}

/**
* Checks whether the specified annotation is a valid constraint annotation. A constraint annotation has to
* fulfill the following conditions:
* Checks whether the specified annotation is a valid constraint annotation. A constraint annotation has to fulfill the following conditions:
* <ul>
* <li>Must be annotated with {@link Constraint}
* <li>Define a message parameter</li>
Expand All @@ -992,7 +994,6 @@ public <A extends Annotation> List<Annotation> getConstraintsFromMultiValueConst
* </ul>
*
* @param annotationType The annotation type to test.
*
* @return {@code true} if the annotation fulfills the above conditions, {@code false} otherwise.
*/
public boolean isConstraintAnnotation(Class<? extends Annotation> annotationType) {
Expand Down Expand Up @@ -1133,10 +1134,7 @@ private boolean isJavaMoneyInClasspath() {
* Returns the default validators for the given constraint type.
*
* @param annotationType The constraint annotation type.
*
* @return A list with the default validators as retrieved from
* {@link Constraint#validatedBy()} or the list of validators for
* built-in constraints.
* @return A list with the default validators as retrieved from {@link Constraint#validatedBy()} or the list of validators for built-in constraints.
*/
@SuppressWarnings("unchecked")
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType) {
Expand Down Expand Up @@ -1173,9 +1171,39 @@ private static <T> T run(PrivilegedAction<T> action) {
}

/**
* A type-safe wrapper around a concurrent map from constraint types to
* associated validator classes. The casts are safe as data is added trough
* the typed API only.
* this method inspects the type of the <code>@Valid</code> annotation and decides, if the annotation is useful.
* <p>
* This method returns false, if the {@link jakarta.validation.Valid} annotation is applied to types matching {@link #BUILTIN_TYPE_NAMES}:
* <ul>
* <li>a native type (int, boolean, etc</li>
* <li>a boxed native type (Integer, Boolean, etc</li>
* <li>some types in java.lang.* package, eg String</li>
* </ul>
* </p>
*
* @param annotation the Valid annotation
* @param constrainable the constraint element
* @param <A> type of annotation
* @return true, if the Valid annotation is not applicable
*/
public <A extends Annotation> boolean isNonApplicableValidAnnotation(A annotation, Constrainable constrainable) {
if ( !( annotation instanceof Valid ) ) {
return false;
}
String typeName = constrainable.getType().getTypeName();
Type typeForResolution = constrainable.getTypeForValidatorResolution();
if ( typeForResolution instanceof ParameterizedType ) {
return Arrays.stream( ( (ParameterizedType) typeForResolution ).getActualTypeArguments() )
.allMatch( pType -> BUILTIN_TYPE_NAMES.matcher( pType.getTypeName() ).matches() );
}
else {
return BUILTIN_TYPE_NAMES.matcher( typeName ).matches();
}
}

/**
* A type-safe wrapper around a concurrent map from constraint types to associated validator classes. The casts are safe as data is added trough the typed
* API only.
*
* @author Gunnar Morling
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,11 @@ protected <A extends Annotation> List<ConstraintDescriptorImpl<?>> findConstrain
return Collections.emptyList();
}

// address HV-1831: do not create an Annotation object for unwanted Valid annotations
if ( constraintCreationContext.getConstraintHelper().isNonApplicableValidAnnotation( annotation, constrainable ) ) {
return Collections.emptyList();
}

List<Annotation> constraints = newArrayList();
Class<? extends Annotation> annotationType = annotation.annotationType();
if ( constraintCreationContext.getConstraintHelper().isConstraintAnnotation( annotationType ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,30 @@
import jakarta.validation.GroupSequence;
import jakarta.validation.groups.Default;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* @author Emmanuel Bernard
*/
@GroupSequence( {Suit.class, Cloth.class })
@GroupSequence({ Suit.class, Cloth.class })
public class Suit {

@Max(value = 50, groups = { Default.class, Cloth.class })
@Min(1)
@Valid // this should be ignored
private Integer size;
@Valid private Trousers trousers;
@Valid
private Trousers trousers;
private Jacket jacket;

@Valid
private boolean awesomeDesign;

@Valid Set<Integer> someSet = new HashSet<>( Arrays.asList( 1, 2, 3, 4, 5, 6 ) );
Set<@Valid Integer> someOtherSet = new HashSet<>( Arrays.asList( 1, 2, 3, 4, 5, 6 ) );

public Trousers getTrousers() {
return trousers;
}
Expand All @@ -48,4 +60,12 @@ public Integer getSize() {
public void setSize(Integer size) {
this.size = size;
}

public boolean isAwesomeDesign() {
return awesomeDesign;
}

public void setAwesomeDesign(boolean awesomeDesign) {
this.awesomeDesign = awesomeDesign;
}
}
5 changes: 4 additions & 1 deletion performance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,10 @@
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<!-- adding sources for BV 2.0 tests -->
<!-- adding sources for BV 2.0 tests
<build>
<plugins>
<plugin>
Expand All @@ -201,6 +202,7 @@
</plugin>
</plugins>
</build>
-->
</profile>
<profile>
<id>hv-6.1</id>
Expand Down Expand Up @@ -234,6 +236,7 @@
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<!-- adding sources for BV 2.0 tests -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.util.stream.Stream;

import org.hibernate.validator.performance.cascaded.CascadedValidation;
import org.hibernate.validator.performance.cascaded.CascadedValidationWithManyPrimitiveValids;
import org.hibernate.validator.performance.cascaded.CascadedValidationWithoutPrimitiveValids;
import org.hibernate.validator.performance.cascaded.CascadedWithLotsOfItemsValidation;
import org.hibernate.validator.performance.simple.SimpleValidation;
import org.hibernate.validator.performance.statistical.StatisticalValidation;
Expand All @@ -32,13 +34,15 @@
public final class BenchmarkRunner {

private static final Stream<? extends Class<?>> DEFAULT_TEST_CLASSES = Stream.of(
SimpleValidation.class.getName(),
CascadedValidation.class.getName(),
CascadedWithLotsOfItemsValidation.class.getName(),
StatisticalValidation.class.getName(),
CascadedValidationWithManyPrimitiveValids.class.getName(),
CascadedValidationWithoutPrimitiveValids.class.getName()
// SimpleValidation.class.getName(),
// CascadedValidation.class.getName(),
// CascadedWithLotsOfItemsValidation.class.getName(),
// StatisticalValidation.class.getName()//,
// Benchmarks specific to Bean Validation 2.0
// Tests are located in a separate source folder only added for implementations compatible with BV 2.0
"org.hibernate.validator.performance.multilevel.MultiLevelContainerValidation"
//"org.hibernate.validator.performance.multilevel.MultiLevelContainerValidation"
).map( BenchmarkRunner::classForName ).filter( Objects::nonNull );

private BenchmarkRunner() {
Expand Down