/**
   * Applies the global validation rules as listed in the given validation configuration on the
   * given object, and registering all global validation errors with the given {@link Errors}.
   *
   * @param configuration The bean validation configuration that holds all the global validation
   *     rules.
   * @param obj The validated object.
   * @param errors The {@link Errors} instance where all global validation errors will be
   *     registered.
   */
  protected void applyGlobalValidationRules(
      BeanValidationConfiguration configuration, Object obj, Errors errors) {
    ValidationRule[] globalRules = configuration.getGlobalRules();
    for (int i = 0; i < globalRules.length; i++) {
      ValidationRule rule = globalRules[i];
      if (rule.isApplicable(obj) && !rule.getCondition().check(obj)) {
        String errorCode =
            errorCodeConverter.convertGlobalErrorCode(rule.getErrorCode(), obj.getClass());

        // if there is a nested path in errors, the global errors should be registered as field
        // errors
        // for the nested path. Otherwise, they should be registered as global errors. Starting from
        // Spring 2.0-rc2
        // this is actually not required - it's just enough to call rejectValue() with null as the
        // field name,
        // but we keep this implementation for now to support earlier versions.

        if (StringUtils.hasLength(errors.getNestedPath())) {
          String nestedPath = errors.getNestedPath();
          String propertyName = nestedPath.substring(0, nestedPath.length() - 1);
          errors.popNestedPath();
          errors.rejectValue(
              propertyName, errorCode, rule.getErrorArguments(obj), rule.getDefaultErrorMessage());
          errors.pushNestedPath(propertyName);
        } else {
          errors.reject(errorCode, rule.getErrorArguments(obj), rule.getDefaultErrorMessage());
        }
      }
    }
  }
 /**
  * Applies the custom validator of the given configuration (if one exists) on the given object.
  *
  * @param configuration The configuration from which the custom validator will be taken from.
  * @param obj The object to be validated.
  * @param errors The {@link Errors} instance where all validation errors will be registered.
  */
 protected void applyCustomValidator(
     BeanValidationConfiguration configuration, Object obj, Errors errors) {
   Validator validator = configuration.getCustomValidator();
   if (validator != null) {
     if (validator.supports(obj.getClass())) {
       validator.validate(obj, errors);
     }
   }
 }
  /**
   * Applies the property validation rules as listed in the given validation configuration on the
   * given object, and registering all property validation errors with the given {@link Errors}.
   *
   * @param configuration The bean validation configuration that holds all the property validation
   *     rules.
   * @param obj The validated object.
   * @param errors The {@link Errors} instance where all property validation errors will be
   *     registered (see {@link Errors#rejectValue(String, String)}).
   */
  protected void applyPropertiesValidationRules(
      BeanValidationConfiguration configuration, Object obj, Errors errors) {
    String[] propertyNames = configuration.getValidatedProperties();
    for (int i = 0; i < propertyNames.length; i++) {
      String propertyName = propertyNames[i];
      if (logger.isDebugEnabled()) {
        logger.debug("Validating property '" + propertyName + "' rules...");
      }
      ValidationRule[] rules = configuration.getPropertyRules(propertyName);

      // only allow one error to be associated with a property at once. This is to prevent
      // situations where
      // dependent validation rules will fail. An example can be a "minLength()" validation rule
      // that is dependent
      // on "notNull()" rule (there is not length to a null value), in this case, if the "notNull()"
      // rule
      // produces an error, the "minLength()" rule should not be applied.
      validateAndShortCircuitRules(rules, propertyName, obj, errors);
    }
  }
  /**
   * The heart of this validator. This is a recursive method that validates the given object
   * (object) under the context of the given object graph root (root). The validation rules to be
   * applied are loaded using the configured {@link
   * org.springmodules.validation.bean.conf.loader.BeanValidationConfigurationLoader}. All errors
   * are registered with the given {@link Errors} object under the context of the object graph root.
   *
   * @param root The root of the object graph.
   * @param obj The object to be validated
   * @param errors The {@link Errors} instance where the validation errors will be registered.
   * @param validatedObjects keeps track of all the validated objects (to prevent revalidating the
   *     same objects when a circular relationship exists).
   */
  protected void validateObjectGraphConstraints(
      Object root, Object obj, Errors errors, Set validatedObjects) {

    // cannot load any validation rules for null values
    if (obj == null) {
      return;
    }

    // if this object was already validated, the skipping this valiation.
    if (validatedObjects.contains(obj)) {
      if (logger.isDebugEnabled()) {
        logger.debug(
            "Skipping validation of object in path '"
                + errors.getObjectName()
                + "' for it was already validated");
      }
      return;
    }

    if (logger.isDebugEnabled()) {
      logger.debug("Validating object in path '" + errors.getNestedPath() + "'");
    }

    // loading the bean validation configuration based on the validated object class.
    Class clazz = obj.getClass();
    BeanValidationConfiguration configuration = configurationLoader.loadConfiguration(clazz);

    if (configuration == null) {
      return; // no validation configuration for this object, then there's nothing to validate.
    }

    // applying all the validation rules for the object and registering the object as "validated"
    applyBeanValidation(configuration, obj, errors);
    validatedObjects.add(obj);

    // after all the validation rules where applied, checking what properties of the object require
    // their own
    // validation and recursively calling this method on them.
    CascadeValidation[] cascadeValidations = configuration.getCascadeValidations();
    BeanWrapper wrapper = wrapBean(obj);
    for (int i = 0; i < cascadeValidations.length; i++) {
      CascadeValidation cascadeValidation = cascadeValidations[i];
      Condition applicabilityCondition = cascadeValidation.getApplicabilityCondition();

      if (!applicabilityCondition.check(obj)) {
        continue;
      }

      String propertyName = cascadeValidation.getPropertyName();
      Class propertyType = wrapper.getPropertyType(propertyName);
      Object propertyValue = wrapper.getPropertyValue(propertyName);

      // if the property value is not there nothing to validate.
      if (propertyValue == null) {
        continue;
      }

      // if the property is an array of a collection, then iterating on it and validating each
      // element. Note that
      // the error codes that are registered for arrays/collection elements follow the pattern
      // supported by
      // spring's PropertyAccessor. Also note that just before each recursive call, the context of
      // the validation
      // is appropriately adjusted using errors.pushNestedPath(...), and after each call it is being
      // adjusted back
      // using errors.popNestedPath().
      if (propertyType.isArray()) {
        validateArrayProperty(root, propertyValue, propertyName, errors, validatedObjects);
      } else if (List.class.isAssignableFrom(propertyType)
          || Set.class.isAssignableFrom(propertyType)) {
        validateListOrSetProperty(
            root, (Collection) propertyValue, propertyName, errors, validatedObjects);
      } else if (Map.class.isAssignableFrom(propertyType)) {
        validateMapProperty(root, ((Map) propertyValue), propertyName, errors, validatedObjects);
      } else {
        // if the object is just a normal object (not an array or a collection), then applying its
        // validation rules.
        validatedSubBean(root, propertyValue, propertyName, errors, validatedObjects);
      }
    }
  }