/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */ package org.hibernate.validator.internal.engine.cascading; import java.lang.reflect.TypeVariable; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import javax.validation.ValidationException; import javax.validation.valueextraction.ValueExtractor; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.TypeHelper; import org.hibernate.validator.internal.util.TypeVariableBindings; import org.hibernate.validator.internal.util.TypeVariables; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; import org.hibernate.validator.internal.util.privilegedactions.LoadClass; import org.hibernate.validator.internal.util.stereotypes.Immutable; /** * @author Gunnar Morling * @author Guillaume Smet */ public class ValueExtractorManager { private static final Log LOG = LoggerFactory.make(); @Immutable public static final Set<ValueExtractorDescriptor> SPEC_DEFINED_EXTRACTORS; static { LinkedHashSet<ValueExtractorDescriptor> specDefinedExtractors = new LinkedHashSet<>(); if ( isJavaFxInClasspath() ) { specDefinedExtractors.add( ObservableValueValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ListPropertyValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ReadOnlyListPropertyValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( MapPropertyValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ReadOnlyMapPropertyValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( MapPropertyKeyExtractor.DESCRIPTOR ); specDefinedExtractors.add( ReadOnlyMapPropertyKeyExtractor.DESCRIPTOR ); specDefinedExtractors.add( SetPropertyValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ReadOnlySetPropertyValueExtractor.DESCRIPTOR ); } specDefinedExtractors.add( ByteArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ShortArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( IntArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( LongArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( FloatArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( DoubleArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( CharArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( BooleanArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ObjectArrayValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( LegacyListValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ListValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( LegacyMapValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( MapValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( MapKeyExtractor.DESCRIPTOR ); specDefinedExtractors.add( LegacyIterableValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( IterableValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( OptionalValueExtractor.DESCRIPTOR ); specDefinedExtractors.add( ObjectValueExtractor.DESCRIPTOR ); SPEC_DEFINED_EXTRACTORS = Collections.unmodifiableSet( specDefinedExtractors ); } @Immutable private final Map<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> valueExtractors; public ValueExtractorManager(Set<ValueExtractor<?>> externalExtractors) { LinkedHashMap<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> tmpValueExtractors = new LinkedHashMap<>(); // first all built-in extractors for ( ValueExtractorDescriptor descriptor : SPEC_DEFINED_EXTRACTORS ) { tmpValueExtractors.put( descriptor.getKey(), descriptor ); } tmpValueExtractors.put( LegacyOptionalValueExtractor.DESCRIPTOR.getKey(), LegacyOptionalValueExtractor.DESCRIPTOR ); // then any custom ones, overriding the built-in ones for ( ValueExtractor<?> valueExtractor : externalExtractors ) { ValueExtractorDescriptor descriptor = new ValueExtractorDescriptor( valueExtractor ); tmpValueExtractors.put( descriptor.getKey(), descriptor ); } valueExtractors = Collections.unmodifiableMap( tmpValueExtractors ); } public ValueExtractorManager(ValueExtractorManager template, Map<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> externalValueExtractorDescriptors) { LinkedHashMap<ValueExtractorDescriptor.Key, ValueExtractorDescriptor> tmpValueExtractors = new LinkedHashMap<>( template.valueExtractors ); tmpValueExtractors.putAll( externalValueExtractorDescriptors ); valueExtractors = Collections.unmodifiableMap( tmpValueExtractors ); } public static Set<ValueExtractor<?>> getDefaultValueExtractors() { return SPEC_DEFINED_EXTRACTORS.stream() .map( d -> d.getValueExtractor() ) .collect( Collectors.collectingAndThen( Collectors.toSet(), Collections::unmodifiableSet ) ); } /** * Returns the most specific value extractor extracting the given type or {@code null} if none was found. */ public ValueExtractorDescriptor getValueExtractor(Class<?> valueType) { List<ValueExtractorDescriptor> typeCompatibleExtractors = valueExtractors.values() .stream() .filter( e -> TypeHelper.isAssignable( TypeHelper.getErasedReferenceType( e.getExtractedType() ), valueType ) ) .collect( Collectors.toList() ); return getMostSpecific( valueType, typeCompatibleExtractors ); } public ValueExtractorDescriptor getValueExtractor(Class<?> valueType, TypeVariable<?> typeParameter) { boolean isInternal = TypeVariables.isInternal( typeParameter ); Map<Class<?>, Map<TypeVariable<?>, TypeVariable<?>>> allBindings = null; if ( !isInternal ) { allBindings = TypeVariableBindings.getTypeVariableBindings( (Class<?>) typeParameter.getGenericDeclaration() ); } List<ValueExtractorDescriptor> typeCompatibleExtractors = valueExtractors.values() .stream() .filter( e -> TypeHelper.isAssignable( e.getExtractedType(), valueType ) ) .collect( Collectors.toList() ); List<ValueExtractorDescriptor> typeParameterCompatibleExtractors = new ArrayList<>(); for ( ValueExtractorDescriptor extractorDescriptor : typeCompatibleExtractors ) { TypeVariable<?> typeParameterBoundToExtractorType; if ( !isInternal ) { Map<TypeVariable<?>, TypeVariable<?>> bindingsForExtractorType = allBindings.get( extractorDescriptor.getExtractedType() ); typeParameterBoundToExtractorType = bind( typeParameter, bindingsForExtractorType ); } else { typeParameterBoundToExtractorType = typeParameter; } if ( Objects.equals( extractorDescriptor.getExtractedTypeParameter(), typeParameterBoundToExtractorType ) ) { typeParameterCompatibleExtractors.add( extractorDescriptor ); } } return getMostSpecific( valueType, typeParameterCompatibleExtractors ); } private ValueExtractorDescriptor getMostSpecific(Class<?> valueType, List<ValueExtractorDescriptor> extractors) { Set<ValueExtractorDescriptor> candidates = CollectionHelper.newHashSet( extractors.size() ); for ( ValueExtractorDescriptor descriptor : extractors ) { if ( candidates.isEmpty() ) { candidates.add( descriptor ); continue; } Iterator<ValueExtractorDescriptor> candidatesIterator = candidates.iterator(); boolean isNewRoot = true; while ( candidatesIterator.hasNext() ) { ValueExtractorDescriptor candidate = candidatesIterator.next(); if ( TypeHelper.isAssignable( candidate.getExtractedType(), descriptor.getExtractedType() ) ) { candidatesIterator.remove(); } else if ( TypeHelper.isAssignable( descriptor.getExtractedType(), candidate.getExtractedType() ) ) { isNewRoot = false; } } if ( isNewRoot ) { candidates.add( descriptor ); } } if ( candidates.isEmpty() ) { return null; } else if ( candidates.size() == 1 ) { return candidates.iterator().next(); } else { @SuppressWarnings("rawtypes") List<Class<? extends ValueExtractor>> valueExtractorCandidates = candidates.stream() .map( valueExtractorDescriptor -> valueExtractorDescriptor.getValueExtractor().getClass() ) .collect( Collectors.toList() ); throw LOG.unableToGetMostSpecificValueExtractorDueToSeveralValueExtractorsDefinedForParallelHierarchies( valueType, valueExtractorCandidates ); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ( ( valueExtractors == null ) ? 0 : valueExtractors.hashCode() ); return result; } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } ValueExtractorManager other = (ValueExtractorManager) obj; return valueExtractors.equals( other.valueExtractors ); } private TypeVariable<?> bind(TypeVariable<?> typeParameter, Map<TypeVariable<?>, TypeVariable<?>> bindings) { return bindings != null ? bindings.get( typeParameter ) : null; } private static boolean isJavaFxInClasspath() { return isClassPresent( "javafx.application.Application", false ); } private static boolean isClassPresent(String className, boolean fallbackOnTCCL) { try { run( LoadClass.action( className, ValueExtractorManager.class.getClassLoader(), fallbackOnTCCL ) ); return true; } catch (ValidationException e) { return false; } } /** * Runs the given privileged action, using a privileged block if required. * <p> * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary * privileged actions within HV's protection domain. */ private static <T> T run(PrivilegedAction<T> action) { return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } }