Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,18 @@ public interface BaseHibernateValidatorConfiguration<S extends BaseHibernateVali
@Incubating
S constraintValidatorPayload(Object constraintValidatorPayload);

/**
* Allows adding a payload which will be available during the constraint validators initialization.
* If the method is called multiple times passing different instances of the same class,
* only the payload passed last will be available for that type.
*
* @param constraintValidatorInitializationPayload the payload to retrieve from the constraint validator initializers
* @return {@code this} following the chaining method pattern
* @since 9.1.0
*/
@Incubating
S addConstraintValidatorInitializationPayload(Object constraintValidatorInitializationPayload);

/**
* Allows to set a getter property selection strategy defining the rules determining if a method is a getter
* or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,18 @@ public interface HibernateConstraintValidatorInitializationContext {
*/
@Incubating
Duration getTemporalValidationTolerance();

/**
* Returns an instance of the specified type or {@code null} if the current constraint initialization context does not
* contain an instance of such type.
*
* @param type the type of payload 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#addConstraintValidatorInitializationPayload(Object)
*/
@Incubating
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a payload thout, is it? More of a shared/reusable/cached context?

Also I wouldn't include ConstraintValidator in the name considering we're in HibernateConstraintValidatorInitializationContext already.

Suggested change
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
<C> C getCached(Class<C> type);

or

Suggested change
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
<C> C getCache(Class<C> type);

Alternatively if you really intend a cache here, you could make this closer to what @Sanne added to Hibernate ORM recently: https://github.com/hibernate/hibernate-orm/blob/bfa7102e228887a7f9e45211a7b051e5b1791215/hibernate-core/src/main/java/org/hibernate/internal/util/cache/InternalCacheFactory.java#L14 . Which would allow integrators to plug a custom cache provider, like Caffeine, which is known to have very good performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That being said if the cache is only there for bootstrap, and gets cleared/deleted once bootstrap is finished, using Caffeine is probably overkill...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah ... naming is hard 😔 🙂
Yes, it's closer to shared context. The other thought that I had about this: if we were always running the validator in the CDI context and with the predefined scope (i.e. all metadata is built at boot and not dynamically at runtime), I'd think of PatternConstraintInitializer as a bean that we inject into the PatternValidator, and the scope of such beans would be the init phase of the validator factory... but we aren't always running in CDI 🙈

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,40 @@
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;

