001 /** 002 * Copyright 2010-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.codehaus.mojo.license; 017 018 import java.io.File; 019 import java.io.FileInputStream; 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.io.OutputStream; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.Collections; 026 import java.util.HashMap; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.SortedMap; 030 import java.util.SortedSet; 031 import java.util.TreeMap; 032 033 import org.apache.commons.collections.CollectionUtils; 034 import org.apache.commons.io.FileUtils; 035 import org.apache.commons.io.IOUtils; 036 import org.apache.commons.lang.StringUtils; 037 import org.apache.maven.plugin.MojoFailureException; 038 import org.apache.maven.plugin.logging.Log; 039 import org.apache.maven.project.MavenProject; 040 import org.apache.maven.project.ProjectBuildingException; 041 import org.codehaus.mojo.license.model.LicenseMap; 042 import org.springframework.core.io.DefaultResourceLoader; 043 import org.springframework.core.io.Resource; 044 import org.springframework.core.io.ResourceLoader; 045 046 /** 047 * Abstract mojo for all third-party mojos. 048 * 049 * @author tchemit <chemit@codelutin.com> 050 * @since 1.0 051 */ 052 public abstract class AbstractAddThirdPartyMojo extends AbstractLicenseMojo { 053 054 /** 055 * Directory where to generate files. 056 * 057 * @parameter expression="${license.outputDirectory}" 058 * default-value="${project.build.directory}/generated-sources/license" 059 * @required 060 * @since 1.0 061 */ 062 protected File outputDirectory; 063 064 /** 065 * File where license information for third party dependencies gets stored 066 * 067 * @parameter expression="${license.thirdPartyFilename}" default-value="THIRD-PARTY.txt" 068 * @required 069 * @since 1.0 070 */ 071 protected String thirdPartyFilename; 072 073 /** 074 * A flag to use the missing licenses file to consolidate the THID-PARTY file. 075 * 076 * @parameter expression="${license.useMissingFile}" default-value="false" 077 * @since 1.0 078 */ 079 protected boolean useMissingFile; 080 081 /** 082 * The file where to fill the license for dependencies with unknown license. 083 * 084 * @parameter expression="${license.missingFile}" default-value="src/license/THIRD-PARTY.properties" 085 * @since 1.0 086 */ 087 protected File missingFile; 088 089 /** 090 * Location of a properties file mapping artifacts without a license to the license that should be used for them. 091 * This supports classpath notation and any other type of URL Spring 3.1 resource loading can understand. 092 * 093 * @parameter expression="${license.artifactLicenseMapping}" default-value="classpath:THIRD-PARTY.properties" 094 * @since 1.0 095 */ 096 protected String artifactLicenseMapping; 097 098 /** 099 * 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 * <licenseMerges> 108 * <licenseMerge>The Apache Software License|Version 2.0,Apache License, Version 2.0</licenseMerge> 109 * </licenseMerges> 110 * </pre> 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 }