View Javadoc

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   * Concrete implementation holding common functionality to all goals in this plugin that use the agent API.
27   *
28   * @author tom.shapira
29   */
30  public abstract class AgentMojo extends WhitesourceMojo {
31  
32      /* --- Static members --- */
33  
34      public static final String POM = "pom";
35      public static final String TYPE = "type";
36  
37      /* --- Members --- */
38  
39      /**
40       * Unique identifier of the organization to update.
41       */
42      @Parameter(alias = "orgToken", property = Constants.ORG_TOKEN, required = true)
43      protected String orgToken;
44  
45      /**
46       * Product to update Name or Unique identifier.
47       */
48      @Parameter(alias = "product", property = Constants.PRODUCT, required = false)
49      protected String product;
50  
51      /**
52       * Product to update version.
53       */
54      @Parameter(alias = "productVersion", property = Constants.PRODUCT_VERSION, required = false)
55      protected String productVersion;
56  
57      /**
58       * Optional. Set to false to include test scope dependencies.
59       */
60      @Parameter(alias = "ignoreTestScopeDependencies", property = Constants.IGNORE_TEST_SCOPE_DEPENDENCIES, required = false, defaultValue = "true")
61      protected boolean ignoreTestScopeDependencies;
62  
63      /**
64       * Output directory for checking policies results.
65       */
66      @Parameter( alias = "outputDirectory", property = Constants.OUTPUT_DIRECTORY, required = false, defaultValue = "${project.reporting.outputDirectory}")
67      protected File outputDirectory;
68  
69      /**
70       * Optional. Unique identifier of the White Source project to update.
71       * If omitted, default naming convention will apply.
72       */
73      @Parameter(alias = "projectToken", property = Constants.PROJECT_TOKEN, required = false)
74      protected String projectToken;
75  
76      /**
77       * Optional. Map of module artifactId to White Source project token.
78       */
79      @Parameter(alias = "moduleTokens", property = Constants.MODULE_TOKENS, required = false)
80      protected Map<String, String> moduleTokens = new HashMap<String, String>();
81  
82      /**
83       * Optional. Use name value pairs for specifying the project tokens to use for modules whose artifactId
84       * is not a valid XML tag.
85       */
86      @Parameter(alias = "specialModuleTokens", property = Constants.SPECIAL_MODULE_TOKENS, required = false)
87      protected Properties specialModuleTokens = new Properties();
88  
89      /**
90       * Optional. Set to true to ignore this maven project. Overrides any include patterns.
91       */
92      @Parameter(alias = "ignore", property = Constants.IGNORE, required = false, defaultValue = "false")
93      protected boolean ignore;
94  
95      /**
96       * Optional. Only modules with an artifactId matching one of these patterns will be processed by the plugin.
97       */
98      @Parameter(alias = "includes", property = Constants.INCLUDES, required = false, defaultValue = "")
99      protected String[] includes;
100 
101     /**
102      * Optional. Modules with an artifactId matching any of these patterns will not be processed by the plugin.
103      */
104     @Parameter(alias = "excludes", property = Constants.EXCLUDES, required = false, defaultValue = "")
105     protected String[] excludes;
106 
107     /**
108      * Optional. Set to true to ignore this maven modules of type pom.
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     /* --- Protected methods --- */
117 
118     protected DependencyInfo getDependencyInfo(Dependency dependency) {
119         DependencyInfo info = new DependencyInfo();
120 
121         // dependency data
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         // exclusions
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         // dependency data
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         // try to calculate SHA-1
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         // exclusions
166         for (org.sonatype.aether.graph.Exclusion exclusion : dependency.getExclusions()) {
167             info.getExclusions().add(new ExclusionInfo(exclusion.getArtifactId(), exclusion.getGroupId()));
168         }
169 
170         // recursively collect children
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         // project token
203         if (project.equals(mavenProject)) {
204             projectInfo.setProjectToken(projectToken);
205         } else {
206             projectInfo.setProjectToken(moduleTokens.get(project.getArtifactId()));
207         }
208 
209         // project coordinates
210         projectInfo.setCoordinates(extractCoordinates(project));
211 
212         // parent coordinates
213         if (project.hasParent()) {
214             projectInfo.setParentCoordinates(extractCoordinates(project.getParent()));
215         }
216 
217         // collect dependencies
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; // exclude test scope dependencies from being sent to the server
238             }
239 
240             DependencyInfo dependencyInfo = getDependencyInfo(dependency);
241 
242             // try to calculate SHA-1
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      * Build the dependency graph of the project in order to resolve all transitive dependencies.
262      * By default resolves filters scopes test and provided, and transitive optional dependencies.
263      *
264      * @param project The maven project.
265      *
266      * @return A collection of {@link DependencyInfo} resolved with children.
267      *
268      * @throws DependencyResolutionException Exception thrown if dependency resolution fails.
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 }