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.AbstractMap;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37 import java.util.function.Predicate;
38 import java.util.stream.Collector;
39 import java.util.stream.Collectors;
40 import java.util.stream.Stream;
41
42 import javax.annotation.processing.Messager;
43 import javax.lang.model.element.Element;
44 import javax.lang.model.element.ElementKind;
45 import javax.lang.model.element.ExecutableElement;
46 import javax.lang.model.element.Modifier;
47 import javax.lang.model.element.TypeElement;
48 import javax.lang.model.element.VariableElement;
49 import javax.lang.model.type.TypeKind;
50 import javax.tools.Diagnostic.Kind;
51
52 import com.salesforce.apt.graph.model.DefinitionModel;
53 import com.salesforce.apt.graph.model.ExpectedModel;
54 import com.salesforce.apt.graph.model.InstanceDependencyModel;
55 import com.salesforce.apt.graph.model.InstanceModel;
56 import com.salesforce.aptspring.Verified;
57
58 public class SpringAnnotationParser {
59
60 private static final List<Modifier> DISALLOWED_ON_METHOD = Collections.unmodifiableList(Arrays.asList(
61 Modifier.ABSTRACT,
62 Modifier.DEFAULT,
63 Modifier.FINAL,
64 Modifier.NATIVE,
65 Modifier.STATIC,
66 Modifier.VOLATILE
67
68
69 ));
70
71 private static final String QUALIFIER_TYPE = "org.springframework.beans.factory.annotation.Qualifier";
72
73 private static final String VALUE_TYPE = "org.springframework.beans.factory.annotation.Value";
74
75 private static final String COMPONENTSCAN_TYPE = "org.springframework.context.annotation.ComponentScan";
76
77 private static final String COMPONENTSCANS_TYPE = "org.springframework.context.annotation.ComponentScans";
78
79 private static final String CONFIGURATION_TYPE = "org.springframework.context.annotation.Configuration";
80
81 private static final String COMPONENT_TYPE = "org.springframework.stereotype.Component";
82
83 private static final String IMPORT_TYPE = "org.springframework.context.annotation.Import";
84
85 private static final String IMPORT_RESOURCE_TYPE = "org.springframework.context.annotation.ImportResource";
86
87 private static final String AUTOWIRED_TYPE = "org.springframework.beans.factory.annotation.Autowired";
88
89 private static final String DEFAULT_ANNOTATION_VALUE = "value";
90
91 private static final Map<String, String> BANNED_ANNOTATIONS = Collections.unmodifiableMap(Stream.of(
92 entry(COMPONENTSCAN_TYPE, "You may not use @ComponentScan(s) on @Verified classes"),
93 entry(COMPONENTSCANS_TYPE, "You may not use @ComponentScan(s) on @Verified classes"),
94 entry(IMPORT_RESOURCE_TYPE, "You may not use @ImportResource on @Verified classes"))
95 .collect(entriesToMap()));
96
97 private static final Map<String, String> COMPONENT_BANNED_ANNOTATIONS = Collections.unmodifiableMap(
98 Stream.concat(BANNED_ANNOTATIONS.entrySet().stream(),
99 Stream.of(
100 entry(IMPORT_TYPE, "You may not use @Import on @Verified @Component classes")))
101 .collect(entriesToMap()));
102
103
104 private static final Map<String, String> BEAN_LITE_BANNED_ANNOTATIONS = Collections.unmodifiableMap(
105 Stream.concat(BANNED_ANNOTATIONS.entrySet().stream(),
106 Stream.of(
107 entry(CONFIGURATION_TYPE, "@Verified annotation must only be used on @Bean LITE factory classes or @Component classes"),
108 entry(COMPONENT_TYPE, "You may not use @Component on @Verified classes with @Bean methods")))
109 .collect(entriesToMap()));
110
111
112
113
114
115 private Predicate<VariableElement> staticPrivateFinalLiteralField = ve -> ve.getModifiers()
116 .containsAll(Arrays.asList(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL))
117 && ve.getModifiers().size() == 3
118 && ve.getConstantValue() != null;
119
120
121
122
123 private Predicate<VariableElement> privateFinalField = ve -> ve.getModifiers()
124 .containsAll(Arrays.asList(Modifier.PRIVATE, Modifier.FINAL))
125 && ve.getModifiers().size() == 2
126 && ve.getConstantValue() == null;
127
128
129
130
131
132
133
134 private static <K, V> Map.Entry<K, V> entry(K key, V value) {
135 return new AbstractMap.SimpleEntry<>(key, value);
136 }
137
138
139
140
141
142 private static <K, U> Collector<Map.Entry<K, U>, ?, Map<K, U>> entriesToMap() {
143 return Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue());
144 }
145
146
147
148
149
150
151
152
153
154 public static DefinitionModel parseDefinition(TypeElement te, Messager messager) {
155 return new SpringAnnotationParser().extractDefinition(te, messager);
156 }
157
158
159
160
161
162
163
164
165 public DefinitionModel extractDefinition(TypeElement te, Messager messager) {
166 Verified verified = te.getAnnotation(Verified.class);
167 DefinitionModel model = new DefinitionModel(te, verified == null ? false : verified.root());
168
169 errorIfInnerClass(te, messager);
170
171 model.addDependencyNames(getImportsTypes(te));
172 String[] componentBeanNames = AnnotationValueExtractor.getAnnotationValue(te, COMPONENT_TYPE, DEFAULT_ANNOTATION_VALUE);
173 if (componentBeanNames != null) {
174 errorOnBannedTypeToMessage(te, messager, COMPONENT_BANNED_ANNOTATIONS);
175 addModelsFromComponent(te, model, componentBeanNames, messager);
176 } else {
177 errorOnBannedTypeToMessage(te, messager, BEAN_LITE_BANNED_ANNOTATIONS);
178 for (Element enclosed : te.getEnclosedElements()) {
179 addBeanMethodsFromBeanLiteConfig(messager, model, enclosed);
180 }
181 }
182
183 for (String expectedBean : verified.expectedBeans()) {
184 model.addDefinition(new ExpectedModel(expectedBean, te));
185 }
186 return model;
187 }
188
189 private List<Modifier> getIllegalModifiers(Set<Modifier> existing, List<Modifier> illegal) {
190 List<Modifier> modifiers = new ArrayList<>(existing);
191 modifiers.removeIf(modifier -> !illegal.contains(modifier));
192 modifiers.sort((m1, m2) -> m1.name().compareTo(m2.name()));
193 return modifiers;
194 }
195
196 private boolean parseBeanMethod(ExecutableElement beanMethod, String[] beanNames, Messager messager) {
197 boolean valid = true;
198 if (beanNames.length == 0) {
199 valid = false;
200 messager.printMessage(Kind.ERROR, "All @Bean annotations must define at least one name for a bean.", beanMethod);
201 }
202 if (beanMethod.getReturnType().getKind() != TypeKind.DECLARED) {
203 valid = false;
204 messager.printMessage(Kind.ERROR, "@Bean methods must return an Object", beanMethod);
205 }
206 if (!beanMethod.getModifiers().contains(Modifier.PUBLIC)) {
207 valid = false;
208 messager.printMessage(Kind.ERROR, "@Bean methods must be marked public", beanMethod);
209 }
210 List<Modifier> illegalModifiers = getIllegalModifiers(beanMethod.getModifiers(), DISALLOWED_ON_METHOD);
211 if (illegalModifiers.size() != 0) {
212 valid = false;
213 messager.printMessage(Kind.ERROR, "Illegal modifiers found on spring @Bean method: "
214 + illegalModifiers.stream().map(m -> m.name()).collect(Collectors.joining(", ")),
215 beanMethod);
216 }
217 return valid;
218 }
219
220 private void addBeanMethodsFromBeanLiteConfig(Messager messager, DefinitionModel model, Element enclosed) {
221 switch (enclosed.getKind()) {
222 case METHOD:
223 ExecutableElement execelement = (ExecutableElement) enclosed;
224 String[] beanNames = AnnotationValueExtractor
225 .getAnnotationValue(execelement, "org.springframework.context.annotation.Bean", "name");
226
227 if (beanNames != null) {
228 List<InstanceDependencyModel> dependencies = execElementDependency(messager, model, execelement);
229 if (parseBeanMethod(execelement, beanNames, messager)) {
230 List<String> names = new ArrayList<>(Arrays.asList(beanNames));
231 String defaultName = names.get(0);
232 names.remove(defaultName);
233 model.addDefinition(new InstanceModel(defaultName, model.getIdentity(), execelement,
234 execelement.getReturnType().toString(), dependencies, names));
235 }
236 } else {
237 messager.printMessage(Kind.ERROR, "All methods on @Configuration must have @Bean annotation", execelement);
238 }
239 break;
240 case FIELD:
241 if (!staticPrivateFinalLiteralField.test((VariableElement) enclosed)) {
242 messager.printMessage(Kind.ERROR, "Only private static final constants are permitted in @Verified @Configuration classes",
243 enclosed);
244 }
245 break;
246 case ENUM_CONSTANT:
247 if (!staticPrivateFinalLiteralField.test((VariableElement) enclosed)) {
248 messager.printMessage(Kind.ERROR, "Only private static final constants are permitted in @Verified @Configuration classes",
249 enclosed);
250 }
251 break;
252 case CONSTRUCTOR:
253 ExecutableElement constelement = (ExecutableElement) enclosed;
254 if (!constelement.getModifiers().contains(Modifier.PUBLIC)) {
255 messager.printMessage(Kind.ERROR, "@Configuration should not have any non-public constructors.", enclosed);
256 }
257 if (constelement.getParameters().size() > 0) {
258 messager.printMessage(Kind.ERROR, "@Configuration should not have any non-default constructors.", enclosed);
259 }
260 break;
261 default:
262 messager.printMessage(Kind.ERROR, "Only @Bean methods, private static final literals, and default constructors "
263 + "are allowed on @Configuration classes", enclosed);
264 break;
265 }
266 }
267
268
269
270
271
272
273
274
275
276
277
278
279
280 private List<InstanceDependencyModel> execElementDependency(Messager messager, DefinitionModel model,
281 ExecutableElement execelement) {
282 List<InstanceDependencyModel> dependencies = new ArrayList<>();
283 boolean hasValues = false;
284 boolean hasQualifiers = false;
285 for (VariableElement varelement : execelement.getParameters()) {
286
287 String[] qualifierNames = AnnotationValueExtractor
288 .getAnnotationValue(varelement, QUALIFIER_TYPE, DEFAULT_ANNOTATION_VALUE);
289 String[] valueNames = AnnotationValueExtractor
290 .getAnnotationValue(varelement, VALUE_TYPE, DEFAULT_ANNOTATION_VALUE);
291
292 if ((qualifierNames == null || qualifierNames.length == 0)
293 && (valueNames == null || valueNames.length == 0)) {
294 messager.printMessage(Kind.ERROR, "All parameters must have an @Qualifier or a @Value annotation with a value", varelement);
295 }
296 if (qualifierNames != null && qualifierNames.length > 0) {
297 dependencies.add(new InstanceDependencyModel(qualifierNames[0], varelement.asType().toString()));
298 hasQualifiers = true;
299 }
300 if (valueNames != null && valueNames.length > 0) {
301
302
303 hasValues = true;
304 }
305 }
306 if (hasValues && hasQualifiers) {
307 messager.printMessage(Kind.ERROR, "No method may define both @Qualifier or a @Value annotations,"
308 + " keep property values in there own beans", execelement);
309 }
310 if (hasValues && !model.isRootNode()) {
311 messager.printMessage(Kind.ERROR, "Only @Verified(root=true) nodes may use @Value annotations to create beans,"
312 + " decouples spring graph from environment", execelement);
313 }
314 return dependencies;
315 }
316
317
318
319
320
321
322
323
324
325 private void addModelsFromComponent(TypeElement te, DefinitionModel dm, String[] names, Messager messager) {
326 List<InstanceDependencyModel> dependencies = new ArrayList<>();
327 ExecutableElement chosenConstructor = findAutowiredConstructor(extractConstructorsFromComponent(te));
328 if (chosenConstructor == null) {
329 messager.printMessage(Kind.ERROR, "No single default constructor or single @Autowired constructor", te);
330 } else {
331 dependencies = execElementDependency(messager, dm, chosenConstructor);
332
333 }
334 te.getEnclosedElements().stream()
335 .filter(el -> el instanceof VariableElement)
336 .map(el -> (VariableElement) el)
337 .filter(ve -> !staticPrivateFinalLiteralField.test(ve) && !privateFinalField.test(ve))
338 .forEach(ve -> messager
339 .printMessage(Kind.ERROR, "@Component classes my only have static final constant fields or final private fields", ve));
340
341 if (names.length > 0) {
342 InstanceModel model = new InstanceModel(names[0],
343 dm.getIdentity(),
344 chosenConstructor,
345 te.getQualifiedName().toString(),
346 dependencies,
347 new ArrayList<>());
348
349 dm.addDefinition(model);
350 for (InstanceDependencyModel dep : dependencies) {
351 ExpectedModel expectedModel = new ExpectedModel(dep.getIdentity());
352 expectedModel.addDefinitionReferenceToType(model.getIdentity(), dep.getType());
353 dm.addDefinition(expectedModel);
354 }
355 } else {
356 messager.printMessage(Kind.ERROR, "@Component classes must have a name", te);
357 }
358 }
359
360
361
362
363
364
365
366
367 private ExecutableElement findAutowiredConstructor(List<ExecutableElement> constructors) {
368 ExecutableElement chosenConstructor = null;
369 if (constructors.size() == 1) {
370 chosenConstructor = constructors.get(0);
371 } else {
372 chosenConstructor = constructors.stream()
373 .filter(ex -> AnnotationValueExtractor.getAnnotationValue(ex, AUTOWIRED_TYPE, "") != null)
374 .limit(2)
375 .reduce((a, b) -> null)
376 .orElse(null);
377 }
378 return chosenConstructor;
379 }
380
381
382
383
384
385
386
387 private List<ExecutableElement> extractConstructorsFromComponent(TypeElement te) {
388 return te.getEnclosedElements().stream()
389 .filter(enclosed -> enclosed instanceof ExecutableElement)
390 .filter(enclosed -> "<init>".equals(enclosed.getSimpleName().toString()))
391 .map(enclosed -> (ExecutableElement) enclosed)
392 .collect(Collectors.toList());
393 }
394
395 private void errorIfInnerClass(TypeElement te, Messager messager) {
396 if (te.getEnclosingElement().getKind() != ElementKind.PACKAGE && !isTest(te)) {
397 messager.printMessage(Kind.ERROR, "The class must be a top level class, not an internal class", te);
398 }
399 }
400
401
402
403
404
405
406
407
408 private boolean isTest(TypeElement te) {
409 String className = te.getEnclosingElement().getSimpleName().toString();
410 return className.startsWith("Test")
411 || className.endsWith("Test")
412 || className.endsWith("Tests")
413 || className.endsWith("TestCase");
414 }
415
416 private void errorOnBannedTypeToMessage(Element el, Messager messager, Map<String, String> typeToMessage) {
417 for (Entry<String, String> banned : typeToMessage.entrySet()) {
418 if (AnnotationValueExtractor.getAnnotationValue(el, banned.getKey(), "") != null) {
419 messager.printMessage(Kind.ERROR, banned.getValue(), el);
420 }
421 }
422 }
423
424 private List<String> getImportsTypes(TypeElement element) {
425 String[] values = AnnotationValueExtractor
426 .getAnnotationValue(element, IMPORT_TYPE, DEFAULT_ANNOTATION_VALUE);
427 if (values == null) {
428 return new ArrayList<>();
429 } else {
430 return Arrays.asList(values);
431 }
432 }
433
434 }