Package org.springframework.ide.eclipse.beans.core.internal.model.validation.rules

Source Code of org.springframework.ide.eclipse.beans.core.internal.model.validation.rules.RequiredPropertyRule

/*******************************************************************************
* Copyright (c) 2007, 2013 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.beans.core.internal.model.validation.rules;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.MethodVisitor;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils;
import org.springframework.ide.eclipse.beans.core.model.IBean;
import org.springframework.ide.eclipse.beans.core.model.validation.AbstractBeanValidationRule;
import org.springframework.ide.eclipse.beans.core.model.validation.IBeansValidationContext;
import org.springframework.ide.eclipse.core.java.Introspector;
import org.springframework.ide.eclipse.core.model.validation.ValidationProblemAttribute;
import org.springframework.ide.eclipse.core.type.asm.AnnotationMetadataReadingVisitor;
import org.springframework.ide.eclipse.core.type.asm.ClassReaderFactory;
import org.springframework.ide.eclipse.core.type.asm.EmptyAnnotationVisitor;
import org.springframework.ide.eclipse.core.type.asm.EmptyMethodVisitor;

/**
* Validates a given {@link IBean}'s if all {@link Required} annotated properties are configured.
*
* @author Christian Dupuis
* @author Terry Denney
* @author Martin Lippert
* @since 2.0.1
*/
public class RequiredPropertyRule extends AbstractBeanValidationRule {

  private static final String REQUIRED_ANNOTATION_TYPE_PROPERTY_NAME = "requiredAnnotationType";

  @Override
  protected boolean supportsBean(IBean bean, IBeansValidationContext context) {
    return !bean.isAbstract()
        && context.isBeanRegistered(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
            RequiredAnnotationBeanPostProcessor.class.getName());
  }

  /**
   * Validates the given {@link IBean}.
   * <p>
   * First checks if the bean is not abstract and if the {@link RequiredAnnotationBeanPostProcessor} is registered in
   * the application context. If so the bean class is scanned by using an ASM-based {@link ClassVisitor} for any
   * {@link Required} annotated property setters.
   */
  @Override
  public void validate(IBean bean, IBeansValidationContext context, IProgressMonitor monitor) {
    BeanDefinition mergedBd = BeansModelUtils.getMergedBeanDefinition(bean, context.getContextElement());
    String mergedClassName = mergedBd.getBeanClassName();
    IType type = ValidationRuleUtils.extractBeanClass(mergedBd, bean, mergedClassName, context);
    if (type != null) {
      validatePropertyNames(type, bean, mergedBd, context);
    }
  }

  /**
   * Validates {@link PropertyValues} of given {link BeanDefinition} if all required are configured.
   * @param type the type whose hierarchy to check for {@link Required} annotated properties
   * @param bean the underlying {@link IBean} instance
   * @param mergedBd the {@link BeanDefinition} behind the {@link IBean}
   * @param context context to retrieve a {@link ClassReaderFactory} and report errors
   */
  private void validatePropertyNames(IType type, IBean bean, BeanDefinition mergedBd, IBeansValidationContext context) {
    try {
      RequiredAnnotationMetadata annotationMetadata = getRequiredAnnotationMetadata(context
          .getClassReaderFactory(), bean, type, getRequiredAnnotationTypes(context));

      List<String> missingProperties = new ArrayList<String>();
      Set<IMethod> properties = Introspector.findAllWritableProperties(type);
      for (IMethod property : properties) {
        String propertyName = java.beans.Introspector.decapitalize(property.getElementName().substring(3));
        if (annotationMetadata.isRequiredProperty(propertyName)
            && mergedBd.getPropertyValues().getPropertyValue(propertyName) == null) {
          missingProperties.add(propertyName);
        }
      }

      // add the error message
      if (missingProperties.size() > 0) {
        String msg = buildExceptionMessage(missingProperties, bean.getElementName());
        context.error(bean, "REQUIRED_PROPERTY_MISSING", msg, new ValidationProblemAttribute("CLASS", type
            .getFullyQualifiedName()), new ValidationProblemAttribute("BEAN_NAME", bean.getElementName()),
            new ValidationProblemAttribute("MISSING_PROPERTIES",
            missingProperties));
      }
    }
    catch (JavaModelException e) {
      BeansCorePlugin.log(e);
    }
  }

