Package org.hibernate.validation.metadata

Source Code of org.hibernate.validation.metadata.ConstraintDescriptorImpl$ClassIndexWrapper

// $Id: ConstraintDescriptorImpl.java 17263 2009-08-11 18:00:25Z epbernard $
/*
* JBoss, Home of Professional Open Source
* Copyright 2008, Red Hat Middleware LLC, 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.validation.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.Set;
import java.security.AccessController;
import javax.validation.Constraint;
import javax.validation.ConstraintDefinitionException;
import javax.validation.ConstraintPayload;
import javax.validation.ConstraintValidator;
import javax.validation.OverridesAttribute;
import javax.validation.ReportAsSingleViolation;
import javax.validation.ValidationException;
import javax.validation.groups.Default;
import javax.validation.metadata.ConstraintDescriptor;

import org.slf4j.Logger;

import org.hibernate.validation.util.LoggerFactory;
import org.hibernate.validation.util.GetAnnotationParameter;
import org.hibernate.validation.util.GetMethods;
import org.hibernate.validation.util.GetMethod;
import org.hibernate.validation.util.GetDeclaredMethods;
import org.hibernate.validation.util.annotationfactory.AnnotationDescriptor;
import org.hibernate.validation.util.annotationfactory.AnnotationFactory;

/**
* Describe a single constraint (including it's composing constraints).
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
*/
public class ConstraintDescriptorImpl<T extends Annotation> implements ConstraintDescriptor<T> {
  private static final Logger log = LoggerFactory.make();
  private static final int OVERRIDES_PARAMETER_DEFAULT_INDEX = -1;
  private static final String GROUPS = "groups";
  private static final String PAYLOAD = "payload";

  /**
   * The actual constraint annotation.
   */
  private final T annotation;

  /**
   * The set of classes implementing the validation for this constraint. See also
   * <code>ConstraintValidator</code> resolution algorithm.
   */
  private final List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorDefinitonClasses;

  /**
   * The groups for which to apply this constraint.
   */
  private final Set<Class<?>> groups;

  /**
   * The constraint parameters as map. The key is the paramter name and the value the
   * parameter value as specified in the constraint.
   */
  private final Map<String, Object> attributes;

  private final Set<Class<ConstraintPayload>> payloads;

  /**
   * The composing constraints for this constraints.
   */
  private final Set<ConstraintDescriptor<?>> composingConstraints;

  /**
   * Flag indicating if in case of a composing constraint a single error or multiple errors should be raised.
   */
  private final boolean isReportAsSingleInvalidConstraint;

  /**
   * Handle to the builtin constraint implementations.
   */
  private final ConstraintHelper constraintHelper;

  public ConstraintDescriptorImpl(T annotation, ConstraintHelper constraintHelper, Class<?> implicitGroup) {
    this.annotation = annotation;
    this.constraintHelper = constraintHelper;
    this.isReportAsSingleInvalidConstraint = annotation.annotationType().isAnnotationPresent(
        ReportAsSingleViolation.class
    );

    // HV-181 - To avoid and thread visibilty issues we are building the different data structures in tmp variables and
    // then assign them to the final variables
    this.attributes = buildAnnotationParameterMap( annotation );
    this.groups = buildGroupSet( implicitGroup );
    this.payloads = buildPayloadSet( annotation );
    this.constraintValidatorDefinitonClasses = findConstraintValidatorClasses();
    this.composingConstraints = parseComposingConstraints();
  }

  public ConstraintDescriptorImpl(T annotation, ConstraintHelper constraintHelper) {
    this( annotation, constraintHelper, null );
  }

  private Set<Class<ConstraintPayload>> buildPayloadSet(T annotation) {
    Set<Class<ConstraintPayload>> payloadSet = new HashSet<Class<ConstraintPayload>>();
    Class<ConstraintPayload>[] payloadFromAnnotation;
    try {
      //TODO be extra safe and make sure this is an array of ConstraintPayload
      GetAnnotationParameter<Class[]> action = GetAnnotationParameter.action( annotation, PAYLOAD, Class[].class );
      if (System.getSecurityManager() != null) {
        payloadFromAnnotation = AccessController.doPrivileged( action );
      }
      else {
        payloadFromAnnotation = action.run();
      }
    }
    catch ( ValidationException e ) {
      //ignore people not defining payloads
      payloadFromAnnotation = null;
    }
    if ( payloadFromAnnotation != null ) {
      payloadSet.addAll( Arrays.asList( payloadFromAnnotation ) );
    }
    return Collections.unmodifiableSet( payloadSet );
  }

  private Set<Class<?>> buildGroupSet(Class<?> implicitGroup) {
    Set<Class<?>> groupSet = new HashSet<Class<?>>();
    final Class<?>[] groupsFromAnnotation;
    GetAnnotationParameter<Class[]> action = GetAnnotationParameter.action( annotation, GROUPS, Class[].class );
    if (System.getSecurityManager() != null) {
      groupsFromAnnotation = AccessController.doPrivileged( action );
    }
    else {
      groupsFromAnnotation = action.run();
    }
    if ( groupsFromAnnotation.length == 0 ) {
      groupSet.add( Default.class );
    }
    else {
      groupSet.addAll( Arrays.asList( groupsFromAnnotation ) );
    }

    // if the constraint is part of the Default group it is automatically part of the implicit group as well
    if ( implicitGroup != null && groupSet.contains( Default.class ) ) {
      groupSet.add( implicitGroup );
    }
    return Collections.unmodifiableSet( groupSet );
  }

  private List<Class<? extends ConstraintValidator<T, ?>>> findConstraintValidatorClasses() {
    final Class<T> annotationType = getAnnotationType();
    final List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses = new ArrayList<Class<? extends ConstraintValidator<T, ?>>>();
    if ( constraintHelper.containsConstraintValidatorDefinition( annotationType ) ) {
      for ( Class<? extends ConstraintValidator<T, ?>> validator : constraintHelper
          .getConstraintValidatorDefinition( annotationType ) ) {
        constraintValidatorClasses.add( validator );
      }
      return Collections.unmodifiableList( constraintValidatorClasses );
    }

    List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> constraintDefinitonClasses = new ArrayList<Class<? extends ConstraintValidator<? extends Annotation, ?>>>();
    if ( constraintHelper.isBuiltinConstraint( annotation.annotationType() ) ) {
      constraintDefinitonClasses.addAll( constraintHelper.getBuiltInConstraints( annotationType ) );
    }
    else {
      Class<? extends ConstraintValidator<?, ?>>[] validatedBy = annotationType
          .getAnnotation( Constraint.class )
          .validatedBy();
      constraintDefinitonClasses.addAll( Arrays.asList( validatedBy ) );
    }

    constraintHelper.addConstraintValidatorDefinition(
        annotation.annotationType(), constraintDefinitonClasses
    );

    for ( Class<? extends ConstraintValidator<? extends Annotation, ?>> validator : constraintDefinitonClasses ) {
      @SuppressWarnings("unchecked")
      Class<? extends ConstraintValidator<T, ?>> safeValidator = ( Class<? extends ConstraintValidator<T, ?>> ) validator;
      constraintValidatorClasses.add( safeValidator );
    }
    return Collections.unmodifiableList( constraintValidatorClasses );
  }

  @SuppressWarnings("unchecked")
  private Class<T> getAnnotationType() {
    return ( Class<T> ) annotation.annotationType();
  }

  public T getAnnotation() {
    return annotation;
  }

  public Set<Class<?>> getGroups() {
    return groups;
  }

  public Set<Class<ConstraintPayload>> getPayload() {
    return payloads;
  }

  public List<Class<? extends ConstraintValidator<T, ?>>> getConstraintValidatorClasses() {
    return constraintValidatorDefinitonClasses;
  }

  public Map<String, Object> getAttributes() {
    return attributes;
  }

  public Set<ConstraintDescriptor<?>> getComposingConstraints() {
    return composingConstraints;
  }

  public boolean isReportAsSingleViolation() {
    return isReportAsSingleInvalidConstraint;
  }

  @Override
  public String toString() {
    return "ConstraintDescriptorImpl{" +
        "annotation=" + annotation +
        ", constraintValidatorDefinitonClasses=" + constraintValidatorDefinitonClasses.toString() +
        ", groups=" + groups +
        ", attributes=" + attributes +
        ", composingConstraints=" + composingConstraints +
        ", isReportAsSingleInvalidConstraint=" + isReportAsSingleInvalidConstraint +
        '}';
  }

  private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {
    GetDeclaredMethods action = GetDeclaredMethods.action( annotation.annotationType() );
    final Method[] declaredMethods;
    if ( System.getSecurityManager() != null ) {
      declaredMethods = AccessController.doPrivileged( action );
    }
    else {
      declaredMethods = action.run();
    }
    Map<String, Object> parameters = new HashMap<String, Object>( declaredMethods.length );
    for ( Method m : declaredMethods ) {
      try {
        parameters.put( m.getName(), m.invoke( annotation ) );
      }
      catch ( IllegalAccessException e ) {
        throw new ValidationException( "Unable to read annotation attributes: " + annotation.getClass(), e );
      }
      catch ( InvocationTargetException e ) {
        throw new ValidationException( "Unable to read annotation attributes: " + annotation.getClass(), e );
      }
    }
    return Collections.unmodifiableMap( parameters );
  }

  private Object getMethodValue(Annotation annotation, Method m) {
    Object value;
    try {
      value = m.invoke( annotation );
    }
    // should never happen
    catch ( IllegalAccessException e ) {
      throw new ValidationException( "Unable to retrieve annotation parameter value." );
    }
    catch ( InvocationTargetException e ) {
      throw new ValidationException( "Unable to retrieve annotation parameter value." );
    }
    return value;
  }

  private Map<ClassIndexWrapper, Map<String, Object>> parseOverrideParameters() {
    Map<ClassIndexWrapper, Map<String, Object>> overrideParameters = new HashMap<ClassIndexWrapper, Map<String, Object>>();
    final Method[] methods;
    final GetMethods getMethods = GetMethods.action( annotation.annotationType() );
    if ( System.getSecurityManager() != null ) {
      methods = AccessController.doPrivileged( getMethods );
    }
    else {
      methods = getMethods.run();
    }

    for ( Method m : methods ) {
      if ( m.getAnnotation( OverridesAttribute.class ) != null ) {
        addOverrideAttributes(
            overrideParameters, m, m.getAnnotation( OverridesAttribute.class )
        );
      }
      else if ( m.getAnnotation( OverridesAttribute.List.class ) != null ) {
        addOverrideAttributes(
            overrideParameters,
            m,
            m.getAnnotation( OverridesAttribute.List.class ).value()
        );
      }
    }
    return overrideParameters;
  }

  private void addOverrideAttributes(Map<ClassIndexWrapper, Map<String, Object>> overrideParameters, Method m, OverridesAttribute... attributes) {

    Object value = getMethodValue( annotation, m );
    for ( OverridesAttribute overridesAttribute : attributes ) {
      ensureAttributeIsOverridable( m, overridesAttribute );

      ClassIndexWrapper wrapper = new ClassIndexWrapper(
          overridesAttribute.constraint(), overridesAttribute.constraintIndex()
      );
      Map<String, Object> map = overrideParameters.get( wrapper );
      if ( map == null ) {
        map = new HashMap<String, Object>();
        overrideParameters.put( wrapper, map );
      }
      map.put( overridesAttribute.name(), value );
    }
  }

  private void ensureAttributeIsOverridable(Method m, OverridesAttribute overridesAttribute) {
    final GetMethod getMethod = GetMethod.action( overridesAttribute.constraint(), overridesAttribute.name() );
    final Method method;
    if ( System.getSecurityManager() != null ) {
      method = AccessController.doPrivileged( getMethod );
    }
    else {
      method = getMethod.run();
    }
    if (method == null) {
      throw new ConstraintDefinitionException(
          "Overriden constraint does not define an attribute with name " + overridesAttribute.name()
      );
    }
    Class<?> returnTypeOfOverridenConstraint = method.getReturnType();
    if ( !returnTypeOfOverridenConstraint.equals( m.getReturnType() ) ) {
      String message = "The overiding type of a composite constraint must be identical to the overwridden one. Expected " + returnTypeOfOverridenConstraint
          .getName() + " found " + m.getReturnType();
      throw new ConstraintDefinitionException( message );
    }
  }

