From e9323043918250b484081f059b6fe6a97d5dcc28 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 24 Feb 2021 19:10:09 +0100 Subject: [PATCH 01/15] HV-1831 Unfinished experiments --- .../BaseHibernateValidatorConfiguration.java | 4 + .../engine/AbstractConfigurationImpl.java | 18 +++++ .../PredefinedScopeValidatorFactoryImpl.java | 48 ++++++----- .../internal/engine/ValidatorFactoryImpl.java | 7 +- .../engine/ValidatorFactoryScopedContext.java | 26 +++++- ...adablesProcessedBeansTrackingStrategy.java | 31 +++++++ ...edScopeProcessedBeansTrackingStrategy.java | 65 +++++++++++++++ .../ProcessedBeansTrackingStrategy.java | 20 +++++ .../AbstractValidationContext.java | 63 +++++++++++---- .../BeanValidationContext.java | 9 ++- .../ParameterExecutableValidationContext.java | 11 ++- ...eturnValueExecutableValidationContext.java | 10 ++- .../ValidatorScopedContext.java | 11 +++ .../PredefinedScopeBeanMetaDataManager.java | 5 ++ performance/pom.xml | 80 ++++++++++++++----- 15 files changed, 342 insertions(+), 66 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 33b53b527d..04b2530793 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -25,6 +25,7 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.ScriptAssert; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; @@ -501,4 +502,7 @@ default S locales(Locale... locales) { */ @Incubating S showValidatedValuesInTraceLogs(boolean enabled); + + @Incubating + S processedBeansTrackingStrategy(ProcessedBeansTrackingStrategy processedBeanTrackingStrategy); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java index a07ed1ec8b..bd4faf23d4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java @@ -43,6 +43,7 @@ import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl; import org.hibernate.validator.internal.engine.resolver.TraversableResolvers; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; @@ -133,6 +134,7 @@ public abstract class AbstractConfigurationImpl getProgrammaticMappings() { return programmaticMappings; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java index 9a290d2346..2ea045560d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java @@ -9,11 +9,11 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowMultipleCascadedValidationOnReturnValues; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowOverridingMethodAlterParameterConstraint; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowParallelMethodsDefineParameterConstraints; -import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel; -import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineFailFast; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineScriptEvaluatorFactory; @@ -46,8 +46,10 @@ import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; +import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; import org.hibernate.validator.internal.engine.constraintvalidation.PredefinedScopeConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.PredefinedScopeProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; @@ -118,24 +120,16 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState determineAllowParallelMethodsDefineParameterConstraints( hibernateSpecificConfig, properties ) ).build(); - this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( - configurationState.getMessageInterpolator(), - configurationState.getTraversableResolver(), - new ExecutableParameterNameProvider( configurationState.getParameterNameProvider() ), - configurationState.getClockProvider(), - determineTemporalValidationTolerance( configurationState, properties ), - determineScriptEvaluatorFactory( configurationState, properties, externalClassLoader ), - determineFailFast( hibernateSpecificConfig, properties ), - determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ), - determineConstraintValidatorPayload( hibernateSpecificConfig ), - determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) - ); + ExecutableParameterNameProvider parameterNameProvider = new ExecutableParameterNameProvider( configurationState.getParameterNameProvider() ); + ScriptEvaluatorFactory scriptEvaluatorFactory = determineScriptEvaluatorFactory( configurationState, properties, externalClassLoader ); + Duration temporalValidationTolerance = determineTemporalValidationTolerance( configurationState, properties ); + + HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext = new HibernateConstraintValidatorInitializationContextImpl( + scriptEvaluatorFactory, configurationState.getClockProvider(), temporalValidationTolerance ); this.constraintValidatorManager = new PredefinedScopeConstraintValidatorManagerImpl( configurationState.getConstraintValidatorFactory(), - this.validatorFactoryScopedContext.getConstraintValidatorInitializationContext() + constraintValidatorInitializationContext ); this.validationOrderGenerator = new ValidationOrderGenerator(); @@ -197,7 +191,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState this.beanMetaDataManager = new PredefinedScopeBeanMetaDataManager( constraintCreationContext, executableHelper, - validatorFactoryScopedContext.getParameterNameProvider(), + parameterNameProvider, javaBeanHelper, validationOrderGenerator, buildMetaDataProviders( constraintCreationContext, xmlMetaDataProvider, constraintMappings ), @@ -206,6 +200,24 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState hibernateSpecificConfig.getBeanClassesToInitialize() ); + this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( + configurationState.getMessageInterpolator(), + configurationState.getTraversableResolver(), + parameterNameProvider, + configurationState.getClockProvider(), + temporalValidationTolerance, + scriptEvaluatorFactory, + determineFailFast( hibernateSpecificConfig, properties ), + determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ), + determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ), + determineConstraintValidatorPayload( hibernateSpecificConfig ), + determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), + determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), + ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingStrategy() != null ) + ? hibernateSpecificConfig.getProcessedBeansTrackingStrategy() + : new PredefinedScopeProcessedBeansTrackingStrategy( beanMetaDataManager ), + constraintValidatorInitializationContext ); + if ( LOG.isDebugEnabled() ) { logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java index 8d90a6976d..070a6eabe9 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java @@ -49,6 +49,7 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.HasCascadablesProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; @@ -165,8 +166,10 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) - ); + ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingStrategy() != null ) + ? hibernateSpecificConfig.getProcessedBeansTrackingStrategy() + : new HasCascadablesProcessedBeansTrackingStrategy(), + determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) ); ConstraintValidatorManager constraintValidatorManager = new ConstraintValidatorManagerImpl( configurationState.getConstraintValidatorFactory(), diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java index 5ab071023e..dc7bbe0a1b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java @@ -15,6 +15,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; @@ -78,6 +79,11 @@ public class ValidatorFactoryScopedContext { */ private final ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel; + /** + * Strategy used to enable or not processed beans tracking. + */ + private final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; + /** * The constraint validator initialization context. */ @@ -99,15 +105,16 @@ public class ValidatorFactoryScopedContext { Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel, + ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, boolean showValidatedValuesInTraceLogs) { this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel, - customViolationExpressionLanguageFeatureLevel, + customViolationExpressionLanguageFeatureLevel, processedBeansTrackingStrategy, new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, temporalValidationTolerance ) ); } - private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, + ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, TraversableResolver traversableResolver, ExecutableParameterNameProvider parameterNameProvider, ClockProvider clockProvider, @@ -118,6 +125,7 @@ private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, boolean showValidatedValuesInTraceLogs, Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel, + ProcessedBeansTrackingStrategy processedBeanTrackingStrategy, HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext) { this.messageInterpolator = messageInterpolator; this.traversableResolver = traversableResolver; @@ -131,6 +139,7 @@ private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, this.constraintExpressionLanguageFeatureLevel = constraintExpressionLanguageFeatureLevel; this.customViolationExpressionLanguageFeatureLevel = customViolationExpressionLanguageFeatureLevel; this.showValidatedValuesInTraceLogs = showValidatedValuesInTraceLogs; + this.processedBeansTrackingStrategy = processedBeanTrackingStrategy; this.constraintValidatorInitializationContext = constraintValidatorInitializationContext; } @@ -186,6 +195,10 @@ public boolean isShowValidatedValuesInTraceLogs() { return showValidatedValuesInTraceLogs; } + public ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return processedBeansTrackingStrategy; + } + static class Builder { private final ValidatorFactoryScopedContext defaultContext; @@ -200,6 +213,7 @@ static class Builder { private Object constraintValidatorPayload; private ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel; private ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel; + private ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; private boolean showValidatedValuesInTraceLogs; private HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext; @@ -220,6 +234,7 @@ static class Builder { this.constraintExpressionLanguageFeatureLevel = defaultContext.constraintExpressionLanguageFeatureLevel; this.customViolationExpressionLanguageFeatureLevel = defaultContext.customViolationExpressionLanguageFeatureLevel; this.showValidatedValuesInTraceLogs = defaultContext.showValidatedValuesInTraceLogs; + this.processedBeansTrackingStrategy = defaultContext.processedBeansTrackingStrategy; this.constraintValidatorInitializationContext = defaultContext.constraintValidatorInitializationContext; } @@ -312,6 +327,12 @@ public ValidatorFactoryScopedContext.Builder setShowValidatedValuesInTraceLogs( return this; } + public ValidatorFactoryScopedContext.Builder setProcessedBeansTrackingStrategy( + ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { + this.processedBeansTrackingStrategy = processedBeansTrackingStrategy; + return this; + } + public ValidatorFactoryScopedContext build() { return new ValidatorFactoryScopedContext( messageInterpolator, @@ -325,6 +346,7 @@ public ValidatorFactoryScopedContext build() { showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel, customViolationExpressionLanguageFeatureLevel, + processedBeansTrackingStrategy, HibernateConstraintValidatorInitializationContextImpl.of( constraintValidatorInitializationContext, scriptEvaluatorFactory, diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java new file mode 100644 index 0000000000..7a7a33083f --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java @@ -0,0 +1,31 @@ +/* + * 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.engine.tracking; + +import java.lang.reflect.Executable; + +public class HasCascadablesProcessedBeansTrackingStrategy implements ProcessedBeansTrackingStrategy { + + @Override + public boolean isEnabledForBean(Class beanClass, boolean hasCascadables) { + return hasCascadables; + } + + @Override + public boolean isEnabledForReturnValue(Executable executable, boolean hasCascadables) { + return hasCascadables; + } + + @Override + public boolean isEnabledForParameters(Executable executable, boolean hasCascadables) { + return hasCascadables; + } + + @Override + public void clear() { + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java new file mode 100644 index 0000000000..9e3a155b25 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -0,0 +1,65 @@ +/* + * 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.engine.tracking; + +import java.lang.reflect.Executable; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; +import org.hibernate.validator.internal.util.CollectionHelper; + +public class PredefinedScopeProcessedBeansTrackingStrategy implements ProcessedBeansTrackingStrategy { + + private final Map, Boolean> trackingEnabledForBeans; + + private final Map trackingEnabledForReturnValues; + + private final Map trackingEnabledForParameters; + + public PredefinedScopeProcessedBeansTrackingStrategy(PredefinedScopeBeanMetaDataManager beanMetaDataManager) { + // TODO: build the maps from the information inside the beanMetaDataManager + + this.trackingEnabledForBeans = CollectionHelper.toImmutableMap( new HashMap<>() ); + this.trackingEnabledForReturnValues = CollectionHelper.toImmutableMap( new HashMap<>() ); + this.trackingEnabledForParameters = CollectionHelper.toImmutableMap( new HashMap<>() ); + } + + @Override + public boolean isEnabledForBean(Class rootBeanClass, boolean hasCascadables) { + if ( !hasCascadables ) { + return false; + } + + return trackingEnabledForBeans.getOrDefault( rootBeanClass, true ); + } + + @Override + public boolean isEnabledForReturnValue(Executable executable, boolean hasCascadables) { + if ( !hasCascadables ) { + return false; + } + + return trackingEnabledForReturnValues.getOrDefault( executable, true ); + } + + @Override + public boolean isEnabledForParameters(Executable executable, boolean hasCascadables) { + if ( !hasCascadables ) { + return false; + } + + return trackingEnabledForParameters.getOrDefault( executable, true ); + } + + @Override + public void clear() { + trackingEnabledForBeans.clear(); + trackingEnabledForReturnValues.clear(); + trackingEnabledForParameters.clear(); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java new file mode 100644 index 0000000000..b5db188e3b --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java @@ -0,0 +1,20 @@ +/* + * 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.engine.tracking; + +import java.lang.reflect.Executable; + +public interface ProcessedBeansTrackingStrategy { + + boolean isEnabledForBean(Class beanClass, boolean hasCascadables); + + boolean isEnabledForReturnValue(Executable executable, boolean hasCascadables); + + boolean isEnabledForParameters(Executable executable, boolean hasCascadables); + + void clear(); +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java index 783bd0f1a9..68a15b4832 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java @@ -7,9 +7,10 @@ package org.hibernate.validator.internal.engine.validationcontext; import java.lang.invoke.MethodHandles; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; -import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -96,25 +97,25 @@ abstract class AbstractValidationContext implements BaseBeanValidationContext /** * Indicates if the tracking of already validated bean should be disabled. */ - private final boolean disableAlreadyValidatedBeanTracking; + private final boolean processedBeanTrackingEnabled; /** * The set of already processed meta constraints per bean - path ({@link BeanPathMetaConstraintProcessedUnit}). */ @Lazy - private Set processedPathUnits; + private HashSet processedPathUnits; /** * The set of already processed groups per bean ({@link BeanGroupProcessedUnit}). */ @Lazy - private Set processedGroupUnits; + private HashSet processedGroupUnits; /** * Maps an object to a list of paths in which it has been validated. The objects are the bean instances. */ @Lazy - private Map> processedPathsPerBean; + private HashMap> processedPathsPerBean; /** * Contains all failing constraints so far. @@ -131,7 +132,7 @@ protected AbstractValidationContext( T rootBean, Class rootBeanClass, BeanMetaData rootBeanMetaData, - boolean disableAlreadyValidatedBeanTracking + boolean processedBeanTrackingEnabled ) { this.constraintValidatorManager = constraintValidatorManager; this.validatorScopedContext = validatorScopedContext; @@ -143,7 +144,7 @@ protected AbstractValidationContext( this.rootBeanClass = rootBeanClass; this.rootBeanMetaData = rootBeanMetaData; - this.disableAlreadyValidatedBeanTracking = disableAlreadyValidatedBeanTracking; + this.processedBeanTrackingEnabled = processedBeanTrackingEnabled; } @Override @@ -193,7 +194,7 @@ public ConstraintValidatorFactory getConstraintValidatorFactory() { @Override public boolean isBeanAlreadyValidated(Object value, Class group, PathImpl path) { - if ( disableAlreadyValidatedBeanTracking ) { + if ( !processedBeanTrackingEnabled ) { return false; } @@ -209,7 +210,7 @@ public boolean isBeanAlreadyValidated(Object value, Class group, PathImpl pat @Override public void markCurrentBeanAsProcessed(ValueContext valueContext) { - if ( disableAlreadyValidatedBeanTracking ) { + if ( !processedBeanTrackingEnabled ) { return; } @@ -338,7 +339,7 @@ private String interpolate( } private boolean isAlreadyValidatedForPath(Object value, PathImpl path) { - Set pathSet = getInitializedProcessedPathsPerBean().get( value ); + ArrayList pathSet = getInitializedProcessedPathsPerBean().get( new ProcessedBean( value ) ); if ( pathSet == null ) { return false; } @@ -374,12 +375,13 @@ private boolean isAlreadyValidatedForCurrentGroup(Object value, Class group) private void markCurrentBeanAsProcessedForCurrentPath(Object bean, PathImpl path) { // HV-1031 The path object is mutated as we traverse the object tree, hence copy it before saving it - Map> processedPathsPerBean = getInitializedProcessedPathsPerBean(); + HashMap> processedPathsPerBean = getInitializedProcessedPathsPerBean(); - Set processedPaths = processedPathsPerBean.get( bean ); + ProcessedBean processedBean = new ProcessedBean( bean ); + ArrayList processedPaths = processedPathsPerBean.get( processedBean ); if ( processedPaths == null ) { - processedPaths = new HashSet<>(); - processedPathsPerBean.put( bean, processedPaths ); + processedPaths = new ArrayList<>(); + processedPathsPerBean.put( processedBean, processedPaths ); } processedPaths.add( PathImpl.createCopy( path ) ); @@ -396,16 +398,16 @@ private Set getInitializedProcessedPathUnit return processedPathUnits; } - private Set getInitializedProcessedGroupUnits() { + private HashSet getInitializedProcessedGroupUnits() { if ( processedGroupUnits == null ) { processedGroupUnits = new HashSet<>(); } return processedGroupUnits; } - private Map> getInitializedProcessedPathsPerBean() { + private HashMap> getInitializedProcessedPathsPerBean() { if ( processedPathsPerBean == null ) { - processedPathsPerBean = new IdentityHashMap<>(); + processedPathsPerBean = new HashMap<>(); } return processedPathsPerBean; } @@ -510,4 +512,31 @@ private int createHashCode() { return result; } } + + private static final class ProcessedBean { + + private Object bean; + private int hashCode = -1; + + ProcessedBean(Object bean) { + this.bean = bean; + } + + @Override + public boolean equals(Object o) { + return this.bean == ((ProcessedBean) o).bean; + } + + @Override + public int hashCode() { + if ( hashCode == -1 ) { + hashCode = createHashCode(); + } + return hashCode; + } + + private int createHashCode() { + return System.identityHashCode( bean ); + } + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java index a3178f5e68..95b5f8bc0f 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java @@ -16,6 +16,7 @@ import org.hibernate.validator.internal.engine.ConstraintViolationImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; @@ -37,12 +38,14 @@ class BeanValidationContext extends AbstractValidationContext { BeanMetaData rootBeanMetaData ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, - rootBean, rootBeanClass, rootBeanMetaData, buildDisableAlreadyValidatedBeanTracking( rootBeanMetaData ) + rootBean, rootBeanClass, rootBeanMetaData, buildProcessedBeansTrackingEnabled( validatorScopedContext.getProcessedBeansTrackingStrategy(), + rootBeanClass, rootBeanMetaData ) ); } - private static boolean buildDisableAlreadyValidatedBeanTracking(BeanMetaData rootBeanMetaData) { - return !rootBeanMetaData.hasCascadables(); + private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, Class rootBeanClass, + BeanMetaData rootBeanMetaData) { + return processedBeansTrackingStrategy.isEnabledForBean( rootBeanClass, rootBeanMetaData.hasCascadables() ); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java index 6dd5370855..6c3ef76b50 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java @@ -24,6 +24,7 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; import org.hibernate.validator.internal.engine.constraintvalidation.CrossParameterConstraintValidatorContextImpl; import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; @@ -68,7 +69,8 @@ public class ParameterExecutableValidationContext extends AbstractValidationC ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, rootBean, rootBeanClass, rootBeanMetaData, - buildDisableAlreadyValidatedBeanTracking( executableMetaData ) + buildProcessedBeansTrackingEnabled( validatorScopedContext.getProcessedBeansTrackingStrategy(), executable, + executableMetaData ) ); this.executable = executable; this.executableMetaData = executableMetaData; @@ -85,13 +87,16 @@ public Optional getExecutableMetaData() { return executableMetaData; } - private static boolean buildDisableAlreadyValidatedBeanTracking(Optional executableMetaData) { + private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, + Executable executable, + Optional executableMetaData) { if ( !executableMetaData.isPresent() ) { // the method is unconstrained so there's no need to worry about the tracking return false; } - return !executableMetaData.get().getValidatableParametersMetaData().hasCascadables(); + return processedBeansTrackingStrategy.isEnabledForReturnValue( executable, + executableMetaData.get().getValidatableParametersMetaData().hasCascadables() ); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java index bcc80ae69b..59d0c1ef62 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java @@ -19,6 +19,7 @@ import org.hibernate.validator.internal.engine.ConstraintViolationImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; @@ -61,7 +62,8 @@ public class ReturnValueExecutableValidationContext extends AbstractValidatio ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, rootBean, rootBeanClass, rootBeanMetaData, - buildDisableAlreadyValidatedBeanTracking( executableMetaData ) + buildProcessedBeansTrackingEnabled( validatorScopedContext.getProcessedBeansTrackingStrategy(), executable, + executableMetaData ) ); this.executable = executable; this.executableMetaData = executableMetaData; @@ -78,13 +80,15 @@ public Optional getExecutableMetaData() { return executableMetaData; } - private static boolean buildDisableAlreadyValidatedBeanTracking(Optional executableMetaData) { + private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, + Executable executable, + Optional executableMetaData) { if ( !executableMetaData.isPresent() ) { // the method is unconstrained so there's no need to worry about the tracking return false; } - return !executableMetaData.get().getReturnValueMetaData().hasCascadables(); + return processedBeansTrackingStrategy.isEnabledForReturnValue( executable, executableMetaData.get().getReturnValueMetaData().hasCascadables() ); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java index abf3496ec3..890117ecb2 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java @@ -13,6 +13,7 @@ import jakarta.validation.Validator; import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory; @@ -78,6 +79,11 @@ public class ValidatorScopedContext { private final boolean showValidatedValuesInTraceLogs; + /** + * Strategy used to enable or not processed beans tracking. + */ + private final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; + public ValidatorScopedContext(ValidatorFactoryScopedContext validatorFactoryScopedContext) { this.messageInterpolator = validatorFactoryScopedContext.getMessageInterpolator(); this.parameterNameProvider = validatorFactoryScopedContext.getParameterNameProvider(); @@ -89,6 +95,7 @@ public ValidatorScopedContext(ValidatorFactoryScopedContext validatorFactoryScop this.constraintValidatorPayload = validatorFactoryScopedContext.getConstraintValidatorPayload(); this.constraintExpressionLanguageFeatureLevel = validatorFactoryScopedContext.getConstraintExpressionLanguageFeatureLevel(); this.customViolationExpressionLanguageFeatureLevel = validatorFactoryScopedContext.getCustomViolationExpressionLanguageFeatureLevel(); + this.processedBeansTrackingStrategy = validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); this.showValidatedValuesInTraceLogs = validatorFactoryScopedContext.isShowValidatedValuesInTraceLogs(); } @@ -135,4 +142,8 @@ public ExpressionLanguageFeatureLevel getCustomViolationExpressionLanguageFeatur public boolean isShowValidatedValuesInTraceLogs() { return showValidatedValuesInTraceLogs; } + + public ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return processedBeansTrackingStrategy; + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java index f09dd89dc8..2bcb968b32 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java @@ -11,6 +11,7 @@ import java.lang.annotation.ElementType; import java.lang.reflect.Executable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -119,6 +120,10 @@ public BeanMetaData getBeanMetaData(Class beanClass) { return beanMetaData; } + public Collection> getBeanMetaData() { + return beanMetaDataMap.values(); + } + @Override public void clear() { beanMetaDataMap.clear(); diff --git a/performance/pom.xml b/performance/pom.xml index f2f4559e01..fd3fbd0800 100644 --- a/performance/pom.xml +++ b/performance/pom.xml @@ -188,8 +188,52 @@ expressly - log4j - log4j + org.apache.logging.log4j + log4j-core + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + + + hv-6.2 + + + validator + hv-6.2 + + + + 2.0.1.Final + Hibernate Validator + 6.2.0.Final + + + + javax.validation + validation-api + ${validation-api.version} + + + ${project.groupId} + hibernate-validator + ${beanvalidation-impl.version} + + + org.glassfish + javax.el + 3.0.1-b11 + + + org.apache.logging.log4j + log4j-core @@ -232,8 +276,8 @@ 3.0.1-b11 - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -276,8 +320,8 @@ 3.0.1-b11 - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -320,8 +364,8 @@ 3.0.1-b11 - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -361,8 +405,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -402,8 +446,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -443,8 +487,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -484,8 +528,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -514,8 +558,8 @@ ${beanvalidation-impl.version} - log4j - log4j + org.apache.logging.log4j + log4j-core From 74bdd10baf472fddb0b6dd6144aa09b0e09268ad Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 25 Feb 2021 11:25:48 +0100 Subject: [PATCH 02/15] HV-1831 Add a couple of examples illustrating various cases --- ...edScopeProcessedBeansTrackingStrategy.java | 5 ++ .../ProcessedBeansTrackingCycles1Test.java | 49 ++++++++++++++++ .../ProcessedBeansTrackingCycles2Test.java | 50 ++++++++++++++++ .../ProcessedBeansTrackingCycles3Test.java | 51 ++++++++++++++++ .../ProcessedBeansTrackingCycles4Test.java | 51 ++++++++++++++++ .../ProcessedBeansTrackingCycles5Test.java | 58 +++++++++++++++++++ .../ProcessedBeansTrackingNoCycles1Test.java | 44 ++++++++++++++ .../ProcessedBeansTrackingNoCycles2Test.java | 45 ++++++++++++++ .../ProcessedBeansTrackingNoCycles3Test.java | 48 +++++++++++++++ 9 files changed, 401 insertions(+) create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles2Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles3Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles4Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles5Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles1Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles3Test.java diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java index 9e3a155b25..a78c7ee43c 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -23,6 +23,11 @@ public class PredefinedScopeProcessedBeansTrackingStrategy implements ProcessedB public PredefinedScopeProcessedBeansTrackingStrategy(PredefinedScopeBeanMetaDataManager beanMetaDataManager) { // TODO: build the maps from the information inside the beanMetaDataManager + // There is a good chance we will need a structure with the whole hierarchy of constraint classes. + // That's something we could add to PredefinedScopeBeanMetaDataManager, as we are already doing similar things + // there (see the ClassHierarchyHelper.getHierarchy call). + // In the predefined scope case, we will have the whole hierarchy of constrained classes passed to + // PredefinedScopeBeanMetaDataManager. this.trackingEnabledForBeans = CollectionHelper.toImmutableMap( new HashMap<>() ); this.trackingEnabledForReturnValues = CollectionHelper.toImmutableMap( new HashMap<>() ); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java new file mode 100644 index 0000000000..e6b86d4be3 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java @@ -0,0 +1,49 @@ +/* + * 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.engine.tracking; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * This is the most simple example. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles1Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + @Valid + private Child child; + } + + private static class Child { + + @NotNull + private String property; + + @Valid + private Parent parent; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles2Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles2Test.java new file mode 100644 index 0000000000..a5f44c612d --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles2Test.java @@ -0,0 +1,50 @@ +/* + * 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.engine.tracking; + +import java.util.List; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * Simple enough but this time the cascading annotation is in the container element. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles2Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + private List<@Valid Child> children; + } + + private static class Child { + + @NotNull + private String property; + + @Valid + private Parent parent; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles3Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles3Test.java new file mode 100644 index 0000000000..df9fd7fc1f --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles3Test.java @@ -0,0 +1,51 @@ +/* + * 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.engine.tracking; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * Simple enough but this time the cascading annotation is deep in a container element. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles3Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + private Map> children; + } + + private static class Child { + + @NotNull + private String property; + + @Valid + private Parent parent; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles4Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles4Test.java new file mode 100644 index 0000000000..c9f6a89772 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles4Test.java @@ -0,0 +1,51 @@ +/* + * 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.engine.tracking; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * Simple enough but this time the cascading annotation is deep in a container element with a bound. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles4Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + private Map> children; + } + + private static class Child { + + @NotNull + private String property; + + @Valid + private Parent parent; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles5Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles5Test.java new file mode 100644 index 0000000000..71f4883b5b --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles5Test.java @@ -0,0 +1,58 @@ +/* + * 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.engine.tracking; + +import java.util.List; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * This one is a bit more tricky: during the validation, when cascading, we take into account the runtime type to get + * the metadata, not the declared type. + *

+ * So even if you couldn't have a cycle with the declared type, when trying to find the cycles, we need to take into + * consideration all the subclasses too. The good news is that we are in a closed world so we have them all passed + * to our PredefinedScopedValidatorFactoryImpl! + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles5Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + private List<@Valid ChildWithNoCycles> children; + } + + private static class ChildWithNoCycles { + + @NotNull + private String property; + } + + private static class Child extends ChildWithNoCycles { + + @Valid + private Parent parent; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles1Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles1Test.java new file mode 100644 index 0000000000..65e03d9a10 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles1Test.java @@ -0,0 +1,44 @@ +/* + * 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.engine.tracking; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingNoCycles1Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + @Valid + private Child child; + } + + private static class Child { + + @NotNull + private String property; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java new file mode 100644 index 0000000000..0ac3d6f569 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java @@ -0,0 +1,45 @@ +/* + * 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.engine.tracking; + +import java.util.List; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingNoCycles2Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + private List<@Valid Child> children; + } + + private static class Child { + + @NotNull + private String property; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles3Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles3Test.java new file mode 100644 index 0000000000..77003b3484 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles3Test.java @@ -0,0 +1,48 @@ +/* + * 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.engine.tracking; + +import java.util.List; + +import jakarta.validation.Valid; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.testutils.ValidatorUtil; +import org.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * In this case, given we will have all the subclasses of Child in the metadata, we should be able to know there are no + * cycles. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingNoCycles3Test { + + @Test + public void testSerializeHibernateEmail() throws Exception { + Validator validator = ValidatorUtil.getValidator(); + + validator.validate( new Parent() ); + } + + private static class Parent { + + @NotNull + private String property; + + private List<@Valid ? extends Child> children; + } + + private static class Child { + + @NotNull + private String property; + } +} From f23eb1e8e8398e143a06e5bdd3b7a628b2c173ff Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 8 Mar 2021 19:41:22 +0100 Subject: [PATCH 03/15] HV-1831 Clean up another experiment that shouldn't have been committed --- .../AbstractValidationContext.java | 53 +++++-------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java index 68a15b4832..0d01c65730 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java @@ -7,10 +7,9 @@ package org.hibernate.validator.internal.engine.validationcontext; import java.lang.invoke.MethodHandles; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -103,19 +102,19 @@ abstract class AbstractValidationContext implements BaseBeanValidationContext * The set of already processed meta constraints per bean - path ({@link BeanPathMetaConstraintProcessedUnit}). */ @Lazy - private HashSet processedPathUnits; + private Set processedPathUnits; /** * The set of already processed groups per bean ({@link BeanGroupProcessedUnit}). */ @Lazy - private HashSet processedGroupUnits; + private Set processedGroupUnits; /** * Maps an object to a list of paths in which it has been validated. The objects are the bean instances. */ @Lazy - private HashMap> processedPathsPerBean; + private Map> processedPathsPerBean; /** * Contains all failing constraints so far. @@ -339,7 +338,7 @@ private String interpolate( } private boolean isAlreadyValidatedForPath(Object value, PathImpl path) { - ArrayList pathSet = getInitializedProcessedPathsPerBean().get( new ProcessedBean( value ) ); + Set pathSet = getInitializedProcessedPathsPerBean().get( value ); if ( pathSet == null ) { return false; } @@ -375,13 +374,12 @@ private boolean isAlreadyValidatedForCurrentGroup(Object value, Class group) private void markCurrentBeanAsProcessedForCurrentPath(Object bean, PathImpl path) { // HV-1031 The path object is mutated as we traverse the object tree, hence copy it before saving it - HashMap> processedPathsPerBean = getInitializedProcessedPathsPerBean(); + Map> processedPathsPerBean = getInitializedProcessedPathsPerBean(); - ProcessedBean processedBean = new ProcessedBean( bean ); - ArrayList processedPaths = processedPathsPerBean.get( processedBean ); + Set processedPaths = processedPathsPerBean.get( bean ); if ( processedPaths == null ) { - processedPaths = new ArrayList<>(); - processedPathsPerBean.put( processedBean, processedPaths ); + processedPaths = new HashSet<>(); + processedPathsPerBean.put( bean, processedPaths ); } processedPaths.add( PathImpl.createCopy( path ) ); @@ -398,16 +396,16 @@ private Set getInitializedProcessedPathUnit return processedPathUnits; } - private HashSet getInitializedProcessedGroupUnits() { + private Set getInitializedProcessedGroupUnits() { if ( processedGroupUnits == null ) { processedGroupUnits = new HashSet<>(); } return processedGroupUnits; } - private HashMap> getInitializedProcessedPathsPerBean() { + private Map> getInitializedProcessedPathsPerBean() { if ( processedPathsPerBean == null ) { - processedPathsPerBean = new HashMap<>(); + processedPathsPerBean = new IdentityHashMap<>(); } return processedPathsPerBean; } @@ -512,31 +510,4 @@ private int createHashCode() { return result; } } - - private static final class ProcessedBean { - - private Object bean; - private int hashCode = -1; - - ProcessedBean(Object bean) { - this.bean = bean; - } - - @Override - public boolean equals(Object o) { - return this.bean == ((ProcessedBean) o).bean; - } - - @Override - public int hashCode() { - if ( hashCode == -1 ) { - hashCode = createHashCode(); - } - return hashCode; - } - - private int createHashCode() { - return System.identityHashCode( bean ); - } - } } From a62acceafe52a2f60d50ed7094683aa0906b0b47 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 10 Mar 2021 10:02:52 -0800 Subject: [PATCH 04/15] HV-1831 Add the same bean to List twice --- .../tracking/ProcessedBeansTrackingNoCycles2Test.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java index 0ac3d6f569..c51d4182f0 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java @@ -6,6 +6,7 @@ */ package org.hibernate.validator.test.internal.engine.tracking; +import java.util.ArrayList; import java.util.List; import jakarta.validation.Valid; @@ -26,7 +27,14 @@ public class ProcessedBeansTrackingNoCycles2Test { public void testSerializeHibernateEmail() throws Exception { Validator validator = ValidatorUtil.getValidator(); - validator.validate( new Parent() ); + final Parent parent = new Parent(); + parent.property = "parent property"; + final Child child = new Child(); + child.property = "child property"; + parent.children = new ArrayList<>(); + parent.children.add( child ); + parent.children.add( child ); + validator.validate( parent ); } private static class Parent { From 987226332060a4d5e254d8f051a1350a6ceace07 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 11 Mar 2021 14:00:12 +0100 Subject: [PATCH 05/15] HV-1831 Copy nodes when changing the nature of the leaf --- .../validator/internal/engine/path/PathImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java index 55de95d73d..632f0b85f4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java @@ -195,7 +195,7 @@ private NodeImpl addMethodNode(String name, Class[] parameterTypes) { } public NodeImpl makeLeafNodeIterable() { - requiresWriteableNodeList(); + copyNodeList(); currentLeafNode = NodeImpl.makeIterable( currentLeafNode ); @@ -205,7 +205,7 @@ public NodeImpl makeLeafNodeIterable() { } public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { - requiresWriteableNodeList(); + copyNodeList(); currentLeafNode = NodeImpl.makeIterableAndSetIndex( currentLeafNode, index ); @@ -215,7 +215,7 @@ public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { } public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) { - requiresWriteableNodeList(); + copyNodeList(); currentLeafNode = NodeImpl.makeIterableAndSetMapKey( currentLeafNode, key ); @@ -300,6 +300,10 @@ private void requiresWriteableNodeList() { return; } + copyNodeList(); + } + + private void copyNodeList() { // Usually, the write operation is about adding one more node, so let's make the list one element larger. List newNodeList = new ArrayList<>( nodeList.size() + 1 ); newNodeList.addAll( nodeList ); From 9489496bb664fc688aaa9dca410a0b4c82613820 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 8 Apr 2021 18:20:24 -0700 Subject: [PATCH 06/15] HV-1831 : Experiment detecting cycles in bean classes --- ...edScopeProcessedBeansTrackingStrategy.java | 155 ++++++++++++- .../ParameterExecutableValidationContext.java | 2 +- .../ProcessedBeansTrackingCycles1Test.java | 73 +++++- ...cessedBeansTrackingCyclesNoCyclesTest.java | 207 ++++++++++++++++++ 4 files changed, 430 insertions(+), 7 deletions(-) create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java index a78c7ee43c..c83d7175ce 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -7,10 +7,16 @@ package org.hibernate.validator.internal.engine.tracking; import java.lang.reflect.Executable; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; +import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.util.CollectionHelper; public class PredefinedScopeProcessedBeansTrackingStrategy implements ProcessedBeansTrackingStrategy { @@ -29,18 +35,163 @@ public PredefinedScopeProcessedBeansTrackingStrategy(PredefinedScopeBeanMetaData // In the predefined scope case, we will have the whole hierarchy of constrained classes passed to // PredefinedScopeBeanMetaDataManager. - this.trackingEnabledForBeans = CollectionHelper.toImmutableMap( new HashMap<>() ); + this.trackingEnabledForBeans = CollectionHelper.toImmutableMap( + new TrackingEnabledStrategyBuilder( beanMetaDataManager ).build() + ); this.trackingEnabledForReturnValues = CollectionHelper.toImmutableMap( new HashMap<>() ); this.trackingEnabledForParameters = CollectionHelper.toImmutableMap( new HashMap<>() ); } + private static class TrackingEnabledStrategyBuilder { + private final PredefinedScopeBeanMetaDataManager beanMetaDataManager; + private final Map, Boolean> classToBeanTrackingEnabled; + + TrackingEnabledStrategyBuilder(PredefinedScopeBeanMetaDataManager beanMetaDataManager) { + this.beanMetaDataManager = beanMetaDataManager; + this.classToBeanTrackingEnabled = new HashMap<>( beanMetaDataManager.getBeanMetaData().size() ); + } + + public Map, Boolean> build() { + final Set> beanClassesInPath = new HashSet<>(); + for ( BeanMetaData beanMetadata : beanMetaDataManager.getBeanMetaData() ) { + determineTrackingRequired( beanMetadata.getBeanClass(), beanClassesInPath ); + if ( !beanClassesInPath.isEmpty() ) { + throw new IllegalStateException( "beanClassesInPath not empty" ); + } + } + return classToBeanTrackingEnabled; + } + + // Do a depth-first search for cycles along paths of cascaded bean classes. + // The algorithm stops due to one of the following: + // 1) The bean class was previously put in classToBeanTrackingEnabled + // (i.e., the bean class was already determined to either have a cycle, + // or not have a cycle). + // 2) A cycle is found. In this case, all bean classes in the particular path, + // starting from beanClass up to first bean class that causes a cycle, will + // be registered in classToBeanTrackingEnabled with a value of true. + // Once a cycle is found, no further bean classes are examined. Those bean + // classes that were examined in the process that are found to not have a + // cycle are registered in classToBeanTrackingEnabled with a value of false. + // 3) No cycle is found. In this case, all bean classes in the tree will be + // registered in classToBeanTrackingEnabled with a value of false. + // + // Examples: An arrow, ->, indicates a cascading constraint from a bean class. + // + // 1) A -> B + // | ^ + // | | + // ---- + // A, B have no cycles. A has 2 paths to B, but there are no cycles, because there is no path from B to A. + // + // 2) A <- + // | | + // --- + // A has a cycle to itself. + // + // 3) A -> B -> C -> D + // ^ | + // | | + // ----- + // A, B, C have cycles; D does not have a cycle. + // + private boolean determineTrackingRequired(Class beanClass, Set> beanClassesInPath) { + + final Boolean isBeanTrackingEnabled = classToBeanTrackingEnabled.get( beanClass ); + if ( isBeanTrackingEnabled != null ) { + // It was already determined for beanClass. + return isBeanTrackingEnabled; + } + + // Add beanClass to the path. + // We don't care about the order of the bean classes in + // beanClassesInPath. We only care about detecting a duplicate, + // which indicates a cycle. If no cycle is found in beanClass, + // it will be removed below. + if ( !beanClassesInPath.add( beanClass ) ) { + // The bean was already present in the path being examined. + // That means that there is cycle involving beanClass. + // Enable tracking for all elements in beanClassesInPath + for ( Class dependency : beanClassesInPath ) { + register( dependency, true ); + } + beanClassesInPath.clear(); + return true; + } + + // Now check the cascaded bean classes. + for ( Class directCascadedBeanClass : getDirectCascadedBeanClasses( beanClass ) ) { + // Check to see if tracking has already been determined for directCascadedBeanClass + Boolean isSubBeanTrackingEnabled = classToBeanTrackingEnabled.get( directCascadedBeanClass ); + if ( isSubBeanTrackingEnabled != null ) { + if ( isSubBeanTrackingEnabled ) { + // We already know that directCascadedBeanClass has a cycle. + // That means that all elements in beanClassesInPath + // will have a cycle. + for ( Class dependency : beanClassesInPath ) { + register( dependency, true ); + } + // No point in checking any others in this loop. + beanClassesInPath.clear(); + return true; + } + else { + // We already know that directCascadedBeanClass is not involved in + // any cycles, so move on to the next iteration. + continue; + } + } + if ( determineTrackingRequired( directCascadedBeanClass, beanClassesInPath ) ) { + // A cycle was found. No point in checking any others in this loop. + // beanClassesInPath should have already been cleared. + assert beanClassesInPath.isEmpty(); + return true; + } + // directCascadedBeanClass does not have a cycle. + // directCascadedBeanClass would have already been removed by the + // call to #determineTrackingRequired above + } + beanClassesInPath.remove( beanClass ); + return register( beanClass, false ); + } + + // TODO: is there a more concise way to do this? + private Set> getDirectCascadedBeanClasses(Class beanClass ) { + final BeanMetaData beanMetaData = beanMetaDataManager.getBeanMetaData( beanClass ); + if ( beanMetaData.hasCascadables() ) { + final Set> directCascadedBeanClasses = new HashSet<>(); + for ( Cascadable cascadable : beanMetaData.getCascadables() ) { + final CascadingMetaData cascadingMetaData = cascadable.getCascadingMetaData(); + if ( cascadingMetaData.isContainer() ) { + throw new UnsupportedOperationException( "Containers are not supported yet." ); + } + else { + // TODO: For now, assume non-container Cascadables are always beans. Truee??? + directCascadedBeanClasses.add( (Class) cascadable.getCascadableType() ); + } + } + return directCascadedBeanClasses; + } + else { + return Collections.emptySet(); + } + } + + private boolean register(Class beanClass, boolean isBeanTrackingEnabled) { + if ( classToBeanTrackingEnabled.put( beanClass, isBeanTrackingEnabled ) != null ) { + throw new IllegalStateException( beanClass.getName() + " registered more than once." ); + } + return isBeanTrackingEnabled; + } + } + @Override public boolean isEnabledForBean(Class rootBeanClass, boolean hasCascadables) { if ( !hasCascadables ) { return false; } - return trackingEnabledForBeans.getOrDefault( rootBeanClass, true ); + return trackingEnabledForBeans.get( rootBeanClass ); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java index 6c3ef76b50..38cd8796cf 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java @@ -95,7 +95,7 @@ private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTracking return false; } - return processedBeansTrackingStrategy.isEnabledForReturnValue( executable, + return processedBeansTrackingStrategy.isEnabledForParameters( executable, executableMetaData.get().getValidatableParametersMetaData().hasCascadables() ); } diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java index e6b86d4be3..742485fc75 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java @@ -6,13 +6,20 @@ */ package org.hibernate.validator.test.internal.engine.tracking; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import jakarta.validation.ConstraintViolation; import jakarta.validation.Valid; +import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.constraints.NotNull; -import org.hibernate.validator.testutils.ValidatorUtil; +import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.testng.annotations.Test; +import static org.testng.Assert.assertTrue; + /** * This is not a real test, just an illustration. *

@@ -23,14 +30,56 @@ public class ProcessedBeansTrackingCycles1Test { @Test - public void testSerializeHibernateEmail() throws Exception { - Validator validator = ValidatorUtil.getValidator(); + public void testValidNull() throws Exception { + final Parent parent = new Parent( "parent property" ); + Set> violations = getValidator().validate( parent ); + assertTrue( violations.isEmpty() ); + } + + @Test + public void testValidNotNull() throws Exception { + final Parent parent = new Parent( "parent property" ); + parent.child = new Child( "child property" ); + + Set> violations = getValidator().validate( parent ); + //Set> violations = ValidatorUtil.getValidator().validate( parent ); + assertTrue( violations.isEmpty() ); + } + + @Test + public void testValidNotNullNonCyclic() throws Exception { + final Parent parent = new Parent( "parent property" ); + parent.child = new Child( "child property" ); + parent.child.parent = new Parent( "other parent property" ); + + Set> violations = getValidator().validate( parent ); + assertTrue( violations.isEmpty() ); + } + + @Test + public void testValidNotNullCyclic() throws Exception { + final Parent parent = new Parent( "parent property" ); + parent.child = new Child( "child property" ); + parent.child.parent = parent; - validator.validate( new Parent() ); + Set> violations = getValidator().validate( parent ); + assertTrue( violations.isEmpty() ); } + private Validator getValidator() { + return Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( new HashSet<>( Arrays.asList( NotNull.class.getName() ) ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( Parent.class, Child.class, Other.class ) ) ) + .buildValidatorFactory() + .getValidator(); + } private static class Parent { + Parent(String property) { + this.property = property; + } + @NotNull private String property; @@ -40,10 +89,26 @@ private static class Parent { private static class Child { + Child(String property) { + this.property = property; + } + @NotNull private String property; @Valid private Parent parent; + + @Valid + private Other other; + } + + private static class Other { + Other(String property) { + this.property = property; + } + + @NotNull + private String property; } } diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java new file mode 100644 index 0000000000..0ce49c5fed --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java @@ -0,0 +1,207 @@ +/* + * 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.engine.tracking; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Valid; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; +import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; +import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * An example of beans with cascading constraints, some cycle and others do not. + * + * A -> B ---> C ------> F -> G <- + * | ^ | ^ ^ | + * | | | | | | + * | -- D <-- | | | + * --------------------> E ------- + * + * A, B, C, D, E, F, and G are beans that get validated. + * + * An arrow, ->, indicates a cascading constraint. + * + * The following are the properties with cascading constraints: + * A.b + * .e + * B.c + * C.d + * .f + * D.b + * E.f + * .g + * F.g + * + * @author Gail Badner + * + */ + +public class ProcessedBeansTrackingCyclesNoCyclesTest { + + @Test + public void testTrackingEnabled() { + + final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = + validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + A.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + B.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + C.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + D.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + E.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + F.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + G.class, + false + ) ); + } + + @Test + public void testValidate() { + final A a = new A(); + final B b = new B(); + final C c = new C(); + final D d = new D(); + final E e = new E(); + final F f = new F(); + final G g = new G(); + + a.b = b; + a.e = e; + b.c = c; + c.d = d; + d.b = b; + e.f = f; + e.g = g; + e.gAnother = g; + f.g = g; + + final Validator validator = getValidator(); + final Set> violationsA = validator.validate( a ); + final Set> violationsB = validator.validate( b ); + final Set> violationsC = validator.validate( c ); + final Set> violationsD = validator.validate( d ); + final Set> violationsE = validator.validate( e ); + final Set> violationsF = validator.validate( f ); + final Set> violationsG = validator.validate( g ); + } + + private Validator getValidator() { + return getValidatorFactory().getValidator(); + } + + private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { + return Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( new HashSet<>( Arrays.asList( NotNull.class.getName() ) ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( + A.class, B.class, C.class, D.class, E.class, F.class, G.class + ) ) ) + .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); + } + + private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + } + + private static class A { + + private String description; + + @Valid + private B b; + + @Valid + private E e; + } + + private static class B { + @Valid + private String description; + + @Valid + private C c; + } + + private static class C { + + private String description; + + @Valid + private D d; + + @Valid + private F f; + } + + private static class D { + + private String description; + + @Valid + private B b; + } + + private static class E { + + private String description; + + @Valid + private F f; + + @Valid + private G g; + + @Valid + private G gAnother; + } + + private static class F { + + private String description; + + @Valid + private G g; + } + + private static class G { + + private String description; + } +} From e5811f50f22bd827e4adda0d7e73df9df1741618 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 22 Apr 2021 20:30:21 -0700 Subject: [PATCH 07/15] HV-1831 : Experiment detecting cycles in bean classes Add support for containers; add tests for List w/ and w/o duplicated values --- ...edScopeProcessedBeansTrackingStrategy.java | 19 +- ...clesNoCyclesListDuplicateElementsTest.java | 209 ++++++++++++++++++ ...edBeansTrackingCyclesNoCyclesListTest.java | 200 +++++++++++++++++ 3 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java index c83d7175ce..167f0856e5 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -7,6 +7,8 @@ package org.hibernate.validator.internal.engine.tracking; import java.lang.reflect.Executable; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -16,6 +18,7 @@ import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; +import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.util.CollectionHelper; @@ -163,7 +166,21 @@ private Set> getDirectCascadedBeanClasses(Class beanClass ) { for ( Cascadable cascadable : beanMetaData.getCascadables() ) { final CascadingMetaData cascadingMetaData = cascadable.getCascadingMetaData(); if ( cascadingMetaData.isContainer() ) { - throw new UnsupportedOperationException( "Containers are not supported yet." ); + final ContainerCascadingMetaData containerCascadingMetaData = (ContainerCascadingMetaData) cascadingMetaData; + if ( containerCascadingMetaData.getEnclosingType() instanceof ParameterizedType ) { + ParameterizedType parameterizedType = (ParameterizedType) containerCascadingMetaData.getEnclosingType(); + for ( Type typeArgument : parameterizedType.getActualTypeArguments() ) { + if ( typeArgument instanceof Class ) { + directCascadedBeanClasses.add( (Class) typeArgument ); + } + else { + throw new UnsupportedOperationException( "Only ParameterizedType values of type Class are supported" ); + } + } + } + else { + throw new UnsupportedOperationException( "Non-parameterized containers are not supported yet." ); + } } else { // TODO: For now, assume non-container Cascadables are always beans. Truee??? diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java new file mode 100644 index 0000000000..5be2a6fe6f --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java @@ -0,0 +1,209 @@ +/* + * 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.engine.tracking; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; +import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; +import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * An example of beans with cascading constraints, some cycle and others do not. + * + * A -> B ---> C ------> F -> G <- + * | ^ | ^ ^ | + * | | | | | | + * | -- D <-- | | | + * --------------------> E ------- + * + * A, B, C, D, E, F, and G are beans that get validated. + * + * An arrow, ->, indicates a cascading constraint. + * + * The following are the properties with cascading List constraints: + * A.bValues + * .eValues + * B.cValues + * C.dValues + * .fValues + * D.bValues + * E.fValues + * .gValues + * .gAnotherValues + * F.gValues + * + * @author Gail Badner + * + */ + +public class ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest { + + @Test + public void testTrackingEnabled() { + + final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = + validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + A.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + B.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + C.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + D.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + E.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + F.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + G.class, + false + ) ); + } + + @Test + public void testValidate() { + final A a = new A(); + final B b = new B(); + final C c = new C(); + final D d = new D(); + final E e = new E(); + final F f = new F(); + final G g = new G(); + + a.bValues.add( b ); + a.bValues.add( b ); + a.eValues.add( e ); + a.eValues.add( e ); + b.cValues.add( c ); + b.cValues.add( c ); + c.dValues.add( d ); + c.dValues.add( d ); + d.bValues.add( b ); + d.bValues.add( b ); + e.fValues.add( f ); + e.fValues.add( f ); + e.gValues.add( g ); + e.gValues.add( g ); + e.gAnotherValues.add( g ); + e.gAnotherValues.add( g ); + f.gValues.add( g ); + f.gValues.add( g ); + + final Validator validator = getValidator(); + final Set> violationsA = validator.validate( a ); + final Set> violationsB = validator.validate( b ); + final Set> violationsC = validator.validate( c ); + final Set> violationsD = validator.validate( d ); + final Set> violationsE = validator.validate( e ); + final Set> violationsF = validator.validate( f ); + final Set> violationsG = validator.validate( g ); + } + + private Validator getValidator() { + return getValidatorFactory().getValidator(); + } + + private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { + return Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( new HashSet<>( Arrays.asList( NotNull.class.getName() ) ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( + A.class, B.class, C.class, D.class, E.class, F.class, G.class + ) ) ) + .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); + } + + private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + } + + private static class A { + + private String description; + + private List<@Valid B> bValues = new ArrayList<>(); + + private List<@Valid E> eValues = new ArrayList<>(); + } + + private static class B { + @Valid + private String description; + + private List<@Valid C> cValues = new ArrayList<>(); + } + + private static class C { + + private String description; + + private List<@Valid D> dValues = new ArrayList<>(); + + private List<@Valid F> fValues = new ArrayList<>(); + } + + private static class D { + + private String description; + + private List<@Valid B> bValues = new ArrayList<>(); + } + + private static class E { + + private String description; + + private List<@Valid F> fValues = new ArrayList<>(); + + private List<@Valid G> gValues = new ArrayList<>(); + + private List<@Valid G> gAnotherValues = new ArrayList<>(); + } + + private static class F { + + private String description; + + private List<@Valid G> gValues = new ArrayList<>(); + } + + private static class G { + + private String description; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java new file mode 100644 index 0000000000..b32982237f --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java @@ -0,0 +1,200 @@ +/* + * 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.engine.tracking; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; +import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; +import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * An example of beans with cascading constraints, some cycle and others do not. + * + * A -> B ---> C ------> F -> G <- + * | ^ | ^ ^ | + * | | | | | | + * | -- D <-- | | | + * --------------------> E ------- + * + * A, B, C, D, E, F, and G are beans that get validated. + * + * An arrow, ->, indicates a cascading constraint. + * + * The following are the properties with cascading List constraints: + * A.bValues + * .eValues + * B.cValues + * C.dValues + * .fValues + * D.bValues + * E.fValues + * .gValues + * .gAnotherValues + * F.gValues + * + * @author Gail Badner + * + */ + +public class ProcessedBeansTrackingCyclesNoCyclesListTest { + + @Test + public void testTrackingEnabled() { + + final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = + validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + A.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + B.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + C.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + D.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + E.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + F.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + G.class, + false + ) ); + } + + @Test + public void testValidate() { + final A a = new A(); + final B b = new B(); + final C c = new C(); + final D d = new D(); + final E e = new E(); + final F f = new F(); + final G g = new G(); + + a.bValues.add( b ); + a.eValues.add( e ); + b.cValues.add( c ); + c.dValues.add( d ); + d.bValues.add( b ); + e.fValues.add( f ); + e.gValues.add( g ); + e.gAnotherValues.add( g ); + f.gValues.add( g ); + + final Validator validator = getValidator(); + final Set> violationsA = validator.validate( a ); + final Set> violationsB = validator.validate( b ); + final Set> violationsC = validator.validate( c ); + final Set> violationsD = validator.validate( d ); + final Set> violationsE = validator.validate( e ); + final Set> violationsF = validator.validate( f ); + final Set> violationsG = validator.validate( g ); + } + + private Validator getValidator() { + return getValidatorFactory().getValidator(); + } + + private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { + return Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( new HashSet<>( Arrays.asList( NotNull.class.getName() ) ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( + A.class, B.class, C.class, D.class, E.class, F.class, G.class + ) ) ) + .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); + } + + private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + } + + private static class A { + + private String description; + + private List<@Valid B> bValues = new ArrayList<>(); + + private List<@Valid E> eValues = new ArrayList<>(); + } + + private static class B { + @Valid + private String description; + + private List<@Valid C> cValues = new ArrayList<>(); + } + + private static class C { + + private String description; + + private List<@Valid D> dValues = new ArrayList<>(); + + private List<@Valid F> fValues = new ArrayList<>(); + } + + private static class D { + + private String description; + + private List<@Valid B> bValues = new ArrayList<>(); + } + + private static class E { + + private String description; + + private List<@Valid F> fValues = new ArrayList<>(); + + private List<@Valid G> gValues = new ArrayList<>(); + + private List<@Valid G> gAnotherValues = new ArrayList<>(); + } + + private static class F { + + private String description; + + private List<@Valid G> gValues = new ArrayList<>(); + } + + private static class G { + + private String description; + } +} From 66c7eed56e272823eaa0339157fb66d638e6cc7a Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 29 Apr 2021 19:42:52 -0700 Subject: [PATCH 08/15] HV-1831 : Experiment detecting cycles in bean classes Add test for Map --- ...clesNoCyclesListDuplicateElementsTest.java | 10 +- ...edBeansTrackingCyclesNoCyclesListTest.java | 10 +- ...sedBeansTrackingCyclesNoCyclesMapTest.java | 189 ++++++++++++++++++ 3 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java index 5be2a6fe6f..8048c900b1 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java @@ -11,11 +11,11 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.validation.ConstraintViolation; -import javax.validation.Valid; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.constraints.NotNull; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Valid; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java index b32982237f..09e0d2b42b 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java @@ -11,11 +11,11 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.validation.ConstraintViolation; -import javax.validation.Valid; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.constraints.NotNull; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Valid; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java new file mode 100644 index 0000000000..301de9d604 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java @@ -0,0 +1,189 @@ +/* + * 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.engine.tracking; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Valid; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; +import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; +import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * An example of beans with cascading constraints, some cycle and others do not. + * + * A -> B ---> C ------> F -> G <- + * | ^ | ^ ^ | + * | | | | | | + * | -- D <-- | | | + * --------------------> E ------- + * + * A, B, C, D, E, F, and G are beans that get validated. + * + * An arrow, ->, indicates a cascading constraint.ProcessedBeansTrackingCyclesNoCyclesMapTest + * + * The following are the properties with cascading Map constraints: + * A.bToEs + * B.cToCs + * C.dToFs + * D.bToBs + * E.fToGs + * .gToGs + * F.gToGs + * + * @author Gail Badner + * + */ + +public class ProcessedBeansTrackingCyclesNoCyclesMapTest { + + @Test + public void testTrackingEnabled() { + + final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = + validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + A.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + B.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + C.class, + true + ) ); + assertTrue( processedBeansTrackingStrategy.isEnabledForBean( + D.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + E.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + F.class, + true + ) ); + assertFalse( processedBeansTrackingStrategy.isEnabledForBean( + G.class, + false + ) ); + } + + @Test + public void testValidate() { + final A a = new A(); + final B b = new B(); + final C c = new C(); + final D d = new D(); + final E e = new E(); + final F f = new F(); + final G g = new G(); + + a.bToEs.put( b, e ); + b.cToCs.put( c, c ); + c.dToFs.put( d, f ); + d.bToBs.put( b, b ); + e.fToGs.put( f, g ); + e.gToGs.put( g, g ); + + final Validator validator = getValidator(); + final Set> violationsA = validator.validate( a ); + final Set> violationsB = validator.validate( b ); + final Set> violationsC = validator.validate( c ); + final Set> violationsD = validator.validate( d ); + final Set> violationsE = validator.validate( e ); + final Set> violationsF = validator.validate( f ); + final Set> violationsG = validator.validate( g ); + } + + private Validator getValidator() { + return getValidatorFactory().getValidator(); + } + + private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { + return Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( new HashSet<>( Arrays.asList( NotNull.class.getName() ) ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( + A.class, B.class, C.class, D.class, E.class, F.class, G.class + ) ) ) + .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); + } + + private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + } + + private static class A { + + private String description; + + private Map<@Valid B, @Valid E> bToEs = new HashMap<>(); + + } + + private static class B { + @Valid + private String description; + + private Map<@Valid C, @Valid C> cToCs = new HashMap<>(); + } + + private static class C { + + private String description; + + private Map<@Valid D, @Valid F> dToFs = new HashMap<>(); + } + + private static class D { + + private String description; + + private Map<@Valid B, @Valid B> bToBs = new HashMap<>(); + } + + private static class E { + + private String description; + + private Map<@Valid F, G> fToGs = new HashMap<>(); + + private Map<@Valid G, @Valid G> gToGs = new HashMap<>(); + } + + private static class F { + + private String description; + + private Map<@Valid G, @Valid G> gToGs = new HashMap<>(); + } + + private static class G { + + private String description; + } +} From 833ef7fff5c0f4e31e76ec2dd9cd4603078b8ecc Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 30 Apr 2021 04:44:28 +0200 Subject: [PATCH 09/15] HV-1831 : Update Cascade tests to use PredefinedScopeHibernateValidator with -p=predefined=true --- performance/pom.xml | 2 +- .../performance/BenchmarkRunner.java | 42 +++++++++++++++++++ .../cascaded/CascadedValidation.java | 8 +++- ...tsOfItemsAndMoreConstraintsValidation.java | 11 ++++- .../CascadedWithLotsOfItemsValidation.java | 10 ++++- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/performance/pom.xml b/performance/pom.xml index fd3fbd0800..cf701121f0 100644 --- a/performance/pom.xml +++ b/performance/pom.xml @@ -213,7 +213,7 @@ 2.0.1.Final Hibernate Validator - 6.2.0.Final + 6.2.1-SNAPSHOT diff --git a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java index 728e66cc81..73ea3386cb 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java +++ b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java @@ -6,9 +6,21 @@ */ package org.hibernate.validator.performance; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Locale; import java.util.Objects; +import java.util.Set; import java.util.stream.Stream; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; +import javax.validation.spi.ValidationProvider; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.performance.cascaded.CascadedValidation; import org.hibernate.validator.performance.cascaded.CascadedWithLotsOfItemsValidation; import org.hibernate.validator.performance.simple.SimpleValidation; @@ -22,6 +34,7 @@ import org.openjdk.jmh.runner.options.CommandLineOptions; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.util.Optional; /** * Class containing main method to run all performance tests. @@ -31,6 +44,10 @@ */ public final class BenchmarkRunner { + private static String PREDEFINED_PARAMETER = "predefined"; + private static boolean IS_PREDEFINED = false; + private static ValidationProvider validationProvider; + private static final Stream> DEFAULT_TEST_CLASSES = Stream.of( SimpleValidation.class.getName(), CascadedValidation.class.getName(), @@ -58,10 +75,35 @@ public static void main(String[] args) throws RunnerException, CommandLineOption DEFAULT_TEST_CLASSES.forEach( testClass -> builder.include( testClass.getName() ) ); } + IS_PREDEFINED = isPredefined( commandLineOptions ); + Options opt = builder.build(); new Runner( opt ).run(); } + public static ValidatorFactory buildValidatorFactory(Set constraintNames, Set> beanClasses) { + return IS_PREDEFINED + ? Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( constraintNames ) + .initializeBeanMetaData( beanClasses ) + .buildValidatorFactory() + : Validation.buildDefaultValidatorFactory(); + } + + private static boolean isPredefined(Options commandLineOptions) throws CommandLineOptionException { + Optional> isPredefinedValues = commandLineOptions.getParameter( PREDEFINED_PARAMETER ); + if ( isPredefinedValues.hasValue() ) { + if ( isPredefinedValues.get().size() == 1 ) { + return Boolean.parseBoolean( isPredefinedValues.get().iterator().next() ); + } + else { + throw new CommandLineOptionException( "More than one value provided for parameter: " + PREDEFINED_PARAMETER ); + } + } + return false; + } + private static Class classForName(String qualifiedName) { try { return Class.forName( qualifiedName ); diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java index 7bef45b7c4..14061c30e5 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -19,6 +20,8 @@ import jakarta.validation.ValidatorFactory; import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.performance.BenchmarkRunner; + import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -42,7 +45,10 @@ public static class CascadedValidationState { public volatile Person person; public CascadedValidationState() { - ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + ValidatorFactory factory = BenchmarkRunner.buildValidatorFactory( + Collections.singleton( NotNull.class.getName() ), + Collections.singleton( Person.class ) + ); validator = factory.getValidator(); // TODO graphs needs to be generated and deeper diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java index 03a34a2109..7a594138b8 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java @@ -9,18 +9,22 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import jakarta.validation.ConstraintViolation; import jakarta.validation.Valid; -import jakarta.validation.Validation; import jakarta.validation.Validator; +import jakarta.validation.Validation; import jakarta.validation.ValidatorFactory; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import org.hibernate.validator.performance.BenchmarkRunner; + import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -47,7 +51,10 @@ public static class CascadedWithLotsOfItemsValidationState { public volatile Shop shop; public CascadedWithLotsOfItemsValidationState() { - ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + ValidatorFactory factory = BenchmarkRunner.buildValidatorFactory( + new HashSet<>( Arrays.asList( NotNull.class.getName(), Size.class.getName() ) ), + new HashSet<>( Arrays.asList( Shop.class, Article.class ) ) + ); validator = factory.getValidator(); shop = createShop(); diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java index 27050653a2..6df10c32ba 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java @@ -9,6 +9,9 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -20,6 +23,8 @@ import jakarta.validation.ValidatorFactory; import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.performance.BenchmarkRunner; + import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -46,7 +51,10 @@ public static class CascadedWithLotsOfItemsValidationState { public volatile Shop shop; public CascadedWithLotsOfItemsValidationState() { - ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + ValidatorFactory factory = BenchmarkRunner.buildValidatorFactory( + Collections.singleton( NotNull.class.getName() ), + new HashSet<>( Arrays.asList( Shop.class, Article.class ) ) + ); validator = factory.getValidator(); shop = createShop(); From 0968f386c1a0941c379e57fd35045a4063688fdd Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 30 Apr 2021 10:22:00 +0200 Subject: [PATCH 10/15] HV-1831 Specific benchmark infrastructure for predefined scope --- performance/README.md | 7 +- performance/pom.xml | 69 +++++++++- .../PredefinedScopeCascadedValidation.java | 100 ++++++++++++++ ...tsOfItemsAndMoreConstraintsValidation.java | 124 ++++++++++++++++++ ...copeCascadedWithLotsOfItemsValidation.java | 116 ++++++++++++++++ .../performance/BenchmarkRunner.java | 43 ------ .../cascaded/CascadedValidation.java | 8 +- ...tsOfItemsAndMoreConstraintsValidation.java | 9 +- .../CascadedWithLotsOfItemsValidation.java | 10 +- 9 files changed, 411 insertions(+), 75 deletions(-) create mode 100644 performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedValidation.java create mode 100644 performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation.java create mode 100644 performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsValidation.java diff --git a/performance/README.md b/performance/README.md index cb9ea70ad9..a8e57b60c0 100644 --- a/performance/README.md +++ b/performance/README.md @@ -11,8 +11,9 @@ To allow performance testing of different Hibernate Validator versions there are Choosing a profile executes the tests against the specified Hibernate Validator or BVal version, respectively. The defined profiles are: -* hv-current (Hibernate Validator 6.1.0-SNAPSHOT) -* hv-6.0 (Hibernate Validator 6.0.15.Final) +* hv-current (Hibernate Validator 6.2.1-SNAPSHOT) +* hv-6.1 (Hibernate Validator 6.1.7.Final) +* hv-6.0 (Hibernate Validator 6.0.22.Final) * hv-5.4 (Hibernate Validator 5.4.3.Final) * hv-5.3 (Hibernate Validator 5.3.4.Final) * hv-5.2 (Hibernate Validator 5.2.4.Final) @@ -64,7 +65,7 @@ If you want to run one of those profilers - pass it as parameter when running a To run a specific benchmark: - java -jar target/hibernate-validator-performance.jar CascadedValidation + java -jar target/hibernate-validator-performance-hv-current.jar CascadedValidation #### Creating reports for all major Hibernate Validator versions diff --git a/performance/pom.xml b/performance/pom.xml index cf701121f0..e5674f5ded 100644 --- a/performance/pom.xml +++ b/performance/pom.xml @@ -192,12 +192,27 @@ log4j-core - + org.codehaus.mojo build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/src/main/java-bv2 + ${project.basedir}/src/main/java-predefined-scope + + + + @@ -236,12 +251,27 @@ log4j-core - + org.codehaus.mojo build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/src/main/java-bv2 + ${project.basedir}/src/main/java-predefined-scope + + + + @@ -257,7 +287,7 @@ 2.0.1.Final Hibernate Validator - 6.1.2.Final + 6.1.7.Final @@ -280,12 +310,27 @@ log4j-core - + org.codehaus.mojo build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/src/main/java-bv2 + ${project.basedir}/src/main/java-predefined-scope + + + + @@ -301,7 +346,7 @@ 2.0.1.Final Hibernate Validator - 6.0.19.Final + 6.0.22.Final @@ -330,6 +375,20 @@ org.codehaus.mojo build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/src/main/java-bv2 + + + + diff --git a/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedValidation.java b/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedValidation.java new file mode 100644 index 0000000000..46944777b3 --- /dev/null +++ b/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedValidation.java @@ -0,0 +1,100 @@ +/* + * 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.performance.cascaded; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author Hardy Ferentschik + * @author Guillaume Smet + */ +public class PredefinedScopeCascadedValidation { + + @State(Scope.Benchmark) + public static class PredefinedScopeCascadedValidationState { + + public volatile Validator validator; + public volatile Person person; + + public PredefinedScopeCascadedValidationState() { + ValidatorFactory factory = Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( Collections.singleton( NotNull.class.getName() ) ) + .initializeBeanMetaData( Collections.singleton( Person.class ) ) + .buildValidatorFactory(); + validator = factory.getValidator(); + + // TODO graphs needs to be generated and deeper + Person kermit = new Person( "kermit" ); + Person piggy = new Person( "miss piggy" ); + Person gonzo = new Person( "gonzo" ); + + kermit.addFriend( piggy ).addFriend( gonzo ); + piggy.addFriend( kermit ).addFriend( gonzo ); + gonzo.addFriend( kermit ).addFriend( piggy ); + + person = kermit; + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + @Fork(value = 1) + @Threads(50) + @Warmup(iterations = 10) + @Measurement(iterations = 20) + public void testPredefinedScopeCascadedValidation(PredefinedScopeCascadedValidationState state, Blackhole bh) { + Set> violations = state.validator.validate( state.person ); + assertThat( violations ).hasSize( 0 ); + + bh.consume( violations ); + } + + public static class Person { + + @NotNull + String name; + + @Valid + Set friends = new HashSet<>(); + + public Person(String name) { + this.name = name; + } + + public Person addFriend(Person friend) { + friends.add( friend ); + return this; + } + } +} diff --git a/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation.java b/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation.java new file mode 100644 index 0000000000..e55523daa1 --- /dev/null +++ b/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation.java @@ -0,0 +1,124 @@ +/* + * 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.performance.cascaded; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author Guillaume Smet + */ +public class PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation { + + private static final int NUMBER_OF_ARTICLES_PER_SHOP = 2000; + + @State(Scope.Benchmark) + public static class PredefinedScopeCascadedWithLotsOfItemsValidationState { + + public volatile Validator validator; + + public volatile Shop shop; + + public PredefinedScopeCascadedWithLotsOfItemsValidationState() { + ValidatorFactory factory = Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( new HashSet<>( Arrays.asList( NotNull.class.getName(), Size.class.getName() ) ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( Shop.class, Article.class ) ) ) + .buildValidatorFactory(); + validator = factory.getValidator(); + + shop = createShop(); + } + + private Shop createShop() { + Shop shop = new Shop( 1, "Shop" ); + + for ( int i = 0; i < NUMBER_OF_ARTICLES_PER_SHOP; i++ ) { + shop.addArticle( new Article( i, "Article " + i ) ); + } + + return shop; + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + @Fork(value = 1) + @Threads(20) + @Warmup(iterations = 10) + @Measurement(iterations = 20) + public void testPredefinedScopeCascadedValidationWithLotsOfItems(PredefinedScopeCascadedWithLotsOfItemsValidationState state, Blackhole bh) { + Set> violations = state.validator.validate( state.shop ); + assertThat( violations ).hasSize( 0 ); + + bh.consume( violations ); + } + + public static class Shop { + + @NotNull + private Integer id; + + @Size(min = 1) + private String name; + + @NotNull + @Valid + private List

articles = new ArrayList<>(); + + public Shop(Integer id, String name) { + this.id = id; + this.name = name; + } + + public void addArticle(Article article) { + articles.add( article ); + } + } + + public static class Article { + + @NotNull + private Integer id; + + @Size(min = 1) + private String name; + + public Article(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsValidation.java b/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsValidation.java new file mode 100644 index 0000000000..c5b8a6e418 --- /dev/null +++ b/performance/src/main/java-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsValidation.java @@ -0,0 +1,116 @@ +/* + * 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.performance.cascaded; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.PredefinedScopeHibernateValidator; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author Guillaume Smet + */ +public class PredefinedScopeCascadedWithLotsOfItemsValidation { + + private static final int NUMBER_OF_ARTICLES_PER_SHOP = 2000; + + @State(Scope.Benchmark) + public static class PredefinedScopeCascadedWithLotsOfItemsValidationState { + + public volatile Validator validator; + + public volatile Shop shop; + + public PredefinedScopeCascadedWithLotsOfItemsValidationState() { + ValidatorFactory factory = Validation.byProvider( PredefinedScopeHibernateValidator.class ) + .configure() + .builtinConstraints( Collections.singleton( NotNull.class.getName() ) ) + .initializeBeanMetaData( new HashSet<>( Arrays.asList( Shop.class, Article.class ) ) ) + .buildValidatorFactory(); + validator = factory.getValidator(); + + shop = createShop(); + } + + private Shop createShop() { + Shop shop = new Shop( 1 ); + + for ( int i = 0; i < NUMBER_OF_ARTICLES_PER_SHOP; i++ ) { + shop.addArticle( new Article( i ) ); + } + + return shop; + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + @Fork(value = 1) + @Threads(20) + @Warmup(iterations = 10) + @Measurement(iterations = 20) + public void testPredefinedScopeCascadedValidationWithLotsOfItems(PredefinedScopeCascadedWithLotsOfItemsValidationState state, Blackhole bh) { + Set> violations = state.validator.validate( state.shop ); + assertThat( violations ).hasSize( 0 ); + + bh.consume( violations ); + } + + public static class Shop { + + @NotNull + private Integer id; + + @NotNull + @Valid + private List
articles = new ArrayList<>(); + + public Shop(Integer id) { + this.id = id; + } + + public void addArticle(Article article) { + articles.add( article ); + } + } + + public static class Article { + + @NotNull + private Integer id; + + public Article(Integer id) { + this.id = id; + } + } +} diff --git a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java index 73ea3386cb..a887c92fe5 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java +++ b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java @@ -6,26 +6,13 @@ */ package org.hibernate.validator.performance; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Locale; import java.util.Objects; -import java.util.Set; import java.util.stream.Stream; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; -import javax.validation.constraints.NotNull; -import javax.validation.spi.ValidationProvider; - -import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.performance.cascaded.CascadedValidation; import org.hibernate.validator.performance.cascaded.CascadedWithLotsOfItemsValidation; import org.hibernate.validator.performance.simple.SimpleValidation; import org.hibernate.validator.performance.statistical.StatisticalValidation; - import org.openjdk.jmh.results.format.ResultFormatType; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; @@ -34,7 +21,6 @@ import org.openjdk.jmh.runner.options.CommandLineOptions; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; -import org.openjdk.jmh.util.Optional; /** * Class containing main method to run all performance tests. @@ -44,10 +30,6 @@ */ public final class BenchmarkRunner { - private static String PREDEFINED_PARAMETER = "predefined"; - private static boolean IS_PREDEFINED = false; - private static ValidationProvider validationProvider; - private static final Stream> DEFAULT_TEST_CLASSES = Stream.of( SimpleValidation.class.getName(), CascadedValidation.class.getName(), @@ -75,35 +57,10 @@ public static void main(String[] args) throws RunnerException, CommandLineOption DEFAULT_TEST_CLASSES.forEach( testClass -> builder.include( testClass.getName() ) ); } - IS_PREDEFINED = isPredefined( commandLineOptions ); - Options opt = builder.build(); new Runner( opt ).run(); } - public static ValidatorFactory buildValidatorFactory(Set constraintNames, Set> beanClasses) { - return IS_PREDEFINED - ? Validation.byProvider( PredefinedScopeHibernateValidator.class ) - .configure() - .builtinConstraints( constraintNames ) - .initializeBeanMetaData( beanClasses ) - .buildValidatorFactory() - : Validation.buildDefaultValidatorFactory(); - } - - private static boolean isPredefined(Options commandLineOptions) throws CommandLineOptionException { - Optional> isPredefinedValues = commandLineOptions.getParameter( PREDEFINED_PARAMETER ); - if ( isPredefinedValues.hasValue() ) { - if ( isPredefinedValues.get().size() == 1 ) { - return Boolean.parseBoolean( isPredefinedValues.get().iterator().next() ); - } - else { - throw new CommandLineOptionException( "More than one value provided for parameter: " + PREDEFINED_PARAMETER ); - } - } - return false; - } - private static Class classForName(String qualifiedName) { try { return Class.forName( qualifiedName ); diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java index 14061c30e5..7bef45b7c4 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java @@ -8,7 +8,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -20,8 +19,6 @@ import jakarta.validation.ValidatorFactory; import jakarta.validation.constraints.NotNull; -import org.hibernate.validator.performance.BenchmarkRunner; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -45,10 +42,7 @@ public static class CascadedValidationState { public volatile Person person; public CascadedValidationState() { - ValidatorFactory factory = BenchmarkRunner.buildValidatorFactory( - Collections.singleton( NotNull.class.getName() ), - Collections.singleton( Person.class ) - ); + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); // TODO graphs needs to be generated and deeper diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java index 7a594138b8..70493f06b2 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java @@ -9,8 +9,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -23,8 +21,6 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import org.hibernate.validator.performance.BenchmarkRunner; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -51,10 +47,7 @@ public static class CascadedWithLotsOfItemsValidationState { public volatile Shop shop; public CascadedWithLotsOfItemsValidationState() { - ValidatorFactory factory = BenchmarkRunner.buildValidatorFactory( - new HashSet<>( Arrays.asList( NotNull.class.getName(), Size.class.getName() ) ), - new HashSet<>( Arrays.asList( Shop.class, Article.class ) ) - ); + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); shop = createShop(); diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java index 6df10c32ba..27050653a2 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java @@ -9,9 +9,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -23,8 +20,6 @@ import jakarta.validation.ValidatorFactory; import jakarta.validation.constraints.NotNull; -import org.hibernate.validator.performance.BenchmarkRunner; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -51,10 +46,7 @@ public static class CascadedWithLotsOfItemsValidationState { public volatile Shop shop; public CascadedWithLotsOfItemsValidationState() { - ValidatorFactory factory = BenchmarkRunner.buildValidatorFactory( - Collections.singleton( NotNull.class.getName() ), - new HashSet<>( Arrays.asList( Shop.class, Article.class ) ) - ); + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); shop = createShop(); From c03f9d1755fea10be4ba97baf740acc72800f3ed Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 20 May 2021 19:30:59 +0200 Subject: [PATCH 11/15] HV-1831 Add some guidance about next step --- .../PredefinedScopeValidatorFactoryImpl.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java index 2ea045560d..93adfec926 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java @@ -188,6 +188,20 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState xmlMetaDataProvider = null; } + // collect all metadata, I don't think we need this work to be in BeanMetaDataManager contract, it can be a specific class (or private method if simple enough) + // it's basically the content of PredefinedScopeBeanMetaDataManager constructor + // the metadata wouldn't be complete because we want to inject the tracking information + + // then you build the tracking information from these incomplete metadata + + // finally you create a PredefinedScopeBeanMetaDataManager with the augmented metadata pushed to it + // you will need to augment both BeanMetaData and ExecutableMetaData + // I would prototype BeanMetaData first then discuss it before going further + + // Note: we want classes to be immutable + // Might be a good idea to push a default method to BeanMetaData as enabling tracking is the default behavior we want + // Maybe first try composition and benchmark it and if good enough, we keep it + this.beanMetaDataManager = new PredefinedScopeBeanMetaDataManager( constraintCreationContext, executableHelper, From 22323b46e283f9b9f1b195891979f213ad3fab5b Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 19 Jul 2021 21:17:11 -0700 Subject: [PATCH 12/15] HV-1831 : Wrap a `BeanMetaData` in a `NonTrackedBeanMetaDataImpl` if tracking is not required --- .../PredefinedScopeValidatorFactoryImpl.java | 7 +- .../BeanValidationContext.java | 9 +- .../PredefinedScopeBeanMetaDataManager.java | 46 +++++++-- .../metadata/aggregated/BeanMetaData.java | 8 ++ .../NonTrackedBeanMetaDataImpl.java | 96 +++++++++++++++++++ 5 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java index 93adfec926..1f09e96c38 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java @@ -49,7 +49,6 @@ import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; import org.hibernate.validator.internal.engine.constraintvalidation.PredefinedScopeConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; -import org.hibernate.validator.internal.engine.tracking.PredefinedScopeProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; @@ -211,7 +210,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState buildMetaDataProviders( constraintCreationContext, xmlMetaDataProvider, constraintMappings ), methodValidationConfiguration, determineBeanMetaDataClassNormalizer( hibernateSpecificConfig ), - hibernateSpecificConfig.getBeanClassesToInitialize() + hibernateSpecificConfig ); this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( @@ -227,9 +226,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingStrategy() != null ) - ? hibernateSpecificConfig.getProcessedBeansTrackingStrategy() - : new PredefinedScopeProcessedBeansTrackingStrategy( beanMetaDataManager ), + beanMetaDataManager.getProcessedBeansTrackingStrategy(), constraintValidatorInitializationContext ); if ( LOG.isDebugEnabled() ) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java index 95b5f8bc0f..80a712d1e5 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java @@ -16,7 +16,6 @@ import org.hibernate.validator.internal.engine.ConstraintViolationImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; @@ -38,16 +37,10 @@ class BeanValidationContext extends AbstractValidationContext { BeanMetaData rootBeanMetaData ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, - rootBean, rootBeanClass, rootBeanMetaData, buildProcessedBeansTrackingEnabled( validatorScopedContext.getProcessedBeansTrackingStrategy(), - rootBeanClass, rootBeanMetaData ) + rootBean, rootBeanClass, rootBeanMetaData, rootBeanMetaData.isTrackingRequired() ); } - private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, Class rootBeanClass, - BeanMetaData rootBeanMetaData) { - return processedBeansTrackingStrategy.isEnabledForBean( rootBeanClass, rootBeanMetaData.hasCascadables() ); - } - @Override protected ConstraintViolation createConstraintViolation( String messageTemplate, String interpolatedMessage, Path propertyPath, diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java index 2bcb968b32..341a0c7437 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -31,12 +32,16 @@ import org.hibernate.validator.internal.engine.ConstraintCreationContext; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; +import org.hibernate.validator.internal.engine.PredefinedScopeConfigurationImpl; import org.hibernate.validator.internal.engine.groups.Sequence; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.PredefinedScopeProcessedBeansTrackingStrategy; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; +import org.hibernate.validator.internal.metadata.aggregated.NonTrackedBeanMetaDataImpl; import org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; @@ -61,15 +66,17 @@ public class PredefinedScopeBeanMetaDataManager implements BeanMetaDataManager { */ private final ConcurrentMap, BeanMetaData> beanMetaDataMap = new ConcurrentHashMap<>(); + private final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; + public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCreationContext, - ExecutableHelper executableHelper, - ExecutableParameterNameProvider parameterNameProvider, - JavaBeanHelper javaBeanHelper, - ValidationOrderGenerator validationOrderGenerator, - List optionalMetaDataProviders, - MethodValidationConfiguration methodValidationConfiguration, - BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, - Set> beanClassesToInitialize) { + ExecutableHelper executableHelper, + ExecutableParameterNameProvider parameterNameProvider, + JavaBeanHelper javaBeanHelper, + ValidationOrderGenerator validationOrderGenerator, + List optionalMetaDataProviders, + MethodValidationConfiguration methodValidationConfiguration, + BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, + PredefinedScopeConfigurationImpl hibernateSpecificConfig) { AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders( optionalMetaDataProviders ); AnnotationMetaDataProvider defaultProvider = new AnnotationMetaDataProvider( constraintCreationContext, @@ -85,7 +92,7 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr metaDataProviders.add( defaultProvider ); metaDataProviders.addAll( optionalMetaDataProviders ); - for ( Class validatedClass : beanClassesToInitialize ) { + for ( Class validatedClass : hibernateSpecificConfig.getBeanClassesToInitialize() ) { Class normalizedValidatedClass = beanMetaDataClassNormalizer.normalize( validatedClass ); @SuppressWarnings("unchecked") @@ -105,6 +112,23 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr } this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer; + + if ( hibernateSpecificConfig.getProcessedBeansTrackingStrategy() != null ) { + this.processedBeansTrackingStrategy = hibernateSpecificConfig.getProcessedBeansTrackingStrategy(); + } + else { + this.processedBeansTrackingStrategy = new PredefinedScopeProcessedBeansTrackingStrategy( + this + ); + } + // Wrap the BeanMetaData objects with NonTrackedBeanMetaDataImpl if tracking is not required. + for ( Map.Entry, BeanMetaData> entry : beanMetaDataMap.entrySet() ) { + final Class beanClass = entry.getKey(); + final BeanMetaData beanMetaData = entry.getValue(); + if ( ! processedBeansTrackingStrategy.isEnabledForBean( beanClass, beanMetaData.hasCascadables() ) ) { + beanMetaDataMap.put( beanClass, new NonTrackedBeanMetaDataImpl<>( beanMetaData ) ); + } + } } @SuppressWarnings("unchecked") @@ -124,6 +148,10 @@ public Collection> getBeanMetaData() { return beanMetaDataMap.values(); } + public ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return processedBeansTrackingStrategy; + } + @Override public void clear() { beanMetaDataMap.clear(); diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java index 6b31eb4d61..7554f66d3e 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java @@ -113,4 +113,12 @@ public interface BeanMetaData extends Validatable { * element itself and goes up the hierarchy chain. Interfaces are not included. */ List> getClassHierarchy(); + + /** + * @return {@code true} if the bean class is required to be tracked; {@code false} otherwise. + */ + default boolean isTrackingRequired() { + // TODO: is this the correct return value for UninitializedBeanMetaData??? + return true; + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java new file mode 100644 index 0000000000..fea1418812 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java @@ -0,0 +1,96 @@ +/* + * 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.metadata.aggregated; + +import java.lang.reflect.Executable; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import javax.validation.metadata.BeanDescriptor; + +import org.hibernate.validator.internal.engine.groups.Sequence; +import org.hibernate.validator.internal.metadata.core.MetaConstraint; +import org.hibernate.validator.internal.metadata.facets.Cascadable; + +public class NonTrackedBeanMetaDataImpl implements BeanMetaData { + private final BeanMetaData beanMetaData; + + public NonTrackedBeanMetaDataImpl(BeanMetaData beanMetaData) { + this.beanMetaData = beanMetaData; + } + + @Override + public Class getBeanClass() { + return beanMetaData.getBeanClass(); + } + + @Override + public boolean hasConstraints() { + return beanMetaData.hasConstraints(); + } + + @Override + public BeanDescriptor getBeanDescriptor() { + return beanMetaData.getBeanDescriptor(); + } + + @Override + public PropertyMetaData getMetaDataFor(String propertyName) { + return beanMetaData.getMetaDataFor( propertyName ); + } + + @Override + public List> getDefaultGroupSequence(T beanState) { + return beanMetaData.getDefaultGroupSequence( beanState ); + } + + @Override + public Iterator getDefaultValidationSequence(T beanState) { + return beanMetaData.getDefaultValidationSequence( beanState ); + } + + @Override + public boolean isDefaultGroupSequenceRedefined() { + return beanMetaData.isDefaultGroupSequenceRedefined(); + } + + @Override + public Set> getMetaConstraints() { + return beanMetaData.getMetaConstraints(); + } + + @Override + public Set> getDirectMetaConstraints() { + return beanMetaData.getDirectMetaConstraints(); + } + + @Override + public Optional getMetaDataFor(Executable executable) throws IllegalArgumentException { + return beanMetaData.getMetaDataFor( executable ); + } + + @Override + public List> getClassHierarchy() { + return beanMetaData.getClassHierarchy(); + } + + @Override + public Iterable getCascadables() { + return beanMetaData.getCascadables(); + } + + @Override + public boolean hasCascadables() { + return beanMetaData.hasCascadables(); + } + + @Override + public boolean isTrackingRequired() { + return false; + } +} From 4c5db5759b3aa2e248a8222ad7ec1967c29b8f7e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 30 Jul 2021 18:55:05 +0200 Subject: [PATCH 13/15] HV-1831 New zero cost approach to processed bean tracking strategy I removed it from the traditional VF for now as I would like us to focus on the case where it is useful first. We will reintroduce it later once we have validated the approach where it is the most useful. I'm a bit unclear right now if we should use the same contract for traditional and predefined scope VF as we are dealing with different things and they won't be evaluated at the same moment. I'm thinking that maybe this needs to be a different contract. --- .../PredefinedScopeValidatorFactoryImpl.java | 5 +- .../internal/engine/ValidatorFactoryImpl.java | 6 +- .../engine/ValidatorFactoryScopedContext.java | 25 +---- ...adablesProcessedBeansTrackingStrategy.java | 31 ------ ...edScopeProcessedBeansTrackingStrategy.java | 79 ++++++++------- .../ProcessedBeansTrackingStrategy.java | 6 +- .../BeanValidationContext.java | 2 +- .../ParameterExecutableValidationContext.java | 11 +-- ...eturnValueExecutableValidationContext.java | 10 +- .../ValidatorScopedContext.java | 11 --- .../PredefinedScopeBeanMetaDataManager.java | 30 +++--- .../AbstractConstraintMetaData.java | 10 ++ .../metadata/aggregated/BeanMetaData.java | 5 +- .../metadata/aggregated/BeanMetaDataImpl.java | 31 ++++++ .../aggregated/ExecutableMetaData.java | 42 ++++++++ .../NonTrackedBeanMetaDataImpl.java | 96 ------------------- ...clesNoCyclesListDuplicateElementsTest.java | 16 ++-- ...edBeansTrackingCyclesNoCyclesListTest.java | 16 ++-- ...sedBeansTrackingCyclesNoCyclesMapTest.java | 16 ++-- ...cessedBeansTrackingCyclesNoCyclesTest.java | 16 ++-- 20 files changed, 182 insertions(+), 282 deletions(-) delete mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java delete mode 100644 engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java index 1f09e96c38..8f5dd12717 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java @@ -226,7 +226,6 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - beanMetaDataManager.getProcessedBeansTrackingStrategy(), constraintValidatorInitializationContext ); if ( LOG.isDebugEnabled() ) { @@ -296,6 +295,10 @@ public boolean isTraversableResolverResultCacheEnabled() { return validatorFactoryScopedContext.isTraversableResolverResultCacheEnabled(); } + public PredefinedScopeBeanMetaDataManager getBeanMetaDataManager() { + return beanMetaDataManager; + } + @Override public T unwrap(Class type) { // allow unwrapping into public super types diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java index 070a6eabe9..82cee93edc 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java @@ -10,9 +10,9 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowOverridingMethodAlterParameterConstraint; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowParallelMethodsDefineParameterConstraints; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload; -import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineFailFast; @@ -49,7 +49,6 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; -import org.hibernate.validator.internal.engine.tracking.HasCascadablesProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; @@ -166,9 +165,6 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingStrategy() != null ) - ? hibernateSpecificConfig.getProcessedBeansTrackingStrategy() - : new HasCascadablesProcessedBeansTrackingStrategy(), determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) ); ConstraintValidatorManager constraintValidatorManager = new ConstraintValidatorManagerImpl( diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java index dc7bbe0a1b..29522c82a2 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java @@ -15,7 +15,6 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; @@ -79,11 +78,6 @@ public class ValidatorFactoryScopedContext { */ private final ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel; - /** - * Strategy used to enable or not processed beans tracking. - */ - private final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; - /** * The constraint validator initialization context. */ @@ -105,11 +99,10 @@ public class ValidatorFactoryScopedContext { Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel, - ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, boolean showValidatedValuesInTraceLogs) { this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel, - customViolationExpressionLanguageFeatureLevel, processedBeansTrackingStrategy, + customViolationExpressionLanguageFeatureLevel, new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, temporalValidationTolerance ) ); } @@ -125,7 +118,6 @@ public class ValidatorFactoryScopedContext { boolean showValidatedValuesInTraceLogs, Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel, - ProcessedBeansTrackingStrategy processedBeanTrackingStrategy, HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext) { this.messageInterpolator = messageInterpolator; this.traversableResolver = traversableResolver; @@ -139,7 +131,6 @@ public class ValidatorFactoryScopedContext { this.constraintExpressionLanguageFeatureLevel = constraintExpressionLanguageFeatureLevel; this.customViolationExpressionLanguageFeatureLevel = customViolationExpressionLanguageFeatureLevel; this.showValidatedValuesInTraceLogs = showValidatedValuesInTraceLogs; - this.processedBeansTrackingStrategy = processedBeanTrackingStrategy; this.constraintValidatorInitializationContext = constraintValidatorInitializationContext; } @@ -195,10 +186,6 @@ public boolean isShowValidatedValuesInTraceLogs() { return showValidatedValuesInTraceLogs; } - public ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { - return processedBeansTrackingStrategy; - } - static class Builder { private final ValidatorFactoryScopedContext defaultContext; @@ -213,8 +200,6 @@ static class Builder { private Object constraintValidatorPayload; private ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel; private ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel; - private ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; - private boolean showValidatedValuesInTraceLogs; private HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext; @@ -234,7 +219,6 @@ static class Builder { this.constraintExpressionLanguageFeatureLevel = defaultContext.constraintExpressionLanguageFeatureLevel; this.customViolationExpressionLanguageFeatureLevel = defaultContext.customViolationExpressionLanguageFeatureLevel; this.showValidatedValuesInTraceLogs = defaultContext.showValidatedValuesInTraceLogs; - this.processedBeansTrackingStrategy = defaultContext.processedBeansTrackingStrategy; this.constraintValidatorInitializationContext = defaultContext.constraintValidatorInitializationContext; } @@ -327,12 +311,6 @@ public ValidatorFactoryScopedContext.Builder setShowValidatedValuesInTraceLogs( return this; } - public ValidatorFactoryScopedContext.Builder setProcessedBeansTrackingStrategy( - ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { - this.processedBeansTrackingStrategy = processedBeansTrackingStrategy; - return this; - } - public ValidatorFactoryScopedContext build() { return new ValidatorFactoryScopedContext( messageInterpolator, @@ -346,7 +324,6 @@ public ValidatorFactoryScopedContext build() { showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel, customViolationExpressionLanguageFeatureLevel, - processedBeansTrackingStrategy, HibernateConstraintValidatorInitializationContextImpl.of( constraintValidatorInitializationContext, scriptEvaluatorFactory, diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java deleted file mode 100644 index 7a7a33083f..0000000000 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/HasCascadablesProcessedBeansTrackingStrategy.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.engine.tracking; - -import java.lang.reflect.Executable; - -public class HasCascadablesProcessedBeansTrackingStrategy implements ProcessedBeansTrackingStrategy { - - @Override - public boolean isEnabledForBean(Class beanClass, boolean hasCascadables) { - return hasCascadables; - } - - @Override - public boolean isEnabledForReturnValue(Executable executable, boolean hasCascadables) { - return hasCascadables; - } - - @Override - public boolean isEnabledForParameters(Executable executable, boolean hasCascadables) { - return hasCascadables; - } - - @Override - public void clear() { - } -} diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java index 167f0856e5..f570ebc9ea 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -6,7 +6,6 @@ */ package org.hibernate.validator.internal.engine.tracking; -import java.lang.reflect.Executable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collections; @@ -15,22 +14,22 @@ import java.util.Map; import java.util.Set; -import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData; import org.hibernate.validator.internal.metadata.facets.Cascadable; +import org.hibernate.validator.internal.properties.Signature; import org.hibernate.validator.internal.util.CollectionHelper; public class PredefinedScopeProcessedBeansTrackingStrategy implements ProcessedBeansTrackingStrategy { private final Map, Boolean> trackingEnabledForBeans; - private final Map trackingEnabledForReturnValues; + private final Map trackingEnabledForReturnValues; - private final Map trackingEnabledForParameters; + private final Map trackingEnabledForParameters; - public PredefinedScopeProcessedBeansTrackingStrategy(PredefinedScopeBeanMetaDataManager beanMetaDataManager) { + public PredefinedScopeProcessedBeansTrackingStrategy(Map, BeanMetaData> rawBeanMetaDataMap) { // TODO: build the maps from the information inside the beanMetaDataManager // There is a good chance we will need a structure with the whole hierarchy of constraint classes. // That's something we could add to PredefinedScopeBeanMetaDataManager, as we are already doing similar things @@ -39,24 +38,24 @@ public PredefinedScopeProcessedBeansTrackingStrategy(PredefinedScopeBeanMetaData // PredefinedScopeBeanMetaDataManager. this.trackingEnabledForBeans = CollectionHelper.toImmutableMap( - new TrackingEnabledStrategyBuilder( beanMetaDataManager ).build() + new TrackingEnabledStrategyBuilder( rawBeanMetaDataMap ).build() ); this.trackingEnabledForReturnValues = CollectionHelper.toImmutableMap( new HashMap<>() ); this.trackingEnabledForParameters = CollectionHelper.toImmutableMap( new HashMap<>() ); } private static class TrackingEnabledStrategyBuilder { - private final PredefinedScopeBeanMetaDataManager beanMetaDataManager; + private final Map, BeanMetaData> rawBeanMetaDataMap; private final Map, Boolean> classToBeanTrackingEnabled; - TrackingEnabledStrategyBuilder(PredefinedScopeBeanMetaDataManager beanMetaDataManager) { - this.beanMetaDataManager = beanMetaDataManager; - this.classToBeanTrackingEnabled = new HashMap<>( beanMetaDataManager.getBeanMetaData().size() ); + TrackingEnabledStrategyBuilder(Map, BeanMetaData> rawBeanMetaDataMap) { + this.rawBeanMetaDataMap = rawBeanMetaDataMap; + this.classToBeanTrackingEnabled = CollectionHelper.newHashMap( rawBeanMetaDataMap.size() ); } public Map, Boolean> build() { final Set> beanClassesInPath = new HashSet<>(); - for ( BeanMetaData beanMetadata : beanMetaDataManager.getBeanMetaData() ) { + for ( BeanMetaData beanMetadata : rawBeanMetaDataMap.values() ) { determineTrackingRequired( beanMetadata.getBeanClass(), beanClassesInPath ); if ( !beanClassesInPath.isEmpty() ) { throw new IllegalStateException( "beanClassesInPath not empty" ); @@ -160,38 +159,38 @@ private boolean determineTrackingRequired(Class beanClass, Set> bean // TODO: is there a more concise way to do this? private Set> getDirectCascadedBeanClasses(Class beanClass ) { - final BeanMetaData beanMetaData = beanMetaDataManager.getBeanMetaData( beanClass ); - if ( beanMetaData.hasCascadables() ) { - final Set> directCascadedBeanClasses = new HashSet<>(); - for ( Cascadable cascadable : beanMetaData.getCascadables() ) { - final CascadingMetaData cascadingMetaData = cascadable.getCascadingMetaData(); - if ( cascadingMetaData.isContainer() ) { - final ContainerCascadingMetaData containerCascadingMetaData = (ContainerCascadingMetaData) cascadingMetaData; - if ( containerCascadingMetaData.getEnclosingType() instanceof ParameterizedType ) { - ParameterizedType parameterizedType = (ParameterizedType) containerCascadingMetaData.getEnclosingType(); - for ( Type typeArgument : parameterizedType.getActualTypeArguments() ) { - if ( typeArgument instanceof Class ) { - directCascadedBeanClasses.add( (Class) typeArgument ); - } - else { - throw new UnsupportedOperationException( "Only ParameterizedType values of type Class are supported" ); - } + final BeanMetaData beanMetaData = rawBeanMetaDataMap.get( beanClass ); + + if ( beanMetaData == null || !beanMetaData.hasCascadables() ) { + return Collections.emptySet(); + } + + final Set> directCascadedBeanClasses = new HashSet<>(); + for ( Cascadable cascadable : beanMetaData.getCascadables() ) { + final CascadingMetaData cascadingMetaData = cascadable.getCascadingMetaData(); + if ( cascadingMetaData.isContainer() ) { + final ContainerCascadingMetaData containerCascadingMetaData = (ContainerCascadingMetaData) cascadingMetaData; + if ( containerCascadingMetaData.getEnclosingType() instanceof ParameterizedType ) { + ParameterizedType parameterizedType = (ParameterizedType) containerCascadingMetaData.getEnclosingType(); + for ( Type typeArgument : parameterizedType.getActualTypeArguments() ) { + if ( typeArgument instanceof Class ) { + directCascadedBeanClasses.add( (Class) typeArgument ); + } + else { + throw new UnsupportedOperationException( "Only ParameterizedType values of type Class are supported" ); } - } - else { - throw new UnsupportedOperationException( "Non-parameterized containers are not supported yet." ); } } else { - // TODO: For now, assume non-container Cascadables are always beans. Truee??? - directCascadedBeanClasses.add( (Class) cascadable.getCascadableType() ); + throw new UnsupportedOperationException( "Non-parameterized containers are not supported yet." ); } } - return directCascadedBeanClasses; - } - else { - return Collections.emptySet(); + else { + // TODO: For now, assume non-container Cascadables are always beans. Truee??? + directCascadedBeanClasses.add( (Class) cascadable.getCascadableType() ); + } } + return directCascadedBeanClasses; } private boolean register(Class beanClass, boolean isBeanTrackingEnabled) { @@ -212,21 +211,21 @@ public boolean isEnabledForBean(Class rootBeanClass, boolean hasCascadables) } @Override - public boolean isEnabledForReturnValue(Executable executable, boolean hasCascadables) { + public boolean isEnabledForReturnValue(Signature signature, boolean hasCascadables) { if ( !hasCascadables ) { return false; } - return trackingEnabledForReturnValues.getOrDefault( executable, true ); + return trackingEnabledForReturnValues.getOrDefault( signature, true ); } @Override - public boolean isEnabledForParameters(Executable executable, boolean hasCascadables) { + public boolean isEnabledForParameters(Signature signature, boolean hasCascadables) { if ( !hasCascadables ) { return false; } - return trackingEnabledForParameters.getOrDefault( executable, true ); + return trackingEnabledForParameters.getOrDefault( signature, true ); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java index b5db188e3b..7945ab7f46 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java @@ -6,15 +6,15 @@ */ package org.hibernate.validator.internal.engine.tracking; -import java.lang.reflect.Executable; +import org.hibernate.validator.internal.properties.Signature; public interface ProcessedBeansTrackingStrategy { boolean isEnabledForBean(Class beanClass, boolean hasCascadables); - boolean isEnabledForReturnValue(Executable executable, boolean hasCascadables); + boolean isEnabledForReturnValue(Signature signature, boolean hasCascadables); - boolean isEnabledForParameters(Executable executable, boolean hasCascadables); + boolean isEnabledForParameters(Signature signature, boolean hasCascadables); void clear(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java index 80a712d1e5..9c470ccdb7 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BeanValidationContext.java @@ -37,7 +37,7 @@ class BeanValidationContext extends AbstractValidationContext { BeanMetaData rootBeanMetaData ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, - rootBean, rootBeanClass, rootBeanMetaData, rootBeanMetaData.isTrackingRequired() + rootBean, rootBeanClass, rootBeanMetaData, rootBeanMetaData.isTrackingEnabled() ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java index 38cd8796cf..c0b6f02bcc 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java @@ -24,7 +24,6 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; import org.hibernate.validator.internal.engine.constraintvalidation.CrossParameterConstraintValidatorContextImpl; import org.hibernate.validator.internal.engine.path.PathImpl; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; @@ -69,8 +68,7 @@ public class ParameterExecutableValidationContext extends AbstractValidationC ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, rootBean, rootBeanClass, rootBeanMetaData, - buildProcessedBeansTrackingEnabled( validatorScopedContext.getProcessedBeansTrackingStrategy(), executable, - executableMetaData ) + isProcessedBeansTrackingEnabled( executableMetaData ) ); this.executable = executable; this.executableMetaData = executableMetaData; @@ -87,16 +85,13 @@ public Optional getExecutableMetaData() { return executableMetaData; } - private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, - Executable executable, - Optional executableMetaData) { + private static boolean isProcessedBeansTrackingEnabled(Optional executableMetaData) { if ( !executableMetaData.isPresent() ) { // the method is unconstrained so there's no need to worry about the tracking return false; } - return processedBeansTrackingStrategy.isEnabledForParameters( executable, - executableMetaData.get().getValidatableParametersMetaData().hasCascadables() ); + return executableMetaData.get().isTrackingEnabledForParameters(); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java index 59d0c1ef62..ab53e1ecba 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ReturnValueExecutableValidationContext.java @@ -19,7 +19,6 @@ import org.hibernate.validator.internal.engine.ConstraintViolationImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; @@ -62,8 +61,7 @@ public class ReturnValueExecutableValidationContext extends AbstractValidatio ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, rootBean, rootBeanClass, rootBeanMetaData, - buildProcessedBeansTrackingEnabled( validatorScopedContext.getProcessedBeansTrackingStrategy(), executable, - executableMetaData ) + isTrackingEnabled( executableMetaData ) ); this.executable = executable; this.executableMetaData = executableMetaData; @@ -80,15 +78,13 @@ public Optional getExecutableMetaData() { return executableMetaData; } - private static boolean buildProcessedBeansTrackingEnabled(ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, - Executable executable, - Optional executableMetaData) { + private static boolean isTrackingEnabled(Optional executableMetaData) { if ( !executableMetaData.isPresent() ) { // the method is unconstrained so there's no need to worry about the tracking return false; } - return processedBeansTrackingStrategy.isEnabledForReturnValue( executable, executableMetaData.get().getReturnValueMetaData().hasCascadables() ); + return executableMetaData.get().isTrackingEnabledForReturnValue(); } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java index 890117ecb2..abf3496ec3 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidatorScopedContext.java @@ -13,7 +13,6 @@ import jakarta.validation.Validator; import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory; @@ -79,11 +78,6 @@ public class ValidatorScopedContext { private final boolean showValidatedValuesInTraceLogs; - /** - * Strategy used to enable or not processed beans tracking. - */ - private final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; - public ValidatorScopedContext(ValidatorFactoryScopedContext validatorFactoryScopedContext) { this.messageInterpolator = validatorFactoryScopedContext.getMessageInterpolator(); this.parameterNameProvider = validatorFactoryScopedContext.getParameterNameProvider(); @@ -95,7 +89,6 @@ public ValidatorScopedContext(ValidatorFactoryScopedContext validatorFactoryScop this.constraintValidatorPayload = validatorFactoryScopedContext.getConstraintValidatorPayload(); this.constraintExpressionLanguageFeatureLevel = validatorFactoryScopedContext.getConstraintExpressionLanguageFeatureLevel(); this.customViolationExpressionLanguageFeatureLevel = validatorFactoryScopedContext.getCustomViolationExpressionLanguageFeatureLevel(); - this.processedBeansTrackingStrategy = validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); this.showValidatedValuesInTraceLogs = validatorFactoryScopedContext.isShowValidatedValuesInTraceLogs(); } @@ -142,8 +135,4 @@ public ExpressionLanguageFeatureLevel getCustomViolationExpressionLanguageFeatur public boolean isShowValidatedValuesInTraceLogs() { return showValidatedValuesInTraceLogs; } - - public ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { - return processedBeansTrackingStrategy; - } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java index 341a0c7437..2ac321c3df 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -41,7 +42,6 @@ import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; -import org.hibernate.validator.internal.metadata.aggregated.NonTrackedBeanMetaDataImpl; import org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; @@ -92,6 +92,7 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr metaDataProviders.add( defaultProvider ); metaDataProviders.addAll( optionalMetaDataProviders ); + Map, BeanMetaData> rawBeanMetaDataMap = new HashMap<>(); for ( Class validatedClass : hibernateSpecificConfig.getBeanClassesToInitialize() ) { Class normalizedValidatedClass = beanMetaDataClassNormalizer.normalize( validatedClass ); @@ -100,11 +101,11 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr // note that the hierarchy also contains the initial class for ( Class hierarchyElement : classHierarchy ) { - if ( this.beanMetaDataMap.containsKey( hierarchyElement ) ) { + if ( rawBeanMetaDataMap.containsKey( hierarchyElement ) ) { continue; } - this.beanMetaDataMap.put( hierarchyElement, + rawBeanMetaDataMap.put( hierarchyElement, createBeanMetaData( constraintCreationContext, executableHelper, parameterNameProvider, javaBeanHelper, validationOrderGenerator, optionalMetaDataProviders, methodValidationConfiguration, metaDataProviders, hierarchyElement ) ); @@ -118,16 +119,13 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr } else { this.processedBeansTrackingStrategy = new PredefinedScopeProcessedBeansTrackingStrategy( - this + rawBeanMetaDataMap ); } - // Wrap the BeanMetaData objects with NonTrackedBeanMetaDataImpl if tracking is not required. - for ( Map.Entry, BeanMetaData> entry : beanMetaDataMap.entrySet() ) { - final Class beanClass = entry.getKey(); - final BeanMetaData beanMetaData = entry.getValue(); - if ( ! processedBeansTrackingStrategy.isEnabledForBean( beanClass, beanMetaData.hasCascadables() ) ) { - beanMetaDataMap.put( beanClass, new NonTrackedBeanMetaDataImpl<>( beanMetaData ) ); - } + + // Inject the processed beans tracking information into the BeanMetaData objects + for ( Map.Entry, BeanMetaData> rawBeanMetaDataEntry : rawBeanMetaDataMap.entrySet() ) { + beanMetaDataMap.put( rawBeanMetaDataEntry.getKey(), injectTrackingInformation( rawBeanMetaDataEntry.getValue(), processedBeansTrackingStrategy ) ); } } @@ -222,6 +220,11 @@ private static List> getBeanConfigurationForHie return configurations; } + private static BeanMetaData injectTrackingInformation(BeanMetaData rawBeanMetaData, + ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { + return new BeanMetaDataImpl( (BeanMetaDataImpl) rawBeanMetaData, processedBeansTrackingStrategy ); + } + private static class UninitializedBeanMetaData implements BeanMetaData { private final Class beanClass; @@ -301,6 +304,11 @@ public Optional getMetaDataFor(Executable executable) throws public List> getClassHierarchy() { return classHierarchy; } + + @Override + public boolean isTrackingEnabled() { + return true; + } } private static class UninitializedBeanDescriptor implements BeanDescriptor { diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java index f94338c132..22527b93fc 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java @@ -70,6 +70,16 @@ public AbstractConstraintMetaData(String name, this.isConstrained = isConstrained; } + protected AbstractConstraintMetaData(AbstractConstraintMetaData originalAbstractConstraintMetaData) { + this.name = originalAbstractConstraintMetaData.name; + this.type = originalAbstractConstraintMetaData.type; + this.directConstraints = originalAbstractConstraintMetaData.directConstraints; + this.containerElementsConstraints = originalAbstractConstraintMetaData.containerElementsConstraints; + this.allConstraints = originalAbstractConstraintMetaData.allConstraints; + this.isCascading = originalAbstractConstraintMetaData.isCascading; + this.isConstrained = originalAbstractConstraintMetaData.isConstrained; + } + @Override public String getName() { return name; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java index 7554f66d3e..9404a291a4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaData.java @@ -117,8 +117,5 @@ public interface BeanMetaData extends Validatable { /** * @return {@code true} if the bean class is required to be tracked; {@code false} otherwise. */ - default boolean isTrackingRequired() { - // TODO: is this the correct return value for UninitializedBeanMetaData??? - return true; - } + boolean isTrackingEnabled(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java index 34adb58879..e82f6587a1 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java @@ -30,6 +30,7 @@ import org.hibernate.validator.internal.engine.groups.Sequence; import org.hibernate.validator.internal.engine.groups.ValidationOrder; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.BeanDescriptorImpl; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; @@ -161,6 +162,11 @@ public final class BeanMetaDataImpl implements BeanMetaData { */ private volatile BeanDescriptor beanDescriptor; + /** + * Whether tracking of processed beans should be enabled for objects of this type. + */ + private final boolean trackingEnabled; + /** * Creates a new {@link BeanMetaDataImpl} * @@ -238,6 +244,26 @@ else if ( constraintMetaData.getKind() == ElementKind.BEAN ) { // We initialize those elements eagerly so that any eventual error is thrown when bootstrapping the bean metadata this.defaultGroupSequenceRedefined = this.defaultGroupSequence.size() > 1 || hasDefaultGroupSequenceProvider(); this.resolvedDefaultGroupSequence = getDefaultGroupSequence( null ); + this.trackingEnabled = hasCascadables(); + } + + public BeanMetaDataImpl(BeanMetaDataImpl originalBeanMetaData, ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { + this.validationOrderGenerator = originalBeanMetaData.validationOrderGenerator; + this.beanClass = originalBeanMetaData.beanClass; + this.propertyMetaDataMap = originalBeanMetaData.propertyMetaDataMap; + this.hasConstraints = originalBeanMetaData.hasConstraints; + this.cascadedProperties = originalBeanMetaData.cascadedProperties; + this.allMetaConstraints = originalBeanMetaData.allMetaConstraints; + this.classHierarchyWithoutInterfaces = originalBeanMetaData.classHierarchyWithoutInterfaces; + this.defaultGroupSequenceProvider = originalBeanMetaData.defaultGroupSequenceProvider; + this.defaultGroupSequence = originalBeanMetaData.defaultGroupSequence; + this.validationOrder = originalBeanMetaData.validationOrder; + this.directMetaConstraints = originalBeanMetaData.directMetaConstraints; + this.executableMetaDataMap = originalBeanMetaData.executableMetaDataMap; + this.unconstrainedExecutables = originalBeanMetaData.unconstrainedExecutables; + this.defaultGroupSequenceRedefined = originalBeanMetaData.defaultGroupSequenceRedefined; + this.resolvedDefaultGroupSequence = originalBeanMetaData.resolvedDefaultGroupSequence; + this.trackingEnabled = processedBeansTrackingStrategy.isEnabledForBean( this.beanClass, hasCascadables() ); } @Override @@ -357,6 +383,11 @@ public List> getClassHierarchy() { return classHierarchyWithoutInterfaces; } + @Override + public boolean isTrackingEnabled() { + return trackingEnabled; + } + private static BeanDescriptor createBeanDescriptor(Class beanClass, Set> allMetaConstraints, Map propertyMetaDataMap, Map executableMetaDataMap, boolean defaultGroupSequenceRedefined, diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java index fb3108ffaa..1a71d19224 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java @@ -23,6 +23,7 @@ import org.hibernate.validator.internal.engine.ConstraintCreationContext; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; +import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.metadata.aggregated.rule.MethodConfigurationRule; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.ExecutableDescriptorImpl; @@ -78,6 +79,9 @@ public class ExecutableMetaData extends AbstractConstraintMetaData { private final ReturnValueMetaData returnValueMetaData; private final ElementKind kind; + private final boolean trackingEnabledForParameters; + private final boolean trackingEnabledForReturnValue; + private ExecutableMetaData( String name, Type returnType, @@ -113,6 +117,35 @@ private ExecutableMetaData( ); this.isGetter = isGetter; this.kind = kind; + this.trackingEnabledForParameters = validatableParametersMetaData.hasCascadables(); + this.trackingEnabledForReturnValue = returnValueMetaData.hasCascadables(); + } + + public ExecutableMetaData(ExecutableMetaData originalExecutableMetaData, ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { + super( originalExecutableMetaData ); + + this.parameterTypes = originalExecutableMetaData.parameterTypes; + this.parameterMetaDataList = originalExecutableMetaData.parameterMetaDataList; + this.validatableParametersMetaData = originalExecutableMetaData.validatableParametersMetaData; + this.crossParameterConstraints = originalExecutableMetaData.crossParameterConstraints; + this.signatures = originalExecutableMetaData.signatures; + this.returnValueMetaData = originalExecutableMetaData.returnValueMetaData; + this.isGetter = originalExecutableMetaData.isGetter; + this.kind = originalExecutableMetaData.kind; + + boolean trackingEnabledForParameters = false; + for ( Signature signature : originalExecutableMetaData.getSignatures() ) { + trackingEnabledForParameters = trackingEnabledForParameters || processedBeansTrackingStrategy.isEnabledForParameters( signature, + originalExecutableMetaData.getValidatableParametersMetaData().hasCascadables() ); + } + this.trackingEnabledForParameters = trackingEnabledForParameters; + + boolean trackingEnabledForReturnValue = false; + for ( Signature signature : originalExecutableMetaData.getSignatures() ) { + trackingEnabledForReturnValue = trackingEnabledForReturnValue || processedBeansTrackingStrategy.isEnabledForReturnValue( signature, + originalExecutableMetaData.getReturnValueMetaData().hasCascadables() ); + } + this.trackingEnabledForReturnValue = trackingEnabledForReturnValue; } /** @@ -199,6 +232,15 @@ public ElementKind getKind() { return kind; } + + public boolean isTrackingEnabledForParameters() { + return trackingEnabledForParameters; + } + + public boolean isTrackingEnabledForReturnValue() { + return trackingEnabledForReturnValue; + } + @Override public String toString() { StringBuilder parameterBuilder = new StringBuilder(); diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java deleted file mode 100644 index fea1418812..0000000000 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonTrackedBeanMetaDataImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.metadata.aggregated; - -import java.lang.reflect.Executable; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import javax.validation.metadata.BeanDescriptor; - -import org.hibernate.validator.internal.engine.groups.Sequence; -import org.hibernate.validator.internal.metadata.core.MetaConstraint; -import org.hibernate.validator.internal.metadata.facets.Cascadable; - -public class NonTrackedBeanMetaDataImpl implements BeanMetaData { - private final BeanMetaData beanMetaData; - - public NonTrackedBeanMetaDataImpl(BeanMetaData beanMetaData) { - this.beanMetaData = beanMetaData; - } - - @Override - public Class getBeanClass() { - return beanMetaData.getBeanClass(); - } - - @Override - public boolean hasConstraints() { - return beanMetaData.hasConstraints(); - } - - @Override - public BeanDescriptor getBeanDescriptor() { - return beanMetaData.getBeanDescriptor(); - } - - @Override - public PropertyMetaData getMetaDataFor(String propertyName) { - return beanMetaData.getMetaDataFor( propertyName ); - } - - @Override - public List> getDefaultGroupSequence(T beanState) { - return beanMetaData.getDefaultGroupSequence( beanState ); - } - - @Override - public Iterator getDefaultValidationSequence(T beanState) { - return beanMetaData.getDefaultValidationSequence( beanState ); - } - - @Override - public boolean isDefaultGroupSequenceRedefined() { - return beanMetaData.isDefaultGroupSequenceRedefined(); - } - - @Override - public Set> getMetaConstraints() { - return beanMetaData.getMetaConstraints(); - } - - @Override - public Set> getDirectMetaConstraints() { - return beanMetaData.getDirectMetaConstraints(); - } - - @Override - public Optional getMetaDataFor(Executable executable) throws IllegalArgumentException { - return beanMetaData.getMetaDataFor( executable ); - } - - @Override - public List> getClassHierarchy() { - return beanMetaData.getClassHierarchy(); - } - - @Override - public Iterable getCascadables() { - return beanMetaData.getCascadables(); - } - - @Override - public boolean hasCascadables() { - return beanMetaData.hasCascadables(); - } - - @Override - public boolean isTrackingRequired() { - return false; - } -} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java index 8048c900b1..03c24c7e06 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java @@ -6,6 +6,9 @@ */ package org.hibernate.validator.test.internal.engine.tracking; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -20,14 +23,9 @@ import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; -import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; - import org.testng.annotations.Test; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - /** * An example of beans with cascading constraints, some cycle and others do not. * @@ -61,10 +59,8 @@ public class ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest { @Test public void testTrackingEnabled() { + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = getProcessedBeansTrackingStrategy(); - final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); - final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = - validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); assertTrue( processedBeansTrackingStrategy.isEnabledForBean( A.class, true @@ -148,8 +144,8 @@ private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); } - private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { - return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + private ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); } private static class A { diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java index 09e0d2b42b..804e1913c2 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java @@ -6,6 +6,9 @@ */ package org.hibernate.validator.test.internal.engine.tracking; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -20,14 +23,9 @@ import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; -import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; - import org.testng.annotations.Test; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - /** * An example of beans with cascading constraints, some cycle and others do not. * @@ -61,10 +59,8 @@ public class ProcessedBeansTrackingCyclesNoCyclesListTest { @Test public void testTrackingEnabled() { + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = getProcessedBeansTrackingStrategy(); - final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); - final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = - validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); assertTrue( processedBeansTrackingStrategy.isEnabledForBean( A.class, true @@ -139,8 +135,8 @@ private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); } - private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { - return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + private ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); } private static class A { diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java index 301de9d604..32385d372c 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java @@ -6,6 +6,9 @@ */ package org.hibernate.validator.test.internal.engine.tracking; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -20,14 +23,9 @@ import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; -import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; - import org.testng.annotations.Test; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - /** * An example of beans with cascading constraints, some cycle and others do not. * @@ -58,10 +56,8 @@ public class ProcessedBeansTrackingCyclesNoCyclesMapTest { @Test public void testTrackingEnabled() { + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = getProcessedBeansTrackingStrategy(); - final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); - final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = - validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); assertTrue( processedBeansTrackingStrategy.isEnabledForBean( A.class, true @@ -133,8 +129,8 @@ private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); } - private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { - return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + private ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); } private static class A { diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java index 0ce49c5fed..091f17a808 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java @@ -6,6 +6,9 @@ */ package org.hibernate.validator.test.internal.engine.tracking; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -18,14 +21,9 @@ import org.hibernate.validator.PredefinedScopeHibernateValidator; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; import org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl; -import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext; import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; - import org.testng.annotations.Test; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - /** * An example of beans with cascading constraints, some cycle and others do not. * @@ -58,10 +56,8 @@ public class ProcessedBeansTrackingCyclesNoCyclesTest { @Test public void testTrackingEnabled() { + final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = getProcessedBeansTrackingStrategy(); - final ValidatorFactoryScopedContext validatorFactoryScopedContext = getValidatorFactoryScopedContext(); - final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = - validatorFactoryScopedContext.getProcessedBeansTrackingStrategy(); assertTrue( processedBeansTrackingStrategy.isEnabledForBean( A.class, true @@ -136,8 +132,8 @@ private PredefinedScopeHibernateValidatorFactory getValidatorFactory() { .buildValidatorFactory().unwrap( PredefinedScopeHibernateValidatorFactory.class ); } - private ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { - return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getValidatorFactoryScopedContext(); + private ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); } private static class A { From dccd3cf608d9b08478175522de316d47b4ef489d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 26 Aug 2021 14:57:15 +0200 Subject: [PATCH 14/15] HV-1831 Create ProcessedBeansTrackingVoter contract This contract allows to override the default bean process tracking behavior without exposing our internal structures. It needs a bit more love on the config side so that we can define it via XML too and some documentation. --- .../BaseHibernateValidatorConfiguration.java | 6 +- .../engine/AbstractConfigurationImpl.java | 16 +-- .../PredefinedScopeValidatorFactoryImpl.java | 4 + .../internal/engine/ValidatorFactoryImpl.java | 11 ++- .../DefaultProcessedBeansTrackingVoter.java | 27 ++++++ .../metadata/BeanMetaDataManagerImpl.java | 11 ++- .../PredefinedScopeBeanMetaDataManager.java | 30 +++--- .../aggregated/BeanMetaDataBuilder.java | 29 ++++-- .../metadata/aggregated/BeanMetaDataImpl.java | 36 ++++++- .../aggregated/ExecutableMetaData.java | 97 ++++++++++++++++--- .../tracking/ProcessedBeansTrackingVoter.java | 24 +++++ .../internal/engine/path/PathImplTest.java | 4 +- .../metadata/BeanMetaDataManagerTest.java | 4 +- .../aggregated/ExecutableMetaDataTest.java | 4 +- .../aggregated/ParameterMetaDataTest.java | 7 +- .../aggregated/PropertyMetaDataTest.java | 4 +- 16 files changed, 250 insertions(+), 64 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/DefaultProcessedBeansTrackingVoter.java create mode 100644 engine/src/main/java/org/hibernate/validator/spi/tracking/ProcessedBeansTrackingVoter.java diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 04b2530793..9beaa1c72a 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -25,15 +25,15 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.ScriptAssert; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; -import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; +import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider; import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy; import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator; import org.hibernate.validator.spi.scripting.ScriptEvaluator; import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; /** * Base interface for Hibernate Validator specific configurations. @@ -504,5 +504,5 @@ default S locales(Locale... locales) { S showValidatedValuesInTraceLogs(boolean enabled); @Incubating - S processedBeansTrackingStrategy(ProcessedBeansTrackingStrategy processedBeanTrackingStrategy); + S processedBeansTrackingVoter(ProcessedBeansTrackingVoter processedBeanTrackingVoter); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java index bd4faf23d4..e9aaeeb200 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java @@ -43,7 +43,6 @@ import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl; import org.hibernate.validator.internal.engine.resolver.TraversableResolvers; -import org.hibernate.validator.internal.engine.tracking.ProcessedBeansTrackingStrategy; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; @@ -68,6 +67,7 @@ import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy; import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator; import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; /** * Hibernate specific {@code Configuration} implementation. @@ -134,7 +134,7 @@ public abstract class AbstractConfigurationImpl getProgrammaticMappings() { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java index 8f5dd12717..6c6498e063 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java @@ -49,6 +49,7 @@ import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; import org.hibernate.validator.internal.engine.constraintvalidation.PredefinedScopeConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; @@ -210,6 +211,9 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState buildMetaDataProviders( constraintCreationContext, xmlMetaDataProvider, constraintMappings ), methodValidationConfiguration, determineBeanMetaDataClassNormalizer( hibernateSpecificConfig ), + ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingVoter() != null ) + ? hibernateSpecificConfig.getProcessedBeansTrackingVoter() + : new DefaultProcessedBeansTrackingVoter(), hibernateSpecificConfig ); diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java index 82cee93edc..1fcae99dc6 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java @@ -49,6 +49,7 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; @@ -69,6 +70,7 @@ import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider; import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy; import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; /** * Factory returning initialized {@code Validator} instances. This is the Hibernate Validator default @@ -134,6 +136,8 @@ public class ValidatorFactoryImpl implements HibernateValidatorFactory { private final ValidationOrderGenerator validationOrderGenerator; + private final ProcessedBeansTrackingVoter processedBeansTrackingVoter; + public ValidatorFactoryImpl(ConfigurationState configurationState) { ClassLoader externalClassLoader = determineExternalClassLoader( configurationState ); @@ -225,6 +229,10 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { this.xmlMetaDataProvider = null; } + this.processedBeansTrackingVoter = ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingVoter() != null ) + ? hibernateSpecificConfig.getProcessedBeansTrackingVoter() + : new DefaultProcessedBeansTrackingVoter(); + if ( LOG.isDebugEnabled() ) { logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext ); } @@ -348,7 +356,8 @@ Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, beanMetadataClassNormalizer, validationOrderGenerator, buildMetaDataProviders(), - methodValidationConfiguration + methodValidationConfiguration, + processedBeansTrackingVoter ) ); diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/DefaultProcessedBeansTrackingVoter.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/DefaultProcessedBeansTrackingVoter.java new file mode 100644 index 0000000000..91ae5140fe --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/DefaultProcessedBeansTrackingVoter.java @@ -0,0 +1,27 @@ +/* + * 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.engine.tracking; + +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; + +public class DefaultProcessedBeansTrackingVoter implements ProcessedBeansTrackingVoter { + + @Override + public Vote isEnabledForBean(Class beanClass, boolean hasCascadables) { + return Vote.DEFAULT; + } + + @Override + public Vote isEnabledForReturnValue(Class beanClass, String name, Class[] parameterTypes, boolean hasCascadables) { + return Vote.DEFAULT; + } + + @Override + public Vote isEnabledForParameters(Class beanClass, String name, Class[] parameterTypes, boolean hasCascadables) { + return Vote.DEFAULT; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java index b9b3e6f8ae..3ee817b920 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java @@ -35,6 +35,7 @@ import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper; import org.hibernate.validator.internal.util.stereotypes.Immutable; import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; /** * This manager is in charge of providing all constraint related meta data @@ -97,6 +98,8 @@ public class BeanMetaDataManagerImpl implements BeanMetaDataManager { private final ValidationOrderGenerator validationOrderGenerator; + private final ProcessedBeansTrackingVoter processedBeansTrackingVoter; + /** * the three properties in this field affect the invocation of rules associated to section 4.5.5 * of the specification. By default they are all false, if true they allow @@ -111,14 +114,15 @@ public BeanMetaDataManagerImpl(ConstraintCreationContext constraintCreationConte BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, ValidationOrderGenerator validationOrderGenerator, List optionalMetaDataProviders, - MethodValidationConfiguration methodValidationConfiguration) { + MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { this.constraintCreationContext = constraintCreationContext; this.executableHelper = executableHelper; this.parameterNameProvider = parameterNameProvider; this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer; this.validationOrderGenerator = validationOrderGenerator; - this.methodValidationConfiguration = methodValidationConfiguration; + this.processedBeansTrackingVoter = processedBeansTrackingVoter; this.beanMetaDataCache = new ConcurrentReferenceHashMap<>( DEFAULT_INITIAL_CAPACITY, @@ -195,7 +199,8 @@ public int numberOfCachedBeanMetaDataInstances() { private BeanMetaDataImpl createBeanMetaData(Class clazz) { BeanMetaDataBuilder builder = BeanMetaDataBuilder.getInstance( constraintCreationContext, executableHelper, parameterNameProvider, - validationOrderGenerator, clazz, methodValidationConfiguration ); + validationOrderGenerator, clazz, methodValidationConfiguration, + processedBeansTrackingVoter ); for ( MetaDataProvider provider : metaDataProviders ) { for ( BeanConfiguration beanConfiguration : getBeanConfigurationForHierarchy( provider, clazz ) ) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java index 2ac321c3df..4197c382c4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/PredefinedScopeBeanMetaDataManager.java @@ -56,6 +56,7 @@ import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper; import org.hibernate.validator.internal.util.classhierarchy.Filters; import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; public class PredefinedScopeBeanMetaDataManager implements BeanMetaDataManager { @@ -76,6 +77,7 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr List optionalMetaDataProviders, MethodValidationConfiguration methodValidationConfiguration, BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, + ProcessedBeansTrackingVoter processedBeansTrackingVoter, PredefinedScopeConfigurationImpl hibernateSpecificConfig) { AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders( optionalMetaDataProviders ); AnnotationMetaDataProvider defaultProvider = new AnnotationMetaDataProvider( @@ -108,24 +110,19 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr rawBeanMetaDataMap.put( hierarchyElement, createBeanMetaData( constraintCreationContext, executableHelper, parameterNameProvider, javaBeanHelper, validationOrderGenerator, optionalMetaDataProviders, methodValidationConfiguration, - metaDataProviders, hierarchyElement ) ); + processedBeansTrackingVoter, metaDataProviders, hierarchyElement ) ); } } this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer; - - if ( hibernateSpecificConfig.getProcessedBeansTrackingStrategy() != null ) { - this.processedBeansTrackingStrategy = hibernateSpecificConfig.getProcessedBeansTrackingStrategy(); - } - else { - this.processedBeansTrackingStrategy = new PredefinedScopeProcessedBeansTrackingStrategy( - rawBeanMetaDataMap - ); - } + this.processedBeansTrackingStrategy = new PredefinedScopeProcessedBeansTrackingStrategy( + rawBeanMetaDataMap + ); // Inject the processed beans tracking information into the BeanMetaData objects - for ( Map.Entry, BeanMetaData> rawBeanMetaDataEntry : rawBeanMetaDataMap.entrySet() ) { - beanMetaDataMap.put( rawBeanMetaDataEntry.getKey(), injectTrackingInformation( rawBeanMetaDataEntry.getValue(), processedBeansTrackingStrategy ) ); + for ( Map.Entry, BeanMetaData> rawBeanMetaDataEntry : rawBeanMetaDataMap.entrySet() ) { + beanMetaDataMap.put( rawBeanMetaDataEntry.getKey(), + injectTrackingInformation( rawBeanMetaDataEntry.getValue(), processedBeansTrackingStrategy, processedBeansTrackingVoter ) ); } } @@ -171,11 +168,13 @@ private static BeanMetaDataImpl createBeanMetaData(ConstraintCreationCont ValidationOrderGenerator validationOrderGenerator, List optionalMetaDataProviders, MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter, List metaDataProviders, Class clazz) { BeanMetaDataBuilder builder = BeanMetaDataBuilder.getInstance( constraintCreationContext, executableHelper, parameterNameProvider, - validationOrderGenerator, clazz, methodValidationConfiguration ); + validationOrderGenerator, clazz, methodValidationConfiguration, + processedBeansTrackingVoter ); for ( MetaDataProvider provider : metaDataProviders ) { for ( BeanConfiguration beanConfiguration : getBeanConfigurationForHierarchy( provider, clazz ) ) { @@ -221,8 +220,9 @@ private static List> getBeanConfigurationForHie } private static BeanMetaData injectTrackingInformation(BeanMetaData rawBeanMetaData, - ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { - return new BeanMetaDataImpl( (BeanMetaDataImpl) rawBeanMetaData, processedBeansTrackingStrategy ); + ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { + return new BeanMetaDataImpl( (BeanMetaDataImpl) rawBeanMetaData, processedBeansTrackingStrategy, processedBeansTrackingVoter ); } private static class UninitializedBeanMetaData implements BeanMetaData { diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java index 4f266dfa27..dc799e29e2 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java @@ -25,6 +25,7 @@ import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.StringHelper; import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; /** * @author Hardy Ferentschik @@ -43,6 +44,7 @@ public class BeanMetaDataBuilder { private final ExecutableHelper executableHelper; private final ExecutableParameterNameProvider parameterNameProvider; private final MethodValidationConfiguration methodValidationConfiguration; + private ProcessedBeansTrackingVoter processedBeansTrackingVoter; private ConfigurationSource sequenceSource; private ConfigurationSource providerSource; @@ -56,13 +58,15 @@ private BeanMetaDataBuilder( ExecutableParameterNameProvider parameterNameProvider, ValidationOrderGenerator validationOrderGenerator, Class beanClass, - MethodValidationConfiguration methodValidationConfiguration) { + MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { this.beanClass = beanClass; this.constraintCreationContext = constraintCreationContext; this.validationOrderGenerator = validationOrderGenerator; this.executableHelper = executableHelper; this.parameterNameProvider = parameterNameProvider; this.methodValidationConfiguration = methodValidationConfiguration; + this.processedBeansTrackingVoter = processedBeansTrackingVoter; } public static BeanMetaDataBuilder getInstance( @@ -71,14 +75,16 @@ public static BeanMetaDataBuilder getInstance( ExecutableParameterNameProvider parameterNameProvider, ValidationOrderGenerator validationOrderGenerator, Class beanClass, - MethodValidationConfiguration methodValidationConfiguration) { + MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { return new BeanMetaDataBuilder<>( constraintCreationContext, executableHelper, parameterNameProvider, validationOrderGenerator, beanClass, - methodValidationConfiguration ); + methodValidationConfiguration, + processedBeansTrackingVoter ); } public void add(BeanConfiguration configuration) { @@ -121,7 +127,8 @@ private void addMetaDataToBuilder(ConstrainedElement constrainableElement, Set build() { defaultGroupSequence, defaultGroupSequenceProvider, aggregatedElements, - validationOrderGenerator + validationOrderGenerator, + processedBeansTrackingVoter ); } @@ -151,6 +159,7 @@ private static class BuilderDelegate { private MetaDataBuilder metaDataBuilder; private ExecutableMetaData.Builder methodBuilder; private final MethodValidationConfiguration methodValidationConfiguration; + private final ProcessedBeansTrackingVoter processedBeansTrackingVoter; private final int hashCode; public BuilderDelegate( @@ -159,7 +168,8 @@ public BuilderDelegate( ConstraintCreationContext constraintCreationContext, ExecutableHelper executableHelper, ExecutableParameterNameProvider parameterNameProvider, - MethodValidationConfiguration methodValidationConfiguration + MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter ) { this.beanClass = beanClass; this.constrainedElement = constrainedElement; @@ -167,6 +177,7 @@ public BuilderDelegate( this.executableHelper = executableHelper; this.parameterNameProvider = parameterNameProvider; this.methodValidationConfiguration = methodValidationConfiguration; + this.processedBeansTrackingVoter = processedBeansTrackingVoter; switch ( constrainedElement.getKind() ) { case FIELD: @@ -192,7 +203,8 @@ public BuilderDelegate( constraintCreationContext, executableHelper, parameterNameProvider, - methodValidationConfiguration + methodValidationConfiguration, + processedBeansTrackingVoter ); } @@ -239,7 +251,8 @@ public boolean add(ConstrainedElement constrainedElement) { constraintCreationContext, executableHelper, parameterNameProvider, - methodValidationConfiguration + methodValidationConfiguration, + processedBeansTrackingVoter ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java index e82f6587a1..401a03b322 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java @@ -46,6 +46,8 @@ import org.hibernate.validator.internal.util.logging.LoggerFactory; import org.hibernate.validator.internal.util.stereotypes.Immutable; import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter.Vote; /** * This class encapsulates all meta data needed for validation. Implementations of {@code Validator} interface can @@ -179,7 +181,8 @@ public BeanMetaDataImpl(Class beanClass, List> defaultGroupSequence, DefaultGroupSequenceProvider defaultGroupSequenceProvider, Set constraintMetaDataSet, - ValidationOrderGenerator validationOrderGenerator) { + ValidationOrderGenerator validationOrderGenerator, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { this.validationOrderGenerator = validationOrderGenerator; this.beanClass = beanClass; @@ -244,10 +247,23 @@ else if ( constraintMetaData.getKind() == ElementKind.BEAN ) { // We initialize those elements eagerly so that any eventual error is thrown when bootstrapping the bean metadata this.defaultGroupSequenceRedefined = this.defaultGroupSequence.size() > 1 || hasDefaultGroupSequenceProvider(); this.resolvedDefaultGroupSequence = getDefaultGroupSequence( null ); - this.trackingEnabled = hasCascadables(); + + Vote processedBeansTrackingVote = processedBeansTrackingVoter.isEnabledForBean( beanClass, hasCascadables() ); + switch ( processedBeansTrackingVote ) { + case NON_TRACKING: + this.trackingEnabled = false; + break; + case TRACKING: + this.trackingEnabled = true; + break; + default: + this.trackingEnabled = hasCascadables(); + break; + } } - public BeanMetaDataImpl(BeanMetaDataImpl originalBeanMetaData, ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { + public BeanMetaDataImpl(BeanMetaDataImpl originalBeanMetaData, ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { this.validationOrderGenerator = originalBeanMetaData.validationOrderGenerator; this.beanClass = originalBeanMetaData.beanClass; this.propertyMetaDataMap = originalBeanMetaData.propertyMetaDataMap; @@ -263,7 +279,19 @@ public BeanMetaDataImpl(BeanMetaDataImpl originalBeanMetaData, ProcessedBeans this.unconstrainedExecutables = originalBeanMetaData.unconstrainedExecutables; this.defaultGroupSequenceRedefined = originalBeanMetaData.defaultGroupSequenceRedefined; this.resolvedDefaultGroupSequence = originalBeanMetaData.resolvedDefaultGroupSequence; - this.trackingEnabled = processedBeansTrackingStrategy.isEnabledForBean( this.beanClass, hasCascadables() ); + + Vote processedBeansTrackingVote = processedBeansTrackingVoter.isEnabledForBean( beanClass, hasCascadables() ); + switch ( processedBeansTrackingVote ) { + case NON_TRACKING: + this.trackingEnabled = false; + break; + case TRACKING: + this.trackingEnabled = true; + break; + default: + this.trackingEnabled = processedBeansTrackingStrategy.isEnabledForBean( this.beanClass, hasCascadables() ); + break; + } } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java index 1a71d19224..3915d8ec67 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java @@ -37,6 +37,8 @@ import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.stereotypes.Immutable; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter; +import org.hibernate.validator.spi.tracking.ProcessedBeansTrackingVoter.Vote; /** * An aggregated view of the constraint related meta data for a given method or @@ -57,6 +59,8 @@ */ public class ExecutableMetaData extends AbstractConstraintMetaData { + private final Class beanClass; + private final Class[] parameterTypes; @Immutable @@ -83,6 +87,7 @@ public class ExecutableMetaData extends AbstractConstraintMetaData { private final boolean trackingEnabledForReturnValue; private ExecutableMetaData( + Class beanClass, String name, Type returnType, Class[] parameterTypes, @@ -94,7 +99,8 @@ private ExecutableMetaData( Set> crossParameterConstraints, CascadingMetaData cascadingMetaData, boolean isConstrained, - boolean isGetter) { + boolean isGetter, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { super( name, returnType, @@ -104,6 +110,7 @@ private ExecutableMetaData( isConstrained ); + this.beanClass = beanClass; this.parameterTypes = parameterTypes; this.parameterMetaDataList = CollectionHelper.toImmutableList( parameterMetaDataList ); this.validatableParametersMetaData = new ValidatableParametersMetaData( parameterMetaDataList ); @@ -117,13 +124,42 @@ private ExecutableMetaData( ); this.isGetter = isGetter; this.kind = kind; - this.trackingEnabledForParameters = validatableParametersMetaData.hasCascadables(); - this.trackingEnabledForReturnValue = returnValueMetaData.hasCascadables(); + + Vote processedBeansTrackingVoteForParameters = processedBeansTrackingVoter.isEnabledForParameters( beanClass, name, parameterTypes, + validatableParametersMetaData.hasCascadables() ); + switch ( processedBeansTrackingVoteForParameters ) { + case NON_TRACKING: + this.trackingEnabledForParameters = false; + break; + case TRACKING: + this.trackingEnabledForParameters = true; + break; + default: + this.trackingEnabledForParameters = validatableParametersMetaData.hasCascadables(); + break; + } + + Vote processedBeansTrackingVoteForReturnValue = processedBeansTrackingVoter.isEnabledForReturnValue( beanClass, name, parameterTypes, + returnValueMetaData.hasCascadables() ); + switch ( processedBeansTrackingVoteForReturnValue ) { + case NON_TRACKING: + this.trackingEnabledForReturnValue = false; + break; + case TRACKING: + this.trackingEnabledForReturnValue = true; + break; + default: + this.trackingEnabledForReturnValue = returnValueMetaData.hasCascadables(); + break; + } } - public ExecutableMetaData(ExecutableMetaData originalExecutableMetaData, ProcessedBeansTrackingStrategy processedBeansTrackingStrategy) { + public ExecutableMetaData(ExecutableMetaData originalExecutableMetaData, + ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { super( originalExecutableMetaData ); + this.beanClass = originalExecutableMetaData.beanClass; this.parameterTypes = originalExecutableMetaData.parameterTypes; this.parameterMetaDataList = originalExecutableMetaData.parameterMetaDataList; this.validatableParametersMetaData = originalExecutableMetaData.validatableParametersMetaData; @@ -133,19 +169,43 @@ public ExecutableMetaData(ExecutableMetaData originalExecutableMetaData, Process this.isGetter = originalExecutableMetaData.isGetter; this.kind = originalExecutableMetaData.kind; - boolean trackingEnabledForParameters = false; - for ( Signature signature : originalExecutableMetaData.getSignatures() ) { - trackingEnabledForParameters = trackingEnabledForParameters || processedBeansTrackingStrategy.isEnabledForParameters( signature, - originalExecutableMetaData.getValidatableParametersMetaData().hasCascadables() ); + Vote processedBeansTrackingVoteForParameters = processedBeansTrackingVoter.isEnabledForParameters( originalExecutableMetaData.beanClass, + originalExecutableMetaData.getName(), parameterTypes, originalExecutableMetaData.getValidatableParametersMetaData().hasCascadables() ); + switch ( processedBeansTrackingVoteForParameters ) { + case NON_TRACKING: + this.trackingEnabledForParameters = false; + break; + case TRACKING: + this.trackingEnabledForParameters = true; + break; + default: + boolean trackingEnabledForParameters = false; + for ( Signature signature : originalExecutableMetaData.getSignatures() ) { + trackingEnabledForParameters = trackingEnabledForParameters || processedBeansTrackingStrategy.isEnabledForParameters( signature, + originalExecutableMetaData.getValidatableParametersMetaData().hasCascadables() ); + } + this.trackingEnabledForParameters = trackingEnabledForParameters; + break; } - this.trackingEnabledForParameters = trackingEnabledForParameters; - boolean trackingEnabledForReturnValue = false; - for ( Signature signature : originalExecutableMetaData.getSignatures() ) { - trackingEnabledForReturnValue = trackingEnabledForReturnValue || processedBeansTrackingStrategy.isEnabledForReturnValue( signature, - originalExecutableMetaData.getReturnValueMetaData().hasCascadables() ); + Vote processedBeansTrackingVoteForReturnValue = processedBeansTrackingVoter.isEnabledForReturnValue( originalExecutableMetaData.beanClass, + originalExecutableMetaData.getName(), parameterTypes, originalExecutableMetaData.getReturnValueMetaData().hasCascadables() ); + switch ( processedBeansTrackingVoteForReturnValue ) { + case NON_TRACKING: + this.trackingEnabledForReturnValue = false; + break; + case TRACKING: + this.trackingEnabledForReturnValue = true; + break; + default: + boolean trackingEnabledForReturnValue = false; + for ( Signature signature : originalExecutableMetaData.getSignatures() ) { + trackingEnabledForReturnValue = trackingEnabledForReturnValue || processedBeansTrackingStrategy.isEnabledForReturnValue( signature, + originalExecutableMetaData.getReturnValueMetaData().hasCascadables() ); + } + this.trackingEnabledForReturnValue = trackingEnabledForReturnValue; + break; } - this.trackingEnabledForReturnValue = trackingEnabledForReturnValue; } /** @@ -304,6 +364,7 @@ public static class Builder extends MetaDataBuilder { private final Set rules; private boolean isConstrained = false; private CascadingMetaDataBuilder cascadingMetaDataBuilder; + private ProcessedBeansTrackingVoter processedBeansTrackingVoter; /** * Holds a merged representation of the configurations for one method @@ -323,11 +384,13 @@ public Builder( ConstraintCreationContext constraintCreationContext, ExecutableHelper executableHelper, ExecutableParameterNameProvider parameterNameProvider, - MethodValidationConfiguration methodValidationConfiguration) { + MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { super( beanClass, constraintCreationContext ); this.executableHelper = executableHelper; this.parameterNameProvider = parameterNameProvider; + this.processedBeansTrackingVoter = processedBeansTrackingVoter; this.kind = constrainedExecutable.getKind(); this.callable = constrainedExecutable.getCallable(); this.rules = methodValidationConfiguration.getConfiguredRuleSet(); @@ -422,6 +485,7 @@ public ExecutableMetaData build() { assertCorrectnessOfConfiguration(); return new ExecutableMetaData( + callable.getDeclaringClass(), callable.getName(), callable.getType(), callable.getParameterTypes(), @@ -434,7 +498,8 @@ public ExecutableMetaData build() { adaptOriginsAndImplicitGroups( crossParameterConstraints ), cascadingMetaDataBuilder.build( constraintCreationContext.getValueExtractorManager(), callable ), isConstrained, - kind == ConstrainedElementKind.GETTER + kind == ConstrainedElementKind.GETTER, + processedBeansTrackingVoter ); } diff --git a/engine/src/main/java/org/hibernate/validator/spi/tracking/ProcessedBeansTrackingVoter.java b/engine/src/main/java/org/hibernate/validator/spi/tracking/ProcessedBeansTrackingVoter.java new file mode 100644 index 0000000000..e3c113a9b1 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/spi/tracking/ProcessedBeansTrackingVoter.java @@ -0,0 +1,24 @@ +/* + * 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.spi.tracking; + +import org.hibernate.validator.Incubating; + +@Incubating +public interface ProcessedBeansTrackingVoter { + + Vote isEnabledForBean(Class beanClass, boolean hasCascadables); + + Vote isEnabledForReturnValue(Class beanClass, String name, Class[] parameterTypes, boolean hasCascadables); + + Vote isEnabledForParameters(Class beanClass, String name, Class[] parameterTypes, boolean hasCascadables); + + enum Vote { + + DEFAULT, NON_TRACKING, TRACKING; + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java index b84caaeb07..1b02d2707a 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java @@ -33,6 +33,7 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; @@ -202,7 +203,8 @@ public void testCreationOfExecutablePath() throws Exception { new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + new DefaultProcessedBeansTrackingVoter() ); ExecutableMetaData executableMetaData = beanMetaDataManager.getBeanMetaData( Container.class ) diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java index d83b3ce7fe..e71adffce8 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java @@ -25,6 +25,7 @@ import org.hibernate.validator.internal.engine.DefaultPropertyNodeNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; @@ -61,7 +62,8 @@ public void setUpBeanMetaDataManager() { new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + new DefaultProcessedBeansTrackingVoter() ); } diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java index 851b3a0843..ecc832dcd9 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java @@ -27,6 +27,7 @@ import org.hibernate.validator.internal.engine.DefaultPropertyNodeNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; @@ -68,7 +69,8 @@ public void setupBeanMetaData() { new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + new DefaultProcessedBeansTrackingVoter() ); beanMetaData = beanMetaDataManager.getBeanMetaData( CustomerRepositoryExt.class ); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java index e2749cabbd..803ffe935f 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java @@ -28,6 +28,7 @@ import org.hibernate.validator.internal.engine.DefaultPropertyNodeNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; @@ -67,7 +68,8 @@ public void setupBeanMetaData() { new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + new DefaultProcessedBeansTrackingVoter() ); beanMetaData = beanMetaDataManager.getBeanMetaData( CustomerRepository.class ); @@ -137,7 +139,8 @@ public void parameterNameInInheritanceHierarchy() throws Exception { new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + new DefaultProcessedBeansTrackingVoter() ); BeanMetaData localBeanMetaData = beanMetaDataManager.getBeanMetaData( ServiceImpl.class ); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java index 61a730e4ab..a3fb065183 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java @@ -21,6 +21,7 @@ import org.hibernate.validator.internal.engine.DefaultPropertyNodeNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; @@ -51,7 +52,8 @@ public void setupBeanMetaDataManager() { new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + new DefaultProcessedBeansTrackingVoter() ); } From 39b70afa80e0538717eadbf29ccb0887496ebe2b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 26 Aug 2021 15:06:13 +0200 Subject: [PATCH 15/15] HV-1831 Enhance ExecutableMetaData with tracking information --- .../internal/metadata/aggregated/BeanMetaDataImpl.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java index 401a03b322..97d4576cee 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java @@ -275,7 +275,12 @@ public BeanMetaDataImpl(BeanMetaDataImpl originalBeanMetaData, ProcessedBeans this.defaultGroupSequence = originalBeanMetaData.defaultGroupSequence; this.validationOrder = originalBeanMetaData.validationOrder; this.directMetaConstraints = originalBeanMetaData.directMetaConstraints; - this.executableMetaDataMap = originalBeanMetaData.executableMetaDataMap; + Map tempExecutableMetaDataMap = newHashMap(); + for ( Entry executableMetaDataEntry : originalBeanMetaData.executableMetaDataMap.entrySet() ) { + tempExecutableMetaDataMap.put( executableMetaDataEntry.getKey(), + new ExecutableMetaData( executableMetaDataEntry.getValue(), processedBeansTrackingStrategy, processedBeansTrackingVoter ) ); + } + this.executableMetaDataMap = CollectionHelper.toImmutableMap( tempExecutableMetaDataMap ); this.unconstrainedExecutables = originalBeanMetaData.unconstrainedExecutables; this.defaultGroupSequenceRedefined = originalBeanMetaData.defaultGroupSequenceRedefined; this.resolvedDefaultGroupSequence = originalBeanMetaData.resolvedDefaultGroupSequence;