  /**
   * Retrieves a instance of {@link RequiredAnnotationMetadata} that contains information about used annotations in
   * the class under question
   */
  private RequiredAnnotationMetadata getRequiredAnnotationMetadata(final ClassReaderFactory classReaderFactory,
      final IBean bean, final IType type, Set<String> requiredAnnotationTypes) {
    String className = type.getFullyQualifiedName();
    RequiredAnnotationMetadata visitor = new RequiredAnnotationMetadata(requiredAnnotationTypes);
    try {
      while (className != null && !Object.class.getName().equals(className)) {
        ClassReader classReader = classReaderFactory.getClassReader(className);
        classReader.accept(visitor, 0);
        className = visitor.getSuperClassName();
      }
    }
    catch (IOException e) {
      // ignore any missing files here as this will be
      // reported as missing bean class
    }
    return visitor;
  }

  /**
   * ASM based visitor that checks the precedence of an {@link Required} annotation on <b>any</b> property setter.
   */
  private static class RequiredAnnotationMetadata extends AnnotationMetadataReadingVisitor {

    private Set<String> requiredAnnotationTypes = new HashSet<String>();

    private Set<String> requiredPropertyNames = new HashSet<String>();

    public RequiredAnnotationMetadata(Set<String> requiredAnnotationTypes) {
      for (String className : requiredAnnotationTypes) {
        this.requiredAnnotationTypes.add('L' + className.replace('.', '/') + ';');
      }
    }

    @Override
    public MethodVisitor visitMethod(int modifier, final String name, String params, String arg3, String[] arg4) {
      if (name.startsWith("set")) {
        return new EmptyMethodVisitor() {
          @Override
          public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
            if (requiredAnnotationTypes.contains(desc)) {
              requiredPropertyNames.add(java.beans.Introspector.decapitalize(name.substring(3)));
            }
            return new EmptyAnnotationVisitor();
          }
        };
      }
      return new EmptyMethodVisitor();
    }

    public boolean isRequiredProperty(String propertyName) {
      return requiredPropertyNames.contains(propertyName);
    }
  }

  /**
   * Extracts the configured <code>requiredAnnotationType</code> values from all registered
   * {@link RequiredAnnotationBeanPostProcessor}.
   * @since 2.0.2
   */
  private Set<String> getRequiredAnnotationTypes(IBeansValidationContext context) {
    Set<String> requiredAnnotationTypes = new HashSet<String>();
    Set<BeanDefinition> bds = context.getRegisteredBeanDefinition(
        AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
        RequiredAnnotationBeanPostProcessor.class.getName());
    for (BeanDefinition bd : bds) {
      PropertyValue property = bd.getPropertyValues().getPropertyValue(REQUIRED_ANNOTATION_TYPE_PROPERTY_NAME);
      if (property != null && property.getValue() instanceof TypedStringValue) {
        requiredAnnotationTypes.add(((TypedStringValue) property.getValue()).getValue());
      }
      else {
        requiredAnnotationTypes.add(Required.class.getName());
      }
    }
    return requiredAnnotationTypes;
  }

  /**
   * Build an exception message for the given list of invalid properties.
   * @param invalidProperties the list of names of invalid properties
   * @param beanName the name of the bean
   * @return the exception message
   */
  private String buildExceptionMessage(List<String> invalidProperties, String beanName) {
    int size = invalidProperties.size();
    StringBuilder sb = new StringBuilder();
    sb.append(size == 1 ? "Property" : "Properties");
    for (int i = 0; i < size; i++) {
      String propertyName = invalidProperties.get(i);
      if (i > 0) {
        if (i == (size - 1)) {
          sb.append(" and");
        }
        else {
          sb.append(",");
        }
      }
      sb.append(" '").append(propertyName).append("'");
    }
    sb.append(size == 1 ? " is" : " are");
    sb.append(" required for bean '").append(beanName).append("'");
    return sb.toString();
  }
}
TOP

Related Classes of org.springframework.ide.eclipse.beans.core.internal.model.validation.rules.RequiredPropertyRule

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.