1 package org.whitesource.maven;
2
3 import org.apache.commons.lang.StringUtils;
4 import org.apache.maven.artifact.Artifact;
5 import org.apache.maven.model.Dependency;
6 import org.apache.maven.model.Exclusion;
7 import org.apache.maven.plugin.MojoExecutionException;
8 import org.apache.maven.plugins.annotations.Parameter;
9 import org.apache.maven.project.DefaultDependencyResolutionRequest;
10 import org.apache.maven.project.DependencyResolutionException;
11 import org.apache.maven.project.DependencyResolutionResult;
12 import org.apache.maven.project.MavenProject;
13 import org.whitesource.agent.api.ChecksumUtils;
14 import org.whitesource.agent.api.dispatch.CheckPoliciesResult;
15 import org.whitesource.agent.api.model.AgentProjectInfo;
16 import org.whitesource.agent.api.model.Coordinates;
17 import org.whitesource.agent.api.model.DependencyInfo;
18 import org.whitesource.agent.api.model.ExclusionInfo;
19 import org.whitesource.agent.report.PolicyCheckReport;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.*;
24
25
26
27
28
29
30 public abstract class AgentMojo extends WhitesourceMojo {
31
32
33
34 public static final String POM = "pom";
35 public static final String TYPE = "type";
36
37
38
39
40
41
42 @Parameter(alias = "orgToken", property = Constants.ORG_TOKEN, required = true)
43 protected String orgToken;
44
45
46
47
48 @Parameter(alias = "product", property = Constants.PRODUCT, required = false)
49 protected String product;
50
51
52
53
54 @Parameter(alias = "productVersion", property = Constants.PRODUCT_VERSION, required = false)
55 protected String productVersion;
56
57
58
59
60 @Parameter(alias = "ignoreTestScopeDependencies", property = Constants.IGNORE_TEST_SCOPE_DEPENDENCIES, required = false, defaultValue = "true")
61 protected boolean ignoreTestScopeDependencies;
62
63
64
65
66 @Parameter( alias = "outputDirectory", property = Constants.OUTPUT_DIRECTORY, required = false, defaultValue = "${project.reporting.outputDirectory}")
67 protected File outputDirectory;
68
69
70
71
72
73 @Parameter(alias = "projectToken", property = Constants.PROJECT_TOKEN, required = false)
74 protected String projectToken;
75
76
77
78
79 @Parameter(alias = "moduleTokens", property = Constants.MODULE_TOKENS, required = false)
80 protected Map<String, String> moduleTokens = new HashMap<String, String>();
81
82
83
84
85
86 @Parameter(alias = "specialModuleTokens", property = Constants.SPECIAL_MODULE_TOKENS, required = false)
87 protected Properties specialModuleTokens = new Properties();
88
89
90
91
92 @Parameter(alias = "ignore", property = Constants.IGNORE, required = false, defaultValue = "false")
93 protected boolean ignore;
94
95
96
97
98 @Parameter(alias = "includes", property = Constants.INCLUDES, required = false, defaultValue = "")
99 protected String[] includes;
100
101
102
103
104 @Parameter(alias = "excludes", property = Constants.EXCLUDES, required = false, defaultValue = "")
105 protected String[] excludes;
106
107
108
109
110 @Parameter(alias = "ignorePomModules", property = Constants.IGNORE_POM_MODULES, required = false, defaultValue = "true")
111 protected boolean ignorePomModules;
112
113 @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = true)
114 protected Collection<MavenProject> reactorProjects;
115
116
117
118 protected DependencyInfo getDependencyInfo(Dependency dependency) {
119 DependencyInfo info = new DependencyInfo();
120
121
122 info.setGroupId(dependency.getGroupId());
123 info.setArtifactId(dependency.getArtifactId());
124 info.setVersion(dependency.getVersion());
125 info.setScope(dependency.getScope());
126 info.setClassifier(dependency.getClassifier());
127 info.setOptional(dependency.isOptional());
128 info.setType(dependency.getType());
129 info.setSystemPath(dependency.getSystemPath());
130
131
132 Collection<ExclusionInfo> exclusions = info.getExclusions();
133 for (Exclusion exclusion : dependency.getExclusions()) {
134 exclusions.add(new ExclusionInfo(exclusion.getArtifactId(), exclusion.getGroupId()));
135 }
136
137 return info;
138 }
139
140 private DependencyInfo getDependencyInfo(org.sonatype.aether.graph.DependencyNode dependencyNode) {
141 DependencyInfo info = new DependencyInfo();
142
143
144 org.sonatype.aether.graph.Dependency dependency = dependencyNode.getDependency();
145 org.sonatype.aether.artifact.Artifact artifact = dependency.getArtifact();
146 info.setGroupId(artifact.getGroupId());
147 info.setArtifactId(artifact.getArtifactId());
148 info.setVersion(artifact.getVersion());
149 info.setScope(dependency.getScope());
150 info.setClassifier(artifact.getClassifier());
151 info.setOptional(dependency.isOptional());
152 info.setType(artifact.getProperty(TYPE, ""));
153
154
155 File artifactFile = artifact.getFile();
156 if (artifactFile != null && artifactFile.exists()) {
157 try {
158 info.setSystemPath(artifactFile.getAbsolutePath());
159 info.setSha1(ChecksumUtils.calculateSHA1(artifactFile));
160 } catch (IOException e) {
161 debug(Constants.ERROR_SHA1 + " for " + dependency.toString());
162 }
163 }
164
165
166 for (org.sonatype.aether.graph.Exclusion exclusion : dependency.getExclusions()) {
167 info.getExclusions().add(new ExclusionInfo(exclusion.getArtifactId(), exclusion.getGroupId()));
168 }
169
170
171 for (org.sonatype.aether.graph.DependencyNode child : dependencyNode.getChildren()) {
172 info.getChildren().add(getDependencyInfo(child));
173 }
174
175 return info;
176 }
177
178 protected void debugProjectInfos(Collection<AgentProjectInfo> projectInfos) {
179 debug("----------------- dumping projectInfos -----------------");
180 debug("Total number of projects : " + projectInfos.size());
181
182 for (AgentProjectInfo projectInfo : projectInfos) {
183 debug("Project coordinates: " + projectInfo.getCoordinates().toString());
184 debug("Project parent coordinates: " + (projectInfo.getParentCoordinates() == null ? "" : projectInfo.getParentCoordinates().toString()));
185 debug("Project token: " + projectInfo.getProjectToken());
186 debug("total # of dependencies: " + projectInfo.getDependencies().size());
187 for (DependencyInfo info : projectInfo.getDependencies()) {
188 debug(info.toString() + " SHA-1: " + info.getSha1());
189 }
190 }
191
192 debug("----------------- dump finished -----------------");
193 }
194
195 protected AgentProjectInfo processProject(MavenProject project) throws MojoExecutionException {
196 long startTime = System.currentTimeMillis();
197
198 info("processing " + project.getId());
199
200 AgentProjectInfo projectInfo = new AgentProjectInfo();
201
202
203 if (project.equals(mavenProject)) {
204 projectInfo.setProjectToken(projectToken);
205 } else {
206 projectInfo.setProjectToken(moduleTokens.get(project.getArtifactId()));
207 }
208
209
210 projectInfo.setCoordinates(extractCoordinates(project));
211
212
213 if (project.hasParent()) {
214 projectInfo.setParentCoordinates(extractCoordinates(project.getParent()));
215 }
216
217
218 try {
219 projectInfo.getDependencies().addAll(collectDependencyStructure(project));
220 } catch (DependencyResolutionException e) {
221 debug("error resolving project dependencies, fallback to direct dependencies only", e);
222 projectInfo.getDependencies().clear();
223 projectInfo.getDependencies().addAll(collectDirectDependencies(project));
224 }
225
226 info("Total processing time is " + (System.currentTimeMillis() - startTime) + " [msec]");
227
228 return projectInfo;
229 }
230
231 protected Collection<DependencyInfo> collectDirectDependencies(MavenProject project) {
232 Collection<DependencyInfo> dependencyInfos = new ArrayList<DependencyInfo>();
233
234 Map<Dependency, Artifact> lut = createLookupTable(project);
235 for (Dependency dependency : project.getDependencies()) {
236 if (ignoreTestScopeDependencies && Artifact.SCOPE_TEST.equals(dependency.getScope())) {
237 continue;
238 }
239
240 DependencyInfo dependencyInfo = getDependencyInfo(dependency);
241
242
243 Artifact artifact = lut.get(dependency);
244 if (artifact != null) {
245 File artifactFile = artifact.getFile();
246 if (artifactFile != null && artifactFile.exists()) {
247 try {
248 dependencyInfo.setSha1(ChecksumUtils.calculateSHA1(artifactFile));
249 } catch (IOException e) {
250 debug(Constants.ERROR_SHA1 + " for " + artifact.getId());
251 }
252 }
253 }
254 dependencyInfos.add(dependencyInfo);
255 }
256
257 return dependencyInfos;
258 }
259
260
261
262
263
264
265
266
267
268
269
270 protected Collection<DependencyInfo> collectDependencyStructure(MavenProject project) throws DependencyResolutionException {
271 DependencyResolutionResult resolutionResult = projectDependenciesResolver.resolve(
272 new DefaultDependencyResolutionRequest(project, repoSession));
273 org.sonatype.aether.graph.DependencyNode rootNode = resolutionResult.getDependencyGraph();
274
275 Collection<DependencyInfo> dependencyInfos = new ArrayList<DependencyInfo>();
276 for (org.sonatype.aether.graph.DependencyNode dependencyNode : rootNode.getChildren()) {
277 DependencyInfo info = getDependencyInfo(dependencyNode);
278 dependencyInfos.add(info);
279 }
280
281 debug("*** printing graph result ***");
282 for (DependencyInfo dependencyInfo : dependencyInfos) {
283 debugPrintChildren(dependencyInfo, "");
284 }
285
286 return dependencyInfos;
287 }
288
289 private void debugPrintChildren(DependencyInfo info, String prefix) {
290 debug(prefix + info.getGroupId() + ":" + info.getArtifactId() + ":" + info.getVersion() + ":" + info.getScope());
291 for (DependencyInfo child : info.getChildren()) {
292 debugPrintChildren(child, prefix + " ");
293 }
294 }
295
296 protected Coordinates extractCoordinates(MavenProject mavenProject) {
297 return new Coordinates(mavenProject.getGroupId(),
298 mavenProject.getArtifactId(),
299 mavenProject.getVersion());
300 }
301
302 protected Map<Dependency, Artifact> createLookupTable(MavenProject project) {
303 Map<Dependency, Artifact> lut = new HashMap<Dependency, Artifact>();
304
305 for (Dependency dependency : project.getDependencies()) {
306 for (Artifact dependencyArtifact : project.getDependencyArtifacts()) {
307 if (match(dependency, dependencyArtifact)) {
308 lut.put(dependency, dependencyArtifact);
309 }
310 }
311 }
312
313 return lut;
314 }
315
316 protected boolean matchAny(String value, String[] patterns) {
317 if (value == null) { return false; }
318
319 boolean match = false;
320
321 for (int i=0; i < patterns.length && !match; i++) {
322 String pattern = patterns[i];
323 if (pattern != null) {
324 String regex = pattern.replace(".", "\\.").replace("*", ".*");
325 match = value.matches(regex);
326 }
327 }
328
329 return match;
330 }
331
332 protected boolean match(Dependency dependency, Artifact artifact) {
333 boolean match = dependency.getGroupId().equals(artifact.getGroupId()) &&
334 dependency.getArtifactId().equals(artifact.getArtifactId()) &&
335 dependency.getVersion().equals(artifact.getVersion());
336
337 if (match) {
338 String artifactClassifier = artifact.getClassifier();
339 if (dependency.getClassifier() == null) {
340 match = artifactClassifier == null || StringUtils.isBlank(artifactClassifier);
341 } else {
342 match = dependency.getClassifier().equals(artifactClassifier);
343 }
344 }
345
346 if (match) {
347 String type = artifact.getType();
348 if (dependency.getType() == null) {
349 match = type == null || "jar".equals(type);
350 } else {
351 match = dependency.getType().equals(type);
352 }
353 }
354
355 return match;
356 }
357
358 protected Collection<AgentProjectInfo> extractProjectInfos() throws MojoExecutionException {
359 Collection<AgentProjectInfo> projectInfos = new ArrayList<AgentProjectInfo>();
360
361 for (MavenProject project : reactorProjects) {
362 if (shouldProcess(project)) {
363 projectInfos.add(processProject(project));
364 } else {
365 info("skipping " + project.getId());
366 }
367 }
368
369 debugProjectInfos(projectInfos);
370
371 return projectInfos;
372 }
373
374 protected boolean shouldProcess(MavenProject project) {
375 if (project == null) { return false; }
376
377 boolean process = true;
378
379 if (ignorePomModules && POM.equals(project.getPackaging())) {
380 process = false;
381 } else if (project.equals(mavenProject)) {
382 process = !ignore;
383 } else if (excludes.length > 0 && matchAny(project.getArtifactId(), excludes)) {
384 process = false;
385 } else if (includes.length > 0 && matchAny(project.getArtifactId(), includes)) {
386 process = true;
387 }
388
389 return process;
390 }
391
392 protected void generateReport(CheckPoliciesResult result) throws MojoExecutionException {
393 info("Generating policy check report");
394 try {
395 PolicyCheckReport report = new PolicyCheckReport(result);
396 report.generate(outputDirectory, false);
397 report.generateJson(outputDirectory);
398 } catch (IOException e) {
399 throw new MojoExecutionException("Error generating report: " + e.getMessage(), e);
400 }
401 }
402
403 }