View Javadoc

1   /*
2    * #%L
3    * License Maven Plugin
4    *
5    * $Id: AbstractAddThirdPartyMojo.java 14605 2011-08-21 01:11:25Z tchemit $
6    * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/AbstractAddThirdPartyMojo.java $
7    * %%
8    * Copyright (C) 2008 - 2011 CodeLutin, Codehaus, Tony Chemit
9    * %%
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as
12   * published by the Free Software Foundation, either version 3 of the
13   * License, or (at your option) any later version.
14   *
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   *
20   * You should have received a copy of the GNU General Lesser Public
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
23   * #L%
24   */
25  
26  package org.codehaus.mojo.license;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.SortedMap;
39  import java.util.SortedSet;
40  import java.util.TreeMap;
41  
42  import org.apache.commons.collections.CollectionUtils;
43  import org.apache.commons.io.FileUtils;
44  import org.apache.commons.io.IOUtils;
45  import org.apache.commons.lang.StringUtils;
46  import org.apache.maven.plugin.MojoFailureException;
47  import org.apache.maven.plugin.logging.Log;
48  import org.apache.maven.project.MavenProject;
49  import org.apache.maven.project.ProjectBuildingException;
50  import org.codehaus.mojo.license.model.LicenseMap;
51  import org.springframework.core.io.DefaultResourceLoader;
52  import org.springframework.core.io.Resource;
53  import org.springframework.core.io.ResourceLoader;
54  
55  /**
56   * Abstract mojo for all third-party mojos.
57   *
58   * @author tchemit <chemit@codelutin.com>
59   * @since 1.0
60   */
61  public abstract class AbstractAddThirdPartyMojo extends AbstractLicenseMojo {
62  
63      /**
64       * Directory where to generate files.
65       *
66       * @parameter expression="${license.outputDirectory}"
67       *            default-value="${project.build.directory}/generated-sources/license"
68       * @required
69       * @since 1.0
70       */
71      protected File outputDirectory;
72  
73      /**
74       * File where to wirte the third-party file.
75       *
76       * @parameter expression="${license.thirdPartyFilename}" default-value="THIRD-PARTY.txt"
77       * @required
78       * @since 1.0
79       */
80      protected String thirdPartyFilename;
81  
82      /**
83       * A flag to use the missing licenses file to consolidate the THID-PARTY file.
84       *
85       * @parameter expression="${license.useMissingFile}" default-value="false"
86       * @since 1.0
87       */
88      protected boolean useMissingFile;
89  
90      /**
91       * The file where to fill the license for dependencies with unknown license.
92       *
93       * @parameter expression="${license.missingFile}" default-value="src/license/THIRD-PARTY.properties"
94       * @since 1.0
95       */
96      protected File missingFile;
97  
98      /**
99       * Location of a properties file mapping artifacts that are published with no license to the license that should be
100      * used for them. This supports classpath notation and any other type of URL Spring 3.1 resource loading can
101      * understand.
102      *
103      * @parameter expression="${license.artifactLicenseMapping}" default-value="THIRD-PARTY.properties"
104      * @since 1.0
105      */
106     protected String artifactLicenseMapping;
107 
108     /**
109      * To merge licenses in final file.
110      * <p/>
111      * Each entry represents a merge (first license is main license to keep), licenses are separated by {@code |}.
112      * <p/>
113      * Example :
114      * <p/>
115      *
116      * <pre>
117      * &lt;licenseMerges&gt;
118      * &lt;licenseMerge&gt;The Apache Software License|Version 2.0,Apache License, Version 2.0&lt;/licenseMerge&gt;
119      * &lt;/licenseMerges&gt;
120      * &lt;/pre&gt;
121      *
122      * @parameter
123      * @since 1.0
124      */
125     protected List<String> licenseMerges;
126 
127     /**
128      * The path of the bundled third party file to produce when {@link #generateBundle} is on.
129      * <p/>
130      * <b>Note:</b> This option is not available for {@code pom} module types.
131      *
132      * @parameter expression="${license.bundleThirdPartyPath}"
133      *            default-value="META-INF/${project.artifactId}-THIRD-PARTY.txt"
134      * @since 1.0
135      */
136     protected String bundleThirdPartyPath;
137 
138     /**
139      * A flag to copy a bundled version of the third-party file. This is usefull to avoid for a final application
140      * collision name of third party file.
141      * <p/>
142      * The file will be copied at the {@link #bundleThirdPartyPath} location.
143      *
144      * @parameter expression="${license.generateBundle}" default-value="false"
145      * @since 1.0
146      */
147     protected boolean generateBundle;
148 
149     /**
150      * To force generation of the third-party file even if every thing is up to date.
151      *
152      * @parameter expression="${license.force}" default-value="false"
153      * @since 1.0
154      */
155     protected boolean force;
156 
157     /**
158      * A flag to fail the build if at least one dependency was detected without a license.
159      *
160      * @parameter expression="${license.failIfWarning}" default-value="false"
161      * @since 1.0
162      */
163     protected boolean failIfWarning;
164 
165     /**
166      * A flag to change the grouping of the generated THIRD-PARTY file.
167      * <p/>
168      * By default, group by dependecies.
169      * <p/>
170      * If sets to {@code true}, the it will group by license type.
171      *
172      * @parameter expression="${license.groupByLicense}" default-value="false"
173      * @since 1.0
174      */
175     protected boolean groupByLicense;
176 
177     /**
178      * A filter to exclude some scopes.
179      *
180      * @parameter expression="${license.excludedScopes}" default-value="system"
181      * @since 1.0
182      */
183     protected String excludedScopes;
184 
185     /**
186      * A filter to include only some scopes, if let empty then all scopes will be used (no filter).
187      *
188      * @parameter expression="${license.includedScopes}" default-value=""
189      * @since 1.0
190      */
191     protected String includedScopes;
192 
193     /**
194      * A filter to exclude some GroupIds
195      *
196      * @parameter expression="${license.excludedGroups}" default-value=""
197      * @since 1.0
198      */
199     protected String excludedGroups;
200 
201     /**
202      * A filter to include only some GroupIds
203      *
204      * @parameter expression="${license.includedGroups}" default-value=""
205      * @since 1.0
206      */
207     protected String includedGroups;
208 
209     /**
210      * A filter to exclude some ArtifactsIds
211      *
212      * @parameter expression="${license.excludedArtifacts}" default-value=""
213      * @since 1.0
214      */
215     protected String excludedArtifacts;
216 
217     /**
218      * A filter to include only some ArtifactsIds
219      *
220      * @parameter expression="${license.includedArtifacts}" default-value=""
221      * @since 1.0
222      */
223     protected String includedArtifacts;
224 
225     /**
226      * Include transitive dependencies when downloading license files.
227      *
228      * @parameter default-value="true"
229      * @since 1.0
230      */
231     protected boolean includeTransitiveDependencies;
232 
233     /**
234      * third party tool.
235      *
236      * @component
237      * @readonly
238      * @since 1.0
239      */
240     private ThirdPartyTool thridPartyTool;
241 
242     private SortedMap<String, MavenProject> projectDependencies;
243 
244     private LicenseMap licenseMap;
245 
246     private SortedSet<MavenProject> unsafeDependencies;
247 
248     private File thirdPartyFile;
249 
250     private SortedProperties unsafeMappings;
251 
252     private boolean doGenerate;
253 
254     private boolean doGenerateBundle;
255 
256     public static final String NO_DEPENDENCIES_MESSAGE = "the project has no dependencies.";
257 
258     private static SortedMap<String, MavenProject> artifactCache;
259 
260     public static SortedMap<String, MavenProject> getArtifactCache() {
261         if (artifactCache == null) {
262             artifactCache = new TreeMap<String, MavenProject>();
263         }
264         return artifactCache;
265     }
266 
267     protected abstract SortedMap<String, MavenProject> loadDependencies();
268 
269     protected abstract SortedProperties createUnsafeMapping() throws ProjectBuildingException, IOException,
270             ThirdPartyToolException;
271 
272     protected boolean exists(String location) {
273         if (StringUtils.isBlank(location)) {
274             return false;
275         }
276         ResourceLoader loader = new DefaultResourceLoader();
277         Resource resource = loader.getResource(location);
278         return resource.exists();
279     }
280 
281     protected File copyToFileSystem(String location) {
282         ResourceLoader loader = new DefaultResourceLoader();
283         Resource resource = loader.getResource(location);
284         if (!resource.exists()) {
285             throw new IllegalArgumentException("Can't locate " + location);
286         }
287 
288         InputStream in = null;
289         OutputStream out = null;
290         try {
291             in = resource.getInputStream();
292             File temp = new File(getProject().getBuild().getDirectory() + "/license/THIRD-PARTY.properties");
293             out = FileUtils.openOutputStream(temp);
294             IOUtils.copy(in, out);
295             getLog().debug("Created " + temp);
296             return temp;
297         } catch (IOException e) {
298             throw new IllegalArgumentException(e);
299         } finally {
300             IOUtils.closeQuietly(in);
301             IOUtils.closeQuietly(out);
302         }
303     }
304 
305     @Override
306     protected void init() throws Exception {
307         if (exists(getArtifactLicenseMapping())) {
308             File propertiesFile = copyToFileSystem(getArtifactLicenseMapping());
309             setMissingFile(propertiesFile);
310         }
311 
312 
313         Log log = getLog();
314 
315         if (log.isDebugEnabled()) {
316 
317             // always be verbose in debug mode
318             setVerbose(true);
319         }
320 
321         File file = new File(getOutputDirectory(), getThirdPartyFilename());
322 
323         setThirdPartyFile(file);
324 
325         long buildTimestamp = getBuildTimestamp();
326 
327         if (isVerbose()) {
328             log.info("Build start   at : " + buildTimestamp);
329             log.info("third-party file : " + file.lastModified());
330         }
331 
332         setDoGenerate(isForce() || !file.exists() || buildTimestamp > file.lastModified());
333 
334         if (isGenerateBundle()) {
335 
336             File bundleFile = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath());
337 
338             if (isVerbose()) {
339                 log.info("bundle third-party file : " + bundleFile.lastModified());
340             }
341             setDoGenerateBundle(isForce() || !bundleFile.exists() || buildTimestamp > bundleFile.lastModified());
342         } else {
343 
344             // not generating bundled file
345             setDoGenerateBundle(false);
346         }
347 
348         projectDependencies = loadDependencies();
349 
350         licenseMap = createLicenseMap(projectDependencies);
351 
352         SortedSet<MavenProject> unsafeDependencies = getThridPartyTool().getProjectsWithNoLicense(licenseMap,
353                 isVerbose());
354 
355         setUnsafeDependencies(unsafeDependencies);
356 
357         if (!CollectionUtils.isEmpty(unsafeDependencies) && isUseMissingFile() && isDoGenerate()) {
358 
359             // load unsafeMapping
360             unsafeMappings = createUnsafeMapping();
361         }
362 
363         if (!CollectionUtils.isEmpty(licenseMerges)) {
364 
365             // check where is not multi licenses merged main licenses (see OJO-1723)
366             Map<String, String[]> mergedLicenses = new HashMap<String, String[]>();
367 
368             for (String merge : licenseMerges) {
369                 merge = merge.trim();
370                 String[] split = merge.split("\\|");
371 
372                 String mainLicense = split[0];
373 
374                 if (mergedLicenses.containsKey(mainLicense)) {
375 
376                     // this license was already describe, fail the build...
377 
378                     throw new MojoFailureException(
379                             "The merge main license "
380                                     + mainLicense
381                                     + " was already registred in the "
382                                     + "configuration, please use only one such entry as describe in example "
383                                     + "http://mojo.codehaus.org/license-maven-plugin/examples/example-thirdparty.html#Merge_licenses.");
384                 }
385                 mergedLicenses.put(mainLicense, split);
386             }
387 
388             // merge licenses in license map
389 
390             for (String[] mergedLicense : mergedLicenses.values()) {
391                 if (isVerbose()) {
392                     getLog().info("Will merge " + Arrays.toString(mergedLicense) + "");
393                 }
394 
395                 thridPartyTool.mergeLicenses(licenseMap, mergedLicense);
396             }
397         }
398     }
399 
400     protected LicenseMap createLicenseMap(SortedMap<String, MavenProject> dependencies) {
401 
402         LicenseMap licenseMap = new LicenseMap();
403 
404         for (MavenProject project : dependencies.values()) {
405             thridPartyTool.addLicense(licenseMap, project, project.getLicenses());
406         }
407         return licenseMap;
408     }
409 
410     protected boolean checkUnsafeDependencies() {
411         SortedSet<MavenProject> unsafeDependencies = getUnsafeDependencies();
412         boolean unsafe = !CollectionUtils.isEmpty(unsafeDependencies);
413         if (unsafe) {
414             Log log = getLog();
415             log.debug("There is " + unsafeDependencies.size() + " dependencies with no license :");
416             for (MavenProject dep : unsafeDependencies) {
417 
418                 // no license found for the dependency
419                 log.info(" - " + MojoHelper.getArtifactId(dep.getArtifact()));
420             }
421         }
422         return unsafe;
423     }
424 
425     protected void writeThirdPartyFile() throws IOException {
426 
427         Log log = getLog();
428         LicenseMap licenseMap = getLicenseMap();
429         File target = getThirdPartyFile();
430 
431         if (isDoGenerate()) {
432             StringBuilder sb = new StringBuilder();
433             if (licenseMap.isEmpty()) {
434                 sb.append(NO_DEPENDENCIES_MESSAGE);
435             } else {
436                 if (isGroupByLicense()) {
437 
438                     // group by license
439                     sb.append("List of third-party dependencies grouped by " + "their license type.");
440                     for (String licenseName : licenseMap.keySet()) {
441                         SortedSet<MavenProject> projects = licenseMap.get(licenseName);
442                         sb.append("\n\n").append(licenseName).append(" : ");
443 
444                         for (MavenProject mavenProject : projects) {
445                             String s = MojoHelper.getArtifactName(mavenProject);
446                             sb.append("\n  * ").append(s);
447                         }
448                     }
449 
450                 } else {
451 
452                     // group by dependencies
453                     SortedMap<MavenProject, String[]> map = licenseMap.toDependencyMap();
454 
455                     sb.append("List of ").append(map.size()).append(" third-party dependencies.\n");
456 
457                     List<String> lines = new ArrayList<String>();
458 
459                     for (Map.Entry<MavenProject, String[]> entry : map.entrySet()) {
460                         String artifact = MojoHelper.getArtifactName(entry.getKey());
461                         StringBuilder buffer = new StringBuilder();
462                         for (String license : entry.getValue()) {
463                             buffer.append(" (").append(license).append(")");
464                         }
465                         String licenses = buffer.toString();
466                         String line = licenses + " " + artifact;
467                         lines.add(line);
468                     }
469 
470                     Collections.sort(lines);
471                     for (String line : lines) {
472                         sb.append('\n').append(line);
473                     }
474                     lines.clear();
475                 }
476             }
477             String content = sb.toString();
478 
479             log.info("Writing third-party file to " + target);
480             if (isVerbose()) {
481                 log.info(content);
482             }
483 
484             FileUtil.writeString(target, content, getEncoding());
485         }
486 
487         if (isDoGenerateBundle()) {
488 
489             // creates the bundled license file
490             File bundleTarget = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath());
491             log.info("Writing bundled third-party file to " + bundleTarget);
492             FileUtil.copyFile(target, bundleTarget);
493         }
494     }
495 
496     public boolean isGroupByLicense() {
497         return groupByLicense;
498     }
499 
500     public void setGroupByLicense(boolean groupByLicense) {
501         this.groupByLicense = groupByLicense;
502     }
503 
504     public File getOutputDirectory() {
505         return outputDirectory;
506     }
507 
508     public String getThirdPartyFilename() {
509         return thirdPartyFilename;
510     }
511 
512     public String getBundleThirdPartyPath() {
513         return bundleThirdPartyPath;
514     }
515 
516     public boolean isGenerateBundle() {
517         return generateBundle;
518     }
519 
520     public boolean isFailIfWarning() {
521         return failIfWarning;
522     }
523 
524     public SortedMap<String, MavenProject> getProjectDependencies() {
525         return projectDependencies;
526     }
527 
528     public SortedSet<MavenProject> getUnsafeDependencies() {
529         return unsafeDependencies;
530     }
531 
532     public void setUnsafeDependencies(SortedSet<MavenProject> unsafeDependencies) {
533         this.unsafeDependencies = unsafeDependencies;
534     }
535 
536     public File getThirdPartyFile() {
537         return thirdPartyFile;
538     }
539 
540     public LicenseMap getLicenseMap() {
541         return licenseMap;
542     }
543 
544     public void setOutputDirectory(File outputDirectory) {
545         this.outputDirectory = outputDirectory;
546     }
547 
548     public void setThirdPartyFilename(String thirdPartyFilename) {
549         this.thirdPartyFilename = thirdPartyFilename;
550     }
551 
552     public void setBundleThirdPartyPath(String bundleThirdPartyPath) {
553         this.bundleThirdPartyPath = bundleThirdPartyPath;
554     }
555 
556     public void setGenerateBundle(boolean generateBundle) {
557         this.generateBundle = generateBundle;
558     }
559 
560     public void setThirdPartyFile(File thirdPartyFile) {
561         this.thirdPartyFile = thirdPartyFile;
562     }
563 
564     public boolean isUseMissingFile() {
565         return useMissingFile;
566     }
567 
568     public File getMissingFile() {
569         return missingFile;
570     }
571 
572     public void setUseMissingFile(boolean useMissingFile) {
573         this.useMissingFile = useMissingFile;
574     }
575 
576     public void setMissingFile(File missingFile) {
577         this.missingFile = missingFile;
578     }
579 
580     public void setFailIfWarning(boolean failIfWarning) {
581         this.failIfWarning = failIfWarning;
582     }
583 
584     public SortedProperties getUnsafeMappings() {
585         return unsafeMappings;
586     }
587 
588     public boolean isForce() {
589         return force;
590     }
591 
592     public boolean isDoGenerate() {
593         return doGenerate;
594     }
595 
596     public void setForce(boolean force) {
597         this.force = force;
598     }
599 
600     public void setDoGenerate(boolean doGenerate) {
601         this.doGenerate = doGenerate;
602     }
603 
604     public boolean isDoGenerateBundle() {
605         return doGenerateBundle;
606     }
607 
608     public void setDoGenerateBundle(boolean doGenerateBundle) {
609         this.doGenerateBundle = doGenerateBundle;
610     }
611 
612     public List<String> getExcludedScopes() {
613         String[] split = excludedScopes == null ? new String[0] : excludedScopes.split(",");
614         return Arrays.asList(split);
615     }
616 
617     public void setExcludedScopes(String excludedScopes) {
618         this.excludedScopes = excludedScopes;
619     }
620 
621     public List<String> getIncludedScopes() {
622         String[] split = includedScopes == null ? new String[0] : includedScopes.split(",");
623         return Arrays.asList(split);
624     }
625 
626     public void setIncludedScopes(String includedScopes) {
627         this.includedScopes = includedScopes;
628     }
629 
630     public String getExcludedGroups() {
631         return excludedGroups;
632     }
633 
634     public void setExcludedGroups(String excludedGroups) {
635         this.excludedGroups = excludedGroups;
636     }
637 
638     public String getIncludedGroups() {
639         return includedGroups;
640     }
641 
642     public void setIncludedGroups(String includedGroups) {
643         this.includedGroups = includedGroups;
644     }
645 
646     public String getExcludedArtifacts() {
647         return excludedArtifacts;
648     }
649 
650     public void setExcludedArtifacts(String excludedArtifacts) {
651         this.excludedArtifacts = excludedArtifacts;
652     }
653 
654     public String getIncludedArtifacts() {
655         return includedArtifacts;
656     }
657 
658     public void setIncludedArtifacts(String includedArtifacts) {
659         this.includedArtifacts = includedArtifacts;
660     }
661 
662     public ThirdPartyTool getThridPartyTool() {
663         return thridPartyTool;
664     }
665 
666     public void setThridPartyTool(ThirdPartyTool thridPartyTool) {
667         this.thridPartyTool = thridPartyTool;
668     }
669 
670     public String getArtifactLicenseMapping() {
671         return artifactLicenseMapping;
672     }
673 
674     public void setArtifactLicenseMapping(String artifactLicenseMapping) {
675         this.artifactLicenseMapping = artifactLicenseMapping;
676     }
677 }