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.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.SortedMap;
29  import java.util.SortedSet;
30  import java.util.TreeMap;
31  
32  import org.apache.commons.collections.CollectionUtils;
33  import org.apache.commons.io.FileUtils;
34  import org.apache.commons.io.IOUtils;
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugin.logging.Log;
38  import org.apache.maven.project.MavenProject;
39  import org.apache.maven.project.ProjectBuildingException;
40  import org.codehaus.mojo.license.model.LicenseMap;
41  import org.springframework.core.io.DefaultResourceLoader;
42  import org.springframework.core.io.Resource;
43  import org.springframework.core.io.ResourceLoader;
44  
45  /**
46   * Abstract mojo for all third-party mojos.
47   *
48   * @author tchemit <chemit@codelutin.com>
49   * @since 1.0
50   */
51  public abstract class AbstractAddThirdPartyMojo extends AbstractLicenseMojo {
52  
53      /**
54       * Directory where to generate files.
55       *
56       * @parameter expression="${license.outputDirectory}"
57       *            default-value="${project.build.directory}/generated-sources/license"
58       * @required
59       * @since 1.0
60       */
61      protected File outputDirectory;
62  
63      /**
64       * File where to wirte the third-party file.
65       *
66       * @parameter expression="${license.thirdPartyFilename}" default-value="THIRD-PARTY.txt"
67       * @required
68       * @since 1.0
69       */
70      protected String thirdPartyFilename;
71  
72      /**
73       * A flag to use the missing licenses file to consolidate the THID-PARTY file.
74       *
75       * @parameter expression="${license.useMissingFile}" default-value="false"
76       * @since 1.0
77       */
78      protected boolean useMissingFile;
79  
80      /**
81       * The file where to fill the license for dependencies with unknown license.
82       *
83       * @parameter expression="${license.missingFile}" default-value="src/license/THIRD-PARTY.properties"
84       * @since 1.0
85       */
86      protected File missingFile;
87  
88      /**
89       * Location of a properties file mapping artifacts that are published with no license to the license that should be
90       * used for them. This supports classpath notation and any other type of URL Spring 3.1 resource loading can
91       * understand.
92       *
93       * @parameter expression="${license.artifactLicenseMapping}" default-value="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 dependecies.
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 thridPartyTool;
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         ResourceLoader loader = new DefaultResourceLoader();
267         Resource resource = loader.getResource(location);
268         return resource.exists();
269     }
270 
271     protected File copyToFileSystem(String location) {
272         ResourceLoader loader = new DefaultResourceLoader();
273         Resource resource = loader.getResource(location);
274         if (!resource.exists()) {
275             throw new IllegalArgumentException("Can't locate " + location);
276         }
277 
278         InputStream in = null;
279         OutputStream out = null;
280         try {
281             in = resource.getInputStream();
282             File temp = new File(getProject().getBuild().getDirectory() + "/license/THIRD-PARTY.properties");
283             out = FileUtils.openOutputStream(temp);
284             IOUtils.copy(in, out);
285             getLog().debug("Created " + temp);
286             return temp;
287         } catch (IOException e) {
288             throw new IllegalArgumentException(e);
289         } finally {
290             IOUtils.closeQuietly(in);
291             IOUtils.closeQuietly(out);
292         }
293     }
294 
295     @Override
296     protected void init() throws Exception {
297         if (exists(getArtifactLicenseMapping())) {
298             File propertiesFile = copyToFileSystem(getArtifactLicenseMapping());
299             setMissingFile(propertiesFile);
300         }
301 
302         Log log = getLog();
303 
304         if (log.isDebugEnabled()) {
305 
306             // always be verbose in debug mode
307             setVerbose(true);
308         }
309 
310         File file = new File(getOutputDirectory(), getThirdPartyFilename());
311 
312         setThirdPartyFile(file);
313 
314         long buildTimestamp = getBuildTimestamp();
315 
316         if (isVerbose()) {
317             log.info("Build start   at : " + buildTimestamp);
318             log.info("third-party file : " + file.lastModified());
319         }
320 
321         setDoGenerate(isForce() || !file.exists() || buildTimestamp > file.lastModified());
322 
323         if (isGenerateBundle()) {
324 
325             File bundleFile = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath());
326 
327             if (isVerbose()) {
328                 log.info("bundle third-party file : " + bundleFile.lastModified());
329             }
330             setDoGenerateBundle(isForce() || !bundleFile.exists() || buildTimestamp > bundleFile.lastModified());
331         } else {
332 
333             // not generating bundled file
334             setDoGenerateBundle(false);
335         }
336 
337         projectDependencies = loadDependencies();
338 
339         licenseMap = createLicenseMap(projectDependencies);
340 
341         SortedSet<MavenProject> unsafeDependencies = getThridPartyTool().getProjectsWithNoLicense(licenseMap,
342                 isVerbose());
343 
344         setUnsafeDependencies(unsafeDependencies);
345 
346         if (!CollectionUtils.isEmpty(unsafeDependencies) && isUseMissingFile() && isDoGenerate()) {
347 
348             // load unsafeMapping
349             unsafeMappings = createUnsafeMapping();
350         }
351 
352         if (!CollectionUtils.isEmpty(licenseMerges)) {
353 
354             // check where is not multi licenses merged main licenses (see OJO-1723)
355             Map<String, String[]> mergedLicenses = new HashMap<String, String[]>();
356 
357             for (String merge : licenseMerges) {
358                 merge = merge.trim();
359                 String[] split = merge.split("\\|");
360 
361                 String mainLicense = split[0];
362 
363                 if (mergedLicenses.containsKey(mainLicense)) {
364 
365                     // this license was already describe, fail the build...
366 
367                     throw new MojoFailureException(
368                             "The merge main license "
369                                     + mainLicense
370                                     + " was already registred in the "
371                                     + "configuration, please use only one such entry as describe in example "
372                                     + "http://mojo.codehaus.org/license-maven-plugin/examples/example-thirdparty.html#Merge_licenses.");
373                 }
374                 mergedLicenses.put(mainLicense, split);
375             }
376 
377             // merge licenses in license map
378 
379             for (String[] mergedLicense : mergedLicenses.values()) {
380                 if (isVerbose()) {
381                     getLog().info("Will merge " + Arrays.toString(mergedLicense) + "");
382                 }
383 
384                 thridPartyTool.mergeLicenses(licenseMap, mergedLicense);
385             }
386         }
387     }
388 
389     protected LicenseMap createLicenseMap(SortedMap<String, MavenProject> dependencies) {
390 
391         LicenseMap licenseMap = new LicenseMap();
392 
393         for (MavenProject project : dependencies.values()) {
394             thridPartyTool.addLicense(licenseMap, project, project.getLicenses());
395         }
396         return licenseMap;
397     }
398 
399     protected boolean checkUnsafeDependencies() {
400         SortedSet<MavenProject> unsafeDependencies = getUnsafeDependencies();
401         boolean unsafe = !CollectionUtils.isEmpty(unsafeDependencies);
402         if (unsafe) {
403             Log log = getLog();
404             log.debug("There is " + unsafeDependencies.size() + " dependencies with no license :");
405             for (MavenProject dep : unsafeDependencies) {
406 
407                 // no license found for the dependency
408                 log.info(" - " + MojoHelper.getArtifactId(dep.getArtifact()));
409             }
410         }
411         return unsafe;
412     }
413 
414     protected void writeThirdPartyFile() throws IOException {
415 
416         Log log = getLog();
417         LicenseMap licenseMap = getLicenseMap();
418         File target = getThirdPartyFile();
419 
420         if (isDoGenerate()) {
421             StringBuilder sb = new StringBuilder();
422             if (licenseMap.isEmpty()) {
423                 sb.append(NO_DEPENDENCIES_MESSAGE);
424             } else {
425                 if (isGroupByLicense()) {
426 
427                     // group by license
428                     sb.append("List of third-party dependencies grouped by " + "their license type.");
429                     for (String licenseName : licenseMap.keySet()) {
430                         SortedSet<MavenProject> projects = licenseMap.get(licenseName);
431                         sb.append("\n\n").append(licenseName).append(" : ");
432 
433                         for (MavenProject mavenProject : projects) {
434                             String s = MojoHelper.getArtifactName(mavenProject);
435                             sb.append("\n  * ").append(s);
436                         }
437                     }
438 
439                 } else {
440 
441                     // group by dependencies
442                     SortedMap<MavenProject, String[]> map = licenseMap.toDependencyMap();
443 
444                     sb.append("List of ").append(map.size()).append(" third-party dependencies.\n");
445 
446                     List<String> lines = new ArrayList<String>();
447 
448                     for (Map.Entry<MavenProject, String[]> entry : map.entrySet()) {
449                         String artifact = MojoHelper.getArtifactName(entry.getKey());
450                         StringBuilder buffer = new StringBuilder();
451                         for (String license : entry.getValue()) {
452                             buffer.append(" (").append(license).append(")");
453                         }
454                         String licenses = buffer.toString();
455                         String line = licenses + " " + artifact;
456                         lines.add(line);
457                     }
458 
459                     Collections.sort(lines);
460                     for (String line : lines) {
461                         sb.append('\n').append(line);
462                     }
463                     lines.clear();
464                 }
465             }
466             String content = sb.toString();
467 
468             log.info("Writing third-party file to " + target);
469             if (isVerbose()) {
470                 log.info(content);
471             }
472 
473             FileUtil.writeString(target, content, getEncoding());
474         }
475 
476         if (isDoGenerateBundle()) {
477 
478             // creates the bundled license file
479             File bundleTarget = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath());
480             log.info("Writing bundled third-party file to " + bundleTarget);
481             FileUtil.copyFile(target, bundleTarget);
482         }
483     }
484 
485     public boolean isGroupByLicense() {
486         return groupByLicense;
487     }
488 
489     public void setGroupByLicense(boolean groupByLicense) {
490         this.groupByLicense = groupByLicense;
491     }
492 
493     public File getOutputDirectory() {
494         return outputDirectory;
495     }
496 
497     public String getThirdPartyFilename() {
498         return thirdPartyFilename;
499     }
500 
501     public String getBundleThirdPartyPath() {
502         return bundleThirdPartyPath;
503     }
504 
505     public boolean isGenerateBundle() {
506         return generateBundle;
507     }
508 
509     public boolean isFailIfWarning() {
510         return failIfWarning;
511     }
512 
513     public SortedMap<String, MavenProject> getProjectDependencies() {
514         return projectDependencies;
515     }
516 
517     public SortedSet<MavenProject> getUnsafeDependencies() {
518         return unsafeDependencies;
519     }
520 
521     public void setUnsafeDependencies(SortedSet<MavenProject> unsafeDependencies) {
522         this.unsafeDependencies = unsafeDependencies;
523     }
524 
525     public File getThirdPartyFile() {
526         return thirdPartyFile;
527     }
528 
529     public LicenseMap getLicenseMap() {
530         return licenseMap;
531     }
532 
533     public void setOutputDirectory(File outputDirectory) {
534         this.outputDirectory = outputDirectory;
535     }
536 
537     public void setThirdPartyFilename(String thirdPartyFilename) {
538         this.thirdPartyFilename = thirdPartyFilename;
539     }
540 
541     public void setBundleThirdPartyPath(String bundleThirdPartyPath) {
542         this.bundleThirdPartyPath = bundleThirdPartyPath;
543     }
544 
545     public void setGenerateBundle(boolean generateBundle) {
546         this.generateBundle = generateBundle;
547     }
548 
549     public void setThirdPartyFile(File thirdPartyFile) {
550         this.thirdPartyFile = thirdPartyFile;
551     }
552 
553     public boolean isUseMissingFile() {
554         return useMissingFile;
555     }
556 
557     public File getMissingFile() {
558         return missingFile;
559     }
560 
561     public void setUseMissingFile(boolean useMissingFile) {
562         this.useMissingFile = useMissingFile;
563     }
564 
565     public void setMissingFile(File missingFile) {
566         this.missingFile = missingFile;
567     }
568 
569     public void setFailIfWarning(boolean failIfWarning) {
570         this.failIfWarning = failIfWarning;
571     }
572 
573     public SortedProperties getUnsafeMappings() {
574         return unsafeMappings;
575     }
576 
577     public boolean isForce() {
578         return force;
579     }
580 
581     public boolean isDoGenerate() {
582         return doGenerate;
583     }
584 
585     public void setForce(boolean force) {
586         this.force = force;
587     }
588 
589     public void setDoGenerate(boolean doGenerate) {
590         this.doGenerate = doGenerate;
591     }
592 
593     public boolean isDoGenerateBundle() {
594         return doGenerateBundle;
595     }
596 
597     public void setDoGenerateBundle(boolean doGenerateBundle) {
598         this.doGenerateBundle = doGenerateBundle;
599     }
600 
601     public List<String> getExcludedScopes() {
602         String[] split = excludedScopes == null ? new String[0] : excludedScopes.split(",");
603         return Arrays.asList(split);
604     }
605 
606     public void setExcludedScopes(String excludedScopes) {
607         this.excludedScopes = excludedScopes;
608     }
609 
610     public List<String> getIncludedScopes() {
611         String[] split = includedScopes == null ? new String[0] : includedScopes.split(",");
612         return Arrays.asList(split);
613     }
614 
615     public void setIncludedScopes(String includedScopes) {
616         this.includedScopes = includedScopes;
617     }
618 
619     public String getExcludedGroups() {
620         return excludedGroups;
621     }
622 
623     public void setExcludedGroups(String excludedGroups) {
624         this.excludedGroups = excludedGroups;
625     }
626 
627     public String getIncludedGroups() {
628         return includedGroups;
629     }
630 
631     public void setIncludedGroups(String includedGroups) {
632         this.includedGroups = includedGroups;
633     }
634 
635     public String getExcludedArtifacts() {
636         return excludedArtifacts;
637     }
638 
639     public void setExcludedArtifacts(String excludedArtifacts) {
640         this.excludedArtifacts = excludedArtifacts;
641     }
642 
643     public String getIncludedArtifacts() {
644         return includedArtifacts;
645     }
646 
647     public void setIncludedArtifacts(String includedArtifacts) {
648         this.includedArtifacts = includedArtifacts;
649     }
650 
651     public ThirdPartyTool getThridPartyTool() {
652         return thridPartyTool;
653     }
654 
655     public void setThridPartyTool(ThirdPartyTool thridPartyTool) {
656         this.thridPartyTool = thridPartyTool;
657     }
658 
659     public String getArtifactLicenseMapping() {
660         return artifactLicenseMapping;
661     }
662 
663     public void setArtifactLicenseMapping(String artifactLicenseMapping) {
664         this.artifactLicenseMapping = artifactLicenseMapping;
665     }
666 }