Package org.hibernate.validator.metadata

Source Code of org.hibernate.validator.metadata.BeanMetaDataImpl

/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hibernate.validator.metadata;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.validation.GroupDefinitionException;
import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.PropertyDescriptor;

import org.slf4j.Logger;

import org.hibernate.validator.group.DefaultGroupSequenceProvider;
import org.hibernate.validator.group.GroupSequenceProvider;
import org.hibernate.validator.metadata.AggregatedMethodMetaData.Builder;
import org.hibernate.validator.method.metadata.TypeDescriptor;
import org.hibernate.validator.util.LoggerFactory;
import org.hibernate.validator.util.ReflectionHelper;

import static org.hibernate.validator.util.CollectionHelper.newArrayList;
import static org.hibernate.validator.util.CollectionHelper.newHashMap;
import static org.hibernate.validator.util.CollectionHelper.newHashSet;
import static org.hibernate.validator.util.ReflectionHelper.computeAllImplementedInterfaces;
import static org.hibernate.validator.util.ReflectionHelper.getMethods;
import static org.hibernate.validator.util.ReflectionHelper.newInstance;

/**
* 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 - SERLI - (kevin.pollet@serli.com)
*/
public final class BeanMetaDataImpl<T> implements BeanMetaData<T> {

  private static final Logger log = LoggerFactory.make();

  /**
   * Used as prefix for parameter names, if no explicit names are given.
   */
  public static final String DEFAULT_PARAMETER_NAME_PREFIX = "arg";

  /**
   * The root bean class for this meta data.
   */
  private final Class<T> beanClass;

  /**
   * Map of all direct constraints which belong to the entity {@code beanClass}. The constraints are mapped to the class
   * (eg super class or interface) in which they are defined.
   */
  private final Map<Class<?>, List<BeanMetaConstraint<?>>> metaConstraints = newHashMap();

  /**
   * Set of all constraints for this bean type (defined on any implemented interfaces or super types)
   */
  private final Set<BeanMetaConstraint<?>> allMetaConstraints;

  /**
   * Set of all constraints which are directly defined on the bean or any of the directly implemented interfaces
   */
  private final Set<BeanMetaConstraint<?>> directMetaConstraints;

  /**
   * The main element descriptor for {@link #beanClass}.
   */
  private BeanDescriptorImpl<T> beanDescriptor;

  /**
   * Contains constrained related meta data for all methods of the type
   * represented by this bean meta data. Keyed by method, values are an
   * aggregated view on each method together with all the methods from the
   * inheritance hierarchy with the same signature.
   */
  private Map<Method, AggregatedMethodMetaData> methodMetaData;

  /**
   * Builders used for gathering method meta data. Used only at construction
   * time.
   */
  private Set<AggregatedMethodMetaData.Builder> methodMetaDataBuilders = newHashSet();

  /**
   * List of cascaded members.
   */
  private Set<Member> cascadedMembers = newHashSet();

  /**
   * Maps field and method names to their {@code ElementDescriptorImpl}.
   */
  private Map<String, PropertyDescriptor> propertyDescriptors = newHashMap();

  /**
   * The default groups sequence for this bean class.
   */
  private List<Class<?>> defaultGroupSequence = newArrayList();

  /**
   * The default group sequence provider.
   *
   * @see org.hibernate.validator.group.GroupSequenceProvider
   * @see DefaultGroupSequenceProvider
   */
  private DefaultGroupSequenceProvider<T> defaultGroupSequenceProvider;

  /**
   * The class hierarchy for this class starting with the class itself going up the inheritance chain. Interfaces
   * are not included.
   */
  private List<Class<?>> classHierarchyWithoutInterfaces;

  /**
   * Object used to determine whether a given annotation is a constraint. Only used during initialization.
   */
  private ConstraintHelper constraintHelper;

  /**
   * A list of all property names in the class (constrained and un-constrained).
   */
  // Used to avoid ReflectionHelper#containsMember which is slow
  private final Set<String> propertyNames = newHashSet( 30 );

  /**
   * Constructor used for creating the bean meta data using annotations only
   *
   * @param beanClass The bean type for which to create the meta data
   * @param constraintHelper constraint helper
   * @param beanMetaDataCache the cache of already configured meta data instances
   */
  public BeanMetaDataImpl(Class<T> beanClass, ConstraintHelper constraintHelper, BeanMetaDataCache beanMetaDataCache) {
    this(
        beanClass,
        constraintHelper,
        new ArrayList<Class<?>>(),
        null,
        new HashMap<Class<?>, List<BeanMetaConstraint<?>>>(),
        new HashSet<AggregatedMethodMetaData>(),
        new HashSet<Member>(),
        new AnnotationIgnores(),
        beanMetaDataCache
    );
  }

