Skip to content

Commit b5a14d2

Browse files
committed
HV-2004 Add constraint initialization payload
and use it to cache patterns in the predefined factory
1 parent 81d13c3 commit b5a14d2

File tree

13 files changed

+170
-20
lines changed

13 files changed

+170
-20
lines changed

engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,18 @@ public interface BaseHibernateValidatorConfiguration<S extends BaseHibernateVali
363363
@Incubating
364364
S constraintValidatorPayload(Object constraintValidatorPayload);
365365

366+
/**
367+
* Allows adding a payload which will be available during the constraint validators initialization.
368+
* If the method is called multiple times passing different instances of the same class,
369+
* only the payload passed last will be available for that type.
370+
*
371+
* @param constraintValidatorInitializationPayload the payload to retrieve from the constraint validator initializers
372+
* @return {@code this} following the chaining method pattern
373+
* @since 9.0.0
374+
*/
375+
@Incubating
376+
S addConstraintValidatorInitializationPayload(Object constraintValidatorInitializationPayload);
377+
366378
/**
367379
* Allows to set a getter property selection strategy defining the rules determining if a method is a getter
368380
* or not.

engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateConstraintValidatorInitializationContext.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,18 @@ public interface HibernateConstraintValidatorInitializationContext {
5656
*/
5757
@Incubating
5858
Duration getTemporalValidationTolerance();
59+
60+
/**
61+
* Returns an instance of the specified type or {@code null} if the current constraint initialization context does not
62+
* contain an instance of such type.
63+
*
64+
* @param type the type of payload to retrieve
65+
* @return an instance of the specified type or {@code null} if the current constraint initialization context does not
66+
* contain an instance of such type
67+
*
68+
* @since 9.0.0
69+
* @see org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationPayload(Object)
70+
*/
71+
@Incubating
72+
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
5973
}

engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/bv/PatternValidator.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,40 @@
88
import java.util.regex.Matcher;
99
import java.util.regex.PatternSyntaxException;
1010

11-
import jakarta.validation.ConstraintValidator;
1211
import jakarta.validation.ConstraintValidatorContext;
1312
import jakarta.validation.constraints.Pattern;
13+
import jakarta.validation.metadata.ConstraintDescriptor;
1414

15+
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidator;
1516
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
17+
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;
18+
import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer;
1619
import org.hibernate.validator.internal.engine.messageinterpolation.util.InterpolationHelper;
1720
import org.hibernate.validator.internal.util.logging.Log;
1821
import org.hibernate.validator.internal.util.logging.LoggerFactory;
1922