/**
* @author Hardy Ferentschik
*/
public class PatternValidator implements ConstraintValidator<Pattern, CharSequence> {
public class PatternValidator implements HibernateConstraintValidator<Pattern, CharSequence> {

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

private java.util.regex.Pattern pattern;
private String escapedRegexp;

@Override
public void initialize(Pattern parameters) {
public void initialize(ConstraintDescriptor<Pattern> constraintDescriptor, HibernateConstraintValidatorInitializationContext initializationContext) {
Pattern parameters = constraintDescriptor.getAnnotation();
Pattern.Flag[] flags = parameters.flags();
int intFlag = 0;
for ( Pattern.Flag flag : flags ) {
intFlag = intFlag | flag.getValue();
}

try {
pattern = java.util.regex.Pattern.compile( parameters.regexp(), intFlag );
pattern = initializationContext.getConstraintValidatorInitializationPayload( PatternConstraintInitializer.class )
.of( parameters.regexp(), intFlag );
}
catch (PatternSyntaxException e) {
throw LOG.getInvalidRegularExpressionException( e );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package org.hibernate.validator.internal.engine;

import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

Expand Down Expand Up @@ -123,6 +124,7 @@ public abstract class AbstractConfigurationImpl<T extends BaseHibernateValidator
private ScriptEvaluatorFactory scriptEvaluatorFactory;
private Duration temporalValidationTolerance;
private Object constraintValidatorPayload;
private final Map<Class<?>, Object> constraintValidatorInitializationPayload = newHashMap();
private GetterPropertySelectionStrategy getterPropertySelectionStrategy;
private Set<Locale> locales = Collections.emptySet();
private Locale defaultLocale = Locale.getDefault();
Expand Down Expand Up @@ -352,6 +354,14 @@ public T constraintValidatorPayload(Object constraintValidatorPayload) {
return thisAsT();
}

@Override
public T addConstraintValidatorInitializationPayload(Object constraintValidatorInitializationPayload) {
Contracts.assertNotNull( constraintValidatorInitializationPayload, MESSAGES.parameterMustNotBeNull( "constraintValidatorInitializationPayload" ) );

this.constraintValidatorInitializationPayload.put( constraintValidatorInitializationPayload.getClass(), constraintValidatorInitializationPayload );
return thisAsT();
}

@Override
public T getterPropertySelectionStrategy(GetterPropertySelectionStrategy getterPropertySelectionStrategy) {
Contracts.assertNotNull( getterPropertySelectionStrategy, MESSAGES.parameterMustNotBeNull( "getterPropertySelectionStrategy" ) );
Expand Down Expand Up @@ -548,6 +558,10 @@ public Object getConstraintValidatorPayload() {
return constraintValidatorPayload;
}

public Map<Class<?>, Object> getConstraintValidatorInitializationPayload() {
return constraintValidatorInitializationPayload;
}

public GetterPropertySelectionStrategy getGetterPropertySelectionStrategy() {
return getterPropertySelectionStrategy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.determineConstraintValidatorInitializationPayload;
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
determineConstraintValidatorInitializationPayload( hibernateSpecificConfig, patternConstraintInitializer ) );

this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext(
configurationState.getMessageInterpolator(),
Expand Down Expand Up @@ -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 );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.lang.invoke.MethodHandles;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -21,6 +22,7 @@
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.PatternConstraintInitializer;
import org.hibernate.validator.internal.engine.messageinterpolation.DefaultLocaleResolver;
import org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory;
import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer;
Expand Down Expand Up @@ -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();
Expand All @@ -263,6 +264,23 @@ static Object determineConstraintValidatorPayload(ConfigurationState configurati
return null;
}

static Map<Class<?>, Object> determineConstraintValidatorInitializationPayload(ConfigurationState configurationState, PatternConstraintInitializer patternConstraintInitializer) {
if ( configurationState instanceof AbstractConfigurationImpl<?> hibernateSpecificConfig ) {
if ( hibernateSpecificConfig.getConstraintValidatorPayload() != null ) {
Map<Class<?>, Object> configured = hibernateSpecificConfig.getConstraintValidatorInitializationPayload();
Map<Class<?>, Object> payload = new HashMap<>();
payload.put( PatternConstraintInitializer.class, patternConstraintInitializer );
if ( configured != null ) {
payload.putAll( configured );
}
LOG.logConstraintValidatorInitializationPayload( payload );
return Collections.unmodifiableMap( payload );
}
}

return Map.of( PatternConstraintInitializer.class, patternConstraintInitializer );
}

static ExpressionLanguageFeatureLevel determineConstraintExpressionLanguageFeatureLevel(AbstractConfigurationImpl<?> hibernateSpecificConfig,
Map<String, String> properties) {
if ( hibernateSpecificConfig != null && hibernateSpecificConfig.getConstraintExpressionLanguageFeatureLevel() != null ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.determineConstraintValidatorInitializationPayload;
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -168,6 +170,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) {
determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ),
determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties ),
determineConstraintValidatorPayload( hibernateSpecificConfig ),
determineConstraintValidatorInitializationPayload( hibernateSpecificConfig, new PatternConstraintInitializer.SimplePatternConstraintInitializer() ),
determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties )
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.hibernate.validator.internal.engine;

import java.time.Duration;
import java.util.Map;

import jakarta.validation.ClockProvider;
import jakarta.validation.MessageInterpolator;
Expand Down Expand Up @@ -103,13 +104,15 @@ public class ValidatorFactoryScopedContext {
boolean traversableResolverResultCacheEnabled,
boolean showValidatedValuesInTraceLogs,
Object constraintValidatorPayload,
Map<Class<?>, Object> constraintValidatorInitializationPayload,
ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel,
ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel) {
this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast,
failFastOnPropertyViolation, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel,
customViolationExpressionLanguageFeatureLevel,
new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider,
temporalValidationTolerance ) );
temporalValidationTolerance, constraintValidatorInitializationPayload
) );
}

ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator,
Expand Down Expand Up @@ -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." );
Expand Down Expand Up @@ -348,7 +351,8 @@ public ValidatorFactoryScopedContext build() {
constraintValidatorInitializationContext,
scriptEvaluatorFactory,
clockProvider,
temporalValidationTolerance
temporalValidationTolerance,
constraintValidatorInitializationContext.getConstraintValidatorInitializationPayload()
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.hibernate.validator.internal.engine.constraintvalidation;

import java.time.Duration;
import java.util.Map;

import jakarta.validation.ClockProvider;

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

private final Duration temporalValidationTolerance;

private final Map<Class<?>, Object> constraintValidatorInitializationPayload;

private final int hashCode;

public HibernateConstraintValidatorInitializationContextImpl(ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider,
Duration temporalValidationTolerance) {
Duration temporalValidationTolerance, Map<Class<?>, Object> constraintValidatorInitializationPayload
) {
this.scriptEvaluatorFactory = scriptEvaluatorFactory;
this.clockProvider = clockProvider;
this.temporalValidationTolerance = temporalValidationTolerance;
this.constraintValidatorInitializationPayload = constraintValidatorInitializationPayload;
this.hashCode = createHashCode();
}

public static HibernateConstraintValidatorInitializationContextImpl of(HibernateConstraintValidatorInitializationContextImpl defaultContext,
ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, Duration temporalValidationTolerance) {
ScriptEvaluatorFactory scriptEvaluatorFactory, ClockProvider clockProvider, Duration temporalValidationTolerance, Map<Class<?>, Object> constraintValidatorInitializationPayload) {
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, constraintValidatorInitializationPayload );
}

@Override
Expand All @@ -59,6 +64,16 @@ public Duration getTemporalValidationTolerance() {
return temporalValidationTolerance;
}

@SuppressWarnings("unchecked") // because of the way we populate that map
@Override
public <C> C getConstraintValidatorInitializationPayload(Class<C> type) {
return ( (C) constraintValidatorInitializationPayload.get( type ) );
}

public Map<Class<?>, Object> getConstraintValidatorInitializationPayload() {
return constraintValidatorInitializationPayload;
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PatternKey, Pattern> cache = new ConcurrentHashMap<PatternKey, Pattern>();

@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) {
}
}

}
Loading
Loading