  /**
   * Constructor used when creating a bean meta data instance via the xml or programmatic API. In this case
   * additional metadata (the already configured constraints, cascaded members, etc) are passed as well.
   *
   * @param beanClass The bean type for which to create the meta data
   * @param constraintHelper constraint helper
   * @param defaultGroupSequence programmatic/xml configured default group sequence (overrides annotations)
   * @param defaultGroupSequenceProvider programmatic configured default group sequence provider class (overrides annotations)
   * @param constraints programmatic/xml configured constraints
   * @param methodMetaDatas programmatic configured method constraints
   * @param cascadedMembers programmatic/xml configured cascaded members
   * @param annotationIgnores in xml configured ignores for annotations
   * @param beanMetaDataCache the cache of already configured meta data instances
   */
  public BeanMetaDataImpl(Class<T> beanClass,
              ConstraintHelper constraintHelper,
              List<Class<?>> defaultGroupSequence,
              Class<? extends DefaultGroupSequenceProvider<?>> defaultGroupSequenceProvider,
              Map<Class<?>, List<BeanMetaConstraint<?>>> constraints,
              Set<AggregatedMethodMetaData> methodMetaDatas,
              Set<Member> cascadedMembers,
              AnnotationIgnores annotationIgnores,
              BeanMetaDataCache beanMetaDataCache) {
    this.beanClass = beanClass;
    this.constraintHelper = constraintHelper;
    this.defaultGroupSequenceProvider = null;
    for ( Member member : cascadedMembers ) {
      addCascadedMember( member );
    }

    classHierarchyWithoutInterfaces = ReflectionHelper.computeClassHierarchy( beanClass, false );

    // start the annotation discovery phase (look for annotations in the whole class hierarchy)
    createMetaData( annotationIgnores, beanMetaDataCache );

    // set the default explicitly specified default group sequence after the discovery process is complete
    if ( !defaultGroupSequence.isEmpty() ) {
      setDefaultGroupSequence( defaultGroupSequence );
    }

    // set the default explicitly specified default group sequence provider after the discovery process is complete
    if ( defaultGroupSequenceProvider != null ) {
      this.defaultGroupSequenceProvider = newGroupSequenceProviderInstance( defaultGroupSequenceProvider );
    }

    // validates that programmatic/xml definition of default group sequence or default group sequence provider
    // doesn't introduce illegal default group sequence definition.
    if ( hasDefaultGroupSequenceProvider() && this.defaultGroupSequence.size() > 1 ) {
      throw new GroupDefinitionException(
          "Default group sequence and default group sequence provider cannot be defined at the same time"
      );
    }

    // add the explicitly configured constraints
    for ( Map.Entry<Class<?>, List<BeanMetaConstraint<?>>> entry : constraints.entrySet() ) {
      Class<?> clazz = entry.getKey();

      // will hold the method constraints (getter and non-getter) of the given class keyed by method
      Map<Method, List<MethodMetaConstraint<?>>> constraintsByMethod = newHashMap();

      for ( BeanMetaConstraint<?> constraint : entry.getValue() ) {

        if ( constraint.getDescriptor().getElementType() == ElementType.METHOD ) {

          List<MethodMetaConstraint<?>> constraintsForMethod = constraintsByMethod.get(
              constraint.getLocation().getMember()
          );
          if ( constraintsForMethod == null ) {
            constraintsForMethod = newArrayList();
            constraintsByMethod.put(
                (Method) constraint.getLocation().getMember(), constraintsForMethod
            );
          }

          constraintsForMethod.add( getAsMethodMetaConstraint( constraint ) );
        }
        //register non-method constraints
        else {
          addMetaConstraint( clazz, constraint );
        }
      }

      // register the constraints for each method in methodMetaConstraints. Constraints at getters will also registered in metaConstraints
      for ( Entry<Method, List<MethodMetaConstraint<?>>> methodAndConstraints : constraintsByMethod
          .entrySet() ) {

        MethodMetaData methodMetaData = new MethodMetaData(
            methodAndConstraints.getKey(),
            methodAndConstraints.getValue(),
            cascadedMembers.contains( methodAndConstraints.getKey() )
        );
        addMethodMetaConstraint( clazz, methodMetaData );
      }

    }

    allMetaConstraints = buildAllConstraintSets();
    directMetaConstraints = buildDirectConstraintSets();

    // add the explicitly configured method constraints, here we need to merge the programmatic and discovered
    // metadata built with the "automatic" discovering.
    if ( !methodMetaDatas.isEmpty() ) {
      for ( AggregatedMethodMetaData aggregatedMethodMetaData : methodMetaDatas ) {
        for ( MethodMetaData methodMetaData : aggregatedMethodMetaData.getAllMethodMetaData() ) {
          Method method = methodMetaData.getMethod();
          addMethodMetaConstraint( method.getDeclaringClass(), methodMetaData );
        }
      }
    }

    this.methodMetaData = Collections.unmodifiableMap( buildMethodMetaData() );

    // reset class members we don't need any longer
    this.methodMetaDataBuilders = null;
    this.constraintHelper = null;
  }

