1 package com.atlassian.plugin.spring.scanner.core;
2
3 import com.atlassian.plugin.spring.scanner.annotation.export.ModuleType;
4 import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
5 import com.atlassian.plugin.spring.scanner.util.CommonConstants;
6 import javassist.bytecode.AnnotationsAttribute;
7 import javassist.bytecode.ClassFile;
8 import javassist.bytecode.Descriptor;
9 import javassist.bytecode.FieldInfo;
10 import javassist.bytecode.MethodInfo;
11 import javassist.bytecode.annotation.Annotation;
12 import org.reflections.Reflections;
13 import org.reflections.scanners.AbstractScanner;
14 import org.reflections.util.ConfigurationBuilder;
15 import org.reflections.util.FilterBuilder;
16 import org.slf4j.Logger;
17 import org.springframework.stereotype.Component;
18
19 import javax.inject.Named;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Set;
25
26 import static com.google.common.base.Strings.isNullOrEmpty;
27 import static java.lang.String.format;
28
29
30
31
32
33
34
35
36
37
38
39
40 @SuppressWarnings("unchecked")
41 public class AtlassianSpringByteCodeScanner extends AbstractScanner {
42 private final Logger log;
43 private final Stats stats;
44 private final Errors errors;
45 private final SpringIndexWriter springIndexWriter;
46 private final ProfileFinder profileFinder;
47 private final JavassistHelper javassistHelper;
48 private boolean dbg;
49
50 public AtlassianSpringByteCodeScanner(final ByteCodeScannerConfiguration configuration) {
51 this.log = configuration.getLog();
52 this.springIndexWriter = new SpringIndexWriter(configuration.getOutputDirectory());
53 this.javassistHelper = new JavassistHelper();
54 this.profileFinder = new ProfileFinder(configuration.getClassPathUrls(), log);
55 this.stats = new Stats();
56 this.errors = new Errors();
57 this.dbg = log.isDebugEnabled() || configuration.isVerbose();
58
59 go(configuration);
60
61 }
62
63 private void go(final ByteCodeScannerConfiguration configuration) {
64
65 ConfigurationBuilder config = new ConfigurationBuilder();
66
67 config.setUrls(configuration.getClassPathUrls());
68
69 if (!isNullOrEmpty(configuration.getIncludeExclude())) {
70 config.filterInputsBy(FilterBuilder.parse(configuration.getIncludeExclude()));
71 }
72
73
74
75
76
77 config.setScanners(this);
78
79
80 try {
81 Reflections.log = null;
82 } catch (Error e) {
83
84 }
85
86
87 new Reflections(config);
88
89
90 springIndexWriter.writeIndexes();
91 }
92
93 public Stats getStats() {
94 return stats;
95 }
96
97
98 public Errors getErrors() {
99 return errors;
100 }
101
102 @Override
103 public void scan(final Object cls) {
104 ClassFile classFile = (ClassFile) cls;
105 try {
106 scanClass(classFile);
107 } catch (Exception e) {
108 log.error(format("Unable to run byte code scanner on class %s. Continuing to the next class...", cls));
109 }
110 }
111
112 private void scanClass(final ClassFile classFile) throws Exception {
113 stats.classesEncountered++;
114
115 Set<String> profiles = profileFinder.getProfiles(classFile);
116
117 String className = getMetadataAdapter().getClassName(classFile);
118 List<String> classAnnotationNames = getMetadataAdapter().getClassAnnotationNames(classFile);
119
120 for (String annotationType : classAnnotationNames) {
121 if (isSuitableClassAnnotation(annotationType)) {
122
123 if (!isSuitableClass(classFile)) {
124 debug(format("\t(X) Class not suitable '%s' for annotation '%s'", className, annotationType));
125 return;
126 }
127 stats.componentClassesEncountered++;
128 String nameFromAnnotation = javassistHelper.getAnnotationMember(classFile, annotationType, "value");
129
130
131 debug(format("(/) Found annotation '%s' inside class '%s'", annotationType, className));
132
133 springIndexWriter.encounteredAnnotation(profiles, annotationType, nameFromAnnotation, className);
134
135
136
137
138 if (ModuleType.class.getCanonicalName().equals(annotationType)) {
139 springIndexWriter.encounteredAnnotation(profiles, Component.class.getCanonicalName(), "", CommonConstants.HOST_CONTAINER_CLASS);
140 }
141 }
142 }
143
144
145 visitConstructors(classFile, profiles);
146
147
148 visitFields(classFile, profiles);
149 }
150
151 private void debug(String msg) {
152 if (dbg) {
153 log.info("\t" + msg);
154 }
155 }
156
157 private boolean isSuitableClass(final ClassFile classFile) {
158 String className = classFile.getName();
159 if (classFile.isInterface()) {
160 log.error(format("Found a type [%s] annotated as a component, but the type is not a concrete class. NOT adding to index file!!", className));
161 return false;
162 }
163 if (classFile.isAbstract()) {
164 log.error(format("Found a type [%s] annotated as a component, but the type is abstract. NOT adding to index file!!", className));
165 return false;
166 }
167
168 return !profileFinder.isPackageClass(classFile);
169 }
170
171 private boolean isSuitableClassAnnotation(final String annotationType) {
172 return super.acceptResult(annotationType) && springIndexWriter.isInteresting(annotationType);
173 }
174
175
176
177
178
179
180
181
182 private void visitConstructors(final ClassFile classFile, final Set<String> profiles) {
183 String className = classFile.getName();
184 List<MethodInfo> methods = classFile.getMethods();
185 for (MethodInfo method : methods) {
186 String methodName = method.getName();
187 if (method.isConstructor()) {
188
189 List<String> parameterTypes = getMetadataAdapter().getParameterNames(method);
190 for (int i = 0; i < parameterTypes.size(); i++) {
191 String parameterType = parameterTypes.get(i);
192 List<Annotation> parameterAnnotationNames = javassistHelper.getParameterAnnotations(method, i);
193
194 for (Annotation annotation : parameterAnnotationNames) {
195 String annotationType = annotation.getTypeName();
196 if (acceptResult(annotationType) && springIndexWriter.isParameterOrFieldAnnotation(annotationType)) {
197 String nameFromAnnotation = javassistHelper.getAnnotationMember(annotation, "value");
198
199 debug(format("(/) Found '%s' inside class '%s' method '%s' parameter '%s'", annotationType, className, methodName, parameterType));
200
201 springIndexWriter.encounteredAnnotation(profiles, annotationType, nameFromAnnotation, parameterType);
202 }
203 }
204
205 }
206 }
207 }
208 }
209
210 private void visitFields(final ClassFile classFile, final Set<String> profiles) {
211 String className = classFile.getName();
212 List<FieldInfo> fields = classFile.getFields();
213 for (FieldInfo field : fields) {
214 final List<String> annotationTypes = new LinkedList<>();
215 String fieldName = field.getName();
216 AnnotationsAttribute annotations = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag);
217 if (annotations != null) {
218 for (Annotation annotation : annotations.getAnnotations()) {
219 String annotationType = annotation.getTypeName();
220 annotationTypes.add(annotationType);
221
222 if (acceptResult(annotationType) && springIndexWriter.isParameterOrFieldAnnotation(annotationType)) {
223 String fieldType = Descriptor.toClassName(field.getDescriptor());
224 String nameFromAnnotation = javassistHelper.getAnnotationMember(annotation, "value");
225
226 debug(format("(/) Found '%s' inside class '%s' on field '%s' of type '%s'", annotationType, className, fieldName, fieldType));
227
228 springIndexWriter.encounteredAnnotation(profiles, annotationType, nameFromAnnotation, fieldType);
229 }
230 }
231 }
232
233 if (annotationTypes.contains(ComponentImport.class.getCanonicalName())) {
234
235 final List<String> productImportsPresentOnField = new ArrayList<>(SpringIndexWriter.KNOWN_PRODUCT_IMPORT_ANNOTATIONS);
236
237 productImportsPresentOnField.retainAll(annotationTypes);
238
239 if (!productImportsPresentOnField.isEmpty()) {
240 errors.addError(String.format("ComponentImport annotation cannot be used with product specific component imports: %s found on %s.%s",
241 Arrays.toString(productImportsPresentOnField.toArray()), classFile.getName(), fieldName));
242 }
243 }
244 }
245 }
246
247 public static class Stats {
248 private int classesEncountered;
249 private int componentClassesEncountered;
250
251 public int getClassesEncountered() {
252 return classesEncountered;
253 }
254
255 public int getComponentClassesEncountered() {
256 return componentClassesEncountered;
257 }
258 }
259
260 public static class Errors {
261 private final List<String> errorsEncountered = new ArrayList<>();
262
263 public void addError(String error) {
264 errorsEncountered.add(error);
265 }
266
267 public List<String> getErrorsEncountered() {
268 return errorsEncountered;
269 }
270 }
271 }