1 package com.atlassian.plugin.spring.scanner.maven;
2
3 import com.atlassian.plugin.spring.scanner.core.AtlassianSpringByteCodeScanner;
4 import com.atlassian.plugin.spring.scanner.core.ByteCodeScannerConfiguration;
5 import com.google.common.base.Function;
6 import com.google.common.base.Predicate;
7 import com.google.common.collect.Iterables;
8 import com.google.common.collect.Lists;
9 import com.google.common.collect.Sets;
10 import org.apache.maven.artifact.Artifact;
11 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
12 import org.apache.maven.model.Dependency;
13 import org.apache.maven.plugin.AbstractMojo;
14 import org.apache.maven.plugin.MojoExecutionException;
15 import org.apache.maven.plugin.MojoFailureException;
16 import org.apache.maven.plugins.annotations.Component;
17 import org.apache.maven.plugins.annotations.LifecyclePhase;
18 import org.apache.maven.plugins.annotations.Mojo;
19 import org.apache.maven.plugins.annotations.Parameter;
20 import org.apache.maven.plugins.annotations.ResolutionScope;
21 import org.apache.maven.project.MavenProject;
22 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
23 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
24 import org.apache.maven.shared.dependency.graph.DependencyNode;
25 import org.reflections.util.ClasspathHelper;
26
27 import javax.annotation.Nullable;
28 import java.io.File;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.NoSuchElementException;
35 import java.util.Set;
36 import java.util.stream.Collectors;
37
38 import static java.lang.String.format;
39 import static org.reflections.util.Utils.isEmpty;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @Mojo(
69 name = "atlassian-spring-scanner",
70 defaultPhase = LifecyclePhase.PREPARE_PACKAGE,
71 requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME
72 )
73 public class AtlassianSpringScannerMojo extends AbstractMojo {
74 private static final String OUR_NAME = "Atlassian Spring Byte Code Scanner";
75 private static final String DEFAULT_INCLUDE_EXCLUDE = "-java\\..*, -javax\\..*, -sun\\..*, -com\\.sun\\..*";
76
77 @Parameter(defaultValue = "${project}", readonly = true)
78 private MavenProject project;
79
80 @Parameter(defaultValue = DEFAULT_INCLUDE_EXCLUDE)
81 private String includeExclude;
82
83 @Parameter(defaultValue = "false")
84 private Boolean parallel;
85
86 @Parameter(defaultValue = "false")
87 private Boolean verbose;
88
89 @Parameter()
90 private List<Dependency> scannedDependencies = new ArrayList<Dependency>();
91
92 @Component(hint = "default")
93 private DependencyGraphBuilder dependencyGraphBuilder;
94
95 public void execute() throws MojoExecutionException, MojoFailureException {
96 getLog().info("Starting " + OUR_NAME + "...");
97 getLog().info("");
98 long then = System.currentTimeMillis();
99
100 String outputDirectory = resolveOutputDirectory();
101 if (!new File(outputDirectory).exists()) {
102 getLog().warn(format("Skipping because %s was not found", outputDirectory));
103 return;
104 }
105
106 warnInvalidScannedDependencies();
107
108 ByteCodeScannerConfiguration.Builder config = ByteCodeScannerConfiguration.builder()
109 .setOutputDirectory(outputDirectory)
110 .setClassPathUrls(parseUrls())
111 .setIncludeExclude(includeExclude)
112 .setLog(makeLogger())
113 .setVerbose(verbose);
114
115
116 AtlassianSpringByteCodeScanner scanner = new AtlassianSpringByteCodeScanner(config.build());
117
118 long ms = System.currentTimeMillis() - then;
119 getLog().info("");
120 getLog().info(format("\tAnalysis ran in %d ms.", ms));
121 getLog().info(format("\tEncountered %d total classes", scanner.getStats().getClassesEncountered()));
122 getLog().info(format("\tProcessed %d annotated classes", scanner.getStats().getComponentClassesEncountered()));
123
124 if (!scanner.getErrors().getErrorsEncountered().isEmpty()) {
125 final String error = format("\t %d errors encountered during class analysis: \n\t %s",
126 scanner.getErrors().getErrorsEncountered().size(),
127 scanner.getErrors().getErrorsEncountered().stream().collect(Collectors.joining("\n\t")));
128 getLog().error(error);
129 throw new IllegalStateException(error);
130 }
131 }
132
133 private org.slf4j.Logger makeLogger() {
134 return new MavenLogAdapter(getLog());
135 }
136
137 private Set<URL> parseUrls() throws MojoExecutionException {
138 final Set<URL> urls = Sets.newHashSet();
139 URL outputDirUrl = parseOutputDirUrl();
140 urls.add(outputDirUrl);
141
142 if (!isEmpty(includeExclude)) {
143 for (String string : includeExclude.split(",")) {
144 String trimmed = string.trim();
145 char prefix = trimmed.charAt(0);
146 String pattern = trimmed.substring(1);
147 if (prefix == '+') {
148 logVerbose(format("\tAdding include / exclude %s", prefix));
149 urls.addAll(ClasspathHelper.forPackage(pattern));
150 }
151 }
152 }
153
154 final Set<URL> dependencyJars = Sets.newLinkedHashSet();
155 final Iterable<Artifact> projectArtifacts = getProjectArtifacts();
156 final Iterable<Artifact> scannedArtifacts = resolveArtifacts(getScannedArtifacts(), projectArtifacts);
157 final Iterable<Artifact> ignoredArtifacts = Iterables.filter(projectArtifacts, new Predicate<Artifact>() {
158 @Override
159 public boolean apply(@Nullable Artifact input) {
160 return !Iterables.any(scannedArtifacts, artifactMatchesGAV(input));
161 }
162 });
163
164 for (Artifact artifact : scannedArtifacts) {
165 logVerbose(format("\t(/) Including dependency for scanning %s:%s:%s", artifact.getGroupId(), artifact.getArtifactId(), artifact.getScope()));
166 File file = artifact.getFile();
167 try {
168 URL url = file.toURI().toURL();
169 dependencyJars.add(url);
170 } catch (MalformedURLException e) {
171 getLog().warn(format("Enable to create URL from plugin artifact : %s", file), e);
172 }
173 }
174
175 for (Artifact artifact : ignoredArtifacts) {
176 logVerbose(format("\t(X) Ignoring dependency for scanning %s:%s:%s", artifact.getGroupId(), artifact.getArtifactId(), artifact.getScope()));
177 }
178
179 urls.addAll(dependencyJars);
180 getLog().info("\t(/) The following directory will be scanned for annotations :");
181 getLog().info(format("\t\t%s", outputDirUrl));
182 if (dependencyJars.size() > 0) {
183 getLog().info("");
184 getLog().info("\t(/) The following dependencies will also be scanned for annotations : ");
185 getLog().info("");
186 for (URL jar : dependencyJars) {
187 getLog().info(format("\t\t%s", jar));
188 }
189 }
190
191 return urls;
192 }
193
194 private void logVerbose(String message) {
195 if (verbose) {
196 getLog().info(message);
197 }
198 }
199
200 private boolean isSensibleScope(final Artifact artifact) {
201 return !Artifact.SCOPE_TEST.equals(artifact.getScope());
202 }
203
204 private URL parseOutputDirUrl() throws MojoExecutionException {
205 try {
206 File outputDirectoryFile = new File(resolveOutputDirectory() + '/');
207 return outputDirectoryFile.toURI().toURL();
208 } catch (MalformedURLException e) {
209 throw new MojoExecutionException(e.getMessage(), e);
210 }
211 }
212
213 private String resolveOutputDirectory() {
214 return getProject().getBuild().getOutputDirectory();
215 }
216
217 private MavenProject getProject() {
218 return project;
219 }
220
221 private Predicate<Artifact> artifactMatchesGAV(final Artifact artifact) {
222 return new Predicate<Artifact>() {
223 @Override
224 public boolean apply(final Artifact input) {
225 return artifact.getGroupId().equals(input.getGroupId()) &&
226 artifact.getArtifactId().equals(input.getArtifactId()) &&
227 artifact.getVersion().equals(input.getVersion()
228 );
229 }
230 };
231 }
232
233
234
235
236
237
238
239
240
241 private Iterable<Artifact> resolveArtifacts(List<Artifact> scannedArtifacts, final Iterable<Artifact> projectArtifacts) {
242 return Iterables.transform(scannedArtifacts, new Function<Artifact, Artifact>() {
243 @Override
244 public Artifact apply(Artifact input) {
245 try {
246 return Iterables.find(projectArtifacts, artifactMatchesGAV(input));
247 } catch (NoSuchElementException e) {
248 throw new RuntimeException("Unable to find " + input, e);
249 }
250 }
251 });
252 }
253
254 private DependencyNode getDependencyGraph(MavenProject project) {
255
256 try {
257 return dependencyGraphBuilder.buildDependencyGraph(project, new ArtifactFilter() {
258 @Override
259 public boolean include(Artifact artifact) {
260 return isSensibleScope(artifact);
261 }
262 });
263 } catch (DependencyGraphBuilderException e) {
264 throw new RuntimeException(e);
265 }
266 }
267
268
269 private List<Artifact> getProjectArtifacts() {
270 ArrayList<Artifact> artifacts = Lists.newArrayList(project.getArtifacts());
271 Collections.sort(artifacts);
272 return artifacts;
273 }
274
275 private List<Artifact> getScannedArtifacts() {
276 DependencyNode dependencyGraph = getDependencyGraph(project);
277 return ScannedDependencyArtifactBuilder.buildScannedArtifacts(dependencyGraph, scannedDependencies);
278 }
279
280 private void warnInvalidScannedDependencies() {
281 for (Dependency dependency : scannedDependencies) {
282 if ((dependency.getArtifactId().contains("*")) && (!"*".equals(dependency.getArtifactId()))) {
283 getLog().warn(format("Invalid artifact ID %s in scannedDependencies. Partial wildcards are not currently supported.", dependency.getArtifactId()));
284 }
285 }
286 }
287 }