  public Class<T> getBeanClass() {
    return beanClass;
  }

  public BeanDescriptor getBeanDescriptor() {
    return beanDescriptor;
  }

  public TypeDescriptor getTypeDescriptor() {
    return beanDescriptor;
  }

  public Set<Member> getCascadedMembers() {
    return Collections.unmodifiableSet( cascadedMembers );
  }

  public Map<Class<?>, List<BeanMetaConstraint<?>>> getMetaConstraintsAsMap() {
    return Collections.unmodifiableMap( metaConstraints );
  }

  public Set<BeanMetaConstraint<?>> getMetaConstraints() {
    return allMetaConstraints;
  }

  public Set<BeanMetaConstraint<?>> getDirectMetaConstraints() {
    return directMetaConstraints;
  }

  public AggregatedMethodMetaData getMetaDataFor(Method method) {
    return methodMetaData.get( method );
  }

  public Set<AggregatedMethodMetaData> getAllMethodMetaData() {
    return new HashSet<AggregatedMethodMetaData>( methodMetaData.values() );
  }

  public PropertyDescriptor getPropertyDescriptor(String property) {
    return propertyDescriptors.get( property );
  }

  public boolean isPropertyPresent(String name) {
    return propertyNames.contains( name );
  }

  public List<Class<?>> getDefaultGroupSequence(T beanState) {
    if ( hasDefaultGroupSequenceProvider() ) {
      List<Class<?>> providerDefaultGroupSequence = defaultGroupSequenceProvider.getValidationGroups( beanState );
      return getValidDefaultGroupSequence( providerDefaultGroupSequence );
    }

    return Collections.unmodifiableList( defaultGroupSequence );
  }

  public boolean defaultGroupSequenceIsRedefined() {
    return defaultGroupSequence.size() > 1 || hasDefaultGroupSequenceProvider();
  }

  private boolean hasDefaultGroupSequenceProvider() {
    return defaultGroupSequenceProvider != null;
  }

  public Set<PropertyDescriptor> getConstrainedProperties() {
    return Collections.unmodifiableSet( new HashSet<PropertyDescriptor>( propertyDescriptors.values() ) );
  }

  private Set<BeanMetaConstraint<?>> buildAllConstraintSets() {
    Set<BeanMetaConstraint<?>> constraints = newHashSet();
    for ( List<BeanMetaConstraint<?>> list : metaConstraints.values() ) {
      constraints.addAll( list );
    }
    return Collections.unmodifiableSet( constraints );
  }

  private Set<BeanMetaConstraint<?>> buildDirectConstraintSets() {
    Set<BeanMetaConstraint<?>> constraints = newHashSet();
    // collect all constraints directly defined in this bean
    if ( metaConstraints.get( beanClass ) != null ) {
      constraints.addAll( metaConstraints.get( beanClass ) );
    }
    Set<Class<?>> classAndInterfaces = computeAllImplementedInterfaces( beanClass );
    for ( Class<?> clazz : classAndInterfaces ) {
      if ( metaConstraints.get( clazz ) != null ) {
        constraints.addAll( metaConstraints.get( clazz ) );
      }
    }
    return Collections.unmodifiableSet( constraints );
  }

  /**
   * Builds up the method meta data for this type by invoking each builder in
   * {@link #methodMetaDataBuilders}.
   */
  private Map<Method, AggregatedMethodMetaData> buildMethodMetaData() {

    Map<Method, AggregatedMethodMetaData> theValue = newHashMap();

    for ( AggregatedMethodMetaData.Builder oneBuilder : methodMetaDataBuilders ) {

      AggregatedMethodMetaData aggregatedMethodMetaData = oneBuilder.build();

      //register the aggregated meta data for each underlying method for a quick
      //read access
      for ( MethodMetaData oneMethodMetaData : aggregatedMethodMetaData.getAllMethodMetaData() ) {
        theValue.put( oneMethodMetaData.getMethod(), aggregatedMethodMetaData );
      }
    }

    return theValue;
  }

