View Javadoc
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  
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.function.Predicate;
35  import java.util.stream.Collectors;
36  
37  import javax.annotation.processing.Messager;
38  import javax.lang.model.element.Element;
39  import javax.lang.model.element.ElementKind;
40  import javax.lang.model.element.ExecutableElement;
41  import javax.lang.model.element.Modifier;
42  import javax.lang.model.element.TypeElement;
43  import javax.lang.model.element.VariableElement;
44  import javax.lang.model.type.TypeKind;
45  import javax.tools.Diagnostic.Kind;
46  
47  import com.salesforce.apt.graph.model.DefinitionModel;
48  import com.salesforce.apt.graph.model.ExpectedModel;
49  import com.salesforce.apt.graph.model.InstanceDependencyModel;
50  import com.salesforce.apt.graph.model.InstanceModel;
51  import com.salesforce.aptspring.Verified;
52  
53  public class SpringAnnotationParser {
54    
55    private static final List<Modifier> DISALLOWED_ON_METHOD = Collections.unmodifiableList(Arrays.asList(
56        Modifier.ABSTRACT,
57        Modifier.DEFAULT,
58        Modifier.FINAL,
59        Modifier.NATIVE,
60        Modifier.STATIC,
61        Modifier.VOLATILE
62        //Modifier.PRIVATE, //checks to see if the method is public, these checks would be redundant.
63        //Modifier.PROTECTED
64        ));
65    
66    private static final String QUALIFIER_TYPE = "org.springframework.beans.factory.annotation.Qualifier";
67    
68    private static final String VALUE_TYPE = "org.springframework.beans.factory.annotation.Value";
69    
70    private static final String COMPONENTSCAN_TYPE = "org.springframework.context.annotation.ComponentScan";
71    
72    private static final String COMPONENTSCANS_TYPE = "org.springframework.context.annotation.ComponentScans";
73    
74    private static final String CONFIGURATION_TYPE = "org.springframework.context.annotation.Configuration";
75    
76    private static final String COMPONENT_TYPE = "org.springframework.stereotype.Component";
77      
78    private static final String AUTOWIRED_TYPE = "org.springframework.beans.factory.annotation.Autowired";
79    
80    private static final String DEFAULT_ANNOTATION_VALUE = "value";
81  
82    /**
83     * Will return true if a class level contains exactly a constant final static private literal field.
84     */
85    private Predicate<VariableElement> staticPrivateFinalLiteralField = ve -> ve.getModifiers()
86        .containsAll(Arrays.asList(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL))
87        && ve.getModifiers().size() == 3
88        && ve.getConstantValue() != null;
89  
90    /**
91     * Will return true if a class level contains exactly a final private field without a constant value.
92     */
93    private Predicate<VariableElement> privateFinalField = ve -> ve.getModifiers()
94        .containsAll(Arrays.asList(Modifier.PRIVATE, Modifier.FINAL))
95        && ve.getModifiers().size() == 2
96        && ve.getConstantValue() == null;  
97   
98    /**
99     * Read a TypeElement to get application structure.
100    * 
101    * @param te definition type element.
102    * @param messager presents error messages for the compiler to pass to the user.
103    * @return the {@link DefinitionModel} parsed from a properly annotated {@link TypeElement}
104    * @deprecated please see {@link SpringAnnotationParser#extractDefinition(TypeElement, Messager)}
105    */
106   public static DefinitionModel parseDefinition(TypeElement te, Messager messager) {  
107     return new SpringAnnotationParser().parseDefinition(te, messager);
108   }
109   
110   /**
111    * Read a TypeElement to get application structure.
112    * 
113    * @param te definition type element.
114    * @param messager presents error messages for the compiler to pass to the user.
115    * @return the {@link DefinitionModel} parsed from a properly annotated {@link TypeElement}
116    */
117   public DefinitionModel extractDefinition(TypeElement te, Messager messager) {  
118     Verified verified = te.getAnnotation(Verified.class);
119     DefinitionModel model = new DefinitionModel(te, verified == null ? false : verified.root());
120 
121     errorIfInvalidClass(te, messager);
122     
123     model.addDependencyNames(getImportsTypes(te));
124     String[] configurationBeanNames  = AnnotationValueExtractor
125         .getAnnotationValue(te, CONFIGURATION_TYPE, DEFAULT_ANNOTATION_VALUE);
126     String[] componentBeanNames  = AnnotationValueExtractor
127         .getAnnotationValue(te, COMPONENT_TYPE, DEFAULT_ANNOTATION_VALUE);
128     if (configurationBeanNames != null) {
129       for (Element enclosed : te.getEnclosedElements()) {
130         handleEnclosedElements(messager, model, enclosed);
131       }
132     } else {
133       if (componentBeanNames != null) {
134         addModelsFromComponent(te, model, componentBeanNames, messager);
135       } else {
136         messager.printMessage(Kind.ERROR, "@Verified annotation must only be used on @Configuration or @Component classes", te);
137       }
138     }
139     
140     for (String expectedBean : verified.expectedBeans()) {
141       model.addDefinition(new ExpectedModel(expectedBean, te));
142     }
143     return model;
144   }
145   
146   private List<Modifier> getIllegalModifiers(Set<Modifier> existing, List<Modifier> illegal) {
147     List<Modifier> modifiers = new ArrayList<>(existing);
148     modifiers.removeIf(modifier -> !illegal.contains(modifier));
149     modifiers.sort((m1, m2) ->  m1.name().compareTo(m2.name())); //in case someone reorders
150     return modifiers;
151   }
152   
153   private boolean parseBeanMethod(ExecutableElement beanMethod, String[] beanNames, Messager messager) {
154     boolean valid = true;
155     if (beanNames.length == 0) {
156       valid = false;
157       messager.printMessage(Kind.ERROR, "All @Bean annotations must define at least one name for a bean.", beanMethod);
158     }
159     if (beanMethod.getReturnType().getKind() != TypeKind.DECLARED) {
160       valid = false;
161       messager.printMessage(Kind.ERROR, "@Bean methods must return an Object", beanMethod);
162     }
163     if (!beanMethod.getModifiers().contains(Modifier.PUBLIC)) {
164       valid = false;
165       messager.printMessage(Kind.ERROR, "@Bean methods must be marked public", beanMethod);
166     }
167     List<Modifier> illegalModifiers = getIllegalModifiers(beanMethod.getModifiers(), DISALLOWED_ON_METHOD);
168     if (illegalModifiers.size() != 0) {
169       valid = false;
170       messager.printMessage(Kind.ERROR, "Illegal modifiers found on spring @Bean method: "
171            + illegalModifiers.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
172           beanMethod);
173     }
174     return valid;
175   }
176 
177   private void handleEnclosedElements(Messager messager, DefinitionModel model, Element enclosed) {
178     switch (enclosed.getKind()) {
179       case METHOD: 
180         ExecutableElement execelement = (ExecutableElement) enclosed;
181         String[] beanNames = AnnotationValueExtractor
182             .getAnnotationValue(execelement, "org.springframework.context.annotation.Bean", "name");
183         
184         if (beanNames != null) {
185           List<InstanceDependencyModel> dependencies = execElementDependency(messager, model, execelement);          
186           if (parseBeanMethod(execelement, beanNames, messager)) {
187             List<String> names = new ArrayList<>(Arrays.asList(beanNames));
188             String defaultName = names.get(0);
189             names.remove(defaultName);
190             model.addDefinition(new InstanceModel(defaultName, model.getIdentity(), execelement,
191                 execelement.getReturnType().toString(), dependencies, names));
192           }
193         } else {
194           messager.printMessage(Kind.ERROR, "All methods on @Configuration must have @Bean annotation", execelement);
195         }
196         break;
197       case FIELD:
198         if (!staticPrivateFinalLiteralField.test((VariableElement) enclosed)) {
199           messager.printMessage(Kind.ERROR, "Only private static final constants are permitted in @Verified @Configuration classes",
200               enclosed);
201         }
202         break;
203       case ENUM_CONSTANT: 
204         if (!staticPrivateFinalLiteralField.test((VariableElement) enclosed)) {
205           messager.printMessage(Kind.ERROR, "Only private static final constants are permitted in @Verified @Configuration classes",
206               enclosed);
207         }
208         break;
209       case CONSTRUCTOR:
210         ExecutableElement constelement = (ExecutableElement) enclosed;
211         if (!constelement.getModifiers().contains(Modifier.PUBLIC)) {
212           messager.printMessage(Kind.ERROR, "@Configuration should not have any non-public constructors.", enclosed);
213         }
214         if (constelement.getParameters().size() > 0) {
215           messager.printMessage(Kind.ERROR, "@Configuration should not have any non-defualt constructors.", enclosed);
216         }
217         break;
218       default:
219         messager.printMessage(Kind.ERROR, "Only @Bean methods, private static final literals, and default constructors "
220             + "are allowed on @Configuration classes", enclosed);
221         break;
222     }
223   }
224 
225   /**
226    * This method is called on {@link ExecutableElement}.
227    * Bean methods on an @Configuration bean, or Constructors on @Component classes.
228    * This method parses the @Qualifier, or @Value annotations if a @Verified=root, and reads the types of each parameter.
229    * That data is used to build a list of {@link InstanceDependencyModel}'s which are part of an {@link InstanceModel}.
230    * All parameters must have an @Qualifier or @Value, and the annotations can not be mixed, errors will result otherwise.
231    * 
232    * @param messager APT messager that will receive error messages.
233    * @param model the DefinitionModel being parse, which may be a @Configuration or @Component annotated entity.
234    * @param execelement the bean method if an @Configuration, or the constructor if an @Component.
235    * @return the dependencies of the to be constructed {@link InstanceModel}
236    */
237   private List<InstanceDependencyModel> execElementDependency(Messager messager, DefinitionModel model,
238       ExecutableElement execelement) {
239     List<InstanceDependencyModel> dependencies = new ArrayList<>();
240     boolean hasValues = false;
241     boolean hasQualifiers = false;
242     for (VariableElement varelement : execelement.getParameters()) {
243       
244       String[] qualifierNames = AnnotationValueExtractor
245           .getAnnotationValue(varelement, QUALIFIER_TYPE, DEFAULT_ANNOTATION_VALUE);
246       String[] valueNames = AnnotationValueExtractor
247           .getAnnotationValue(varelement, VALUE_TYPE, DEFAULT_ANNOTATION_VALUE);
248       
249       if (qualifierNames == null && valueNames == null) {
250         messager.printMessage(Kind.ERROR, "All parameters must have an @Qualifier or a @Value annotation", varelement);
251       } 
252       if (qualifierNames != null) {
253         dependencies.add(new InstanceDependencyModel(qualifierNames[0], varelement.asType().toString()));
254         hasQualifiers = true;
255       }
256       if (valueNames != null) {
257         //ignore values as they will be used to build beans and pass the data on, and
258         //are not beans themselves... and cannot be intermingled with @Qualifiers.
259         hasValues = true;
260       }
261     }
262     if (hasValues && hasQualifiers) {
263       messager.printMessage(Kind.ERROR, "No method may define both @Qualifier or a @Value annotations,"
264           + " keep property values in there own beans", execelement);
265     }
266     if (hasValues &&  !model.isRootNode()) {
267       messager.printMessage(Kind.ERROR, "Only @Verified(root=true) nodes may use @Value annotations to create beans,"
268           + " decouples spring graph from environment", execelement);
269     }
270     return dependencies;
271   }
272 
273   /**
274    * Builds an instance model by finding the autowired constructor of an @Component model as well as 
275    * 
276    * @param te the TypeElement corresponding to the @Component class.
277    * @param dm the definitionModel built from the @Component.
278    * @param names the list if names in an @Component annotation.  Users must explicitly define one.
279    * @param messager errors are added to this APT messager.
280    */
281   private void addModelsFromComponent(TypeElement te, DefinitionModel dm, String[] names, Messager messager) {
282     List<InstanceDependencyModel> dependencies = new ArrayList<>();
283     ExecutableElement chosenConstructor = findAutowiredConstructor(extractConstructorsFromComponent(te));
284     if (chosenConstructor == null) {
285       messager.printMessage(Kind.ERROR, "No single default constructor or single @Autowired constructor", te);
286     } else {
287       dependencies = execElementDependency(messager, dm, chosenConstructor);
288       //dm.getExpectedDefinitions()
289     }
290     te.getEnclosedElements().stream()
291         .filter(el -> el instanceof VariableElement)
292         .map(el -> (VariableElement) el)
293         .filter(ve -> !staticPrivateFinalLiteralField.test(ve) && !privateFinalField.test(ve))
294         .forEach(ve -> messager
295             .printMessage(Kind.ERROR, "@Component classes my only have static final constant fields or final private fields", ve));
296     
297     InstanceModel model = new InstanceModel(names[0],
298         dm.getIdentity(), 
299         chosenConstructor, 
300         te.getQualifiedName().toString(),
301         dependencies, 
302         new ArrayList<>());
303     
304     dm.addDefinition(model);
305     for (InstanceDependencyModel dep : dependencies) {
306       ExpectedModel expectedModel = new ExpectedModel(dep.getIdentity());
307       expectedModel.addDefinitionReferenceToType(model.getIdentity(), dep.getType());
308       dm.addDefinition(expectedModel);
309     }
310   }
311 
312   /**
313    * Analyzes a list of constructors from an @Component, looking for a single constructor, or if multiple
314    * constructors exist, a single constructor marked with @Autowire.
315    *
316    * @param constructors a list of constructors from an @Component.
317    * @return the executable element, or null.
318    */
319   private ExecutableElement findAutowiredConstructor(List<ExecutableElement> constructors) {
320     ExecutableElement chosenConstructor = null;
321     if (constructors.size() == 1) {
322       chosenConstructor = constructors.get(0);
323     } else {
324       chosenConstructor = constructors.stream()
325         .filter(ex -> AnnotationValueExtractor.getAnnotationValue(ex, AUTOWIRED_TYPE, "") != null)
326         .limit(2) //stop at two. efficiency.
327         .reduce((a, b) -> null) //if more than one return null.
328         .orElse(null);
329     }
330     return chosenConstructor;
331   }
332 
333   /**
334    * Given an @Component's {@link TypeElement} find's all constructors of that type.
335    * 
336    * @param te a representation of an @Component class.
337    * @return a list of executable elements representing all found constructors.
338    */
339   private List<ExecutableElement> extractConstructorsFromComponent(TypeElement te) {
340     return te.getEnclosedElements().stream()
341       .filter(enclosed -> enclosed instanceof ExecutableElement)
342       .filter(enclosed -> "<init>".equals(enclosed.getSimpleName().toString()))
343       .map(enclosed -> (ExecutableElement) enclosed)
344       .collect(Collectors.toList());
345   }
346 
347   private void errorIfInvalidClass(TypeElement te, Messager messager) {
348     if (te.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
349       messager.printMessage(Kind.ERROR, "The class must be a top level class, not an internal class", te);
350     }
351     if (AnnotationValueExtractor.getAnnotationValue(te, COMPONENTSCAN_TYPE, "basePackages") != null
352         || AnnotationValueExtractor.getAnnotationValue(te, COMPONENTSCANS_TYPE, "basePackages") != null) {
353       messager.printMessage(Kind.ERROR, "You may not use @ComponentScan(s) on @Verified classes", te);
354     }
355   }
356   
357   private List<String> getImportsTypes(TypeElement element) {
358     String[] values = AnnotationValueExtractor
359         .getAnnotationValue(element, "org.springframework.context.annotation.Import", DEFAULT_ANNOTATION_VALUE);
360     if (values == null) {
361       return new ArrayList<>();
362     } else {
363       return Arrays.asList(values);
364     }
365   }
366 
367 }