From 33dc3e44c634cefa7cf794858bb05f0489142029 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 24 Feb 2021 19:10:09 +0100 Subject: [PATCH] HV-1831 Enhance ExecutableMetaData with tracking information HV-1831 Create ProcessedBeansTrackingVoter contract This contract allows to override the default bean process tracking behavior without exposing our internal structures. It needs a bit more love on the config side so that we can define it via XML too and some documentation. HV-1831 New zero cost approach to processed bean tracking strategy I removed it from the traditional VF for now as I would like us to focus on the case where it is useful first. We will reintroduce it later once we have validated the approach where it is the most useful. I'm a bit unclear right now if we should use the same contract for traditional and predefined scope VF as we are dealing with different things and they won't be evaluated at the same moment. I'm thinking that maybe this needs to be a different contract. HV-1831 : Wrap a `BeanMetaData` in a `NonTrackedBeanMetaDataImpl` if tracking is not required HV-1831 Add some guidance about next step HV-1831 Specific benchmark infrastructure for predefined scope HV-1831 : Update Cascade tests to use PredefinedScopeHibernateValidator with -p=predefined=true HV-1831 : Experiment detecting cycles in bean classes Add test for Map HV-1831 : Experiment detecting cycles in bean classes Add support for containers; add tests for List w/ and w/o duplicated values HV-1831 : Experiment detecting cycles in bean classes HV-1831 Copy nodes when changing the nature of the leaf HV-1831 Add the same bean to List twice HV-1831 Clean up another experiment that shouldn't have been committed HV-1831 Add a couple of examples illustrating various cases HV-1831 Unfinished experiments --- .../BaseHibernateValidatorConfiguration.java | 4 + .../engine/AbstractConfigurationImpl.java | 18 ++ .../PredefinedScopeValidatorFactoryImpl.java | 59 ++++- .../internal/engine/ValidatorFactoryImpl.java | 15 +- .../engine/ValidatorFactoryScopedContext.java | 10 +- .../internal/engine/path/PathImpl.java | 10 +- .../DefaultProcessedBeansTrackingVoter.java | 25 ++ ...edScopeProcessedBeansTrackingStrategy.java | 235 ++++++++++++++++++ .../ProcessedBeansTrackingStrategy.java | 18 ++ .../AbstractValidationContext.java | 10 +- .../BeanValidationContext.java | 6 +- .../ParameterExecutableValidationContext.java | 6 +- ...eturnValueExecutableValidationContext.java | 6 +- .../metadata/BeanMetaDataManagerImpl.java | 11 +- .../PredefinedScopeBeanMetaDataManager.java | 78 +++++- .../AbstractConstraintMetaData.java | 10 + .../metadata/aggregated/BeanMetaData.java | 5 + .../aggregated/BeanMetaDataBuilder.java | 29 ++- .../metadata/aggregated/BeanMetaDataImpl.java | 70 +++++- .../aggregated/ExecutableMetaData.java | 115 ++++++++- .../tracking/ProcessedBeansTrackingVoter.java | 22 ++ .../internal/engine/path/PathImplTest.java | 4 +- .../ProcessedBeansTrackingCycles1Test.java | 115 +++++++++ .../ProcessedBeansTrackingCycles2Test.java | 49 ++++ .../ProcessedBeansTrackingCycles3Test.java | 50 ++++ .../ProcessedBeansTrackingCycles4Test.java | 50 ++++ .../ProcessedBeansTrackingCycles5Test.java | 57 +++++ ...clesNoCyclesListDuplicateElementsTest.java | 205 +++++++++++++++ ...edBeansTrackingCyclesNoCyclesListTest.java | 196 +++++++++++++++ ...sedBeansTrackingCyclesNoCyclesMapTest.java | 185 ++++++++++++++ ...cessedBeansTrackingCyclesNoCyclesTest.java | 203 +++++++++++++++ .../ProcessedBeansTrackingNoCycles1Test.java | 43 ++++ .../ProcessedBeansTrackingNoCycles2Test.java | 52 ++++ .../ProcessedBeansTrackingNoCycles3Test.java | 47 ++++ .../metadata/BeanMetaDataManagerTest.java | 4 +- .../aggregated/ExecutableMetaDataTest.java | 4 +- .../aggregated/ParameterMetaDataTest.java | 7 +- .../aggregated/PropertyMetaDataTest.java | 4 +- performance/README.md | 9 +- performance/pom.xml | 48 +++- .../PredefinedScopeCascadedValidation.java | 99 ++++++++ ...tsOfItemsAndMoreConstraintsValidation.java | 123 +++++++++ ...copeCascadedWithLotsOfItemsValidation.java | 115 +++++++++ .../PredefinedScopeCascadedValidation.java | 99 ++++++++ ...tsOfItemsAndMoreConstraintsValidation.java | 123 +++++++++ ...copeCascadedWithLotsOfItemsValidation.java | 115 +++++++++ 46 files changed, 2692 insertions(+), 76 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/DefaultProcessedBeansTrackingVoter.java create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java create mode 100644 engine/src/main/java/org/hibernate/validator/spi/tracking/ProcessedBeansTrackingVoter.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles2Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles3Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles4Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles5Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles1Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles3Test.java create mode 100644 performance/src/main/jakarta-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedValidation.java create mode 100644 performance/src/main/jakarta-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation.java create mode 100644 performance/src/main/jakarta-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsValidation.java create mode 100644 performance/src/main/javax-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedValidation.java create mode 100644 performance/src/main/javax-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsAndMoreConstraintsValidation.java create mode 100644 performance/src/main/javax-predefined-scope/org/hibernate/validator/performance/cascaded/PredefinedScopeCascadedWithLotsOfItemsValidation.java diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 93eb1900f6..dc0de55251 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -31,6 +31,7 @@ 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. @@ -486,4 +487,7 @@ default S locales(Locale... locales) { */ @Incubating S failFastOnPropertyViolation(boolean failFastOnPropertyViolation); + + @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 61a9436457..768ae1dafc 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 @@ -62,6 +62,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. @@ -129,6 +130,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 1fc874b195..eb5a55bd31 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 @@ -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,6 +120,14 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState determineAllowParallelMethodsDefineParameterConstraints( hibernateSpecificConfig, properties ) ).build(); + 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.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( configurationState.getMessageInterpolator(), configurationState.getTraversableResolver(), @@ -128,15 +138,16 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState determineFailFast( hibernateSpecificConfig, properties ), determineFailFastOnPropertyViolation( hibernateSpecificConfig, properties ), determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ), + determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ), determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) + constraintValidatorInitializationContext ); this.constraintValidatorManager = new PredefinedScopeConstraintValidatorManagerImpl( configurationState.getConstraintValidatorFactory(), - this.validatorFactoryScopedContext.getConstraintValidatorInitializationContext() + constraintValidatorInitializationContext ); this.validationOrderGenerator = new ValidationOrderGenerator(); @@ -147,11 +158,14 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState this.valueExtractorManager = new ValueExtractorManager( configurationState.getValueExtractors() ); ConstraintHelper constraintHelper = ConstraintHelper.forBuiltinConstraints( hibernateSpecificConfig.getBuiltinConstraints(), - hibernateSpecificConfig.isIncludeBeansAndConstraintsDefinedOnlyInXml() ); + hibernateSpecificConfig.isIncludeBeansAndConstraintsDefinedOnlyInXml() + ); TypeResolutionHelper typeResolutionHelper = new TypeResolutionHelper(); - ConstraintCreationContext constraintCreationContext = new ConstraintCreationContext( constraintHelper, - constraintValidatorManager, typeResolutionHelper, valueExtractorManager ); + ConstraintCreationContext constraintCreationContext = new ConstraintCreationContext( + constraintHelper, + constraintValidatorManager, typeResolutionHelper, valueExtractorManager + ); ExecutableHelper executableHelper = new ExecutableHelper( typeResolutionHelper ); JavaBeanHelper javaBeanHelper = new JavaBeanHelper( getterPropertySelectionStrategy, propertyNodeNameProvider ); @@ -164,15 +178,18 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState javaBeanHelper, externalClassLoader ), - constraintHelper ); + constraintHelper + ); // we parse all XML mappings but only register constraint validators and delay constraint mappings building till // we collect all the constraint validators. // HV-302; don't load XmlMappingParser if not necessary MappingXmlParser mappingParser = null; if ( !configurationState.getMappingStreams().isEmpty() ) { - mappingParser = new MappingXmlParser( constraintCreationContext, - javaBeanHelper, externalClassLoader ); + mappingParser = new MappingXmlParser( + constraintCreationContext, + javaBeanHelper, externalClassLoader + ); mappingParser.parse( configurationState.getMappingStreams() ); } @@ -202,15 +219,32 @@ 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.getProcessedBeansTrackingVoter() != null ) + ? hibernateSpecificConfig.getProcessedBeansTrackingVoter() + : new DefaultProcessedBeansTrackingVoter(), beanClassesToInitialize ); @@ -281,6 +315,10 @@ public boolean isTraversableResolverResultCacheEnabled() { return validatorFactoryScopedContext.isTraversableResolverResultCacheEnabled(); } + public PredefinedScopeBeanMetaDataManager getBeanMetaDataManager() { + return beanMetaDataManager; + } + @Override public T unwrap(Class type) { // allow unwrapping into public super types @@ -322,7 +360,8 @@ Validator createValidator(ValidatorFactoryScopedContext validatorFactoryScopedCo private static List buildMetaDataProviders( ConstraintCreationContext constraintCreationContext, XmlMetaDataProvider xmlMetaDataProvider, - Set constraintMappings) { + Set constraintMappings + ) { List metaDataProviders = newArrayList(); if ( xmlMetaDataProvider != null ) { metaDataProviders.add( xmlMetaDataProvider ); 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 b9b5a98419..aa0f7a0187 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 @@ -48,6 +48,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; @@ -68,6 +69,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 @@ -133,6 +135,8 @@ public class ValidatorFactoryImpl implements HibernateValidatorFactory { private final ValidationOrderGenerator validationOrderGenerator; + private final ProcessedBeansTrackingVoter processedBeansTrackingVoter; + public ValidatorFactoryImpl(ConfigurationState configurationState) { ClassLoader externalClassLoader = determineExternalClassLoader( configurationState ); @@ -162,10 +166,10 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineFailFast( hibernateSpecificConfig, properties ), determineFailFastOnPropertyViolation( hibernateSpecificConfig, properties ), determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ), + determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ), determineConstraintValidatorPayload( hibernateSpecificConfig ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), - determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ) + determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ) ); ConstraintValidatorManager constraintValidatorManager = new ConstraintValidatorManagerImpl( @@ -226,6 +230,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 +357,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 558b4a860b..ae56d924da 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 @@ -101,10 +101,10 @@ public class ValidatorFactoryScopedContext { boolean failFast, boolean failFastOnPropertyViolation, boolean traversableResolverResultCacheEnabled, + boolean showValidatedValuesInTraceLogs, Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, - ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel, - boolean showValidatedValuesInTraceLogs) { + ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel) { this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast, failFastOnPropertyViolation, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel, customViolationExpressionLanguageFeatureLevel, @@ -112,7 +112,7 @@ public class ValidatorFactoryScopedContext { temporalValidationTolerance ) ); } - private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, + ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, TraversableResolver traversableResolver, ExecutableParameterNameProvider parameterNameProvider, ClockProvider clockProvider, @@ -121,7 +121,8 @@ private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, boolean failFast, boolean failFastOnPropertyViolation, boolean traversableResolverResultCacheEnabled, - boolean showValidatedValuesInTraceLogs, Object constraintValidatorPayload, + boolean showValidatedValuesInTraceLogs, + Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel, HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext) { @@ -212,7 +213,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 e0ec36458d..8e275afb0f 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 @@ -204,7 +204,7 @@ private NodeImpl addMethodNode(String name, Class[] parameterTypes) { } public NodeImpl makeLeafNodeIterable() { - requiresWriteableNodeList(); + copyNodeList(); currentLeafNode = NodeImpl.makeIterable( currentLeafNode ); @@ -214,7 +214,7 @@ public NodeImpl makeLeafNodeIterable() { } public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { - requiresWriteableNodeList(); + copyNodeList(); currentLeafNode = NodeImpl.makeIterableAndSetIndex( currentLeafNode, index ); @@ -224,7 +224,7 @@ public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { } public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) { - requiresWriteableNodeList(); + copyNodeList(); currentLeafNode = NodeImpl.makeIterableAndSetMapKey( currentLeafNode, key ); @@ -309,6 +309,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..e6384cb1af --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/DefaultProcessedBeansTrackingVoter.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..d76ad0c3dd --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/PredefinedScopeProcessedBeansTrackingStrategy.java @@ -0,0 +1,235 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..06df92cae5 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/tracking/ProcessedBeansTrackingStrategy.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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 bef5b4560a..7c0f8a6d62 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 @@ -94,7 +94,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}). @@ -129,7 +129,7 @@ protected AbstractValidationContext( T rootBean, Class rootBeanClass, BeanMetaData rootBeanMetaData, - boolean disableAlreadyValidatedBeanTracking + boolean processedBeanTrackingEnabled ) { this.constraintValidatorManager = constraintValidatorManager; this.validatorScopedContext = validatorScopedContext; @@ -141,7 +141,7 @@ protected AbstractValidationContext( this.rootBeanClass = rootBeanClass; this.rootBeanMetaData = rootBeanMetaData; - this.disableAlreadyValidatedBeanTracking = disableAlreadyValidatedBeanTracking; + this.processedBeanTrackingEnabled = processedBeanTrackingEnabled; } @Override @@ -196,7 +196,7 @@ public ConstraintValidatorFactory getConstraintValidatorFactory() { @Override public boolean isBeanAlreadyValidated(Object value, Class group, PathImpl path) { - if ( disableAlreadyValidatedBeanTracking ) { + if ( !processedBeanTrackingEnabled ) { return false; } @@ -212,7 +212,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 806c985eec..f0ac4a8fac 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 @@ -35,14 +35,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 ae7cf85f1c..59af2f2aaf 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 @@ -66,7 +66,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; @@ -83,13 +83,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 e015fcab55..4b20101dd6 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 @@ -59,7 +59,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; @@ -76,13 +76,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 1033ff7837..967798728e 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 @@ -33,6 +33,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 @@ -95,6 +96,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 @@ -109,14 +112,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, @@ -193,7 +197,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 56b312440d..0bc6678e0c 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 @@ -9,9 +9,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,6 +33,8 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; 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; @@ -48,6 +53,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 { @@ -58,7 +64,10 @@ public class PredefinedScopeBeanMetaDataManager implements BeanMetaDataManager { */ private final ConcurrentMap, BeanMetaData> beanMetaDataMap = new ConcurrentHashMap<>(); - public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCreationContext, + private final ProcessedBeansTrackingStrategy processedBeansTrackingStrategy; + + public PredefinedScopeBeanMetaDataManager( + ConstraintCreationContext constraintCreationContext, ExecutableHelper executableHelper, ExecutableParameterNameProvider parameterNameProvider, JavaBeanHelper javaBeanHelper, @@ -66,7 +75,9 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr List optionalMetaDataProviders, MethodValidationConfiguration methodValidationConfiguration, BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, - Set> beanClassesToInitialize) { + ProcessedBeansTrackingVoter processedBeansTrackingVoter, + Set> beanClassesToInitialize + ) { AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders( optionalMetaDataProviders ); AnnotationMetaDataProvider defaultProvider = new AnnotationMetaDataProvider( constraintCreationContext, @@ -82,27 +93,45 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr metaDataProviders.add( defaultProvider ); metaDataProviders.addAll( optionalMetaDataProviders ); + Map, BeanMetaData> rawBeanMetaDataMap = new HashMap<>(); for ( Class validatedClass : beanClassesToInitialize ) { Class normalizedValidatedClass = beanMetaDataClassNormalizer.normalize( validatedClass ); @SuppressWarnings("unchecked") - List> classHierarchy = (List>) (Object) ClassHierarchyHelper.getHierarchy( normalizedValidatedClass, - Filters.excludeInterfaces( normalizedValidatedClass ) ); + List> classHierarchy = (List>) (Object) ClassHierarchyHelper.getHierarchy( + normalizedValidatedClass, + Filters.excludeInterfaces( normalizedValidatedClass ) + ); // 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, - createBeanMetaData( constraintCreationContext, executableHelper, parameterNameProvider, + 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") @@ -118,6 +147,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(); @@ -129,21 +166,25 @@ public void clear() { * * @param The type of interest. * @param clazz The type's class. - * * @return A bean meta data object for the given type. */ - private static BeanMetaDataImpl createBeanMetaData(ConstraintCreationContext constraintCreationContext, + private static BeanMetaDataImpl createBeanMetaData( + ConstraintCreationContext constraintCreationContext, ExecutableHelper executableHelper, ExecutableParameterNameProvider parameterNameProvider, JavaBeanHelper javaBeanHelper, ValidationOrderGenerator validationOrderGenerator, List optionalMetaDataProviders, MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter, List metaDataProviders, - Class clazz) { + 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 ) ) { @@ -188,6 +229,14 @@ 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; @@ -287,6 +336,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 d28d01e352..7614cfe6a8 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 @@ -64,6 +64,16 @@ public AbstractConstraintMetaData(String name, Type type, Set> 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 aae63e0f50..12487ae063 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 @@ -137,4 +137,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 9715d5b86c..40f915117e 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 @@ -23,6 +23,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 @@ -41,6 +42,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; @@ -54,13 +56,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( @@ -69,14 +73,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) { @@ -117,7 +123,8 @@ private void addMetaDataToBuilder(ConstrainedElement constrainableElement, Set build() { defaultGroupSequence, defaultGroupSequenceProvider, aggregatedElements, - validationOrderGenerator + validationOrderGenerator, + processedBeansTrackingVoter ); } @@ -147,6 +155,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( @@ -155,7 +164,8 @@ public BuilderDelegate( ConstraintCreationContext constraintCreationContext, ExecutableHelper executableHelper, ExecutableParameterNameProvider parameterNameProvider, - MethodValidationConfiguration methodValidationConfiguration + MethodValidationConfiguration methodValidationConfiguration, + ProcessedBeansTrackingVoter processedBeansTrackingVoter ) { this.beanClass = beanClass; this.constrainedElement = constrainedElement; @@ -163,6 +173,7 @@ public BuilderDelegate( this.executableHelper = executableHelper; this.parameterNameProvider = parameterNameProvider; this.methodValidationConfiguration = methodValidationConfiguration; + this.processedBeansTrackingVoter = processedBeansTrackingVoter; switch ( constrainedElement.getKind() ) { case FIELD: @@ -188,7 +199,8 @@ public BuilderDelegate( constraintCreationContext, executableHelper, parameterNameProvider, - methodValidationConfiguration + methodValidationConfiguration, + processedBeansTrackingVoter ); } @@ -235,7 +247,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 e1b7a76015..1069b571c6 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 @@ -29,6 +29,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; @@ -43,6 +44,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 @@ -183,6 +186,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} * @@ -195,7 +203,8 @@ public BeanMetaDataImpl(Class beanClass, List> defaultGroupSequence, DefaultGroupSequenceProvider defaultGroupSequenceProvider, Set constraintMetaDataSet, - ValidationOrderGenerator validationOrderGenerator) { + ValidationOrderGenerator validationOrderGenerator, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { this.validationOrderGenerator = validationOrderGenerator; this.beanClass = beanClass; @@ -270,6 +279,60 @@ 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.allDirectMetaConstraints = originalBeanMetaData.allDirectMetaConstraints; + this.classMetaConstraints = originalBeanMetaData.classMetaConstraints; + this.propertyMetaConstraints = originalBeanMetaData.propertyMetaConstraints; + this.directClassMetaConstraints = originalBeanMetaData.directClassMetaConstraints; + this.directPropertyMetaConstraints = originalBeanMetaData.directPropertyMetaConstraints; + this.classHierarchyWithoutInterfaces = originalBeanMetaData.classHierarchyWithoutInterfaces; + this.defaultGroupSequenceProvider = originalBeanMetaData.defaultGroupSequenceProvider; + this.defaultGroupSequence = originalBeanMetaData.defaultGroupSequence; + this.validationOrder = originalBeanMetaData.validationOrder; + 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 @@ -409,6 +472,11 @@ public List> getClassHierarchy() { return classHierarchyWithoutInterfaces; } + @Override + public boolean isTrackingEnabled() { + return trackingEnabled; + } + private static BeanDescriptor createBeanDescriptor(Class beanClass, Set> classMetaConstraints, 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 19bd9c0c08..3ee5ff9f65 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 @@ -21,6 +21,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; @@ -34,6 +35,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 @@ -54,6 +57,8 @@ */ public class ExecutableMetaData extends AbstractConstraintMetaData { + private final Class beanClass; + private final Class[] parameterTypes; @Immutable @@ -76,7 +81,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, @@ -88,7 +97,8 @@ private ExecutableMetaData( Set> crossParameterConstraints, CascadingMetaData cascadingMetaData, boolean isConstrained, - boolean isGetter) { + boolean isGetter, + ProcessedBeansTrackingVoter processedBeansTrackingVoter) { super( name, returnType, @@ -98,6 +108,7 @@ private ExecutableMetaData( isConstrained ); + this.beanClass = beanClass; this.parameterTypes = parameterTypes; this.parameterMetaDataList = CollectionHelper.toImmutableList( parameterMetaDataList ); this.validatableParametersMetaData = new ValidatableParametersMetaData( parameterMetaDataList ); @@ -111,6 +122,90 @@ 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; + } } /** @@ -197,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(); @@ -258,6 +362,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 @@ -277,11 +382,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(); @@ -373,6 +480,7 @@ public ExecutableMetaData build() { assertCorrectnessOfConfiguration(); return new ExecutableMetaData( + callable.getDeclaringClass(), callable.getName(), callable.getType(), callable.getParameterTypes(), @@ -384,7 +492,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..7529b7c172 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/spi/tracking/ProcessedBeansTrackingVoter.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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 f06fe1a2c1..706a14eac1 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 @@ -31,6 +31,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; @@ -242,7 +243,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..d4601f0f53 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles1Test.java @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.test.internal.engine.tracking; + +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.testng.annotations.Test; + +/** + * This is not a real test, just an illustration. + *

