package org.hibernate.validator.internal.engine.constraintvalidation;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintValidatorFactory;
import jakarta.validation.constraints.Null;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
public class ConstraintValidatorManagerImpl extends AbstractConstraintValidatorManagerImpl {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
private static final ConstraintValidator<?, ?> DUMMY_CONSTRAINT_VALIDATOR = new ConstraintValidator<Null, Object>() {
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return false;
}
};
private volatile ConstraintValidatorFactory mostRecentlyUsedNonDefaultConstraintValidatorFactory;
private volatile HibernateConstraintValidatorInitializationContext mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext;
private final Object mostRecentlyUsedNonDefaultConstraintValidatorFactoryAndInitializationContextMutex = new Object();
private final ConcurrentHashMap<CacheKey, ConstraintValidator<?, ?>> constraintValidatorCache;
public ConstraintValidatorManagerImpl(ConstraintValidatorFactory defaultConstraintValidatorFactory,
HibernateConstraintValidatorInitializationContext defaultConstraintValidatorInitializationContext) {
super( defaultConstraintValidatorFactory, defaultConstraintValidatorInitializationContext );
this.constraintValidatorCache = new ConcurrentHashMap<>();
}
@Override
public boolean isPredefinedScope() {
return false;
}
@Override
public <A extends Annotation> ConstraintValidator<A, ?> getInitializedValidator(
Type validatedValueType,
ConstraintDescriptorImpl<A> descriptor,
ConstraintValidatorFactory constraintValidatorFactory,
HibernateConstraintValidatorInitializationContext initializationContext) {
Contracts.assertNotNull( validatedValueType );
Contracts.assertNotNull( descriptor );
Contracts.assertNotNull( constraintValidatorFactory );
Contracts.assertNotNull( initializationContext );
CacheKey key = new CacheKey( descriptor.getAnnotationDescriptor(), validatedValueType, constraintValidatorFactory, initializationContext );
@SuppressWarnings("unchecked")
ConstraintValidator<A, ?> constraintValidator = (ConstraintValidator<A, ?>) constraintValidatorCache.get( key );
if ( constraintValidator == null ) {
constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );
constraintValidator = cacheValidator( key, constraintValidator );
}
else {
LOG.tracef( "Constraint validator %s found in cache.", constraintValidator );
}
return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator;
}
private <A extends Annotation> ConstraintValidator<A, ?> cacheValidator(CacheKey key,
ConstraintValidator<A, ?> constraintValidator) {
if ( ( key.getConstraintValidatorFactory() != getDefaultConstraintValidatorFactory()
&& key.getConstraintValidatorFactory() != mostRecentlyUsedNonDefaultConstraintValidatorFactory ) ||
( key.getConstraintValidatorInitializationContext() != getDefaultConstraintValidatorInitializationContext()
&& key.getConstraintValidatorInitializationContext() != mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext ) ) {
synchronized ( mostRecentlyUsedNonDefaultConstraintValidatorFactoryAndInitializationContextMutex ) {
if ( key.constraintValidatorFactory != mostRecentlyUsedNonDefaultConstraintValidatorFactory ||
key.constraintValidatorInitializationContext != mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext ) {
clearEntries( mostRecentlyUsedNonDefaultConstraintValidatorFactory, mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext );
mostRecentlyUsedNonDefaultConstraintValidatorFactory = key.getConstraintValidatorFactory();
mostRecentlyUsedNonDefaultConstraintValidatorInitializationContext = key.getConstraintValidatorInitializationContext();
}
}
}
@SuppressWarnings("unchecked")
ConstraintValidator<A, ?> cached = (ConstraintValidator<A, ?>) constraintValidatorCache.putIfAbsent( key,
constraintValidator != null ? constraintValidator : DUMMY_CONSTRAINT_VALIDATOR );
return cached != null ? cached : constraintValidator;
}
private void clearEntries(ConstraintValidatorFactory constraintValidatorFactory, HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext) {
Iterator<Entry<CacheKey, ConstraintValidator<?, ?>>> cacheEntries = constraintValidatorCache.entrySet().iterator();
while ( cacheEntries.hasNext() ) {
Entry<CacheKey, ConstraintValidator<?, ?>> cacheEntry = cacheEntries.next();
if ( cacheEntry.getKey().getConstraintValidatorFactory() == constraintValidatorFactory &&
cacheEntry.getKey().getConstraintValidatorInitializationContext() == constraintValidatorInitializationContext ) {
constraintValidatorFactory.releaseInstance( cacheEntry.getValue() );
cacheEntries.remove();
}
}
}
@Override
public void clear() {
for ( Map.Entry<CacheKey, ConstraintValidator<?, ?>> entry : constraintValidatorCache.entrySet() ) {
entry.getKey().getConstraintValidatorFactory().releaseInstance( entry.getValue() );
}
constraintValidatorCache.clear();
}
public int numberOfCachedConstraintValidatorInstances() {
return constraintValidatorCache.size();
}
private static final class CacheKey {
private ConstraintAnnotationDescriptor<?> annotationDescriptor;
private Type validatedType;
private ConstraintValidatorFactory constraintValidatorFactory;
private HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext;
private int hashCode;
private CacheKey(ConstraintAnnotationDescriptor<?> annotationDescriptor, Type validatorType, ConstraintValidatorFactory constraintValidatorFactory,
HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext) {
this.annotationDescriptor = annotationDescriptor;
this.validatedType = validatorType;
this.constraintValidatorFactory = constraintValidatorFactory;
this.constraintValidatorInitializationContext = constraintValidatorInitializationContext;
this.hashCode = createHashCode();
}
public ConstraintValidatorFactory getConstraintValidatorFactory() {
return constraintValidatorFactory;
}
public HibernateConstraintValidatorInitializationContext getConstraintValidatorInitializationContext() {
return constraintValidatorInitializationContext;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null ) {
return false;
}
CacheKey other = (CacheKey) o;
if ( !annotationDescriptor.equals( other.annotationDescriptor ) ) {
return false;
}
if ( !validatedType.equals( other.validatedType ) ) {
return false;
}
if ( !constraintValidatorFactory.equals( other.constraintValidatorFactory ) ) {
return false;
}
if ( !constraintValidatorInitializationContext.equals( other.constraintValidatorInitializationContext ) ) {
return false;
}
return true;
}
@Override
public int hashCode() {
return hashCode;
}
private int createHashCode() {
int result = annotationDescriptor.hashCode();
result = 31 * result + validatedType.hashCode();
result = 31 * result + constraintValidatorFactory.hashCode();
result = 31 * result + constraintValidatorInitializationContext.hashCode();
return result;
}
}
}