org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl Maven / Gradle / Ivy
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or .
*/
package org.hibernate.validator.internal.metadata.aggregated;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ElementKind;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import org.hibernate.validator.internal.engine.groups.Sequence;
import org.hibernate.validator.internal.engine.groups.ValidationOrder;
import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
import org.hibernate.validator.internal.metadata.core.MetaConstraint;
import org.hibernate.validator.internal.metadata.descriptor.BeanDescriptorImpl;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
import org.hibernate.validator.internal.metadata.descriptor.ExecutableDescriptorImpl;
import org.hibernate.validator.internal.metadata.facets.Cascadable;
import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind;
import org.hibernate.validator.internal.util.CollectionHelper;
import org.hibernate.validator.internal.util.ExecutableHelper;
import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper;
import org.hibernate.validator.internal.util.classhierarchy.Filters;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.internal.util.stereotypes.Immutable;
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
/**
* This class encapsulates all meta data needed for validation. Implementations of {@code Validator} interface can
* instantiate an instance of this class and delegate the metadata extraction to it.
*
* @author Hardy Ferentschik
* @author Gunnar Morling
* @author Kevin Pollet <[email protected]> (C) 2011 SERLI
* @author Chris Beckey <[email protected]>
* @author Guillaume Smet
* @author Marko Bekhta
*/
public final class BeanMetaDataImpl implements BeanMetaData {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
/**
* Represents the "sequence" of just Default.class.
*/
private static final List> DEFAULT_GROUP_SEQUENCE = Collections.>singletonList( Default.class );
/**
* Whether there are any constraints or cascades at all.
*/
private final boolean hasConstraints;
private final ValidationOrderGenerator validationOrderGenerator;
/**
* The root bean class for this meta data.
*/
private final Class beanClass;
/**
* Set of all constraints for this bean type (defined on any implemented interfaces or super types)
*/
@Immutable
private final Set> allMetaConstraints;
/**
* Set of all constraints which are directly defined on the bean or any of the directly implemented interfaces
*/
@Immutable
private final Set> directMetaConstraints;
/**
* Contains constrained related meta data for all the constrained methods and constructors of the type represented
* by this bean meta data. Keyed by executable, values are an aggregated view on each executable together with all
* the executables from the inheritance hierarchy with the same signature.
*
* An entry will be stored once under the signature of the represented method and all the methods it overrides
* (there will only be more than one entry in case of generics in the parameters, e.g. in case of a super-type
* method {@code foo(T)} and an overriding sub-type method {@code foo(String)} two entries for the same executable
* meta-data will be stored).
*/
@Immutable
private final Map executableMetaDataMap;
/**
* The set of unconstrained executables of the bean. It contains all the relevant signatures, following the same
* rules as {@code executableMetaDataMap}.
*/
@Immutable
private final Set unconstrainedExecutables;
/**
* Property meta data keyed against the property name
*/
@Immutable
private final Map propertyMetaDataMap;
/**
* The cascaded properties of this bean.
*/
@Immutable
private final Set cascadedProperties;
/**
* The default groups sequence for this bean class.
*/
@Immutable
private final List> defaultGroupSequence;
/**
* The default group sequence provider.
*
* @see org.hibernate.validator.group.GroupSequenceProvider
* @see DefaultGroupSequenceProvider
*/
private final DefaultGroupSequenceProvider defaultGroupSequenceProvider;
private final ValidationOrder validationOrder;
/**
* The class hierarchy for this class starting with the class itself going up the inheritance chain. Interfaces
* are not included.
*/
@Immutable
private final List> classHierarchyWithoutInterfaces;
/**
* {code true} if the default group sequence is redefined, either via a group sequence redefinition or a group
* sequence provider.
*/
private final boolean defaultGroupSequenceRedefined;
/**
* The resolved default group sequence.
*/
private final List> resolvedDefaultGroupSequence;
/**
* The bean descriptor for this bean. Lazily created.
*/
private volatile BeanDescriptor beanDescriptor;
/**
* Creates a new {@link BeanMetaDataImpl}
*
* @param beanClass The Java type represented by this meta data object.
* @param defaultGroupSequence The default group sequence.
* @param defaultGroupSequenceProvider The default group sequence provider if set.
* @param constraintMetaDataSet All constraint meta data relating to the represented type.
*/
public BeanMetaDataImpl(Class beanClass,
List> defaultGroupSequence,
DefaultGroupSequenceProvider defaultGroupSequenceProvider,
Set constraintMetaDataSet,
ValidationOrderGenerator validationOrderGenerator) {
this.validationOrderGenerator = validationOrderGenerator;
this.beanClass = beanClass;
this.propertyMetaDataMap = newHashMap();
Set propertyMetaDataSet = newHashSet();
Set executableMetaDataSet = newHashSet();
Set tmpUnconstrainedExecutables = newHashSet();
boolean hasConstraints = false;
Set> allMetaConstraints = newHashSet();
for ( ConstraintMetaData constraintMetaData : constraintMetaDataSet ) {
boolean elementHasConstraints = constraintMetaData.isCascading() || constraintMetaData.isConstrained();
hasConstraints |= elementHasConstraints;
if ( constraintMetaData.getKind() == ElementKind.PROPERTY ) {
propertyMetaDataSet.add( (PropertyMetaData) constraintMetaData );
}
else if ( constraintMetaData.getKind() == ElementKind.BEAN ) {
allMetaConstraints.addAll( ( (ClassMetaData) constraintMetaData ).getAllConstraints() );
}
else {
ExecutableMetaData executableMetaData = (ExecutableMetaData) constraintMetaData;
if ( elementHasConstraints ) {
executableMetaDataSet.add( executableMetaData );
}
else {
tmpUnconstrainedExecutables.addAll( executableMetaData.getSignatures() );
}
}
}
Set cascadedProperties = newHashSet();
for ( PropertyMetaData propertyMetaData : propertyMetaDataSet ) {
propertyMetaDataMap.put( propertyMetaData.getName(), propertyMetaData );
cascadedProperties.addAll( propertyMetaData.getCascadables() );
allMetaConstraints.addAll( propertyMetaData.getAllConstraints() );
}
this.hasConstraints = hasConstraints;
this.cascadedProperties = CollectionHelper.toImmutableSet( cascadedProperties );
this.allMetaConstraints = CollectionHelper.toImmutableSet( allMetaConstraints );
this.classHierarchyWithoutInterfaces = CollectionHelper.toImmutableList( ClassHierarchyHelper.getHierarchy(
beanClass,
Filters.excludeInterfaces()
) );
DefaultGroupSequenceContext defaultGroupContext = getDefaultGroupSequenceData( beanClass, defaultGroupSequence, defaultGroupSequenceProvider, validationOrderGenerator );
this.defaultGroupSequenceProvider = defaultGroupContext.defaultGroupSequenceProvider;
this.defaultGroupSequence = CollectionHelper.toImmutableList( defaultGroupContext.defaultGroupSequence );
this.validationOrder = defaultGroupContext.validationOrder;
this.directMetaConstraints = getDirectConstraints();
this.executableMetaDataMap = CollectionHelper.toImmutableMap( bySignature( executableMetaDataSet ) );
this.unconstrainedExecutables = CollectionHelper.toImmutableSet( tmpUnconstrainedExecutables );
// We initialize those elements eagerly so that any eventual error is thrown when bootstrapping the bean metadata
this.defaultGroupSequenceRedefined = this.defaultGroupSequence.size() > 1 || hasDefaultGroupSequenceProvider();
this.resolvedDefaultGroupSequence = getDefaultGroupSequence( null );
}
@Override
public Class getBeanClass() {
return beanClass;
}
@Override
public boolean hasConstraints() {
return hasConstraints;
}
@Override
public BeanDescriptor getBeanDescriptor() {
BeanDescriptor beanDescriptor = this.beanDescriptor;
if ( beanDescriptor == null ) {
synchronized (this) {
beanDescriptor = this.beanDescriptor;
if ( beanDescriptor == null ) {
beanDescriptor = createBeanDescriptor( beanClass, allMetaConstraints, propertyMetaDataMap, executableMetaDataMap,
defaultGroupSequenceRedefined, resolvedDefaultGroupSequence );
this.beanDescriptor = beanDescriptor;
}
}
}
return beanDescriptor;
}
@Override
public Set getCascadables() {
return cascadedProperties;
}
@Override
public boolean hasCascadables() {
return !cascadedProperties.isEmpty();
}
@Override
public PropertyMetaData getMetaDataFor(String propertyName) {
PropertyMetaData propertyMetaData = propertyMetaDataMap.get( propertyName );
if ( propertyMetaData == null ) {
throw LOG.getPropertyNotDefinedByValidatedTypeException( beanClass, propertyName );
}
return propertyMetaData;
}
@Override
public Set> getMetaConstraints() {
return allMetaConstraints;
}
@Override
public Set> getDirectMetaConstraints() {
return directMetaConstraints;
}
@Override
public Optional getMetaDataFor(Executable executable) {
String signature = ExecutableHelper.getSignature( executable );
if ( unconstrainedExecutables.contains( signature ) ) {
return Optional.empty();
}
ExecutableMetaData executableMetaData = executableMetaDataMap.get( ExecutableHelper.getSignature( executable ) );
if ( executableMetaData == null ) {
// there is no executable metadata - specified object and method do not match
throw LOG.getMethodOrConstructorNotDefinedByValidatedTypeException(
beanClass,
executable
);
}
return Optional.of( executableMetaData );
}
@Override
public List> getDefaultGroupSequence(T beanState) {
if ( hasDefaultGroupSequenceProvider() ) {
List> providerDefaultGroupSequence = defaultGroupSequenceProvider.getValidationGroups( beanState );
return getValidDefaultGroupSequence( beanClass, providerDefaultGroupSequence );
}
return defaultGroupSequence;
}
@Override
public Iterator getDefaultValidationSequence(T beanState) {
if ( hasDefaultGroupSequenceProvider() ) {
List> providerDefaultGroupSequence = defaultGroupSequenceProvider.getValidationGroups( beanState );
return validationOrderGenerator.getDefaultValidationOrder(
beanClass,
getValidDefaultGroupSequence( beanClass, providerDefaultGroupSequence )
)
.getSequenceIterator();
}
else {
return validationOrder.getSequenceIterator();
}
}
@Override
public boolean isDefaultGroupSequenceRedefined() {
return defaultGroupSequenceRedefined;
}
@Override
public List> getClassHierarchy() {
return classHierarchyWithoutInterfaces;
}
private static BeanDescriptor createBeanDescriptor(Class beanClass, Set> allMetaConstraints,
Map propertyMetaDataMap, Map executableMetaDataMap, boolean defaultGroupSequenceRedefined,
List> resolvedDefaultGroupSequence) {
Map propertyDescriptors = getConstrainedPropertiesAsDescriptors(
propertyMetaDataMap,
defaultGroupSequenceRedefined,
resolvedDefaultGroupSequence
);
Map methodsDescriptors = getConstrainedMethodsAsDescriptors(
executableMetaDataMap,
defaultGroupSequenceRedefined,
resolvedDefaultGroupSequence
);
Map constructorsDescriptors = getConstrainedConstructorsAsDescriptors(
executableMetaDataMap,
defaultGroupSequenceRedefined,
resolvedDefaultGroupSequence
);
return new BeanDescriptorImpl(
beanClass,
getClassLevelConstraintsAsDescriptors( allMetaConstraints ),
propertyDescriptors,
methodsDescriptors,
constructorsDescriptors,
defaultGroupSequenceRedefined,
resolvedDefaultGroupSequence
);
}
private static Set> getClassLevelConstraintsAsDescriptors(Set> constraints) {
return constraints.stream()
.filter( c -> c.getConstraintLocationKind() == ConstraintLocationKind.TYPE )
.map( MetaConstraint::getDescriptor )
.collect( Collectors.toSet() );
}
private static Map getConstrainedPropertiesAsDescriptors(Map propertyMetaDataMap,
boolean defaultGroupSequenceIsRedefined, List> resolvedDefaultGroupSequence) {
Map theValue = newHashMap();
for ( Entry entry : propertyMetaDataMap.entrySet() ) {
if ( entry.getValue().isConstrained() && entry.getValue().getName() != null ) {
theValue.put(
entry.getKey(),
entry.getValue().asDescriptor(
defaultGroupSequenceIsRedefined,
resolvedDefaultGroupSequence
)
);
}
}
return theValue;
}
private static Map getConstrainedMethodsAsDescriptors(Map executableMetaDataMap,
boolean defaultGroupSequenceIsRedefined, List> resolvedDefaultGroupSequence) {
Map constrainedMethodDescriptors = newHashMap();
for ( ExecutableMetaData executableMetaData : executableMetaDataMap.values() ) {
if ( executableMetaData.getKind() == ElementKind.METHOD
&& executableMetaData.isConstrained() ) {
ExecutableDescriptorImpl descriptor = executableMetaData.asDescriptor(
defaultGroupSequenceIsRedefined,
resolvedDefaultGroupSequence
);
for ( String signature : executableMetaData.getSignatures() ) {
constrainedMethodDescriptors.put( signature, descriptor );
}
}
}
return constrainedMethodDescriptors;
}
private static Map getConstrainedConstructorsAsDescriptors(Map executableMetaDataMap,
boolean defaultGroupSequenceIsRedefined, List> resolvedDefaultGroupSequence) {
Map constrainedMethodDescriptors = newHashMap();
for ( ExecutableMetaData executableMetaData : executableMetaDataMap.values() ) {
if ( executableMetaData.getKind() == ElementKind.CONSTRUCTOR && executableMetaData.isConstrained() ) {
constrainedMethodDescriptors.put(
// constructors never override, so there will be exactly one identifier
executableMetaData.getSignatures().iterator().next(),
executableMetaData.asDescriptor(
defaultGroupSequenceIsRedefined,
resolvedDefaultGroupSequence
)
);
}
}
return constrainedMethodDescriptors;
}
private static DefaultGroupSequenceContext getDefaultGroupSequenceData(Class beanClass, List> defaultGroupSequence, DefaultGroupSequenceProvider defaultGroupSequenceProvider, ValidationOrderGenerator validationOrderGenerator) {
if ( defaultGroupSequence != null && defaultGroupSequenceProvider != null ) {
throw LOG.getInvalidDefaultGroupSequenceDefinitionException();
}
DefaultGroupSequenceContext context = new DefaultGroupSequenceContext<>();
if ( defaultGroupSequenceProvider != null ) {
context.defaultGroupSequenceProvider = defaultGroupSequenceProvider;
context.defaultGroupSequence = Collections.emptyList();
context.validationOrder = null;
}
else if ( defaultGroupSequence != null && !defaultGroupSequence.isEmpty() ) {
context.defaultGroupSequence = getValidDefaultGroupSequence( beanClass, defaultGroupSequence );
context.validationOrder = validationOrderGenerator.getDefaultValidationOrder( beanClass, context.defaultGroupSequence );
}
else {
context.defaultGroupSequence = DEFAULT_GROUP_SEQUENCE;
context.validationOrder = ValidationOrder.DEFAULT_SEQUENCE;
}
return context;
}
private Set> getDirectConstraints() {
Set> constraints = newHashSet();
Set> classAndInterfaces = newHashSet();
classAndInterfaces.add( beanClass );
classAndInterfaces.addAll( ClassHierarchyHelper.getDirectlyImplementedInterfaces( beanClass ) );
for ( Class clazz : classAndInterfaces ) {
for ( MetaConstraint metaConstraint : allMetaConstraints ) {
if ( metaConstraint.getLocation().getDeclaringClass().equals( clazz ) ) {
constraints.add( metaConstraint );
}
}
}
return CollectionHelper.toImmutableSet( constraints );
}
/**
* Builds up the method meta data for this type; each meta-data entry will be stored under the signature of the
* represented method and all the methods it overrides.
*/
private Map bySignature(Set executables) {
Map theValue = newHashMap();
for ( ExecutableMetaData executableMetaData : executables ) {
for ( String signature : executableMetaData.getSignatures() ) {
theValue.put( signature, executableMetaData );
}
}
return theValue;
}
private static List> getValidDefaultGroupSequence(Class beanClass, List> groupSequence) {
List> validDefaultGroupSequence = new ArrayList<>();
boolean groupSequenceContainsDefault = false;
if ( groupSequence != null ) {
for ( Class group : groupSequence ) {
if ( group.getName().equals( beanClass.getName() ) ) {
validDefaultGroupSequence.add( Default.class );
groupSequenceContainsDefault = true;
}
else if ( group.getName().equals( Default.class.getName() ) ) {
throw LOG.getNoDefaultGroupInGroupSequenceException();
}
else {
validDefaultGroupSequence.add( group );
}
}
}
if ( !groupSequenceContainsDefault ) {
throw LOG.getBeanClassMustBePartOfRedefinedDefaultGroupSequenceException( beanClass );
}
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Members of the default group sequence for bean %s are: %s.",
beanClass.getName(),
validDefaultGroupSequence
);
}
return validDefaultGroupSequence;
}
private boolean hasDefaultGroupSequenceProvider() {
return defaultGroupSequenceProvider != null;
}
@Override
public String toString() {
return "BeanMetaDataImpl"
+ "{beanClass=" + beanClass.getSimpleName()
+ ", constraintCount=" + getMetaConstraints().size()
+ ", cascadedPropertiesCount=" + cascadedProperties.size()
+ ", defaultGroupSequence=" + getDefaultGroupSequence( null ) + '}';
}
/**
* Tuple for returning default group sequence, provider and validation order at once.
*/
private static class DefaultGroupSequenceContext {
List> defaultGroupSequence;
DefaultGroupSequenceProvider defaultGroupSequenceProvider;
ValidationOrder validationOrder;
}
}