2023
/**
2124
* @author Hardy Ferentschik
2225
*/
23-
public class PatternValidator implements ConstraintValidator<Pattern, CharSequence> {
26+
public class PatternValidator implements HibernateConstraintValidator<Pattern, CharSequence> {
2427

2528
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
2629

2730
private java.util.regex.Pattern pattern;
2831
private String escapedRegexp;
2932

3033
@Override
31-
public void initialize(Pattern parameters) {
34+
public void initialize(ConstraintDescriptor<Pattern> constraintDescriptor, HibernateConstraintValidatorInitializationContext initializationContext) {
35+
Pattern parameters = constraintDescriptor.getAnnotation();
3236
Pattern.Flag[] flags = parameters.flags();
3337
int intFlag = 0;
3438
for ( Pattern.Flag flag : flags ) {
3539
intFlag = intFlag | flag.getValue();
3640
}
3741

3842
try {
39-
pattern = java.util.regex.Pattern.compile( parameters.regexp(), intFlag );
43+
pattern = initializationContext.getConstraintValidatorInitializationPayload( PatternConstraintInitializer.class )
44+
.of( parameters.regexp(), intFlag );
4045
}
4146
catch (PatternSyntaxException e) {
4247
throw LOG.getInvalidRegularExpressionException( e );

engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
package org.hibernate.validator.internal.engine;
66

7+
import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
78
import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
89
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
910

@@ -122,6 +123,7 @@ public abstract class AbstractConfigurationImpl<T extends BaseHibernateValidator
122123
private ScriptEvaluatorFactory scriptEvaluatorFactory;
123124
private Duration temporalValidationTolerance;
124125
private Object constraintValidatorPayload;
126+
private final Map<Class<?>, Object> constraintValidatorInitializationPayload = newHashMap();
125127
private GetterPropertySelectionStrategy getterPropertySelectionStrategy;
126128
private Set<Locale> locales = Collections.emptySet();
127129
private Locale defaultLocale = Locale.getDefault();
@@ -350,6 +352,14 @@ public T constraintValidatorPayload(Object constraintValidatorPayload) {
350352
return thisAsT();
351353
}
352354

355+
@Override
356+
public T addConstraintValidatorInitializationPayload(Object constraintValidatorInitializationPayload) {
357+
Contracts.assertNotNull( constraintValidatorInitializationPayload, MESSAGES.parameterMustNotBeNull( "constraintValidatorInitializationPayload" ) );
358+
359+
this.constraintValidatorInitializationPayload.put( constraintValidatorInitializationPayload.getClass(), constraintValidatorInitializationPayload );
360+
return thisAsT();
361+
}
362+
353363
@Override
354364
public T getterPropertySelectionStrategy(GetterPropertySelectionStrategy getterPropertySelectionStrategy) {
355365
Contracts.assertNotNull( getterPropertySelectionStrategy, MESSAGES.parameterMustNotBeNull( "getterPropertySelectionStrategy" ) );
@@ -546,6 +556,10 @@ public Object getConstraintValidatorPayload() {
546556
return constraintValidatorPayload;
547557
}
548558

559+
public Map<Class<?>, Object> getConstraintValidatorInitializationPayload() {
560+
return constraintValidatorInitializationPayload;
561+
}
562+
549563
public GetterPropertySelectionStrategy getGetterPropertySelectionStrategy() {
550564
return getterPropertySelectionStrategy;
551565
}

engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer;
1111
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel;
1212
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings;
13+
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorInitializationPayload;
1314
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload;
1415
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel;
1516
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader;
@@ -46,6 +47,7 @@
4647
import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory;
4748
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
4849
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
50+
import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer;
4951
import org.hibernate.validator.internal.engine.constraintvalidation.PredefinedScopeConstraintValidatorManagerImpl;
5052
import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
5153
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
@@ -118,6 +120,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
118120
determineAllowParallelMethodsDefineParameterConstraints( hibernateSpecificConfig, properties )
119121
).build();
120122

123+
PatternConstraintInitializer.CachingPatternConstraintInitializer patternConstraintInitializer = new PatternConstraintInitializer.CachingPatternConstraintInitializer();
121124
this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext(
122125
configurationState.getMessageInterpolator(),
123126
configurationState.getTraversableResolver(),
@@ -129,6 +132,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
129132
determineFailFastOnPropertyViolation( hibernateSpecificConfig, properties ),
130133
determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ),
131134
determineConstraintValidatorPayload( hibernateSpecificConfig ),
135+
determineConstraintValidatorInitializationPayload( hibernateSpecificConfig, patternConstraintInitializer ),
132136
determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
133137
determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
134138
determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties )
@@ -214,6 +218,9 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
214218
beanClassesToInitialize
215219
);
216220

221+
// at this point all constraints had to be initialized, so we can clear up the pattern cache:
222+
patternConstraintInitializer.close();
223+
217224
if ( LOG.isDebugEnabled() ) {
218225
logValidatorFactoryScopedConfiguration( validatorFactoryScopedContext );
219226
}

engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryConfigurationHelper.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.lang.invoke.MethodHandles;
1212
import java.time.Duration;
1313
import java.util.Collections;
14+
import java.util.HashMap;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Set;
@@ -21,6 +22,7 @@
2122
import org.hibernate.validator.cfg.ConstraintMapping;
2223
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
2324
import org.hibernate.validator.internal.engine.constraintdefinition.ConstraintDefinitionContribution;
25+
import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer;
2426
import org.hibernate.validator.internal.engine.messageinterpolation.DefaultLocaleResolver;
2527
import org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory;
2628
import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer;
@@ -252,8 +254,7 @@ static Duration determineTemporalValidationTolerance(ConfigurationState configur
252254
}
253255

254256
static Object determineConstraintValidatorPayload(ConfigurationState configurationState) {
255-
if ( configurationState instanceof AbstractConfigurationImpl ) {
256-
AbstractConfigurationImpl<?> hibernateSpecificConfig = (AbstractConfigurationImpl<?>) configurationState;
257+
if ( configurationState instanceof AbstractConfigurationImpl<?> hibernateSpecificConfig ) {
257258
if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) {
258259
LOG.logConstraintValidatorPayload( hibernateSpecificConfig.getConstraintValidatorPayload() );
259260
return hibernateSpecificConfig.getConstraintValidatorPayload();
@@ -263,6 +264,23 @@ static Object determineConstraintValidatorPayload(ConfigurationState configurati
263264
return null;
264265
}
265266

267+
static Map<Class<?>, Object> determineConstraintValidatorInitializationPayload(ConfigurationState configurationState, PatternConstraintInitializer patternConstraintInitializer) {
268+
if ( configurationState instanceof AbstractConfigurationImpl<?> hibernateSpecificConfig ) {
269+
if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) {
270+
Map<Class<?>, Object> configured = hibernateSpecificConfig.getConstraintValidatorInitializationPayload();
271+
Map<Class<?>, Object> payload = new HashMap<>();
272+
payload.put( PatternConstraintInitializer.class, patternConstraintInitializer );
273+
if ( configured != null ) {
274+
payload.putAll( configured );
275+
}
276+
LOG.logConstraintValidatorInitializationPayload( payload );
277+
return Collections.unmodifiableMap( payload );
278+
}
279+
}
280+
281+
return Map.of( PatternConstraintInitializer.class, patternConstraintInitializer );
282+
}
283+
266284
static ExpressionLanguageFeatureLevel determineConstraintExpressionLanguageFeatureLevel(AbstractConfigurationImpl<?> hibernateSpecificConfig,
267285
Map<String, String> properties) {
268286
if ( hibernateSpecificConfig != null && hibernateSpecificConfig.getConstraintExpressionLanguageFeatureLevel() != null ) {

engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer;
1111
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintExpressionLanguageFeatureLevel;
1212
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings;
13+
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorInitializationPayload;
1314
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload;
1415
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineCustomViolationExpressionLanguageFeatureLevel;
1516
import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader;
@@ -47,6 +48,7 @@
4748
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
4849
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
4950
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl;
51+
import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer;
5052
import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
5153
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
5254
import org.hibernate.validator.internal.metadata.BeanMetaDataManager;
@@ -163,6 +165,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) {
163165
determineFailFastOnPropertyViolation( hibernateSpecificConfig, properties ),
164166
determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ),
165167
determineConstraintValidatorPayload( hibernateSpecificConfig ),
168+
determineConstraintValidatorInitializationPayload( hibernateSpecificConfig, new PatternConstraintInitializer.SimplePatternConstraintInitializer() ),
166169
determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
167170
determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
168171
determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties )

engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryScopedContext.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.validator.internal.engine;
66

77
import java.time.Duration;
8+
import java.util.Map;
89

910
import jakarta.validation.ClockProvider;
1011
import jakarta.validation.MessageInterpolator;
@@ -102,14 +103,16 @@ public class ValidatorFactoryScopedContext {
102103
boolean failFastOnPropertyViolation,
103104
boolean traversableResolverResultCacheEnabled,
104105
Object constraintValidatorPayload,
106+
Map<Class<?>, Object> constraintValidatorInitializationPayload,
105107
ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel,
106108
ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel,
107109
boolean showValidatedValuesInTraceLogs) {
108110
this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast,
109111
failFastOnPropertyViolation, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel,
110112
customViolationExpressionLanguageFeatureLevel,
111113
new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider,
112-
temporalValidationTolerance ) );
114+
temporalValidationTolerance, constraintValidatorInitializationPayload
115+
) );
113116
}
114117

115118
private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator,
@@ -214,7 +217,7 @@ static class Builder {
214217
private ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel;
215218

216219
private boolean showValidatedValuesInTraceLogs;
217-
private HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext;
220+
private final HibernateConstraintValidatorInitializationContextImpl constraintValidatorInitializationContext;
218221

219222
Builder(ValidatorFactoryScopedContext defaultContext) {
220223
Contracts.assertNotNull( defaultContext, "Default context cannot be null." );
@@ -348,7 +351,8 @@ public ValidatorFactoryScopedContext build() {
348351
constraintValidatorInitializationContext,
349352
scriptEvaluatorFactory,
350353
clockProvider,
351-
temporalValidationTolerance
354+
temporalValidationTolerance,
355+
constraintValidatorInitializationContext.getConstraintValidatorInitializationPayload()
352356
)
353357
);
354358
}

engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/HibernateConstraintValidatorInitializationContextImpl.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.validator.internal.engine.constraintvalidation;
66

77
import java.time.Duration;
8+
import java.util.Map;
89

910
import jakarta.validation.ClockProvider;
1011

@@ -23,25 +24,29 @@ public class HibernateConstraintValidatorInitializationContextImpl implements Hi
2324

2425
private final Duration temporalValidationTolerance;
2526

27+
private final Map<Class<?>, Object> constraintValidatorInitializationPayload;
28+
2629
private final int hashCode;
2730

2831
public HibernateConstraintValidatorInitializationContextImpl(ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider,
29-
Duration temporalValidationTolerance) {
32+
Duration temporalValidationTolerance, Map<Class<?>, Object> constraintValidatorInitializationPayload
33+
) {
3034
this.scriptEvaluatorFactory = scriptEvaluatorFactory;
3135
this.clockProvider = clockProvider;
3236
this.temporalValidationTolerance = temporalValidationTolerance;
37+
this.constraintValidatorInitializationPayload = constraintValidatorInitializationPayload;
3338
this.hashCode = createHashCode();
3439
}
3540

3641
public static HibernateConstraintValidatorInitializationContextImpl of(HibernateConstraintValidatorInitializationContextImpl defaultContext,
37-
ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, Duration temporalValidationTolerance) {
42+
ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, Duration temporalValidationTolerance, Map<Class<?>, Object> constraintValidatorInitializationPayload) {
3843
if ( scriptEvaluatorFactory == defaultContext.scriptEvaluatorFactory
3944
&& clockProvider == defaultContext.clockProvider
4045
&& temporalValidationTolerance.equals( defaultContext.temporalValidationTolerance ) ) {
4146
return defaultContext;
4247
}
4348

44-
return new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, temporalValidationTolerance );
49+
return new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider, temporalValidationTolerance, constraintValidatorInitializationPayload );
4550
}
4651

4752
@Override
@@ -59,6 +64,16 @@ public Duration getTemporalValidationTolerance() {
5964
return temporalValidationTolerance;
6065
}
6166

67+
@SuppressWarnings("unchecked") // because of the way we populate that map
68+
@Override
69+
public <C> C getConstraintValidatorInitializationPayload(Class<C> type) {
70+
return ( (C) constraintValidatorInitializationPayload.get( type ) );
71+
}
72+
73+
public Map<Class<?>, Object> getConstraintValidatorInitializationPayload() {
74+
return constraintValidatorInitializationPayload;
75+
}
76+
6277
@Override
6378
public boolean equals(Object o) {
6479
if ( this == o ) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.validator.internal.engine.constraintvalidation;
6+
7+
import java.util.Map;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.regex.Pattern;
10+
11+
public interface PatternConstraintInitializer extends AutoCloseable {
12+
13+
Pattern of(String pattern, int flags);
14+
15+
@Override
16+
default void close() {
17+
}
18+
19+
class SimplePatternConstraintInitializer implements PatternConstraintInitializer {
20+
21+
@Override
22+
public Pattern of(String pattern, int flags) {
23+
return Pattern.compile( pattern, flags );
24+
}
25+
}
26+
27+
class CachingPatternConstraintInitializer implements PatternConstraintInitializer {
28+
private final Map<PatternKey, Pattern> cache = new ConcurrentHashMap<PatternKey, Pattern>();
29+
30+
@Override
31+
public Pattern of(String pattern, int flags) {
32+
return cache.computeIfAbsent( new PatternKey( pattern, flags ), key -> Pattern.compile( pattern, flags ) );
33+
}
34+
35+
@Override
36+
public void close() {
37+
cache.clear();
38+
}
39+
40+
private record PatternKey(String pattern, int flags) {
41+
}
42+
}
43+
44+
}

0 commit comments

Comments
 (0)