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