+ * This is the most simple example. + * + * @author Guillaume Smet + */ +public class ProcessedBeansTrackingCycles1Test { + + @Test + public void 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..bd7ca0e78b --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles2Test.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..8d5452e936 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles3Test.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..6752d25512 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles4Test.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..f8e2c7e740 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCycles5Test.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..11d810c5cb --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListDuplicateElementsTest.java @@ -0,0 +1,205 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..f7cd8159a4 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesListTest.java @@ -0,0 +1,196 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..cb9741bf16 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesMapTest.java @@ -0,0 +1,185 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..92e3a84cca --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingCyclesNoCyclesTest.java @@ -0,0 +1,203 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..0ee2e90206 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles1Test.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..999ee71edc --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles2Test.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..243a576997 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/tracking/ProcessedBeansTrackingNoCycles3Test.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +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 ae7d99c151..77849faad6 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 @@ -23,6 +23,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; @@ -60,7 +61,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 d5f5237fa6..d182960f34 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 @@ -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.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( 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 8b374e66a7..9baec4461b 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 @@ -26,6 +26,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; @@ -66,7 +67,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 4938cb3837..3a968a689b 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 @@ -19,6 +19,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; @@ -50,7 +51,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 0b0dc0775e..5eb1b5a3ab 100644 --- a/performance/README.md +++ b/performance/README.md @@ -11,8 +11,11 @@ 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 9.0.0-SNAPSHOT) +* hv-8.0 (Hibernate Validator 8.0.2.Final) +* hv-6.2 (Hibernate Validator 6.2.5.Final) +* 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 +67,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 9588bc43de..08919c2a12 100644 --- a/performance/pom.xml +++ b/performance/pom.xml @@ -32,6 +32,7 @@ --> ${project.basedir}/src/main/java ${project.basedir}/src/main/java + ${project.basedir}/src/main/java