/**
  * This validator supports only those classes that are supported by the validation configuration
  * loader it uses.
  *
  * @see org.springmodules.validation.bean.RuleBasedValidator#supports(Class)
  * @see
  *     org.springmodules.validation.bean.conf.loader.BeanValidationConfigurationLoader#supports(Class)
  */
 public boolean supports(Class clazz) {
   return configurationLoader.supports(clazz) || super.supports(clazz);
 }
  /**
   * 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);
      }
    }
  }