Skip to content

HV-2004 Add constraint initialization payload #1587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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 @@ -363,6 +363,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.0.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.0.0
* @see org.hibernate.validator.HibernateValidatorConfiguration#addConstraintValidatorInitializationPayload(Object)
*/
@Incubating
<C> C getConstraintValidatorInitializationPayload(Class<C> type);
}
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 @@ -122,6 +123,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 @@ -350,6 +352,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 @@ -546,6 +556,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 @@ -46,6 +47,7 @@
import org.hibernate.validator.PredefinedScopeHibernateValidatorFactory;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
import org.hibernate.validator.internal.engine.constraintvalidation.PatternConstraintInitializer;
import org.hibernate.validator.internal.engine.constraintvalidation.PredefinedScopeConstraintValidatorManagerImpl;
import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
Expand Down Expand Up @@ -118,6 +120,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
determineAllowParallelMethodsDefineParameterConstraints( hibernateSpecificConfig, properties )
).build();

PatternConstraintInitializer.CachingPatternConstraintInitializer patternConstraintInitializer = new PatternConstraintInitializer.CachingPatternConstraintInitializer();
this.validatorFactoryScopedContext = new ValidatorFactoryScopedContext(
configurationState.getMessageInterpolator(),
configurationState.getTraversableResolver(),
Expand All @@ -129,6 +132,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
determineFailFastOnPropertyViolation( hibernateSpecificConfig, properties ),
determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ),
determineConstraintValidatorPayload( hibernateSpecificConfig ),
determineConstraintValidatorInitializationPayload( hibernateSpecificConfig, patternConstraintInitializer ),
determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
determineShowValidatedValuesInTraceLogs( hibernateSpecificConfig, properties )
Expand Down Expand Up @@ -214,6 +218,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.valueextraction.ValueExtractorManager;
import org.hibernate.validator.internal.metadata.BeanMetaDataManager;
Expand Down Expand Up @@ -163,6 +165,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) {
determineFailFastOnPropertyViolation( hibernateSpecificConfig, properties ),
determineTraversableResolverResultCacheEnabled( hibernateSpecificConfig, properties ),
determineConstraintValidatorPayload( hibernateSpecificConfig ),
determineConstraintValidatorInitializationPayload( hibernateSpecificConfig, new PatternConstraintInitializer.SimplePatternConstraintInitializer() ),
determineConstraintExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
determineCustomViolationExpressionLanguageFeatureLevel( hibernateSpecificConfig, properties ),
determineShowValidatedValuesInTraceLogs( 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 @@ -102,14 +103,16 @@ public class ValidatorFactoryScopedContext {
boolean failFastOnPropertyViolation,
boolean traversableResolverResultCacheEnabled,
Object constraintValidatorPayload,
Map<Class<?>, Object> constraintValidatorInitializationPayload,
ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel,
ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel,
boolean showValidatedValuesInTraceLogs) {
this( messageInterpolator, traversableResolver, parameterNameProvider, clockProvider, temporalValidationTolerance, scriptEvaluatorFactory, failFast,
failFastOnPropertyViolation, traversableResolverResultCacheEnabled, showValidatedValuesInTraceLogs, constraintValidatorPayload, constraintExpressionLanguageFeatureLevel,
customViolationExpressionLanguageFeatureLevel,
new HibernateConstraintValidatorInitializationContextImpl( scriptEvaluatorFactory, clockProvider,
temporalValidationTolerance ) );
temporalValidationTolerance, constraintValidatorInitializationPayload
) );
}

private ValidatorFactoryScopedContext(MessageInterpolator messageInterpolator,
Expand Down Expand Up @@ -214,7 +217,7 @@ static class Builder {
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