  private void setDefaultGroupSequence(List<Class<?>> groupSequence) {
    defaultGroupSequence = getValidDefaultGroupSequence( groupSequence );
  }

  private List<Class<?>> getValidDefaultGroupSequence(List<Class<?>> groupSequence) {
    List<Class<?>> validDefaultGroupSequence = new ArrayList<Class<?>>();

    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 new GroupDefinitionException( "'Default.class' cannot appear in default group sequence list." );
        }
        else {
          validDefaultGroupSequence.add( group );
        }
      }
    }
    if ( !groupSequenceContainsDefault ) {
      throw new GroupDefinitionException( beanClass.getName() + " must be part of the redefined default group sequence." );
    }
    if ( log.isTraceEnabled() ) {
      log.trace(
          "Members of the default group sequence for bean {} are: {}",
          beanClass.getName(),
          validDefaultGroupSequence
      );
    }

    return validDefaultGroupSequence;
  }

  private void addMetaConstraint(Class<?> clazz, BeanMetaConstraint<?> metaConstraint) {
    // first we add the meta constraint to our meta constraint map
    List<BeanMetaConstraint<?>> constraintList;
    if ( !metaConstraints.containsKey( clazz ) ) {
      constraintList = new ArrayList<BeanMetaConstraint<?>>();
      metaConstraints.put( clazz, constraintList );
    }
    else {
      constraintList = metaConstraints.get( clazz );
    }
    constraintList.add( metaConstraint );

    // but we also have to update the descriptors exposing the BV metadata API
    if ( metaConstraint.getElementType() == ElementType.TYPE ) {
      beanDescriptor.addConstraintDescriptor( metaConstraint.getDescriptor() );
    }
    else {
      PropertyDescriptorImpl propertyDescriptor = (PropertyDescriptorImpl) propertyDescriptors.get(
          metaConstraint.getLocation().getPropertyName()
      );
      if ( propertyDescriptor == null ) {
        Member member = metaConstraint.getLocation().getMember();
        propertyDescriptor = addPropertyDescriptorForMember( member, isValidAnnotationPresent( member ) );
      }
      propertyDescriptor.addConstraintDescriptor( metaConstraint.getDescriptor() );
    }
  }

  private void addMethodMetaConstraint(Class<?> clazz, MethodMetaData methodMetaData) {

    addToBuilder( methodMetaData );

    if ( ReflectionHelper.isGetterMethod( methodMetaData.getMethod() ) ) {

      addToPropertyNameList( methodMetaData.getMethod() );
      ReflectionHelper.setAccessibility( methodMetaData.getMethod() );

      for ( MethodMetaConstraint<?> metaConstraint : methodMetaData ) {

        addMetaConstraint( clazz, getAsBeanMetaConstraint( metaConstraint, methodMetaData.getMethod() ) );
      }

      if ( methodMetaData.isCascading() ) {
        addCascadedMember( methodMetaData.getMethod() );
      }
    }
  }

  /**
   * <p>
   * Adds the given method meta data to the aggregated method meta data
   * builder for this method signature. In case no aggregation builder exists
   * yet for this method signature, one will be created with the given method
   * meta data as base entry.
   * </p>
   * <p>
   * As the type hierarchy is traversed bottom-up when building the bean meta
   * data for a given type, it is ensured that for each method the aggregated
   * method meta data is based on the lowest implementation of this method in
   * the hierarchy.
   * </p>
   *
   * @param metaData The method meta data to add.
   */
  private void addToBuilder(MethodMetaData metaData) {

    // add the given meta data to an existing aggregation builder ...
    for ( Builder oneBuilder : methodMetaDataBuilders ) {

      if ( oneBuilder.accepts( metaData ) ) {
        oneBuilder.addMetaData( metaData );
        return;
      }
    }

    // ... or create a new builder
    Builder newBuilder = new AggregatedMethodMetaData.Builder( metaData );
    methodMetaDataBuilders.add( newBuilder );
  }

  private void addCascadedMember(Member member) {
    ReflectionHelper.setAccessibility( member );
    cascadedMembers.add( member );
    addPropertyDescriptorForMember( member, true );
  }

  public List<Class<?>> getClassHierarchy() {
    return classHierarchyWithoutInterfaces;
  }

  /**
   * Create bean descriptor, find all classes/subclasses/interfaces which have to be taken in consideration
   * for this validator and create meta data.
   *
   * @param annotationIgnores Data structure keeping track on which annotation should be ignored
   * @param beanMetaDataCache The bean meta data cache
   */
  private void createMetaData(AnnotationIgnores annotationIgnores, BeanMetaDataCache beanMetaDataCache) {
    beanDescriptor = new BeanDescriptorImpl<T>( this );
    initDefaultGroupSequence();
    for ( Class<?> current : ReflectionHelper.computeClassHierarchy( beanClass, true ) ) {
      initClass( current, annotationIgnores, beanMetaDataCache );
    }
  }

  private void initClass(Class<?> clazz, AnnotationIgnores annotationIgnores, BeanMetaDataCache beanMetaDataCache) {
    initClassConstraints( clazz, annotationIgnores, beanMetaDataCache );
    initMethodConstraints( clazz, annotationIgnores, beanMetaDataCache );
    initFieldConstraints( clazz, annotationIgnores, beanMetaDataCache );
  }

  /**
   * Checks whether there is a default group sequence defined for this class.
   * See HV-113.
   */
  private void initDefaultGroupSequence() {
    List<Class<?>> groupSequence = new ArrayList<Class<?>>();
    GroupSequenceProvider groupSequenceProviderAnnotation = beanClass.getAnnotation( GroupSequenceProvider.class );
    GroupSequence groupSequenceAnnotation = beanClass.getAnnotation( GroupSequence.class );

    if ( groupSequenceAnnotation != null && groupSequenceProviderAnnotation != null ) {
      throw new GroupDefinitionException(
          "GroupSequence and GroupSequenceProvider annotations cannot be used at the same time"
      );
    }

    if ( groupSequenceProviderAnnotation != null ) {
      defaultGroupSequenceProvider = newGroupSequenceProviderInstance( groupSequenceProviderAnnotation.value() );
    }
    else if ( groupSequenceAnnotation != null ) {
      groupSequence.addAll( Arrays.asList( groupSequenceAnnotation.value() ) );
      setDefaultGroupSequence( groupSequence );
    }
    else {
      groupSequence.add( beanClass );
      setDefaultGroupSequence( groupSequence );
    }
  }

  private void initFieldConstraints(Class<?> clazz, AnnotationIgnores annotationIgnores, BeanMetaDataCache beanMetaDataCache) {
    final Field[] fields = ReflectionHelper.getDeclaredFields( clazz );
    for ( Field field : fields ) {
      addToPropertyNameList( field );

      // HV-172
      if ( Modifier.isStatic( field.getModifiers() ) ) {
        continue;
      }

      if ( annotationIgnores.isIgnoreAnnotations( field ) ) {
        continue;
      }

      // HV-262
      BeanMetaDataImpl<?> cachedMetaData = beanMetaDataCache.getBeanMetaData( clazz );
      boolean metaDataCached = cachedMetaData != null;
      List<ConstraintDescriptorImpl<?>> fieldMetaData;
      if ( metaDataCached && cachedMetaData.getMetaConstraintsAsMap().get( clazz ) != null ) {
        fieldMetaData = new ArrayList<ConstraintDescriptorImpl<?>>();
        for ( BeanMetaConstraint<?> metaConstraint : cachedMetaData.getMetaConstraintsAsMap()
            .get( clazz ) ) {
          ConstraintDescriptorImpl<?> descriptor = metaConstraint.getDescriptor();
          if ( descriptor.getElementType() == ElementType.FIELD
              && metaConstraint.getLocation()
              .getPropertyName()
              .equals( ReflectionHelper.getPropertyName( field ) ) ) {
            fieldMetaData.add( descriptor );
          }
        }
      }
      else {
        fieldMetaData = findConstraints( field, ElementType.FIELD );
      }

      for ( ConstraintDescriptorImpl<?> constraintDescription : fieldMetaData ) {
        ReflectionHelper.setAccessibility( field );
        BeanMetaConstraint<?> metaConstraint = createBeanMetaConstraint( clazz, field, constraintDescription );
        addMetaConstraint( clazz, metaConstraint );
      }

      // HV-433 Make sure the field is marked as cascaded in case it was configured via xml/programmatic API or
      // it hosts the @Valid annotation
      boolean isCascadedField = metaDataCached && cachedMetaData.getCascadedMembers().contains( field );
      if ( isCascadedField || field.isAnnotationPresent( Valid.class ) ) {
        addCascadedMember( field );
      }
    }
  }

  private void addToPropertyNameList(Member member) {
    String name = ReflectionHelper.getPropertyName( member );
    if ( name != null ) {
      propertyNames.add( name );
    }
  }

  private void initMethodConstraints(Class<?> clazz, AnnotationIgnores annotationIgnores, BeanMetaDataCache beanMetaDataCache) {
    final Method[] declaredMethods = ReflectionHelper.getDeclaredMethods( clazz );

    for ( Method method : declaredMethods ) {

      // HV-172; ignoring synthetic methods (inserted by the compiler), as they can't have any constraints
      // anyway and possibly hide the actual method with the same signature in the built meta model
      if ( Modifier.isStatic( method.getModifiers() ) || annotationIgnores.isIgnoreAnnotations( method ) || method
          .isSynthetic() ) {
        continue;
      }

      // try to get meta data from cache, otherwise retrieve it from the class
      MethodMetaData methodMetaData = getFromCache( clazz, method, beanMetaDataCache );

      if ( methodMetaData == null ) {
        methodMetaData = findMethodMetaData( method );
      }

      addMethodMetaConstraint( clazz, methodMetaData );
    }
  }

  private MethodMetaData getFromCache(Class<?> clazz, Method method, BeanMetaDataCache beanMetaDataCache) {
    BeanMetaDataImpl<?> cachedBeanMetaData = beanMetaDataCache.getBeanMetaData( clazz );

    if ( cachedBeanMetaData != null ) {
      AggregatedMethodMetaData cachedMethodMetaData = cachedBeanMetaData.getMetaDataFor( method );

      if ( cachedMethodMetaData != null ) {
        return cachedMethodMetaData.getSingleMetaDataFor( method );
      }
    }

    return null;
  }

  private PropertyDescriptorImpl addPropertyDescriptorForMember(Member member, boolean isCascaded) {
    String name = ReflectionHelper.getPropertyName( member );
    PropertyDescriptorImpl propertyDescriptor = (PropertyDescriptorImpl) propertyDescriptors.get(
        name
    );
    if ( propertyDescriptor == null ) {
      propertyDescriptor = new PropertyDescriptorImpl(
          ReflectionHelper.getType( member ),
          isCascaded,
          name,
          this
      );
      propertyDescriptors.put( name, propertyDescriptor );
    }
    return propertyDescriptor;
  }

  private boolean isValidAnnotationPresent(Member member) {
    return ( (AnnotatedElement) member ).isAnnotationPresent( Valid.class );
  }

  private void initClassConstraints(Class<?> clazz, AnnotationIgnores annotationIgnores, BeanMetaDataCache beanMetaDataCache) {
    if ( annotationIgnores.isIgnoreAnnotations( clazz ) ) {
      return;
    }

    // HV-262
    BeanMetaDataImpl<?> cachedMetaData = beanMetaDataCache.getBeanMetaData( clazz );
    List<ConstraintDescriptorImpl<?>> classMetaData;
    if ( cachedMetaData != null && cachedMetaData.getMetaConstraintsAsMap().get( clazz ) != null ) {
      classMetaData = new ArrayList<ConstraintDescriptorImpl<?>>();
      for ( MetaConstraint<?> metaConstraint : cachedMetaData.getMetaConstraintsAsMap().get( clazz ) ) {
        ConstraintDescriptorImpl<?> descriptor = metaConstraint.getDescriptor();
        if ( descriptor.getElementType() == ElementType.TYPE ) {
          classMetaData.add( descriptor );
        }
      }
    }
    else {
      classMetaData = findClassLevelConstraints( clazz );
    }

    for ( ConstraintDescriptorImpl<?> constraintDescription : classMetaData ) {
      BeanMetaConstraint<?> metaConstraint = createBeanMetaConstraint( clazz, null, constraintDescription );
      addMetaConstraint( clazz, metaConstraint );
    }
  }

  private <A extends Annotation> BeanMetaConstraint<?> createBeanMetaConstraint(Class<?> declaringClass, Member m, ConstraintDescriptorImpl<A> descriptor) {
    return new BeanMetaConstraint<A>( descriptor, declaringClass, m );
  }

  private <A extends Annotation> MethodMetaConstraint<A> createParameterMetaConstraint(Method method, int parameterIndex, ConstraintDescriptorImpl<A> descriptor) {
    return new MethodMetaConstraint<A>( descriptor, method, parameterIndex );
  }

  private <A extends Annotation> MethodMetaConstraint<A> createReturnValueMetaConstraint(Method method, ConstraintDescriptorImpl<A> descriptor) {
    return new MethodMetaConstraint<A>( descriptor, method );
  }

  /**
   * Examines the given annotation to see whether it is a single- or multi-valued constraint annotation.
   *
   * @param clazz the class we are currently processing
   * @param annotation The annotation to examine
   * @param type the element type on which the annotation/constraint is placed on
   *
   * @return A list of constraint descriptors or the empty list in case <code>annotation</code> is neither a
   *         single nor multi-valued annotation.
   */
  private <A extends Annotation> List<ConstraintDescriptorImpl<?>> findConstraintAnnotations(Class<?> clazz, A annotation, ElementType type) {
    List<ConstraintDescriptorImpl<?>> constraintDescriptors = new ArrayList<ConstraintDescriptorImpl<?>>();

    List<Annotation> constraints = new ArrayList<Annotation>();
    Class<? extends Annotation> annotationType = annotation.annotationType();
    if ( constraintHelper.isConstraintAnnotation( annotationType )
        || constraintHelper.isBuiltinConstraint( annotationType ) ) {
      constraints.add( annotation );
    }
    else if ( constraintHelper.isMultiValueConstraint( annotationType ) ) {
      constraints.addAll( constraintHelper.getMultiValueConstraints( annotation ) );
    }

    for ( Annotation constraint : constraints ) {
      final ConstraintDescriptorImpl<?> constraintDescriptor = buildConstraintDescriptor(
          clazz, constraint, type
      );
      constraintDescriptors.add( constraintDescriptor );
    }
    return constraintDescriptors;
  }

  private <A extends Annotation> ConstraintDescriptorImpl<A> buildConstraintDescriptor(Class<?> clazz, A annotation, ElementType type) {
    ConstraintDescriptorImpl<A> constraintDescriptor;
    ConstraintOrigin definedIn = determineOrigin( clazz );
    if ( clazz.isInterface() && !clazz.equals( beanClass ) ) {
      constraintDescriptor = new ConstraintDescriptorImpl<A>(
          annotation, constraintHelper, clazz, type, definedIn
      );
    }
    else {
      constraintDescriptor = new ConstraintDescriptorImpl<A>( annotation, constraintHelper, type, definedIn );
    }
    return constraintDescriptor;
  }

  /**
   * Finds all constraint annotations defined for the given class and returns them in a list of
   * constraint descriptors.
   *
   * @param beanClass The class to check for constraints annotations.
   *
   * @return A list of constraint descriptors for all constraint specified on the given class.
   */
  private List<ConstraintDescriptorImpl<?>> findClassLevelConstraints(Class<?> beanClass) {
    List<ConstraintDescriptorImpl<?>> metaData = new ArrayList<ConstraintDescriptorImpl<?>>();
    for ( Annotation annotation : beanClass.getAnnotations() ) {
      metaData.addAll( findConstraintAnnotations( beanClass, annotation, ElementType.TYPE ) );
    }
    return metaData;
  }

  /**
   * Finds all constraint annotations defined for the given field/method and returns them in a list of
   * constraint descriptors.
   *
   * @param member The fields or method to check for constraints annotations.
   * @param type The element type the constraint/annotation is placed on.
   *
   * @return A list of constraint descriptors for all constraint specified for the given field or method.
   */
  private List<ConstraintDescriptorImpl<?>> findConstraints(Member member, ElementType type) {
    assert member instanceof Field || member instanceof Method;

    List<ConstraintDescriptorImpl<?>> metaData = new ArrayList<ConstraintDescriptorImpl<?>>();
    for ( Annotation annotation : ( (AnnotatedElement) member ).getAnnotations() ) {
      metaData.addAll( findConstraintAnnotations( member.getDeclaringClass(), annotation, type ) );
    }

    return metaData;
  }

  /**
   * Finds all constraint annotations defined for the given method.
   *
   * @param method The method to check for constraints annotations.
   *
   * @return A meta data object describing the constraints specified for the
   *         given method.
   */
  private MethodMetaData findMethodMetaData(Method method) {

    List<ParameterMetaData> parameterConstraints = getParameterMetaData( method );
    boolean isCascading = isValidAnnotationPresent( method ) || cascadedMembers.contains( method );
    List<MethodMetaConstraint<?>> constraints =
        convertToMetaConstraints( findConstraints( method, ElementType.METHOD ), method );

    return new MethodMetaData( method, parameterConstraints, constraints, isCascading );
  }

  private List<MethodMetaConstraint<?>> convertToMetaConstraints(List<ConstraintDescriptorImpl<?>> constraintsDescriptors, Method method) {

    List<MethodMetaConstraint<?>> constraints = newArrayList();

    for ( ConstraintDescriptorImpl<?> oneDescriptor : constraintsDescriptors ) {
      constraints.add( createReturnValueMetaConstraint( method, oneDescriptor ) );
    }

    return constraints;
  }

  /**
   * Retrieves constraint related meta data for the parameters of the given
   * method.
   *
   * @param method The method of interest.
   *
   * @return A list with parameter meta data for the given method.
   */
  private List<ParameterMetaData> getParameterMetaData(Method method) {
    List<ParameterMetaData> metaData = newArrayList();

    int i = 0;
    Class<?>[] parameterTypes = method.getParameterTypes();

    for ( Annotation[] annotationsOfOneParameter : method.getParameterAnnotations() ) {

      boolean parameterIsCascading = false;
      String parameterName = DEFAULT_PARAMETER_NAME_PREFIX + i;
      List<MethodMetaConstraint<?>> constraintsOfOneParameter = newArrayList();

      for ( Annotation oneAnnotation : annotationsOfOneParameter ) {

        //1. collect constraints if this annotation is a constraint annotation
        List<ConstraintDescriptorImpl<?>> constraints = findConstraintAnnotations(
            method.getDeclaringClass(), oneAnnotation, ElementType.PARAMETER
        );
        for ( ConstraintDescriptorImpl<?> constraintDescriptorImpl : constraints ) {
          constraintsOfOneParameter.add(
              createParameterMetaConstraint(
                  method, i, constraintDescriptorImpl
              )
          );
        }

        //2. mark parameter as cascading if this annotation is the @Valid annotation
        if ( oneAnnotation.annotationType().equals( Valid.class ) ) {
          parameterIsCascading = true;
        }
      }

      metaData.add(
          new ParameterMetaData(
              i, parameterTypes[i], parameterName, constraintsOfOneParameter, parameterIsCascading
          )
      );
      i++;
    }

    return metaData;
  }

  private <A extends Annotation> MethodMetaConstraint<A> getAsMethodMetaConstraint(BeanMetaConstraint<A> beanMetaConstraint) {
    return new MethodMetaConstraint<A>(
        beanMetaConstraint.getDescriptor(), (Method) beanMetaConstraint.getLocation().getMember()
    );
  }

  private <A extends Annotation> BeanMetaConstraint<A> getAsBeanMetaConstraint(MethodMetaConstraint<A> methodMetaConstraint, Method method) {
    return new BeanMetaConstraint<A>(
        methodMetaConstraint.getDescriptor(), methodMetaConstraint.getLocation().getBeanClass(), method
    );
  }

  private ConstraintOrigin determineOrigin(Class<?> clazz) {
    if ( clazz.equals( beanClass ) ) {
      return ConstraintOrigin.DEFINED_LOCALLY;
    }
    else {
      return ConstraintOrigin.DEFINED_IN_HIERARCHY;
    }
  }

  @SuppressWarnings("unchecked")
  private <U extends DefaultGroupSequenceProvider<?>> DefaultGroupSequenceProvider<T> newGroupSequenceProviderInstance(Class<U> providerClass) {
    Method[] providerMethods = getMethods( providerClass );
    for ( Method method : providerMethods ) {
      Class<?>[] paramTypes = method.getParameterTypes();
      if ( "getValidationGroups".equals( method.getName() ) && !method.isBridge()
          && paramTypes.length == 1 && paramTypes[0].isAssignableFrom( beanClass ) ) {

        return (DefaultGroupSequenceProvider<T>) newInstance(
            providerClass, "the default group sequence provider"
        );
      }
    }

    throw new GroupDefinitionException(
        "The default group sequence provider defined for " + beanClass.getName() + " has the wrong type"
    );
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append( "BeanMetaDataImpl" );
    sb.append( "{beanClass=" ).append( beanClass.getSimpleName() );
    sb.append( ", constraintCount=" ).append( getMetaConstraints().size() );
    sb.append( ", cascadedMemberCount=" ).append( cascadedMembers.size() );
    sb.append( ", defaultGroupSequence=" ).append( getDefaultGroupSequence( null ) );
    sb.append( '}' );
    return sb.toString();
  }
}
TOP

Related Classes of org.hibernate.validator.metadata.BeanMetaDataImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.