1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
63
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
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
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
100
101
102
103
104
105
106 public static DefinitionModel parseDefinition(TypeElement te, Messager messager) {
107 return new SpringAnnotationParser().parseDefinition(te, messager);
108 }
109
110
111
112
113
114
115
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()));
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
227
228
229
230
231
232
233
234
235
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
258
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
275
276
277
278
279
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
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
314
315
316
317
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)
327 .reduce((a, b) -> null)
328 .orElse(null);
329 }
330 return chosenConstructor;
331 }
332
333
334
335
336
337
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 }