001 /*
002 * #%L
003 * License Maven Plugin
004 *
005 * $Id: AbstractAddThirdPartyMojo.java 14605 2011-08-21 01:11:25Z tchemit $
006 * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/AbstractAddThirdPartyMojo.java $
007 * %%
008 * Copyright (C) 2008 - 2011 CodeLutin, Codehaus, Tony Chemit
009 * %%
010 * This program is free software: you can redistribute it and/or modify
011 * it under the terms of the GNU Lesser General Public License as
012 * published by the Free Software Foundation, either version 3 of the
013 * License, or (at your option) any later version.
014 *
015 * This program is distributed in the hope that it will be useful,
016 * but WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
018 * GNU General Lesser Public License for more details.
019 *
020 * You should have received a copy of the GNU General Lesser Public
021 * License along with this program. If not, see
022 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
023 * #L%
024 */
025
026 package org.codehaus.mojo.license;
027
028 import java.io.File;
029 import java.io.IOException;
030 import java.io.InputStream;
031 import java.io.OutputStream;
032 import java.util.ArrayList;
033 import java.util.Arrays;
034 import java.util.Collections;
035 import java.util.HashMap;
036 import java.util.List;
037 import java.util.Map;
038 import java.util.SortedMap;
039 import java.util.SortedSet;
040 import java.util.TreeMap;
041
042 import org.apache.commons.collections.CollectionUtils;
043 import org.apache.commons.io.FileUtils;
044 import org.apache.commons.io.IOUtils;
045 import org.apache.commons.lang.StringUtils;
046 import org.apache.maven.plugin.MojoFailureException;
047 import org.apache.maven.plugin.logging.Log;
048 import org.apache.maven.project.MavenProject;
049 import org.apache.maven.project.ProjectBuildingException;
050 import org.codehaus.mojo.license.model.LicenseMap;
051 import org.springframework.core.io.DefaultResourceLoader;
052 import org.springframework.core.io.Resource;
053 import org.springframework.core.io.ResourceLoader;
054
055 /**
056 * Abstract mojo for all third-party mojos.
057 *
058 * @author tchemit <chemit@codelutin.com>
059 * @since 1.0
060 */
061 public abstract class AbstractAddThirdPartyMojo extends AbstractLicenseMojo {
062
063 /**
064 * Directory where to generate files.
065 *
066 * @parameter expression="${license.outputDirectory}"
067 * default-value="${project.build.directory}/generated-sources/license"
068 * @required
069 * @since 1.0
070 */
071 protected File outputDirectory;
072
073 /**
074 * File where to wirte the third-party file.
075 *
076 * @parameter expression="${license.thirdPartyFilename}" default-value="THIRD-PARTY.txt"
077 * @required
078 * @since 1.0
079 */
080 protected String thirdPartyFilename;
081
082 /**
083 * A flag to use the missing licenses file to consolidate the THID-PARTY file.
084 *
085 * @parameter expression="${license.useMissingFile}" default-value="false"
086 * @since 1.0
087 */
088 protected boolean useMissingFile;
089
090 /**
091 * The file where to fill the license for dependencies with unknown license.
092 *
093 * @parameter expression="${license.missingFile}" default-value="src/license/THIRD-PARTY.properties"
094 * @since 1.0
095 */
096 protected File missingFile;
097
098 /**
099 * 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 * <licenseMerges>
118 * <licenseMerge>The Apache Software License|Version 2.0,Apache License, Version 2.0</licenseMerge>
119 * </licenseMerges>
120 * </pre>
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 }