1 package com.atlassian.plugin.spring.scanner.core;
2
3 import com.atlassian.plugin.spring.scanner.annotation.component.BambooComponent;
4 import com.atlassian.plugin.spring.scanner.annotation.component.BitbucketComponent;
5 import com.atlassian.plugin.spring.scanner.annotation.component.ClasspathComponent;
6 import com.atlassian.plugin.spring.scanner.annotation.component.ConfluenceComponent;
7 import com.atlassian.plugin.spring.scanner.annotation.component.FecruComponent;
8 import com.atlassian.plugin.spring.scanner.annotation.component.JiraComponent;
9 import com.atlassian.plugin.spring.scanner.annotation.component.RefappComponent;
10 import com.atlassian.plugin.spring.scanner.annotation.component.StashComponent;
11 import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsDevService;
12 import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
13 import com.atlassian.plugin.spring.scanner.annotation.export.ModuleType;
14 import com.atlassian.plugin.spring.scanner.annotation.imports.BambooImport;
15 import com.atlassian.plugin.spring.scanner.annotation.imports.BitbucketImport;
16 import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
17 import com.atlassian.plugin.spring.scanner.annotation.imports.ConfluenceImport;
18 import com.atlassian.plugin.spring.scanner.annotation.imports.FecruImport;
19 import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport;
20 import com.atlassian.plugin.spring.scanner.annotation.imports.RefappImport;
21 import com.atlassian.plugin.spring.scanner.annotation.imports.StashImport;
22 import com.atlassian.plugin.spring.scanner.core.vfs.VirtualFile;
23 import com.atlassian.plugin.spring.scanner.core.vfs.VirtualFileFactory;
24 import com.atlassian.plugin.spring.scanner.util.CommonConstants;
25 import com.google.common.base.Function;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Maps;
28 import org.springframework.context.annotation.Primary;
29 import org.springframework.stereotype.Component;
30 import org.springframework.stereotype.Controller;
31 import org.springframework.stereotype.Repository;
32 import org.springframework.stereotype.Service;
33
34 import javax.annotation.Nullable;
35 import javax.inject.Named;
36 import java.io.File;
37 import java.io.IOException;
38 import java.nio.file.FileVisitResult;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.nio.file.SimpleFileVisitor;
42 import java.nio.file.attribute.BasicFileAttributes;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.TreeSet;
49
50 import static com.atlassian.plugin.spring.scanner.ProductFilter.BAMBOO;
51 import static com.atlassian.plugin.spring.scanner.ProductFilter.BITBUCKET;
52 import static com.atlassian.plugin.spring.scanner.ProductFilter.CONFLUENCE;
53 import static com.atlassian.plugin.spring.scanner.ProductFilter.FECRU;
54 import static com.atlassian.plugin.spring.scanner.ProductFilter.JIRA;
55 import static com.atlassian.plugin.spring.scanner.ProductFilter.REFAPP;
56 import static com.atlassian.plugin.spring.scanner.ProductFilter.STASH;
57 import static com.atlassian.plugin.spring.scanner.util.CommonConstants.COMPONENT_DEV_EXPORT_KEY;
58 import static com.atlassian.plugin.spring.scanner.util.CommonConstants.COMPONENT_EXPORT_KEY;
59 import static com.atlassian.plugin.spring.scanner.util.CommonConstants.COMPONENT_IMPORT_KEY;
60 import static com.atlassian.plugin.spring.scanner.util.CommonConstants.COMPONENT_KEY;
61 import static com.atlassian.plugin.spring.scanner.util.CommonConstants.COMPONENT_PRIMARY_KEY;
62
63
64
65
66
67
68
69
70 public class SpringIndexWriter {
71 public static final List<String> KNOWN_PRODUCT_IMPORT_ANNOTATIONS = ImmutableList.of(
72 BambooImport.class.getCanonicalName(),
73 BitbucketImport.class.getCanonicalName(),
74 ConfluenceImport.class.getCanonicalName(),
75 FecruImport.class.getCanonicalName(),
76 JiraImport.class.getCanonicalName(),
77 RefappImport.class.getCanonicalName(),
78 StashImport.class.getCanonicalName());
79
80 private final Map<String, RecordedAnnotations> recordedProfiles = new HashMap<String, RecordedAnnotations>();
81 private final VirtualFileFactory fileFactory;
82
83
84
85
86
87
88 public SpringIndexWriter(final String baseDir) {
89
90
91 cleanDirectory(new File(baseDir, CommonConstants.INDEX_FILES_DIR));
92
93 fileFactory = baseDir != null ? new VirtualFileFactory(new File(baseDir)) : null;
94 }
95
96 public boolean isInteresting(String annotationType) {
97 return (null != MeaningfulAnnotation.fromCanonicalName(annotationType));
98 }
99
100 public boolean isParameterOrFieldAnnotation(final String annotationType) {
101 final MeaningfulAnnotation meaningfulAnnotation = MeaningfulAnnotation.fromCanonicalName(annotationType);
102 return (null != meaningfulAnnotation) && meaningfulAnnotation.parameterOrFieldAnnotation;
103 }
104
105 public void encounteredAnnotation(Set<String> targetProfiles, String annotationType, String nameFromAnnotation, String className) {
106 Set<String> profiles = new HashSet<String>(targetProfiles);
107 if (profiles.isEmpty()) {
108
109 profiles.add(CommonConstants.DEFAULT_PROFILE_NAME);
110 }
111
112 for (String profile : profiles) {
113 RecordedAnnotations recordedAnnotations = recordedProfiles.get(profile);
114 if (recordedAnnotations == null) {
115 recordedAnnotations = new RecordedAnnotations();
116 recordedProfiles.put(profile, recordedAnnotations);
117 }
118 recordedAnnotations.record(annotationType, nameFromAnnotation, className);
119 }
120
121 }
122
123 public void writeIndexes() {
124 for (Map.Entry<String, RecordedAnnotations> annotationsEntry : recordedProfiles.entrySet()) {
125 writeProfileIndexes(annotationsEntry.getKey(), annotationsEntry.getValue());
126 }
127
128 }
129
130 private void writeProfileIndexes(final String profileName, final RecordedAnnotations annotations) {
131
132
133
134
135
136
137
138
139
140
141 Map<String, Set<String>> fileNameToComponents = new HashMap<String, Set<String>>();
142 for (Map.Entry<MeaningfulAnnotation, Set<String>> entry : annotations.getRecordedAnnotations().entrySet()) {
143 MeaningfulAnnotation meaningfulAnnotation = entry.getKey();
144 if (meaningfulAnnotation.isWrittenToDisk()) {
145 Set<String> perAnnotationComponents = entry.getValue();
146 String indexFileName = meaningfulAnnotation.getFileName();
147 Set<String> currentComponents = fileNameToComponents.get(indexFileName);
148 if (currentComponents == null) {
149 currentComponents = new TreeSet<String>();
150 fileNameToComponents.put(indexFileName, currentComponents);
151 }
152 currentComponents.addAll(perAnnotationComponents);
153 }
154 }
155
156
157 for (Map.Entry<String, Set<String>> entry : fileNameToComponents.entrySet()) {
158 try {
159 writeIndexFile(profileName, entry.getKey(), entry.getValue());
160 } catch (IOException e) {
161 throw new RuntimeException(e);
162 }
163 }
164 }
165
166 private void writeIndexFile(final String profileName, final String indexFileName, final Set<String> entries)
167 throws IOException {
168 if (fileFactory==null) {
169 return;
170 }
171
172 File file = makeProfiledFileName(profileName, indexFileName);
173
174 VirtualFile vf = fileFactory.getFile(file.getPath());
175
176 Set<String> lines = new TreeSet<String>();
177
178 lines.addAll(vf.readLines());
179 lines.addAll(entries);
180 vf.writeLines(lines);
181 }
182
183 private File makeProfiledFileName(final String profileName, String fileName) {
184 File file = new File(CommonConstants.PROFILE_PREFIX + profileName, fileName);
185 if (CommonConstants.DEFAULT_PROFILE_NAME.equals(profileName)) {
186
187
188 file = new File(fileName);
189 }
190 return new File(CommonConstants.INDEX_FILES_DIR, file.getPath());
191 }
192
193 private void cleanDirectory(final File destination) {
194 if (destination.exists()) {
195 try {
196 Files.walkFileTree(destination.toPath(), new SimpleFileVisitor<Path>() {
197 @Override
198 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
199 if (e != null) {
200 throw e;
201 }
202 Files.delete(dir);
203 return FileVisitResult.CONTINUE;
204 }
205
206 @Override
207 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
208 Files.delete(file);
209 return FileVisitResult.CONTINUE;
210 }
211 });
212 } catch (IOException e) {
213 throw new RuntimeException("Unable to delete directory " + destination, e);
214 }
215 }
216 }
217
218 private enum MeaningfulAnnotation {
219
220 Component(COMPONENT_KEY, Component.class),
221 Service(COMPONENT_KEY, Service.class),
222 Controller(COMPONENT_KEY, Controller.class),
223 Repository(COMPONENT_KEY, Repository.class),
224 Primary(COMPONENT_PRIMARY_KEY, Primary.class),
225
226
227 Named(COMPONENT_KEY, Named.class, true),
228
229
230 ClasspathComponent(COMPONENT_KEY, ClasspathComponent.class, true),
231 BambooComponent(BAMBOO.getPerProductFile(COMPONENT_KEY), BambooComponent.class, true),
232 BitbucketComponent(BITBUCKET.getPerProductFile(COMPONENT_KEY), BitbucketComponent.class, true),
233 ConfluenceComponent(CONFLUENCE.getPerProductFile(COMPONENT_KEY), ConfluenceComponent.class, true),
234 JiraComponent(JIRA.getPerProductFile(COMPONENT_KEY), JiraComponent.class, true),
235 FecruComponent(FECRU.getPerProductFile(COMPONENT_KEY), FecruComponent.class, true),
236 RefappComponent(REFAPP.getPerProductFile(COMPONENT_KEY), RefappComponent.class, true),
237 StashComponent(STASH.getPerProductFile(COMPONENT_KEY), StashComponent.class, true),
238
239
240 Imports(COMPONENT_IMPORT_KEY, ComponentImport.class, true),
241 BambooImports(BAMBOO.getPerProductFile(COMPONENT_IMPORT_KEY), BambooImport.class, true),
242 BitbucketImports(BITBUCKET.getPerProductFile(COMPONENT_IMPORT_KEY), BitbucketImport.class, true),
243 ConfluenceImports(CONFLUENCE.getPerProductFile(COMPONENT_IMPORT_KEY), ConfluenceImport.class, true),
244 FecruImports(FECRU.getPerProductFile(COMPONENT_IMPORT_KEY), FecruImport.class, true),
245 JiraImports(JIRA.getPerProductFile(COMPONENT_IMPORT_KEY), JiraImport.class, true),
246 RefappImports(REFAPP.getPerProductFile(COMPONENT_IMPORT_KEY), RefappImport.class, true),
247 StashImports(STASH.getPerProductFile(COMPONENT_IMPORT_KEY), StashImport.class, true),
248
249
250 ExportAsService(COMPONENT_EXPORT_KEY, ExportAsService.class, true),
251 ExportAsDevService(COMPONENT_DEV_EXPORT_KEY, ExportAsDevService.class, true),
252 ModuleType(COMPONENT_EXPORT_KEY, ModuleType.class);
253
254 private static final Map<String, MeaningfulAnnotation> canonicalNameIndex = Maps.uniqueIndex(
255 ImmutableList.copyOf(values()),
256 new Function<MeaningfulAnnotation, String>() {
257 @Override
258 public String apply(@Nullable final MeaningfulAnnotation meaningfulAnnotation) {
259 return meaningfulAnnotation.forAnnotation.getCanonicalName();
260 }
261 });
262
263 private final String fileName;
264 private final Class forAnnotation;
265 private final boolean parameterOrFieldAnnotation;
266
267 MeaningfulAnnotation(final String fileName, final Class forAnnotation) {
268 this(fileName, forAnnotation, false);
269 }
270
271 MeaningfulAnnotation(final Class forAnnotation) {
272 this(null, forAnnotation, false);
273 }
274
275 MeaningfulAnnotation(final String fileName, final Class forAnnotation, boolean parameterOrFieldAnnotation) {
276 this.fileName = fileName;
277 this.forAnnotation = forAnnotation;
278 this.parameterOrFieldAnnotation = parameterOrFieldAnnotation;
279 }
280
281 private static MeaningfulAnnotation fromCanonicalName(String annotationType) {
282 return canonicalNameIndex.get(annotationType);
283 }
284
285 private String getFileName() {
286 return fileName;
287 }
288
289 private boolean isWrittenToDisk() {
290 return null != fileName;
291 }
292 }
293
294
295
296
297 private class RecordedAnnotations {
298 final Map<MeaningfulAnnotation, Set<String>> recordedAnnotations = new HashMap<MeaningfulAnnotation, Set<String>>();
299
300 public Map<MeaningfulAnnotation, Set<String>> getRecordedAnnotations() {
301 return recordedAnnotations;
302 }
303
304 public void record(final String annotationType, final String nameFromAnnotation, final String className) {
305 StringBuilder sb = new StringBuilder(className);
306 if (nameFromAnnotation != null) {
307 String trimmed = nameFromAnnotation.trim();
308 if (!trimmed.isEmpty()) {
309 sb.append("#").append(trimmed);
310 }
311 }
312 final MeaningfulAnnotation annotation = MeaningfulAnnotation.fromCanonicalName(annotationType);
313 if (null == annotation) {
314 throw new IllegalStateException("Stop asking me for the impossible. Annotation " + annotationType + " not found");
315 }
316
317 addTo(annotation, sb.toString());
318 }
319
320 private void addTo(final MeaningfulAnnotation meaningfulAnnotation, final String value) {
321 Set<String> values = recordedAnnotations.get(meaningfulAnnotation);
322 if (values == null) {
323 values = new TreeSet<String>();
324 recordedAnnotations.put(meaningfulAnnotation, values);
325 }
326 values.add(value);
327 }
328 }
329
330
331 }