View Javadoc

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