SpringAnnotationParser.java

  1. /*
  2.  * Copyright © 2017, Saleforce.com, Inc
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions are met:
  7.  *     * Redistributions of source code must retain the above copyright
  8.  *       notice, this list of conditions and the following disclaimer.
  9.  *     * Redistributions in binary form must reproduce the above copyright
  10.  *       notice, this list of conditions and the following disclaimer in the
  11.  *       documentation and/or other materials provided with the distribution.
  12.  *     * Neither the name of the <organization> nor the
  13.  *       names of its contributors may be used to endorse or promote products
  14.  *       derived from this software without specific prior written permission.
  15.  *
  16.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19.  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  20.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26.  */
  27. package com.salesforce.aptspring.processor;

  28. import java.util.ArrayList;
  29. import java.util.Arrays;
  30. import java.util.Collections;
  31. import java.util.List;
  32. import java.util.Set;
  33. import java.util.function.Predicate;
  34. import java.util.stream.Collectors;

  35. import javax.annotation.processing.Messager;
  36. import javax.lang.model.element.Element;
  37. import javax.lang.model.element.ElementKind;
  38. import javax.lang.model.element.ExecutableElement;
  39. import javax.lang.model.element.Modifier;
  40. import javax.lang.model.element.TypeElement;
  41. import javax.lang.model.element.VariableElement;
  42. import javax.lang.model.type.TypeKind;
  43. import javax.tools.Diagnostic.Kind;

  44. import com.salesforce.apt.graph.model.DefinitionModel;
  45. import com.salesforce.apt.graph.model.ExpectedModel;
  46. import com.salesforce.apt.graph.model.InstanceDependencyModel;
  47. import com.salesforce.apt.graph.model.InstanceModel;
  48. import com.salesforce.aptspring.Verified;

  49. public class SpringAnnotationParser {
  50.  
  51.   private static final List<Modifier> DISALLOWED_ON_METHOD = Collections.unmodifiableList(Arrays.asList(
  52.       Modifier.ABSTRACT,
  53.       Modifier.DEFAULT,
  54.       Modifier.FINAL,
  55.       Modifier.NATIVE,
  56.       Modifier.STATIC,
  57.       Modifier.VOLATILE
  58.       //Modifier.PRIVATE, //checks to see if the method is public, these checks would be redundant.
  59.       //Modifier.PROTECTED
  60.       ));
  61.  
  62.   private static final String QUALIFIER_TYPE = "org.springframework.beans.factory.annotation.Qualifier";
  63.  
  64.   private static final String VALUE_TYPE = "org.springframework.beans.factory.annotation.Value";
  65.  
  66.   private static final String COMPONENTSCAN_TYPE = "org.springframework.context.annotation.ComponentScan";
  67.  
  68.   private static final String COMPONENTSCANS_TYPE = "org.springframework.context.annotation.ComponentScans";
  69.  
  70.   private static final String CONFIGURATION_TYPE = "org.springframework.context.annotation.Configuration";
  71.  
  72.   private static final String COMPONENT_TYPE = "org.springframework.stereotype.Component";
  73.    
  74.   private static final String AUTOWIRED_TYPE = "org.springframework.beans.factory.annotation.Autowired";
  75.  
  76.   private static final String DEFAULT_ANNOTATION_VALUE = "value";

  77.   /**
  78.    * Will return true if a class level contains exactly a constant final static private literal field.
  79.    */
  80.   private Predicate<VariableElement> staticPrivateFinalLiteralField = ve -> ve.getModifiers()
  81.       .containsAll(Arrays.asList(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL))
  82.       && ve.getModifiers().size() == 3
  83.       && ve.getConstantValue() != null;

  84.   /**
  85.    * Will return true if a class level contains exactly a final private field without a constant value.
  86.    */
  87.   private Predicate<VariableElement> privateFinalField = ve -> ve.getModifiers()
  88.       .containsAll(Arrays.asList(Modifier.PRIVATE, Modifier.FINAL))
  89.       && ve.getModifiers().size() == 2
  90.       && ve.getConstantValue() == null;  
  91.  
  92.   /**
  93.    * Read a TypeElement to get application structure.
  94.    *
  95.    * @param te definition type element.
  96.    * @param messager presents error messages for the compiler to pass to the user.
  97.    * @return the {@link DefinitionModel} parsed from a properly annotated {@link TypeElement}
  98.    * @deprecated please see {@link SpringAnnotationParser#extractDefinition(TypeElement, Messager)}
  99.    */
  100.   public static DefinitionModel parseDefinition(TypeElement te, Messager messager) {  
  101.     return new SpringAnnotationParser().extractDefinition(te, messager);
  102.   }
  103.  
  104.   /**
  105.    * Read a TypeElement to get application structure.
  106.    *
  107.    * @param te definition type element.
  108.    * @param messager presents error messages for the compiler to pass to the user.
  109.    * @return the {@link DefinitionModel} parsed from a properly annotated {@link TypeElement}
  110.    */
  111.   public DefinitionModel extractDefinition(TypeElement te, Messager messager) {  
  112.     Verified verified = te.getAnnotation(Verified.class);
  113.     DefinitionModel model = new DefinitionModel(te, verified == null ? false : verified.root());

  114.     errorIfInvalidClass(te, messager);
  115.    
  116.     model.addDependencyNames(getImportsTypes(te));
  117.     String[] configurationBeanNames  = AnnotationValueExtractor
  118.         .getAnnotationValue(te, CONFIGURATION_TYPE, DEFAULT_ANNOTATION_VALUE);
  119.     String[] componentBeanNames  = AnnotationValueExtractor
  120.         .getAnnotationValue(te, COMPONENT_TYPE, DEFAULT_ANNOTATION_VALUE);
  121.     if (configurationBeanNames == null && componentBeanNames == null) {
  122.       for (Element enclosed : te.getEnclosedElements()) {
  123.         handleEnclosedElements(messager, model, enclosed);
  124.       }
  125.     } else {
  126.       if (componentBeanNames != null) {
  127.         addModelsFromComponent(te, model, componentBeanNames, messager);
  128.       } else {
  129.         messager.printMessage(Kind.ERROR, "@Verified annotation must only be used on "
  130.             + "@Bean LITE factory classes or @Component classes", te);
  131.       }
  132.     }
  133.    
  134.     for (String expectedBean : verified.expectedBeans()) {
  135.       model.addDefinition(new ExpectedModel(expectedBean, te));
  136.     }
  137.     return model;
  138.   }
  139.  
  140.   private List<Modifier> getIllegalModifiers(Set<Modifier> existing, List<Modifier> illegal) {
  141.     List<Modifier> modifiers = new ArrayList<>(existing);
  142.     modifiers.removeIf(modifier -> !illegal.contains(modifier));
  143.     modifiers.sort((m1, m2) ->  m1.name().compareTo(m2.name())); //in case someone reorders
  144.     return modifiers;
  145.   }
  146.  
  147.   private boolean parseBeanMethod(ExecutableElement beanMethod, String[] beanNames, Messager messager) {
  148.     boolean valid = true;
  149.     if (beanNames.length == 0) {
  150.       valid = false;
  151.       messager.printMessage(Kind.ERROR, "All @Bean annotations must define at least one name for a bean.", beanMethod);
  152.     }
  153.     if (beanMethod.getReturnType().getKind() != TypeKind.DECLARED) {
  154.       valid = false;
  155.       messager.printMessage(Kind.ERROR, "@Bean methods must return an Object", beanMethod);
  156.     }
  157.     if (!beanMethod.getModifiers().contains(Modifier.PUBLIC)) {
  158.       valid = false;
  159.       messager.printMessage(Kind.ERROR, "@Bean methods must be marked public", beanMethod);
  160.     }
  161.     List<Modifier> illegalModifiers = getIllegalModifiers(beanMethod.getModifiers(), DISALLOWED_ON_METHOD);
  162.     if (illegalModifiers.size() != 0) {
  163.       valid = false;
  164.       messager.printMessage(Kind.ERROR, "Illegal modifiers found on spring @Bean method: "
  165.            + illegalModifiers.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
  166.           beanMethod);
  167.     }
  168.     return valid;
  169.   }

  170.   private void handleEnclosedElements(Messager messager, DefinitionModel model, Element enclosed) {
  171.     switch (enclosed.getKind()) {
  172.       case METHOD:
  173.         ExecutableElement execelement = (ExecutableElement) enclosed;
  174.         String[] beanNames = AnnotationValueExtractor
  175.             .getAnnotationValue(execelement, "org.springframework.context.annotation.Bean", "name");
  176.        
  177.         if (beanNames != null) {
  178.           List<InstanceDependencyModel> dependencies = execElementDependency(messager, model, execelement);          
  179.           if (parseBeanMethod(execelement, beanNames, messager)) {
  180.             List<String> names = new ArrayList<>(Arrays.asList(beanNames));
  181.             String defaultName = names.get(0);
  182.             names.remove(defaultName);
  183.             model.addDefinition(new InstanceModel(defaultName, model.getIdentity(), execelement,
  184.                 execelement.getReturnType().toString(), dependencies, names));
  185.           }
  186.         } else {
  187.           messager.printMessage(Kind.ERROR, "All methods on @Configuration must have @Bean annotation", execelement);
  188.         }
  189.         break;
  190.       case FIELD:
  191.         if (!staticPrivateFinalLiteralField.test((VariableElement) enclosed)) {
  192.           messager.printMessage(Kind.ERROR, "Only private static final constants are permitted in @Verified @Configuration classes",
  193.               enclosed);
  194.         }
  195.         break;
  196.       case ENUM_CONSTANT:
  197.         if (!staticPrivateFinalLiteralField.test((VariableElement) enclosed)) {
  198.           messager.printMessage(Kind.ERROR, "Only private static final constants are permitted in @Verified @Configuration classes",
  199.               enclosed);
  200.         }
  201.         break;
  202.       case CONSTRUCTOR:
  203.         ExecutableElement constelement = (ExecutableElement) enclosed;
  204.         if (!constelement.getModifiers().contains(Modifier.PUBLIC)) {
  205.           messager.printMessage(Kind.ERROR, "@Configuration should not have any non-public constructors.", enclosed);
  206.         }
  207.         if (constelement.getParameters().size() > 0) {
  208.           messager.printMessage(Kind.ERROR, "@Configuration should not have any non-defualt constructors.", enclosed);
  209.         }
  210.         break;
  211.       default:
  212.         messager.printMessage(Kind.ERROR, "Only @Bean methods, private static final literals, and default constructors "
  213.             + "are allowed on @Configuration classes", enclosed);
  214.         break;
  215.     }
  216.   }

  217.   /**
  218.    * This method is called on {@link ExecutableElement}.
  219.    * Bean methods on an @Configuration bean, or Constructors on @Component classes.
  220.    * This method parses the @Qualifier, or @Value annotations if a @Verified=root, and reads the types of each parameter.
  221.    * That data is used to build a list of {@link InstanceDependencyModel}'s which are part of an {@link InstanceModel}.
  222.    * All parameters must have an @Qualifier or @Value, and the annotations can not be mixed, errors will result otherwise.
  223.    *
  224.    * @param messager APT messager that will receive error messages.
  225.    * @param model the DefinitionModel being parse, which may be a @Configuration or @Component annotated entity.
  226.    * @param execelement the bean method if an @Configuration, or the constructor if an @Component.
  227.    * @return the dependencies of the to be constructed {@link InstanceModel}
  228.    */
  229.   private List<InstanceDependencyModel> execElementDependency(Messager messager, DefinitionModel model,
  230.       ExecutableElement execelement) {
  231.     List<InstanceDependencyModel> dependencies = new ArrayList<>();
  232.     boolean hasValues = false;
  233.     boolean hasQualifiers = false;
  234.     for (VariableElement varelement : execelement.getParameters()) {
  235.      
  236.       String[] qualifierNames = AnnotationValueExtractor
  237.           .getAnnotationValue(varelement, QUALIFIER_TYPE, DEFAULT_ANNOTATION_VALUE);
  238.       String[] valueNames = AnnotationValueExtractor
  239.           .getAnnotationValue(varelement, VALUE_TYPE, DEFAULT_ANNOTATION_VALUE);
  240.      
  241.       if (qualifierNames == null && valueNames == null) {
  242.         messager.printMessage(Kind.ERROR, "All parameters must have an @Qualifier or a @Value annotation", varelement);
  243.       }
  244.       if (qualifierNames != null) {
  245.         dependencies.add(new InstanceDependencyModel(qualifierNames[0], varelement.asType().toString()));
  246.         hasQualifiers = true;
  247.       }
  248.       if (valueNames != null) {
  249.         //ignore values as they will be used to build beans and pass the data on, and
  250.         //are not beans themselves... and cannot be intermingled with @Qualifiers.
  251.         hasValues = true;
  252.       }
  253.     }
  254.     if (hasValues && hasQualifiers) {
  255.       messager.printMessage(Kind.ERROR, "No method may define both @Qualifier or a @Value annotations,"
  256.           + " keep property values in there own beans", execelement);
  257.     }
  258.     if (hasValues &&  !model.isRootNode()) {
  259.       messager.printMessage(Kind.ERROR, "Only @Verified(root=true) nodes may use @Value annotations to create beans,"
  260.           + " decouples spring graph from environment", execelement);
  261.     }
  262.     return dependencies;
  263.   }

  264.   /**
  265.    * Builds an instance model by finding the autowired constructor of an @Component model.
  266.    *
  267.    * @param te the TypeElement corresponding to the @Component class.
  268.    * @param dm the definitionModel built from the @Component.
  269.    * @param names the list if names in an @Component annotation.  Users must explicitly define one.
  270.    * @param messager errors are added to this APT messager.
  271.    */
  272.   private void addModelsFromComponent(TypeElement te, DefinitionModel dm, String[] names, Messager messager) {
  273.     List<InstanceDependencyModel> dependencies = new ArrayList<>();
  274.     ExecutableElement chosenConstructor = findAutowiredConstructor(extractConstructorsFromComponent(te));
  275.     if (chosenConstructor == null) {
  276.       messager.printMessage(Kind.ERROR, "No single default constructor or single @Autowired constructor", te);
  277.     } else {
  278.       dependencies = execElementDependency(messager, dm, chosenConstructor);
  279.       //dm.getExpectedDefinitions()
  280.     }
  281.     te.getEnclosedElements().stream()
  282.         .filter(el -> el instanceof VariableElement)
  283.         .map(el -> (VariableElement) el)
  284.         .filter(ve -> !staticPrivateFinalLiteralField.test(ve) && !privateFinalField.test(ve))
  285.         .forEach(ve -> messager
  286.             .printMessage(Kind.ERROR, "@Component classes my only have static final constant fields or final private fields", ve));
  287.    
  288.     InstanceModel model = new InstanceModel(names[0],
  289.         dm.getIdentity(),
  290.         chosenConstructor,
  291.         te.getQualifiedName().toString(),
  292.         dependencies,
  293.         new ArrayList<>());
  294.    
  295.     dm.addDefinition(model);
  296.     for (InstanceDependencyModel dep : dependencies) {
  297.       ExpectedModel expectedModel = new ExpectedModel(dep.getIdentity());
  298.       expectedModel.addDefinitionReferenceToType(model.getIdentity(), dep.getType());
  299.       dm.addDefinition(expectedModel);
  300.     }
  301.   }

  302.   /**
  303.    * Analyzes a list of constructors from an @Component, looking for a single constructor, or if multiple
  304.    * constructors exist, a single constructor marked with @Autowire.
  305.    *
  306.    * @param constructors a list of constructors from an @Component.
  307.    * @return the executable element, or null.
  308.    */
  309.   private ExecutableElement findAutowiredConstructor(List<ExecutableElement> constructors) {
  310.     ExecutableElement chosenConstructor = null;
  311.     if (constructors.size() == 1) {
  312.       chosenConstructor = constructors.get(0);
  313.     } else {
  314.       chosenConstructor = constructors.stream()
  315.         .filter(ex -> AnnotationValueExtractor.getAnnotationValue(ex, AUTOWIRED_TYPE, "") != null)
  316.         .limit(2) //stop at two. efficiency.
  317.         .reduce((a, b) -> null) //if more than one return null.
  318.         .orElse(null);
  319.     }
  320.     return chosenConstructor;
  321.   }

  322.   /**
  323.    * Given an @Component's {@link TypeElement} find's all constructors of that type.
  324.    *
  325.    * @param te a representation of an @Component class.
  326.    * @return a list of executable elements representing all found constructors.
  327.    */
  328.   private List<ExecutableElement> extractConstructorsFromComponent(TypeElement te) {
  329.     return te.getEnclosedElements().stream()
  330.       .filter(enclosed -> enclosed instanceof ExecutableElement)
  331.       .filter(enclosed -> "<init>".equals(enclosed.getSimpleName().toString()))
  332.       .map(enclosed -> (ExecutableElement) enclosed)
  333.       .collect(Collectors.toList());
  334.   }

  335.   private void errorIfInvalidClass(TypeElement te, Messager messager) {
  336.     if (te.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
  337.       messager.printMessage(Kind.ERROR, "The class must be a top level class, not an internal class", te);
  338.     }
  339.     if (AnnotationValueExtractor.getAnnotationValue(te, COMPONENTSCAN_TYPE, "basePackages") != null
  340.         || AnnotationValueExtractor.getAnnotationValue(te, COMPONENTSCANS_TYPE, "basePackages") != null) {
  341.       messager.printMessage(Kind.ERROR, "You may not use @ComponentScan(s) on @Verified classes", te);
  342.     }
  343.   }
  344.  
  345.   private List<String> getImportsTypes(TypeElement element) {
  346.     String[] values = AnnotationValueExtractor
  347.         .getAnnotationValue(element, "org.springframework.context.annotation.Import", DEFAULT_ANNOTATION_VALUE);
  348.     if (values == null) {
  349.       return new ArrayList<>();
  350.     } else {
  351.       return Arrays.asList(values);
  352.     }
  353.   }

  354. }