diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 33b53b527d..9beaa1c72a 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -26,13 +26,14 @@ import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.ScriptAssert; 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. @@ -501,4 +502,7 @@ default S locales(Locale... locales) { */ @Incubating S showValidatedValuesInTraceLogs(boolean enabled); + + @Incubating + 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 a07ed1ec8b..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 @@ -67,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. @@ -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..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 @@ -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.DefaultProcessedBeansTrackingVoter; 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(); @@ -194,18 +188,50 @@ 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, - validatorFactoryScopedContext.getParameterNameProvider(), + parameterNameProvider, javaBeanHelper, validationOrderGenerator, buildMetaDataProviders( constraintCreationContext, xmlMetaDataProvider, constraintMappings ), methodValidationConfiguration, determineBeanMetaDataClassNormalizer( hibernateSpecificConfig ), - hibernateSpecificConfig.getBeanClassesToInitialize() + ( hibernateSpecificConfig != null && hibernateSpecificConfig.getProcessedBeansTrackingVoter() != null ) + ? hibernateSpecificConfig.getProcessedBeansTrackingVoter() + : new DefaultProcessedBeansTrackingVoter(), + hibernateSpecificConfig ); + 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 ), + constraintValidatorInitializationContext ); + if ( LOG.isDebugEnabled() ) { logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext ); } @@ -273,6 +299,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 8d90a6976d..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 @@ -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,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 ); @@ -165,8 +169,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) - ); + determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) ); ConstraintValidatorManager constraintValidatorManager = new ConstraintValidatorManagerImpl( configurationState.getConstraintValidatorFactory(), @@ -226,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 ); } @@ -349,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/ValidatorFactoryScopedContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java index 5ab071023e..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 @@ -107,7 +107,7 @@ public class ValidatorFactoryScopedContext { temporalValidationTolerance ) ); } - private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, + ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, TraversableResolver traversableResolver, ExecutableParameterNameProvider parameterNameProvider, ClockProvider clockProvider, @@ -200,7 +200,6 @@ static class Builder { private Object constraintValidatorPayload; private ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel; private ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel; - private boolean showValidatedValuesInTraceLogs; private HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext; 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 ); 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/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java new file mode 100644 index 0000000000..f570ebc9ea --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -0,0 +1,237 @@ +/* + * 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.ParameterizedType; +import java.lang.reflect.Type; +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.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 trackingEnabledForParameters; + + 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 + // 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 TrackingEnabledStrategyBuilder( rawBeanMetaDataMap ).build() + ); + this.trackingEnabledForReturnValues = CollectionHelper.toImmutableMap( new HashMap<>() ); + this.trackingEnabledForParameters = CollectionHelper.toImmutableMap( new HashMap<>() ); + } + + private static class TrackingEnabledStrategyBuilder { + private final Map, BeanMetaData> rawBeanMetaDataMap; + private final Map, Boolean> classToBeanTrackingEnabled; + + 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 : rawBeanMetaDataMap.values() ) { + 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 = 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() ); + } + } + return directCascadedBeanClasses; + } + + 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.get( rootBeanClass ); + } + + @Override + public boolean isEnabledForReturnValue(Signature signature, boolean hasCascadables) { + if ( !hasCascadables ) { + return false; + } + + return trackingEnabledForReturnValues.getOrDefault( signature, true ); + } + + @Override + public boolean isEnabledForParameters(Signature signature, boolean hasCascadables) { + if ( !hasCascadables ) { + return false; + } + + return trackingEnabledForParameters.getOrDefault( signature, 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..7945ab7f46 --- /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 org.hibernate.validator.internal.properties.Signature; + +public interface ProcessedBeansTrackingStrategy { + + boolean isEnabledForBean(Class beanClass, boolean hasCascadables); + + boolean isEnabledForReturnValue(Signature signature, boolean hasCascadables); + + boolean isEnabledForParameters(Signature signature, 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..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 @@ -96,7 +96,7 @@ 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}). @@ -131,7 +131,7 @@ protected AbstractValidationContext( T rootBean, Class rootBeanClass, BeanMetaData rootBeanMetaData, - boolean disableAlreadyValidatedBeanTracking + boolean processedBeanTrackingEnabled ) { this.constraintValidatorManager = constraintValidatorManager; this.validatorScopedContext = validatorScopedContext; @@ -143,7 +143,7 @@ protected AbstractValidationContext( this.rootBeanClass = rootBeanClass; this.rootBeanMetaData = rootBeanMetaData; - this.disableAlreadyValidatedBeanTracking = disableAlreadyValidatedBeanTracking; + this.processedBeanTrackingEnabled = processedBeanTrackingEnabled; } @Override @@ -193,7 +193,7 @@ public ConstraintValidatorFactory getConstraintValidatorFactory() { @Override public boolean isBeanAlreadyValidated(Object value, Class group, PathImpl path) { - if ( disableAlreadyValidatedBeanTracking ) { + if ( !processedBeanTrackingEnabled ) { return false; } @@ -209,7 +209,7 @@ public boolean isBeanAlreadyValidated(Object value, Class group, PathImpl pat @Override public void markCurrentBeanAsProcessed(ValueContext valueContext) { - if ( disableAlreadyValidatedBeanTracking ) { + if ( !processedBeanTrackingEnabled ) { return; } 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..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,14 +37,10 @@ class BeanValidationContext extends AbstractValidationContext { BeanMetaData rootBeanMetaData ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, - rootBean, rootBeanClass, rootBeanMetaData, buildDisableAlreadyValidatedBeanTracking( rootBeanMetaData ) + rootBean, rootBeanClass, rootBeanMetaData, rootBeanMetaData.isTrackingEnabled() ); } - private static boolean buildDisableAlreadyValidatedBeanTracking(BeanMetaData rootBeanMetaData) { - return !rootBeanMetaData.hasCascadables(); - } - @Override protected ConstraintViolation createConstraintViolation( String messageTemplate, String interpolatedMessage, Path propertyPath, 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..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 @@ -68,7 +68,7 @@ public class ParameterExecutableValidationContext extends AbstractValidationC ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, rootBean, rootBeanClass, rootBeanMetaData, - buildDisableAlreadyValidatedBeanTracking( executableMetaData ) + isProcessedBeansTrackingEnabled( executableMetaData ) ); this.executable = executable; this.executableMetaData = executableMetaData; @@ -85,13 +85,13 @@ public Optional getExecutableMetaData() { return executableMetaData; } - private static boolean buildDisableAlreadyValidatedBeanTracking(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 !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 bcc80ae69b..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 @@ -61,7 +61,7 @@ public class ReturnValueExecutableValidationContext extends AbstractValidatio ) { super( constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, traversableResolver, constraintValidatorInitializationContext, rootBean, rootBeanClass, rootBeanMetaData, - buildDisableAlreadyValidatedBeanTracking( executableMetaData ) + isTrackingEnabled( executableMetaData ) ); this.executable = executable; this.executableMetaData = executableMetaData; @@ -78,13 +78,13 @@ public Optional getExecutableMetaData() { return executableMetaData; } - private static boolean buildDisableAlreadyValidatedBeanTracking(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 !executableMetaData.get().getReturnValueMetaData().hasCascadables(); + return executableMetaData.get().isTrackingEnabledForReturnValue(); } @Override 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 f09dd89dc8..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 @@ -11,9 +11,12 @@ 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.HashMap; 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; @@ -30,8 +33,11 @@ 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; @@ -50,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 { @@ -60,15 +67,18 @@ 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, + ProcessedBeansTrackingVoter processedBeansTrackingVoter, + PredefinedScopeConfigurationImpl hibernateSpecificConfig) { AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders( optionalMetaDataProviders ); AnnotationMetaDataProvider defaultProvider = new AnnotationMetaDataProvider( constraintCreationContext, @@ -84,7 +94,8 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr metaDataProviders.add( defaultProvider ); metaDataProviders.addAll( optionalMetaDataProviders ); - for ( Class validatedClass : beanClassesToInitialize ) { + Map, BeanMetaData> rawBeanMetaDataMap = new HashMap<>(); + for ( Class validatedClass : hibernateSpecificConfig.getBeanClassesToInitialize() ) { Class normalizedValidatedClass = beanMetaDataClassNormalizer.normalize( validatedClass ); @SuppressWarnings("unchecked") @@ -92,18 +103,27 @@ 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 ) ); + processedBeansTrackingVoter, metaDataProviders, hierarchyElement ) ); } } this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer; + 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, processedBeansTrackingVoter ) ); + } } @SuppressWarnings("unchecked") @@ -119,6 +139,14 @@ public BeanMetaData getBeanMetaData(Class beanClass) { return beanMetaData; } + public Collection> getBeanMetaData() { + return beanMetaDataMap.values(); + } + + public ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return processedBeansTrackingStrategy; + } + @Override public void clear() { beanMetaDataMap.clear(); @@ -140,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 ) ) { @@ -189,6 +219,12 @@ private static List> getBeanConfigurationForHie return configurations; } + private static BeanMetaData injectTrackingInformation(BeanMetaData rawBeanMetaData, + ProcessedBeansTrackingStrategy processedBeansTrackingStrategy, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { + return new BeanMetaDataImpl( (BeanMetaDataImpl) rawBeanMetaData, processedBeansTrackingStrategy, processedBeansTrackingVoter ); + } + private static class UninitializedBeanMetaData implements BeanMetaData { private final Class beanClass; @@ -268,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 6b31eb4d61..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 @@ -113,4 +113,9 @@ 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. + */ + boolean isTrackingEnabled(); } 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 34adb58879..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 @@ -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; @@ -45,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 @@ -161,6 +164,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} * @@ -173,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; @@ -238,6 +247,56 @@ 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 ); + + 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, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { + 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; + 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; + + 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 @@ -357,6 +416,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..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 @@ -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; @@ -36,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 @@ -56,6 +59,8 @@ */ public class ExecutableMetaData extends AbstractConstraintMetaData { + private final Class beanClass; + private final Class[] parameterTypes; @Immutable @@ -78,7 +83,11 @@ public class ExecutableMetaData extends AbstractConstraintMetaData { private final ReturnValueMetaData returnValueMetaData; private final ElementKind kind; + private final boolean trackingEnabledForParameters; + private final boolean trackingEnabledForReturnValue; + private ExecutableMetaData( + Class beanClass, String name, Type returnType, Class[] parameterTypes, @@ -90,7 +99,8 @@ private ExecutableMetaData( Set> crossParameterConstraints, CascadingMetaData cascadingMetaData, boolean isConstrained, - boolean isGetter) { + boolean isGetter, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { super( name, returnType, @@ -100,6 +110,7 @@ private ExecutableMetaData( isConstrained ); + this.beanClass = beanClass; this.parameterTypes = parameterTypes; this.parameterMetaDataList = CollectionHelper.toImmutableList( parameterMetaDataList ); this.validatableParametersMetaData = new ValidatableParametersMetaData( parameterMetaDataList ); @@ -113,6 +124,88 @@ private ExecutableMetaData( ); this.isGetter = isGetter; this.kind = kind; + + 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, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { + super( originalExecutableMetaData ); + + this.beanClass = originalExecutableMetaData.beanClass; + 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; + + 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; + } + + 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; + } } /** @@ -199,6 +292,15 @@ public ElementKind getKind() { return kind; } + + public boolean isTrackingEnabledForParameters() { + return trackingEnabledForParameters; + } + + public boolean isTrackingEnabledForReturnValue() { + return trackingEnabledForReturnValue; + } + @Override public String toString() { StringBuilder parameterBuilder = new StringBuilder(); @@ -262,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 @@ -281,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(); @@ -380,6 +485,7 @@ public ExecutableMetaData build() { assertCorrectnessOfConfiguration(); return new ExecutableMetaData( + callable.getDeclaringClass(), callable.getName(), callable.getType(), callable.getParameterTypes(), @@ -392,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/engine/tracking/ProcessedBeansTrackingCycles1Test.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java new file mode 100644 index 0000000000..742485fc75 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java @@ -0,0 +1,114 @@ +/* + * 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.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; + +/** + * This is not a real test, just an illustration. + *

+ * This is the most simple example. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles1Test { + + @Test + 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; + + 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; + + @Valid + private Child child; + } + + 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/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/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java new file mode 100644 index 0000000000..03c24c7e06 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java @@ -0,0 +1,205 @@ +/* + * 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 static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +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.tracking.ProcessedBeansTrackingStrategy; +import org.testng.annotations.Test; + +/** + * 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 ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = 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 ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); + } + + 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..804e1913c2 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java @@ -0,0 +1,196 @@ +/* + * 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 static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +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.tracking.ProcessedBeansTrackingStrategy; +import org.testng.annotations.Test; + +/** + * 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 ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = 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 ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); + } + + 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/ProcessedBeansTrackingCyclesNoCyclesMapTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java new file mode 100644 index 0000000000..32385d372c --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java @@ -0,0 +1,185 @@ +/* + * 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 static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +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.tracking.ProcessedBeansTrackingStrategy; +import org.testng.annotations.Test; + +/** + * 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 ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = 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 ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); + } + + 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; + } +} 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..091f17a808 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java @@ -0,0 +1,203 @@ +/* + * 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 static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +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.tracking.ProcessedBeansTrackingStrategy; +import org.testng.annotations.Test; + +/** + * 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 ProcessedBeansTrackingStrategy processedBeansTrackingStrategy = 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 ProcessedBeansTrackingStrategy getProcessedBeansTrackingStrategy() { + return ( (PredefinedScopeValidatorFactoryImpl) getValidatorFactory() ).getBeanMetaDataManager().getProcessedBeansTrackingStrategy(); + } + + 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; + } +} 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..c51d4182f0 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java @@ -0,0 +1,53 @@ +/* + * 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.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(); + + 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 { + + @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; + } +} 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() ); } 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 f2f4559e01..e5674f5ded 100644 --- a/performance/pom.xml +++ b/performance/pom.xml @@ -188,16 +188,90 @@ expressly - log4j - log4j + org.apache.logging.log4j + 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 + + + + + + + + + + hv-6.2 + + + validator + hv-6.2 + + + + 2.0.1.Final + Hibernate Validator + 6.2.1-SNAPSHOT + + + + 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 + + + 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 + + + + @@ -213,7 +287,7 @@ 2.0.1.Final Hibernate Validator - 6.1.2.Final + 6.1.7.Final @@ -232,16 +306,31 @@ 3.0.1-b11 - log4j - log4j + org.apache.logging.log4j + 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 +346,7 @@ 2.0.1.Final Hibernate Validator - 6.0.19.Final + 6.0.22.Final @@ -276,8 +365,8 @@ 3.0.1-b11 - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -286,6 +375,20 @@ org.codehaus.mojo build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.basedir}/src/main/java-bv2 + + + + @@ -320,8 +423,8 @@ 3.0.1-b11 - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -361,8 +464,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -402,8 +505,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -443,8 +546,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -484,8 +587,8 @@ ${javax-el.version} - log4j - log4j + org.apache.logging.log4j + log4j-core @@ -514,8 +617,8 @@ ${beanvalidation-impl.version} - log4j - log4j + org.apache.logging.log4j + log4j-core 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 728e66cc81..a887c92fe5 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java +++ b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java @@ -13,7 +13,6 @@ 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; 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..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 @@ -15,8 +15,8 @@ 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;