  private Set<ConstraintDescriptor<?>> parseComposingConstraints() {
    Set<ConstraintDescriptor<?>> composingConstraintsSet = new HashSet<ConstraintDescriptor<?>>();
    Map<ClassIndexWrapper, Map<String, Object>> overrideParameters = parseOverrideParameters();

    for ( Annotation declaredAnnotation : annotation.annotationType().getDeclaredAnnotations() ) {
      if ( constraintHelper.isConstraintAnnotation( declaredAnnotation )
          || constraintHelper.isBuiltinConstraint( declaredAnnotation.annotationType() ) ) {
        ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
            declaredAnnotation, overrideParameters, OVERRIDES_PARAMETER_DEFAULT_INDEX
        );
        composingConstraintsSet.add( descriptor );
        log.debug( "Adding composing constraint: " + descriptor );
      }
      else if ( constraintHelper.isMultiValueConstraint( declaredAnnotation ) ) {
        List<Annotation> multiValueConstraints = constraintHelper.getMultiValueConstraints( declaredAnnotation );
        int index = 0;
        for ( Annotation constraintAnnotation : multiValueConstraints ) {
          ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
              constraintAnnotation, overrideParameters, index
          );
          composingConstraintsSet.add( descriptor );
          log.debug( "Adding composing constraint: " + descriptor );
          index++;
        }
      }
    }
    return Collections.unmodifiableSet( composingConstraintsSet );
  }

  private <U extends Annotation> ConstraintDescriptorImpl<U> createComposingConstraintDescriptor(U declaredAnnotation, Map<ClassIndexWrapper, Map<String, Object>> overrideParameters, int index) {
    //TODO don't quite understand this warning
    //TODO assuming U.getClass() returns Class<U>
    @SuppressWarnings("unchecked")
    final Class<U> annotationType = ( Class<U> ) declaredAnnotation.annotationType();
    return createComposingConstraintDescriptor(
        overrideParameters,
        index,
        declaredAnnotation,
        annotationType
    );
  }

  private <U extends Annotation> ConstraintDescriptorImpl<U> createComposingConstraintDescriptor(Map<ClassIndexWrapper, Map<String, Object>> overrideParameters, int index, U constraintAnnotation, Class<U> annotationType) {
    // use a annotation proxy
    AnnotationDescriptor<U> annotationDescriptor = new AnnotationDescriptor<U>(
        annotationType, buildAnnotationParameterMap( constraintAnnotation )
    );

    // get the right override parameters
    Map<String, Object> overrides = overrideParameters.get(
        new ClassIndexWrapper(
            annotationType, index
        )
    );
    if ( overrides != null ) {
      for ( Map.Entry<String, Object> entry : overrides.entrySet() ) {
        annotationDescriptor.setValue( entry.getKey(), entry.getValue() );
      }
    }

    // groups get inherited from the parent
    annotationDescriptor.setValue( GROUPS, groups.toArray( new Class<?>[groups.size()]) );

    U annotationProxy = AnnotationFactory.create( annotationDescriptor );
    return new ConstraintDescriptorImpl<U>(
        annotationProxy, constraintHelper
    );
  }

  /**
   * A wrapper class to keep track for which compposing constraints (class and index) a given attribute override applies to.
   */
  private class ClassIndexWrapper {
    final Class<?> clazz;
    final int index;

    ClassIndexWrapper(Class<?> clazz, int index) {
      this.clazz = clazz;
      this.index = index;
    }

    @Override
    @SuppressWarnings("SimplifiableIfStatement")
    public boolean equals(Object o) {
      if ( this == o ) {
        return true;
      }
      if ( o == null || getClass() != o.getClass() ) {
        return false;
      }

      @SuppressWarnings("unchecked") // safe due to the check above
          ClassIndexWrapper that = ( ClassIndexWrapper ) o;

      if ( index != that.index ) {
        return false;
      }
      if ( clazz != null && !clazz.equals( that.clazz ) ) {
        return false;
      }
      if ( clazz == null && that.clazz != null ) {
        return false;
      }

      return true;
    }

    @Override
    public int hashCode() {
      int result = clazz != null ? clazz.hashCode() : 0;
      result = 31 * result + index;
      return result;
    }
  }
}
TOP

Related Classes of org.hibernate.validation.metadata.ConstraintDescriptorImpl$ClassIndexWrapper

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.