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 }