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 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 }