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.IOException; 020 import java.io.InputStream; 021 import java.io.OutputStream; 022 import java.util.ArrayList; 023 import java.util.Arrays; 024 import java.util.Collections; 025 import java.util.HashMap; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.SortedMap; 029 import java.util.SortedSet; 030 import java.util.TreeMap; 031 032 import org.apache.commons.collections.CollectionUtils; 033 import org.apache.commons.io.FileUtils; 034 import org.apache.commons.io.IOUtils; 035 import org.apache.commons.lang.StringUtils; 036 import org.apache.maven.plugin.MojoFailureException; 037 import org.apache.maven.plugin.logging.Log; 038 import org.apache.maven.project.MavenProject; 039 import org.apache.maven.project.ProjectBuildingException; 040 import org.codehaus.mojo.license.model.LicenseMap; 041 import org.springframework.core.io.DefaultResourceLoader; 042 import org.springframework.core.io.Resource; 043 import org.springframework.core.io.ResourceLoader; 044 045 /** 046 * Abstract mojo for all third-party mojos. 047 * 048 * @author tchemit <chemit@codelutin.com> 049 * @since 1.0 050 */ 051 public abstract class AbstractAddThirdPartyMojo extends AbstractLicenseMojo { 052 053 /** 054 * Directory where to generate files. 055 * 056 * @parameter expression="${license.outputDirectory}" 057 * default-value="${project.build.directory}/generated-sources/license" 058 * @required 059 * @since 1.0 060 */ 061 protected File outputDirectory; 062 063 /** 064 * File where to wirte the third-party file. 065 * 066 * @parameter expression="${license.thirdPartyFilename}" default-value="THIRD-PARTY.txt" 067 * @required 068 * @since 1.0 069 */ 070 protected String thirdPartyFilename; 071 072 /** 073 * A flag to use the missing licenses file to consolidate the THID-PARTY file. 074 * 075 * @parameter expression="${license.useMissingFile}" default-value="false" 076 * @since 1.0 077 */ 078 protected boolean useMissingFile; 079 080 /** 081 * The file where to fill the license for dependencies with unknown license. 082 * 083 * @parameter expression="${license.missingFile}" default-value="src/license/THIRD-PARTY.properties" 084 * @since 1.0 085 */ 086 protected File missingFile; 087 088 /** 089 * Location of a properties file mapping artifacts that are published with no license to the license that should be 090 * used for them. This supports classpath notation and any other type of URL Spring 3.1 resource loading can 091 * understand. 092 * 093 * @parameter expression="${license.artifactLicenseMapping}" default-value="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 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.debug(" - " + 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 432 // Don't print the license if it isn't being used 433 if (projects == null || projects.size() == 0) { 434 continue; 435 } 436 437 sb.append("\n\n").append(licenseName).append(" : "); 438 439 for (MavenProject mavenProject : projects) { 440 String s = MojoHelper.getArtifactName(mavenProject); 441 sb.append("\n * ").append(s); 442 } 443 } 444 445 } else { 446 447 // group by dependencies 448 SortedMap<MavenProject, String[]> map = licenseMap.toDependencyMap(); 449 450 sb.append("List of ").append(map.size()).append(" third-party dependencies.\n"); 451 452 List<String> lines = new ArrayList<String>(); 453 454 for (Map.Entry<MavenProject, String[]> entry : map.entrySet()) { 455 String artifact = MojoHelper.getArtifactName(entry.getKey()); 456 StringBuilder buffer = new StringBuilder(); 457 for (String license : entry.getValue()) { 458 buffer.append(" (").append(license).append(")"); 459 } 460 String licenses = buffer.toString(); 461 String line = licenses + " " + artifact; 462 lines.add(line); 463 } 464 465 Collections.sort(lines); 466 for (String line : lines) { 467 sb.append('\n').append(line); 468 } 469 lines.clear(); 470 } 471 } 472 String content = sb.toString(); 473 474 log.info("Writing third-party file to " + target); 475 if (isVerbose()) { 476 log.info(content); 477 } 478 479 FileUtil.writeString(target, content, getEncoding()); 480 } 481 482 if (isDoGenerateBundle()) { 483 484 // creates the bundled license file 485 File bundleTarget = FileUtil.getFile(getOutputDirectory(), getBundleThirdPartyPath()); 486 log.info("Writing bundled third-party file to " + bundleTarget); 487 FileUtil.copyFile(target, bundleTarget); 488 } 489 } 490 491 public boolean isGroupByLicense() { 492 return groupByLicense; 493 } 494 495 public void setGroupByLicense(boolean groupByLicense) { 496 this.groupByLicense = groupByLicense; 497 } 498 499 public File getOutputDirectory() { 500 return outputDirectory; 501 } 502 503 public String getThirdPartyFilename() { 504 return thirdPartyFilename; 505 } 506 507 public String getBundleThirdPartyPath() { 508 return bundleThirdPartyPath; 509 } 510 511 public boolean isGenerateBundle() { 512 return generateBundle; 513 } 514 515 public boolean isFailIfWarning() { 516 return failIfWarning; 517 } 518 519 public SortedMap<String, MavenProject> getProjectDependencies() { 520 return projectDependencies; 521 } 522 523 public SortedSet<MavenProject> getUnsafeDependencies() { 524 return unsafeDependencies; 525 } 526 527 public void setUnsafeDependencies(SortedSet<MavenProject> unsafeDependencies) { 528 this.unsafeDependencies = unsafeDependencies; 529 } 530 531 public File getThirdPartyFile() { 532 return thirdPartyFile; 533 } 534 535 public LicenseMap getLicenseMap() { 536 return licenseMap; 537 } 538 539 public void setOutputDirectory(File outputDirectory) { 540 this.outputDirectory = outputDirectory; 541 } 542 543 public void setThirdPartyFilename(String thirdPartyFilename) { 544 this.thirdPartyFilename = thirdPartyFilename; 545 } 546 547 public void setBundleThirdPartyPath(String bundleThirdPartyPath) { 548 this.bundleThirdPartyPath = bundleThirdPartyPath; 549 } 550 551 public void setGenerateBundle(boolean generateBundle) { 552 this.generateBundle = generateBundle; 553 } 554 555 public void setThirdPartyFile(File thirdPartyFile) { 556 this.thirdPartyFile = thirdPartyFile; 557 } 558 559 public boolean isUseMissingFile() { 560 return useMissingFile; 561 } 562 563 public File getMissingFile() { 564 return missingFile; 565 } 566 567 public void setUseMissingFile(boolean useMissingFile) { 568 this.useMissingFile = useMissingFile; 569 } 570 571 public void setMissingFile(File missingFile) { 572 this.missingFile = missingFile; 573 } 574 575 public void setFailIfWarning(boolean failIfWarning) { 576 this.failIfWarning = failIfWarning; 577 } 578 579 public SortedProperties getUnsafeMappings() { 580 return unsafeMappings; 581 } 582 583 public boolean isForce() { 584 return force; 585 } 586 587 public boolean isDoGenerate() { 588 return doGenerate; 589 } 590 591 public void setForce(boolean force) { 592 this.force = force; 593 } 594 595 public void setDoGenerate(boolean doGenerate) { 596 this.doGenerate = doGenerate; 597 } 598 599 public boolean isDoGenerateBundle() { 600 return doGenerateBundle; 601 } 602 603 public void setDoGenerateBundle(boolean doGenerateBundle) { 604 this.doGenerateBundle = doGenerateBundle; 605 } 606 607 public List<String> getExcludedScopes() { 608 String[] split = excludedScopes == null ? new String[0] : excludedScopes.split(","); 609 return Arrays.asList(split); 610 } 611 612 public void setExcludedScopes(String excludedScopes) { 613 this.excludedScopes = excludedScopes; 614 } 615 616 public List<String> getIncludedScopes() { 617 String[] split = includedScopes == null ? new String[0] : includedScopes.split(","); 618 return Arrays.asList(split); 619 } 620 621 public void setIncludedScopes(String includedScopes) { 622 this.includedScopes = includedScopes; 623 } 624 625 public String getExcludedGroups() { 626 return excludedGroups; 627 } 628 629 public void setExcludedGroups(String excludedGroups) { 630 this.excludedGroups = excludedGroups; 631 } 632 633 public String getIncludedGroups() { 634 return includedGroups; 635 } 636 637 public void setIncludedGroups(String includedGroups) { 638 this.includedGroups = includedGroups; 639 } 640 641 public String getExcludedArtifacts() { 642 return excludedArtifacts; 643 } 644 645 public void setExcludedArtifacts(String excludedArtifacts) { 646 this.excludedArtifacts = excludedArtifacts; 647 } 648 649 public String getIncludedArtifacts() { 650 return includedArtifacts; 651 } 652 653 public void setIncludedArtifacts(String includedArtifacts) { 654 this.includedArtifacts = includedArtifacts; 655 } 656 657 public ThirdPartyTool getThridPartyTool() { 658 return thridPartyTool; 659 } 660 661 public void setThridPartyTool(ThirdPartyTool thridPartyTool) { 662 this.thridPartyTool = thridPartyTool; 663 } 664 665 public String getArtifactLicenseMapping() { 666 return artifactLicenseMapping; 667 } 668 669 public void setArtifactLicenseMapping(String artifactLicenseMapping) { 670 this.artifactLicenseMapping = artifactLicenseMapping; 671 } 672 }