From 13233a83b5842f2aef99dae70f73eade41fa89c0 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 13 Mar 2025 17:53:37 +0100 Subject: [PATCH 1/3] HV-2004 Add constraint initialization payload and use it to cache patterns in the predefined factory Signed-off-by: marko-bekhta --- .../BaseHibernateValidatorConfiguration.java | 24 +++++++++ ...straintValidatorInitializationContext.java | 16 ++++++ .../bv/PatternValidator.java | 13 +++-- .../engine/AbstractConfigurationImpl.java | 26 +++++++++- .../PredefinedScopeValidatorFactoryImpl.java | 10 +++- .../ValidatorFactoryConfigurationHelper.java | 22 +++++++- .../internal/engine/ValidatorFactoryImpl.java | 3 ++ .../engine/ValidatorFactoryScopedContext.java | 10 ++-- ...intValidatorInitializationContextImpl.java | 20 ++++++-- ...torInitializationSharedServiceManager.java | 50 +++++++++++++++++++ .../PatternConstraintInitializer.java | 44 ++++++++++++++++ .../bv/PatternValidatorTest.java | 17 ++++--- ...nstraintValidatorInitializationHelper.java | 9 ++++ 13 files changed, 241 insertions(+), 23 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java create mode 100644 engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 5db0cc56e5..0d9d732bae 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -364,6 +364,30 @@ public interface BaseHibernateValidatorConfiguration S addConstraintValidatorInitializationSharedService(Class serviceClass, V constraintValidatorInitializationSharedService); + /** * Allows to set a getter property selection strategy defining the rules determining if a method is a getter * or not. diff --git a/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateConstraintValidatorInitializationContext.java b/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateConstraintValidatorInitializationContext.java index 24f5cd4d38..7017ec12a9 100644 --- a/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateConstraintValidatorInitializationContext.java +++ b/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateConstraintValidatorInitializationContext.java @@ -56,4 +56,20 @@ public interface HibernateConstraintValidatorInitializationContext { */ @Incubating Duration getTemporalValidationTolerance(); + + /** + * Returns an instance of the specified service type or {@code null} if the current context does not + * contain such service. + * The requested service type must match the one with which it was originally added with + * {@link org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationSharedService(Object)}. + * + * @param type the type of service to retrieve + * @return an instance of the specified type or {@code null} if the current constraint initialization context does not + * contain an instance of such type + * + * @since 9.1.0 + * @see org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationSharedService(Object) + */ + @Incubating + C getConstraintValidatorInitializationSharedService(Class type); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/bv/PatternValidator.java b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/bv/PatternValidator.java index 6475693b76..6f83f1abce 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/bv/PatternValidator.java +++ b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/bv/PatternValidator.java @@ -8,11 +8,14 @@ import java.util.regex.Matcher; import java.util.regex.PatternSyntaxException; -import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.constraints.Pattern; +import jakarta.validation.metadata.ConstraintDescriptor; +import org.hibernate.validator.constraintvalidation.HibernateConstraintValidator; import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; +import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; +import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer; import org.hibernate.validator.internal.engine.messageinterpolation.util.InterpolationHelper; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; @@ -20,7 +23,7 @@ /** * @author Hardy Ferentschik */ -public class PatternValidator implements ConstraintValidator { +public class PatternValidator implements HibernateConstraintValidator { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); @@ -28,7 +31,8 @@ public class PatternValidator implements ConstraintValidator constraintDescriptor, HibernateConstraintValidatorInitializationContext initializationContext) { + Pattern parameters = constraintDescriptor.getAnnotation(); Pattern.Flag[] flags = parameters.flags(); int intFlag = 0; for ( Pattern.Flag flag : flags ) { @@ -36,7 +40,8 @@ public void initialize(Pattern parameters) { } try { - pattern = java.util.regex.Pattern.compile( parameters.regexp(), intFlag ); + pattern = initializationContext.getConstraintValidatorInitializationSharedService( PatternConstraintInitializer.class ) + .of( parameters.regexp(), intFlag ); } catch (PatternSyntaxException e) { throw LOG.getInvalidRegularExpressionException( e ); 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 768ae1dafc..1041f9642e 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 @@ -37,6 +37,7 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.constraintvalidation.spi.DefaultConstraintValidatorFactory; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; +import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationSharedServiceManager; import org.hibernate.validator.internal.engine.resolver.TraversableResolvers; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; @@ -114,11 +115,12 @@ public abstract class AbstractConfigurationImpl valueExtractorDescriptors = new HashMap<>(); // HV-specific options + private final HibernateConstraintValidatorInitializationSharedServiceManager constraintValidatorInitializationSharedServiceManager; private final Set programmaticMappings = newHashSet(); + private final MethodValidationConfiguration.Builder methodValidationConfigurationBuilder = new MethodValidationConfiguration.Builder(); private boolean failFast; private boolean failFastOnPropertyViolation; private ClassLoader externalClassLoader; - private final MethodValidationConfiguration.Builder methodValidationConfigurationBuilder = new MethodValidationConfiguration.Builder(); private boolean traversableResolverResultCacheEnabled = true; private ScriptEvaluatorFactory scriptEvaluatorFactory; private Duration temporalValidationTolerance; @@ -159,6 +161,7 @@ private AbstractConfigurationImpl() { this.defaultParameterNameProvider = new DefaultParameterNameProvider(); this.defaultClockProvider = DefaultClockProvider.INSTANCE; this.defaultPropertyNodeNameProvider = new DefaultPropertyNodeNameProvider(); + this.constraintValidatorInitializationSharedServiceManager = new HibernateConstraintValidatorInitializationSharedServiceManager(); } @Override @@ -352,6 +355,23 @@ public T constraintValidatorPayload(Object constraintValidatorPayload) { return thisAsT(); } + @Override + public T addConstraintValidatorInitializationSharedService(Object constraintValidatorInitializationSharedService) { + Contracts.assertNotNull( constraintValidatorInitializationSharedService, MESSAGES.parameterMustNotBeNull( "constraintValidatorInitializationSharedService" ) ); + + this.constraintValidatorInitializationSharedServiceManager.register( constraintValidatorInitializationSharedService ); + return thisAsT(); + } + + @Override + public T addConstraintValidatorInitializationSharedService(Class serviceClass, S constraintValidatorInitializationSharedService) { + Contracts.assertNotNull( constraintValidatorInitializationSharedService, MESSAGES.parameterMustNotBeNull( "serviceClass" ) ); + Contracts.assertNotNull( constraintValidatorInitializationSharedService, MESSAGES.parameterMustNotBeNull( "constraintValidatorInitializationSharedService" ) ); + this.constraintValidatorInitializationSharedServiceManager.register( serviceClass, constraintValidatorInitializationSharedService ); + + return thisAsT(); + } + @Override public T getterPropertySelectionStrategy(GetterPropertySelectionStrategy getterPropertySelectionStrategy) { Contracts.assertNotNull( getterPropertySelectionStrategy, MESSAGES.parameterMustNotBeNull( "getterPropertySelectionStrategy" ) ); @@ -548,6 +568,10 @@ public Object getConstraintValidatorPayload() { return constraintValidatorPayload; } + public HibernateConstraintValidatorInitializationSharedServiceManager getConstraintValidatorInitializationSharedServiceManager() { + return constraintValidatorInitializationSharedServiceManager; + } + public GetterPropertySelectionStrategy getGetterPropertySelectionStrategy() { return getterPropertySelectionStrategy; } 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 eb5a55bd31..07d8fd588e 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 @@ -10,6 +10,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorInitializationSharedServices; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; @@ -47,6 +48,7 @@ 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.PatternConstraintInitializer; import org.hibernate.validator.internal.engine.constraintvalidation.PredefinedScopeConstraintValidatorManagerImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; @@ -124,9 +126,10 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState ScriptEvaluatorFactory scriptEvaluatorFactory = determineScriptEvaluatorFactory( configurationState, properties, externalClassLoader ); Duration temporalValidationTolerance = determineTemporalValidationTolerance( configurationState, properties ); + PatternConstraintInitializer.CachingPatternConstraintInitializer patternConstraintInitializer = new PatternConstraintInitializer.CachingPatternConstraintInitializer(); HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext = new HibernateConstraintValidatorInitializationContextImpl( - scriptEvaluatorFactory, configurationState.getClockProvider(), temporalValidationTolerance ); - + scriptEvaluatorFactory, configurationState.getClockProvider(), temporalValidationTolerance, + determineConstraintValidatorInitializationSharedServices( hibernateSpecificConfig, patternConstraintInitializer ) ); this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( configurationState.getMessageInterpolator(), @@ -248,6 +251,9 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState beanClassesToInitialize ); + // at this point all constraints had to be initialized, so we can clear up the pattern cache: + patternConstraintInitializer.close(); + if ( LOG.isDebugEnabled() ) { logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java index e835053b55..887c7f3b47 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java @@ -21,6 +21,8 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintdefinition.ConstraintDefinitionContribution; +import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationSharedServiceManager; +import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer; import org.hibernate.validator.internal.engine.messageinterpolation.DefaultLocaleResolver; import org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; @@ -252,8 +254,7 @@ static Duration determineTemporalValidationTolerance(ConfigurationState configur } static Object determineConstraintValidatorPayload(ConfigurationState configurationState) { - if ( configurationState instanceof AbstractConfigurationImpl ) { - AbstractConfigurationImpl hibernateSpecificConfig = (AbstractConfigurationImpl) configurationState; + if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) { LOG.logConstraintValidatorPayload( hibernateSpecificConfig.getConstraintValidatorPayload() ); return hibernateSpecificConfig.getConstraintValidatorPayload(); @@ -263,6 +264,23 @@ static Object determineConstraintValidatorPayload(ConfigurationState configurati return null; } + static HibernateConstraintValidatorInitializationSharedServiceManager determineConstraintValidatorInitializationSharedServices(ConfigurationState configurationState, + PatternConstraintInitializer patternConstraintInitializer) { + HibernateConstraintValidatorInitializationSharedServiceManager configured = null; + if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { + if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) { + configured = hibernateSpecificConfig.getConstraintValidatorInitializationSharedServiceManager(); + } + } + if ( configured == null ) { + configured = new HibernateConstraintValidatorInitializationSharedServiceManager(); + } + + return configured.immutableWithDefaultServices( + Map.of( PatternConstraintInitializer.class, patternConstraintInitializer ) + ); + } + static ExpressionLanguageFeatureLevel determineConstraintExpressionLanguageFeatureLevel(AbstractConfigurationImpl hibernateSpecificConfig, Map properties) { if ( hibernateSpecificConfig != null && hibernateSpecificConfig.getConstraintExpressionLanguageFeatureLevel() != null ) { 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 aa0f7a0187..a6f26a4c66 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java @@ -10,6 +10,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorInitializationSharedServices; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; @@ -47,6 +48,7 @@ import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl; +import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.tracking.DefaultProcessedBeansTrackingVoter; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; @@ -168,6 +170,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ), determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ), determineConstraintValidatorPayload( hibernateSpecificConfig ), + determineConstraintValidatorInitializationSharedServices( hibernateSpecificConfig, new PatternConstraintInitializer.SimplePatternConstraintInitializer() ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ) ); 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 ae56d924da..2c86fece8e 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 @@ -13,6 +13,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; +import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationSharedServiceManager; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; @@ -103,13 +104,15 @@ public class ValidatorFactoryScopedContext { boolean traversableResolverResultCacheEnabled, boolean showValidatedValuesInTraceLogs, Object constraintValidatorPayload, + HibernateConstraintValidatorInitializationSharedServiceManager constraintValidatorInitializationSharedServiceManager, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel) { this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast, failFastOnPropertyViolation, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel, customViolationExpressionLanguageFeatureLevel, new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, - temporalValidationTolerance ) ); + temporalValidationTolerance, constraintValidatorInitializationSharedServiceManager + ) ); } ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator, @@ -214,7 +217,7 @@ static class Builder { private ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel; private ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel; private boolean showValidatedValuesInTraceLogs; - private HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext; + private final HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext; Builder(ValidatorFactoryScopedContext defaultContext) { Contracts.assertNotNull( defaultContext, "Default context cannot be null." ); @@ -348,7 +351,8 @@ public ValidatorFactoryScopedContext build() { constraintValidatorInitializationContext, scriptEvaluatorFactory, clockProvider, - temporalValidationTolerance + temporalValidationTolerance, + constraintValidatorInitializationContext.getConstraintValidatorInitializationSharedServiceManager() ) ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationContextImpl.java index a3df91dc06..da7eb1132d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationContextImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationContextImpl.java @@ -23,25 +23,30 @@ public class HibernateConstraintValidatorInitializationContextImpl implements Hi private final Duration temporalValidationTolerance; + private final HibernateConstraintValidatorInitializationSharedServiceManager constraintValidatorInitializationSharedServiceManager; + private final int hashCode; public HibernateConstraintValidatorInitializationContextImpl(ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, - Duration temporalValidationTolerance) { + Duration temporalValidationTolerance, HibernateConstraintValidatorInitializationSharedServiceManager constraintValidatorInitializationSharedServiceManager + ) { this.scriptEvaluatorFactory = scriptEvaluatorFactory; this.clockProvider = clockProvider; this.temporalValidationTolerance = temporalValidationTolerance; + this.constraintValidatorInitializationSharedServiceManager = constraintValidatorInitializationSharedServiceManager; this.hashCode = createHashCode(); } public static HibernateConstraintValidatorInitializationContextImpl of(HibernateConstraintValidatorInitializationContextImpl defaultContext, - ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, Duration temporalValidationTolerance) { + ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, Duration temporalValidationTolerance, + HibernateConstraintValidatorInitializationSharedServiceManager constraintValidatorInitializationSharedServiceManager) { if ( scriptEvaluatorFactory == defaultContext.scriptEvaluatorFactory && clockProvider == defaultContext.clockProvider && temporalValidationTolerance.equals( defaultContext.temporalValidationTolerance ) ) { return defaultContext; } - return new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, temporalValidationTolerance ); + return new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, temporalValidationTolerance, constraintValidatorInitializationSharedServiceManager ); } @Override @@ -59,6 +64,15 @@ public Duration getTemporalValidationTolerance() { return temporalValidationTolerance; } + @Override + public C getConstraintValidatorInitializationSharedService(Class type) { + return constraintValidatorInitializationSharedServiceManager.retrieve( type ); + } + + public HibernateConstraintValidatorInitializationSharedServiceManager getConstraintValidatorInitializationSharedServiceManager() { + return constraintValidatorInitializationSharedServiceManager; + } + @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java new file mode 100644 index 0000000000..4308d2e745 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.internal.engine.constraintvalidation; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.validator.internal.util.Contracts; + +public class HibernateConstraintValidatorInitializationSharedServiceManager { + private final Map, Object> services; + + public HibernateConstraintValidatorInitializationSharedServiceManager() { + this( new HashMap<>() ); + } + + private HibernateConstraintValidatorInitializationSharedServiceManager(Map, Object> services) { + this.services = services; + } + + public void register(Object service) { + Contracts.assertNotNull( service, "Service must not be null" ); + + services.put( service.getClass(), service ); + } + + public void register(Class serviceClass, S service) { + Contracts.assertNotNull( service, "Service must not be null" ); + + services.put( serviceClass, service ); + } + + @SuppressWarnings("unchecked") // because of the way we populate that map + public T retrieve(Class serviceClass) { + Contracts.assertNotNull( serviceClass, "Service class must not be null" ); + return (T) services.get( serviceClass ); + } + + public HibernateConstraintValidatorInitializationSharedServiceManager immutableWithDefaultServices(Map, Object> defaultServices) { + Map, Object> services = new HashMap<>( this.services ); + for ( var entry : defaultServices.entrySet() ) { + services.putIfAbsent( entry.getKey(), entry.getValue() ); + } + return new HibernateConstraintValidatorInitializationSharedServiceManager( + Map.copyOf( services ) + ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java new file mode 100644 index 0000000000..07c86c6b6b --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.internal.engine.constraintvalidation; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public interface PatternConstraintInitializer extends AutoCloseable { + + Pattern of(String pattern, int flags); + + @Override + default void close() { + } + + class SimplePatternConstraintInitializer implements PatternConstraintInitializer { + + @Override + public Pattern of(String pattern, int flags) { + return Pattern.compile( pattern, flags ); + } + } + + class CachingPatternConstraintInitializer implements PatternConstraintInitializer { + private final Map cache = new ConcurrentHashMap<>(); + + @Override + public Pattern of(String pattern, int flags) { + return cache.computeIfAbsent( new PatternKey( pattern, flags ), key -> Pattern.compile( pattern, flags ) ); + } + + @Override + public void close() { + cache.clear(); + } + + private record PatternKey(String pattern, int flags) { + } + } + +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/bv/PatternValidatorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/bv/PatternValidatorTest.java index ab287fbc2f..588bc91eed 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/bv/PatternValidatorTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/constraintvalidators/bv/PatternValidatorTest.java @@ -4,6 +4,7 @@ */ package org.hibernate.validator.test.internal.constraintvalidators.bv; +import static org.hibernate.validator.testutils.ConstraintValidatorInitializationHelper.initialize; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -26,10 +27,10 @@ public void testIsValid() { ConstraintAnnotationDescriptor.Builder descriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>( Pattern.class ); descriptorBuilder.setAttribute( "regexp", "foobar" ); descriptorBuilder.setMessage( "pattern does not match" ); - Pattern p = descriptorBuilder.build().getAnnotation(); + ConstraintAnnotationDescriptor descriptor = descriptorBuilder.build(); PatternValidator constraint = new PatternValidator(); - constraint.initialize( p ); + initialize( constraint, descriptor ); assertTrue( constraint.isValid( null, null ) ); assertFalse( constraint.isValid( "", null ) ); @@ -42,10 +43,10 @@ public void testIsValid() { public void testIsValidForCharSequence() { ConstraintAnnotationDescriptor.Builder descriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>( Pattern.class ); descriptorBuilder.setAttribute( "regexp", "char sequence" ); - Pattern p = descriptorBuilder.build().getAnnotation(); + ConstraintAnnotationDescriptor descriptor = descriptorBuilder.build(); PatternValidator constraint = new PatternValidator(); - constraint.initialize( p ); + initialize( constraint, descriptor ); assertTrue( constraint.isValid( new MyCustomStringImpl( "char sequence" ), null ) ); } @@ -55,10 +56,10 @@ public void testIsValidForEmptyStringRegexp() { ConstraintAnnotationDescriptor.Builder descriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>( Pattern.class ); descriptorBuilder.setAttribute( "regexp", "|^.*foo$" ); descriptorBuilder.setMessage( "pattern does not match" ); - Pattern p = descriptorBuilder.build().getAnnotation(); + ConstraintAnnotationDescriptor descriptor = descriptorBuilder.build(); PatternValidator constraint = new PatternValidator(); - constraint.initialize( p ); + initialize( constraint, descriptor ); assertTrue( constraint.isValid( null, null ) ); assertTrue( constraint.isValid( "", null ) ); @@ -72,9 +73,9 @@ public void testInvalidRegularExpression() { ConstraintAnnotationDescriptor.Builder descriptorBuilder = new ConstraintAnnotationDescriptor.Builder<>( Pattern.class ); descriptorBuilder.setAttribute( "regexp", "(unbalanced parentheses" ); descriptorBuilder.setMessage( "pattern does not match" ); - Pattern p = descriptorBuilder.build().getAnnotation(); + ConstraintAnnotationDescriptor descriptor = descriptorBuilder.build(); PatternValidator constraint = new PatternValidator(); - constraint.initialize( p ); + initialize( constraint, descriptor ); } } diff --git a/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java b/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java index c99741b3ba..e5b079763a 100644 --- a/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java +++ b/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java @@ -17,6 +17,7 @@ import org.hibernate.validator.internal.engine.ConstraintCreationContext; import org.hibernate.validator.internal.engine.DefaultClockProvider; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl; +import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer; import org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; @@ -96,6 +97,14 @@ public ClockProvider getClockProvider() { public Duration getTemporalValidationTolerance() { return duration; } + + @Override + public C getConstraintValidatorInitializationSharedService(Class type) { + if ( PatternConstraintInitializer.class.equals( type ) ) { + return (C) new PatternConstraintInitializer.SimplePatternConstraintInitializer(); + } + return null; + } }; } } From d8f319372c31f642d5df5466a0b1794cc5cade7f Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Wed, 3 Sep 2025 11:27:50 +0200 Subject: [PATCH 2/3] HV-2004 Create observer to track basic lifecycle factory events Signed-off-by: marko-bekhta --- .../BaseHibernateValidatorConfiguration.java | 22 ++++- .../HibernateValidatorFactoryObserver.java | 43 ++++++++++ .../engine/AbstractConfigurationImpl.java | 19 ++++- .../PredefinedScopeValidatorFactoryImpl.java | 16 ++++ .../ValidatorFactoryConfigurationHelper.java | 53 ++++++++++-- .../internal/engine/ValidatorFactoryImpl.java | 15 ++++ .../validator/internal/util/logging/Log.java | 8 ++ .../ValidatorFactoryObserverTest.java | 80 +++++++++++++++++++ 8 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateValidatorFactoryObserver.java create mode 100644 engine/src/test/java/org/hibernate/validator/test/internal/engine/factoryobserver/ValidatorFactoryObserverTest.java diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 0d9d732bae..3f9b4cb35a 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -23,6 +23,7 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.ScriptAssert; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; @@ -40,7 +41,6 @@ * {@link PredefinedScopeHibernateValidatorConfiguration}. * * @param The actual type of the configuration. - * * @author Emmanuel Bernard * @author Gunnar Morling * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI @@ -179,6 +179,15 @@ public interface BaseHibernateValidatorConfiguration * Returns the {@link ResourceBundleLocator} used by the @@ -522,4 +531,15 @@ default S locales(Locale... locales) { */ @Incubating S processedBeansTrackingVoter(ProcessedBeansTrackingVoter processedBeanTrackingVoter); + + /** + * Allows adding validator factory observers tracking basic lifecycle factory events. + * + * @param observer An observer to register. + * @return {@code this} following the chaining method pattern + * + * @since 9.1 + */ + @Incubating + S addHibernateValidatorFactoryObserver(HibernateValidatorFactoryObserver observer); } diff --git a/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateValidatorFactoryObserver.java b/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateValidatorFactoryObserver.java new file mode 100644 index 0000000000..727cd411e6 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateValidatorFactoryObserver.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.constraintvalidation; + +import org.hibernate.validator.HibernateValidatorFactory; +import org.hibernate.validator.Incubating; + +/** + * Allows observing the basic validator factory lifecycle events. + *

+ * Observers are required to handle exceptions internally and must not propagate them. + * + * @see org.hibernate.validator.HibernateValidatorConfiguration#addHibernateValidatorFactoryObserver(HibernateValidatorFactoryObserver) + * @since 9.1.0 + */ +@Incubating +public interface HibernateValidatorFactoryObserver { + /** + * A callback invoked upon the successful creation of a factory. + * + * @param factory The fully initialized factory. + */ + default void factoryCreated(HibernateValidatorFactory factory) { + } + + /** + * A callback invoked just before the factory is shut down. + * + * @param factory The factory that is about to be closed. + */ + default void factoryClosing(HibernateValidatorFactory factory) { + } + + /** + * A callback invoked after the factory has been successfully shut down. + * + * @param factory The closed factory. + */ + default void factoryClosed(HibernateValidatorFactory factory) { + } +} 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 1041f9642e..3da3751fbf 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 @@ -12,6 +12,7 @@ import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -35,6 +36,7 @@ import org.hibernate.validator.BaseHibernateValidatorConfiguration; import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; import org.hibernate.validator.constraintvalidation.spi.DefaultConstraintValidatorFactory; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationSharedServiceManager; @@ -134,6 +136,7 @@ public abstract class AbstractConfigurationImpl hibernateValidatorFactoryObservers; protected AbstractConfigurationImpl(BootstrapState state) { this(); @@ -711,7 +714,17 @@ public T processedBeansTrackingVoter(ProcessedBeansTrackingVoter processedBeansT return thisAsT(); } - public ProcessedBeansTrackingVoter getProcessedBeansTrackingVoter() { + @Override + public T addHibernateValidatorFactoryObserver(HibernateValidatorFactoryObserver observer) { + Contracts.assertNotNull( observer, MESSAGES.parameterMustNotBeNull( "observer" ) ); + if ( hibernateValidatorFactoryObservers == null ) { + hibernateValidatorFactoryObservers = new ArrayList<>(); + } + hibernateValidatorFactoryObservers.add( observer ); + return thisAsT(); + } + + public final ProcessedBeansTrackingVoter getProcessedBeansTrackingVoter() { return processedBeansTrackingVoter; } @@ -719,6 +732,10 @@ public final Set getProgrammaticMappings() { return programmaticMappings; } + public final List getHibernateValidatorFactoryObservers() { + return hibernateValidatorFactoryObservers == null ? List.of() : hibernateValidatorFactoryObservers; + } + private boolean isSpecificProvider() { return validationBootstrapParameters.getProvider() != null; } 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 07d8fd588e..7c52cb407b 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 @@ -4,6 +4,9 @@ */ package org.hibernate.validator.internal.engine; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.OBSERVE_FACTORY_CLOSED; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.OBSERVE_FACTORY_CLOSING; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.OBSERVE_FACTORY_CREATED; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowMultipleCascadedValidationOnReturnValues; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowOverridingMethodAlterParameterConstraint; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowParallelMethodsDefineParameterConstraints; @@ -16,6 +19,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineFailFast; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineFailFastOnPropertyViolation; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineHibernateValidatorFactoryObservers; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineScriptEvaluatorFactory; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineServiceLoadedConstraintMappings; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineShowValidatedValuesInTraceLogs; @@ -23,6 +27,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineTraversableResolverResultCacheEnabled; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.logValidatorFactoryScopedConfiguration; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.registerCustomConstraintValidators; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.safeObserve; import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList; import java.lang.invoke.MethodHandles; @@ -45,6 +50,7 @@ import org.hibernate.validator.HibernateValidatorContext; import org.hibernate.validator.HibernateValidatorFactory; import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; @@ -104,6 +110,8 @@ public class PredefinedScopeValidatorFactoryImpl implements PredefinedScopeHiber private final ValidationOrderGenerator validationOrderGenerator; + private final List hibernateValidatorFactoryObservers; + public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState) { Contracts.assertTrue( configurationState instanceof PredefinedScopeConfigurationImpl, "Only PredefinedScopeConfigurationImpl is supported." ); @@ -254,9 +262,13 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState // at this point all constraints had to be initialized, so we can clear up the pattern cache: patternConstraintInitializer.close(); + this.hibernateValidatorFactoryObservers = determineHibernateValidatorFactoryObservers( configurationState, properties, externalClassLoader ); + safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CREATED ); + if ( LOG.isDebugEnabled() ) { logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext ); } + } @Override @@ -342,10 +354,14 @@ public HibernateValidatorContext usingContext() { @Override public void close() { + safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CLOSING ); + constraintValidatorManager.clear(); beanMetaDataManager.clear(); validatorFactoryScopedContext.getScriptEvaluatorFactory().clear(); valueExtractorManager.clear(); + + safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CLOSED ); } public ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java index 887c7f3b47..c74f31088e 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java @@ -14,11 +14,14 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import jakarta.validation.spi.ConfigurationState; import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.HibernateValidatorFactory; import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintdefinition.ConstraintDefinitionContribution; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationSharedServiceManager; @@ -48,6 +51,10 @@ final class ValidatorFactoryConfigurationHelper { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + static final BiConsumer OBSERVE_FACTORY_CLOSING = HibernateValidatorFactoryObserver::factoryClosing; + static final BiConsumer OBSERVE_FACTORY_CLOSED = HibernateValidatorFactoryObserver::factoryClosed; + static final BiConsumer OBSERVE_FACTORY_CREATED = HibernateValidatorFactoryObserver::factoryCreated; + private ValidatorFactoryConfigurationHelper() { } @@ -61,9 +68,7 @@ static Set determineConstraintMappings(TypeResolutionH ConfigurationState configurationState, JavaBeanHelper javaBeanHelper, ClassLoader externalClassLoader) { Set constraintMappings = newHashSet(); - if ( configurationState instanceof AbstractConfigurationImpl ) { - AbstractConfigurationImpl hibernateConfiguration = (AbstractConfigurationImpl) configurationState; - + if ( configurationState instanceof AbstractConfigurationImpl hibernateConfiguration ) { // programmatic config /* We add these first so that constraint mapping created through DefaultConstraintMappingBuilder will take * these programmatically defined mappings into account when checking for constraint definition uniqueness @@ -204,8 +209,7 @@ static boolean determineFailFastOnPropertyViolation(AbstractConfigurationImpl static ScriptEvaluatorFactory determineScriptEvaluatorFactory(ConfigurationState configurationState, Map properties, ClassLoader externalClassLoader) { - if ( configurationState instanceof AbstractConfigurationImpl ) { - AbstractConfigurationImpl hibernateSpecificConfig = (AbstractConfigurationImpl) configurationState; + if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { if ( hibernateSpecificConfig.getScriptEvaluatorFactory() != null ) { LOG.usingScriptEvaluatorFactory( hibernateSpecificConfig.getScriptEvaluatorFactory().getClass() ); return hibernateSpecificConfig.getScriptEvaluatorFactory(); @@ -231,8 +235,7 @@ static ScriptEvaluatorFactory determineScriptEvaluatorFactory(ConfigurationState } static Duration determineTemporalValidationTolerance(ConfigurationState configurationState, Map properties) { - if ( configurationState instanceof AbstractConfigurationImpl ) { - AbstractConfigurationImpl hibernateSpecificConfig = (AbstractConfigurationImpl) configurationState; + if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { if ( hibernateSpecificConfig.getTemporalValidationTolerance() != null ) { LOG.logTemporalValidationTolerance( hibernateSpecificConfig.getTemporalValidationTolerance() ); return hibernateSpecificConfig.getTemporalValidationTolerance(); @@ -453,6 +456,42 @@ static boolean determineShowValidatedValuesInTraceLogs(AbstractConfigurationImpl return tmpShowValidatedValuesInTraceLogging; } + static List determineHibernateValidatorFactoryObservers(ConfigurationState configurationState, Map properties, ClassLoader externalClassLoader) { + List observers = newArrayList(); + if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { + observers.addAll( hibernateSpecificConfig.getHibernateValidatorFactoryObservers() ); + } + + String[] observerFqcn = properties.getOrDefault( HibernateValidatorConfiguration.HIBERNATE_VALIDATOR_FACTORY_OBSERVER, "" ).split( "," ); + for ( String fqcn : observerFqcn ) { + if ( fqcn == null || fqcn.isBlank() ) { + continue; + } + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) LoadClass.action( fqcn, externalClassLoader ); + HibernateValidatorFactoryObserver observer = NewInstance.action( clazz, "Hibernate Validator factory observer class" ); + observers.add( observer ); + } + catch (Exception e) { + throw LOG.getUnableToInstantiateFactoryObserverClassException( fqcn, e ); + } + } + return observers; + } + + static void safeObserve(List hibernateValidatorFactoryObservers, HibernateValidatorFactory factory, + BiConsumer action) { + for ( HibernateValidatorFactoryObserver observer : hibernateValidatorFactoryObservers ) { + try { + action.accept( observer, factory ); + } + catch (Exception e) { + LOG.unexpectedObserverException( observer, e ); + } + } + } + static void logValidatorFactoryScopedConfiguration(ValidatorFactoryScopedContext context) { LOG.logValidatorFactoryScopedConfiguration( context.getMessageInterpolator().getClass(), "message interpolator" ); LOG.logValidatorFactoryScopedConfiguration( context.getTraversableResolver().getClass(), "traversable resolver" ); 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 a6f26a4c66..05e34c6758 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 @@ -4,6 +4,9 @@ */ package org.hibernate.validator.internal.engine; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.OBSERVE_FACTORY_CLOSED; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.OBSERVE_FACTORY_CLOSING; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.OBSERVE_FACTORY_CREATED; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowMultipleCascadedValidationOnReturnValues; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowOverridingMethodAlterParameterConstraint; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowParallelMethodsDefineParameterConstraints; @@ -16,6 +19,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineFailFast; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineFailFastOnPropertyViolation; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineHibernateValidatorFactoryObservers; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineScriptEvaluatorFactory; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineServiceLoadedConstraintMappings; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineShowValidatedValuesInTraceLogs; @@ -23,6 +27,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineTraversableResolverResultCacheEnabled; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.logValidatorFactoryScopedConfiguration; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.registerCustomConstraintValidators; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.safeObserve; import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList; import java.lang.invoke.MethodHandles; @@ -45,6 +50,7 @@ import org.hibernate.validator.HibernateValidatorContext; import org.hibernate.validator.HibernateValidatorFactory; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl; @@ -139,6 +145,8 @@ public class ValidatorFactoryImpl implements HibernateValidatorFactory { private final ProcessedBeansTrackingVoter processedBeansTrackingVoter; + private final List hibernateValidatorFactoryObservers; + public ValidatorFactoryImpl(ConfigurationState configurationState) { ClassLoader externalClassLoader = determineExternalClassLoader( configurationState ); @@ -237,6 +245,9 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { ? hibernateSpecificConfig.getProcessedBeansTrackingVoter() : new DefaultProcessedBeansTrackingVoter(); + this.hibernateValidatorFactoryObservers = determineHibernateValidatorFactoryObservers( configurationState, properties, externalClassLoader ); + safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CREATED ); + if ( LOG.isDebugEnabled() ) { logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext ); } @@ -333,6 +344,8 @@ public HibernateValidatorContext usingContext() { @Override public void close() { + safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CLOSING ); + constraintCreationContext.getConstraintValidatorManager().clear(); constraintCreationContext.getConstraintHelper().clear(); for ( BeanMetaDataManager beanMetaDataManager : beanMetaDataManagers.values() ) { @@ -340,6 +353,8 @@ public void close() { } validatorFactoryScopedContext.getScriptEvaluatorFactory().clear(); constraintCreationContext.getValueExtractorManager().clear(); + + safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CLOSED ); } public ValidatorFactoryScopedContext getValidatorFactoryScopedContext() { diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java index 141300a767..273d244106 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java @@ -45,6 +45,7 @@ import jakarta.validation.valueextraction.ValueExtractorDeclarationException; import jakarta.validation.valueextraction.ValueExtractorDefinitionException; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.ConstraintType; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; @@ -959,4 +960,11 @@ ConstraintDefinitionException getConstraintValidatorDefinitionConstraintMismatch @Message(id = 272, value = "Using `@Valid` on a container is deprecated. You should apply the annotation on the type argument(s). (%1$s) can potentially be a container at runtime. Affected element: %2$s") void potentiallyDeprecatedUseOfValidOnContainer(@FormatWith(ClassObjectFormatter.class) Class valueType, Object context); + + @Message(id = 273, value = "Unable to instantiate the Hibernate Validator factory observer class %s.") + ValidationException getUnableToInstantiateFactoryObserverClassException(String observerFQCN, @Cause Exception e); + + @LogMessage(level = WARN) + @Message(id = 274, value = "Unexpected exception occurred for the Hibernate Validator observer %s: %s") + void unexpectedObserverException(HibernateValidatorFactoryObserver observer, Exception e); } diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/factoryobserver/ValidatorFactoryObserverTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/factoryobserver/ValidatorFactoryObserverTest.java new file mode 100644 index 0000000000..bdbdb42e4e --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/factoryobserver/ValidatorFactoryObserverTest.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.test.internal.engine.factoryobserver; + + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.HibernateValidatorFactory; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; +import org.hibernate.validator.testutils.ValidatorUtil; + +import org.testng.annotations.Test; + +public class ValidatorFactoryObserverTest { + + @Test + public void testProperty() { + final HibernateValidatorConfiguration configuration = ValidatorUtil.getConfiguration( HibernateValidator.class ); + configuration.addProperty( HibernateValidatorConfiguration.HIBERNATE_VALIDATOR_FACTORY_OBSERVER, MyObserver.class.getName() ); + + try ( ValidatorFactory validatorFactory = configuration.buildValidatorFactory() ) { + final Validator validator = validatorFactory.getValidator(); + } + } + + @Test + public void testAdd() { + final HibernateValidatorConfiguration configuration = ValidatorUtil.getConfiguration( HibernateValidator.class ); + MyObserver observer = new MyObserver(); + configuration.addHibernateValidatorFactoryObserver( observer ); + + try ( ValidatorFactory validatorFactory = configuration.buildValidatorFactory() ) { + final Validator validator = validatorFactory.getValidator(); + } + + assertThat( observer.observed() ).isTrue(); + } + + public static class MyObserver implements HibernateValidatorFactoryObserver { + boolean created = false; + boolean closing = false; + boolean closed = false; + + @Override + public void factoryCreated(HibernateValidatorFactory factory) { + assertThat( created ).isFalse(); + assertThat( closing ).isFalse(); + assertThat( closed ).isFalse(); + created = true; + } + + @Override + public void factoryClosing(HibernateValidatorFactory factory) { + assertThat( created ).isTrue(); + assertThat( closing ).isFalse(); + assertThat( closed ).isFalse(); + closing = true; + } + + @Override + public void factoryClosed(HibernateValidatorFactory factory) { + assertThat( created ).isTrue(); + assertThat( closing ).isTrue(); + assertThat( closed ).isFalse(); + closed = true; + } + + public boolean observed() { + return created && closing && closed; + } + } + +} From 3267cb03c6e96bbc053767cb1e826d072fd70fc8 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Wed, 3 Sep 2025 14:19:35 +0200 Subject: [PATCH 3/3] HV-2004 Use observer to clean up the pattern services Signed-off-by: marko-bekhta --- .../BaseHibernateValidatorConfiguration.java | 2 + .../PredefinedScopeValidatorFactoryImpl.java | 9 ++-- .../ValidatorFactoryConfigurationHelper.java | 15 +++++-- .../internal/engine/ValidatorFactoryImpl.java | 5 ++- ...torInitializationSharedServiceManager.java | 6 +++ .../PatternConstraintInitializer.java | 42 +++++++++++-------- ...nstraintValidatorInitializationHelper.java | 2 +- 7 files changed, 50 insertions(+), 31 deletions(-) diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index 3f9b4cb35a..ffb2b1e72e 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -389,6 +389,8 @@ public interface BaseHibernateValidatorConfiguration + * If the added service also implements {@link HibernateValidatorFactoryObserver} such service will be registered as an observer automatically. * * @param constraintValidatorInitializationSharedService the payload to retrieve from the constraint validator initializers * @return {@code this} following the chaining method pattern 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 7c52cb407b..8348f6393b 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 @@ -134,10 +134,10 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState ScriptEvaluatorFactory scriptEvaluatorFactory = determineScriptEvaluatorFactory( configurationState, properties, externalClassLoader ); Duration temporalValidationTolerance = determineTemporalValidationTolerance( configurationState, properties ); - PatternConstraintInitializer.CachingPatternConstraintInitializer patternConstraintInitializer = new PatternConstraintInitializer.CachingPatternConstraintInitializer(); + List sharedServicesObservers = newArrayList(); HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext = new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, configurationState.getClockProvider(), temporalValidationTolerance, - determineConstraintValidatorInitializationSharedServices( hibernateSpecificConfig, patternConstraintInitializer ) ); + determineConstraintValidatorInitializationSharedServices( hibernateSpecificConfig, PatternConstraintInitializer.predefined(), sharedServicesObservers ) ); this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( configurationState.getMessageInterpolator(), @@ -259,10 +259,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState beanClassesToInitialize ); - // at this point all constraints had to be initialized, so we can clear up the pattern cache: - patternConstraintInitializer.close(); - - this.hibernateValidatorFactoryObservers = determineHibernateValidatorFactoryObservers( configurationState, properties, externalClassLoader ); + this.hibernateValidatorFactoryObservers = determineHibernateValidatorFactoryObservers( sharedServicesObservers, configurationState, properties, externalClassLoader ); safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CREATED ); if ( LOG.isDebugEnabled() ) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java index c74f31088e..de7a2fb857 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java @@ -268,7 +268,7 @@ static Object determineConstraintValidatorPayload(ConfigurationState configurati } static HibernateConstraintValidatorInitializationSharedServiceManager determineConstraintValidatorInitializationSharedServices(ConfigurationState configurationState, - PatternConstraintInitializer patternConstraintInitializer) { + PatternConstraintInitializer patternConstraintInitializer, List sharedServicesObservers) { HibernateConstraintValidatorInitializationSharedServiceManager configured = null; if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) { @@ -279,9 +279,15 @@ static HibernateConstraintValidatorInitializationSharedServiceManager determineC configured = new HibernateConstraintValidatorInitializationSharedServiceManager(); } - return configured.immutableWithDefaultServices( + HibernateConstraintValidatorInitializationSharedServiceManager sharedServiceManager = configured.immutableWithDefaultServices( Map.of( PatternConstraintInitializer.class, patternConstraintInitializer ) ); + for ( Object service : sharedServiceManager.registeredServices() ) { + if ( service instanceof HibernateValidatorFactoryObserver observer ) { + sharedServicesObservers.add( observer ); + } + } + return sharedServiceManager; } static ExpressionLanguageFeatureLevel determineConstraintExpressionLanguageFeatureLevel(AbstractConfigurationImpl hibernateSpecificConfig, @@ -456,8 +462,9 @@ static boolean determineShowValidatedValuesInTraceLogs(AbstractConfigurationImpl return tmpShowValidatedValuesInTraceLogging; } - static List determineHibernateValidatorFactoryObservers(ConfigurationState configurationState, Map properties, ClassLoader externalClassLoader) { - List observers = newArrayList(); + static List determineHibernateValidatorFactoryObservers(List sharedServicesObservers, + ConfigurationState configurationState, Map properties, ClassLoader externalClassLoader) { + List observers = newArrayList( sharedServicesObservers ); if ( configurationState instanceof AbstractConfigurationImpl hibernateSpecificConfig ) { observers.addAll( hibernateSpecificConfig.getHibernateValidatorFactoryObservers() ); } 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 05e34c6758..87df719188 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 @@ -166,6 +166,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineAllowParallelMethodsDefineParameterConstraints( hibernateSpecificConfig, properties ) ).build(); + List sharedServicesObservers = newArrayList(); this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext( configurationState.getMessageInterpolator(), configurationState.getTraversableResolver(), @@ -178,7 +179,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ), determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ), determineConstraintValidatorPayload( hibernateSpecificConfig ), - determineConstraintValidatorInitializationSharedServices( hibernateSpecificConfig, new PatternConstraintInitializer.SimplePatternConstraintInitializer() ), + determineConstraintValidatorInitializationSharedServices( hibernateSpecificConfig, PatternConstraintInitializer.simple(), sharedServicesObservers ), determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ), determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ) ); @@ -245,7 +246,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { ? hibernateSpecificConfig.getProcessedBeansTrackingVoter() : new DefaultProcessedBeansTrackingVoter(); - this.hibernateValidatorFactoryObservers = determineHibernateValidatorFactoryObservers( configurationState, properties, externalClassLoader ); + this.hibernateValidatorFactoryObservers = determineHibernateValidatorFactoryObservers( sharedServicesObservers, configurationState, properties, externalClassLoader ); safeObserve( hibernateValidatorFactoryObservers, this, OBSERVE_FACTORY_CREATED ); if ( LOG.isDebugEnabled() ) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java index 4308d2e745..23ad62b241 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationSharedServiceManager.java @@ -4,6 +4,8 @@ */ package org.hibernate.validator.internal.engine.constraintvalidation; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -47,4 +49,8 @@ public HibernateConstraintValidatorInitializationSharedServiceManager immutableW Map.copyOf( services ) ); } + + public Collection registeredServices() { + return Collections.unmodifiableCollection( services.values() ); + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java index 07c86c6b6b..311bd08099 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/PatternConstraintInitializer.java @@ -8,37 +8,43 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; -public interface PatternConstraintInitializer extends AutoCloseable { +import org.hibernate.validator.HibernateValidatorFactory; +import org.hibernate.validator.constraintvalidation.HibernateValidatorFactoryObserver; - Pattern of(String pattern, int flags); +public abstract sealed class PatternConstraintInitializer implements HibernateValidatorFactoryObserver + permits PatternConstraintInitializer.SimplePatternConstraintInitializer, PatternConstraintInitializer.PredefinedPatternConstraintInitializer { + private final Map cache = new ConcurrentHashMap<>(); - @Override - default void close() { + public static PatternConstraintInitializer predefined() { + return new PredefinedPatternConstraintInitializer(); } - class SimplePatternConstraintInitializer implements PatternConstraintInitializer { + public static PatternConstraintInitializer simple() { + return new SimplePatternConstraintInitializer(); + } - @Override - public Pattern of(String pattern, int flags) { - return Pattern.compile( pattern, flags ); - } + public final Pattern of(String pattern, int flags) { + return cache.computeIfAbsent( new PatternKey( pattern, flags ), key -> Pattern.compile( pattern, flags ) ); } - class CachingPatternConstraintInitializer implements PatternConstraintInitializer { - private final Map cache = new ConcurrentHashMap<>(); + protected void clearCache() { + cache.clear(); + } + static final class SimplePatternConstraintInitializer extends PatternConstraintInitializer { @Override - public Pattern of(String pattern, int flags) { - return cache.computeIfAbsent( new PatternKey( pattern, flags ), key -> Pattern.compile( pattern, flags ) ); + public void factoryClosing(HibernateValidatorFactory factory) { + clearCache(); } + } + static final class PredefinedPatternConstraintInitializer extends PatternConstraintInitializer { @Override - public void close() { - cache.clear(); - } - - private record PatternKey(String pattern, int flags) { + public void factoryCreated(HibernateValidatorFactory factory) { + clearCache(); } } + private record PatternKey(String pattern, int flags) { + } } diff --git a/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java b/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java index e5b079763a..d664426351 100644 --- a/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java +++ b/engine/src/test/java/org/hibernate/validator/testutils/ConstraintValidatorInitializationHelper.java @@ -101,7 +101,7 @@ public Duration getTemporalValidationTolerance() { @Override public C getConstraintValidatorInitializationSharedService(Class type) { if ( PatternConstraintInitializer.class.equals( type ) ) { - return (C) new PatternConstraintInitializer.SimplePatternConstraintInitializer(); + return (C) PatternConstraintInitializer.simple(); } return null; }