001 /* 002 * #%L 003 * License Maven Plugin 004 * 005 * $Id: DownloadLicensesMojo.java 14414 2011-08-10 23:10:24Z tchemit $ 006 * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/DownloadLicensesMojo.java $ 007 * %% 008 * Copyright (C) 2010 - 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 package org.codehaus.mojo.license; 026 027 import org.apache.maven.artifact.Artifact; 028 import org.apache.maven.artifact.repository.ArtifactRepository; 029 import org.apache.maven.model.License; 030 import org.apache.maven.plugin.AbstractMojo; 031 import org.apache.maven.plugin.MojoExecutionException; 032 import org.apache.maven.project.MavenProject; 033 import org.codehaus.mojo.license.model.ProjectLicenseInfo; 034 035 import java.io.File; 036 import java.io.FileInputStream; 037 import java.io.FileNotFoundException; 038 import java.io.IOException; 039 import java.net.MalformedURLException; 040 import java.net.URL; 041 import java.util.*; 042 043 /** 044 * Download the license files of all the current project's dependencies, and generate a summary file containing a list 045 * of all dependencies and their licenses. 046 * 047 * @author Paul Gier 048 * @version $Revision: 14414 $ 049 * @phase package 050 * @goal download-licenses 051 * @requiresDependencyResolution test 052 * @since 1.0 053 */ 054 public class DownloadLicensesMojo 055 extends AbstractMojo 056 implements MavenProjectDependenciesConfigurator 057 { 058 059 /** 060 * The Maven Project Object 061 * 062 * @parameter default-value="${project}" 063 * @readonly 064 * @since 1.0 065 */ 066 private MavenProject project; 067 068 /** 069 * Location of the local repository. 070 * 071 * @parameter default-value="${localRepository}" 072 * @readonly 073 * @since 1.0 074 */ 075 private ArtifactRepository localRepository; 076 077 /** 078 * List of Remote Repositories used by the resolver 079 * 080 * @parameter default-value="${project.remoteArtifactRepositories}" 081 * @readonly 082 * @since 1.0 083 */ 084 private List remoteRepositories; 085 086 /** 087 * Input file containing a mapping between each dependency and it's license information. 088 * 089 * @parameter default-value="${project.basedir}/src/license/licenses.xml" expression="${licensesConfigFile}" 090 * @since 1.0 091 */ 092 private File licensesConfigFile; 093 094 /** 095 * The directory to which the dependency licenses should be written. 096 * 097 * @parameter default-value="${project.build.directory}/generated-resources/licenses" expression="${licensesOutputDirectory} 098 * @since 1.0 099 */ 100 private File licensesOutputDirectory; 101 102 /** 103 * The output file containing a mapping between each dependency and it's license information. 104 * 105 * @parameter default-value="${project.build.directory}/generated-resources/licenses.xml" expression="${licensesOutputFile} 106 * @since 1.0 107 */ 108 private File licensesOutputFile; 109 110 /** 111 * A filter to exclude some scopes. 112 * 113 * @parameter expression="${license.excludedScopes}" default-value="system" 114 * @since 1.0 115 */ 116 private String excludedScopes; 117 118 /** 119 * A filter to include only some scopes, if let empty then all scopes will be used (no filter). 120 * 121 * @parameter expression="${license.includedScopes}" default-value="" 122 * @since 1.0 123 */ 124 private String includedScopes; 125 126 /** 127 * Settings offline flag (will not download anything if setted to true). 128 * 129 * @parameter default-value="${settings.offline}" 130 * @since 1.0 131 */ 132 private boolean offline; 133 134 /** 135 * Don't show warnings about bad or missing license files. 136 * 137 * @parameter default-value="false" 138 * @since 1.0 139 */ 140 private boolean quiet; 141 142 /** 143 * Include transitive dependencies when downloading license files. 144 * 145 * @parameter default-value="true" 146 * @since 1.0 147 */ 148 private boolean includeTransitiveDependencies; 149 150 /** 151 * dependencies tool. 152 * 153 * @component 154 * @readonly 155 * @since 1.0 156 */ 157 private DependenciesTool dependenciesTool; 158 159 /** 160 * Keeps a collection of the URLs of the licenses that have been downlaoded. This helps the plugin to avoid 161 * downloading the same license multiple times. 162 */ 163 private Set<String> downloadedLicenseURLs = new HashSet<String>(); 164 165 /** 166 * Main Maven plugin execution 167 */ 168 public void execute() 169 throws MojoExecutionException 170 { 171 172 if ( offline ) 173 { 174 175 getLog().warn( "Offline flag is on, download-licenses goal is skip." ); 176 return; 177 } 178 initDirectories(); 179 180 Map<String, ProjectLicenseInfo> configuredDepLicensesMap = new HashMap<String, ProjectLicenseInfo>(); 181 182 // License info from previous build 183 if ( licensesOutputFile.exists() ) 184 { 185 loadLicenseInfo( configuredDepLicensesMap, licensesOutputFile, true ); 186 } 187 188 // Manually configured license info, loaded second to override previously loaded info 189 if ( licensesConfigFile.exists() ) 190 { 191 loadLicenseInfo( configuredDepLicensesMap, licensesConfigFile, false ); 192 } 193 194 SortedMap<String, MavenProject> dependencies = 195 dependenciesTool.loadProjectDependencies( project, this, localRepository, remoteRepositories, null ); 196 197 // The resulting list of licenses after dependency resolution 198 List<ProjectLicenseInfo> depProjectLicenses = new ArrayList<ProjectLicenseInfo>(); 199 200 for ( MavenProject project : dependencies.values() ) 201 { 202 Artifact artifact = project.getArtifact(); 203 getLog().debug( "Checking licenses for project " + artifact ); 204 String artifactProjectId = getArtifactProjectId( artifact ); 205 ProjectLicenseInfo depProject; 206 if ( configuredDepLicensesMap.containsKey( artifactProjectId ) ) 207 { 208 depProject = configuredDepLicensesMap.get( artifactProjectId ); 209 depProject.setVersion( artifact.getVersion() ); 210 } 211 else 212 { 213 depProject = createDependencyProject( project ); 214 } 215 downloadLicenses( depProject ); 216 depProjectLicenses.add( depProject ); 217 } 218 219 try 220 { 221 LicenseSummaryWriter.writeLicenseSummary( depProjectLicenses, licensesOutputFile ); 222 } 223 catch ( Exception e ) 224 { 225 throw new MojoExecutionException( "Unable to write license summary file: " + licensesOutputFile, e ); 226 } 227 228 } 229 230 private void initDirectories() 231 throws MojoExecutionException 232 { 233 try 234 { 235 FileUtil.createDirectoryIfNecessary( licensesOutputDirectory ); 236 237 FileUtil.createDirectoryIfNecessary( licensesOutputFile.getParentFile() ); 238 } 239 catch ( IOException e ) 240 { 241 throw new MojoExecutionException( "Unable to create a directory...", e ); 242 } 243 } 244 245 /** 246 * Load the license information contained in a file if it exists. Will overwrite existing license information in the 247 * map for dependencies with the same id. If the config file does not exist, the method does nothing. 248 * 249 * @param configuredDepLicensesMap A map between the dependencyId and the license info 250 * @param licenseConfigFile The license configuration file to load 251 * @param previouslyDownloaded Whether these licenses were already downloaded 252 * @throws MojoExecutionException if could not load license infos 253 */ 254 private void loadLicenseInfo( Map<String, ProjectLicenseInfo> configuredDepLicensesMap, File licenseConfigFile, 255 boolean previouslyDownloaded ) 256 throws MojoExecutionException 257 { 258 FileInputStream fis = null; 259 try 260 { 261 fis = new FileInputStream( licenseConfigFile ); 262 List<ProjectLicenseInfo> licensesList = LicenseSummaryReader.parseLicenseSummary( fis ); 263 for ( ProjectLicenseInfo dep : licensesList ) 264 { 265 configuredDepLicensesMap.put( dep.getId(), dep ); 266 if ( previouslyDownloaded ) 267 { 268 for ( License license : dep.getLicenses() ) 269 { 270 // Save the URL so we don't download it again 271 downloadedLicenseURLs.add( license.getUrl() ); 272 } 273 } 274 } 275 } 276 catch ( Exception e ) 277 { 278 throw new MojoExecutionException( "Unable to parse license summary output file: " + licenseConfigFile, e ); 279 } 280 finally 281 { 282 FileUtil.tryClose( fis ); 283 } 284 } 285 286 /** 287 * Returns the project ID for the artifact 288 * 289 * @param artifact the artifact 290 * @return groupId:artifactId 291 */ 292 public String getArtifactProjectId( Artifact artifact ) 293 { 294 return artifact.getGroupId() + ":" + artifact.getArtifactId(); 295 } 296 297 /** 298 * Create a simple DependencyProject object containing the GAV and license info from the Maven Artifact 299 * 300 * @param depMavenProject the dependency maven project 301 * @return DependencyProject with artifact and license info 302 */ 303 public ProjectLicenseInfo createDependencyProject( MavenProject depMavenProject ) 304 { 305 ProjectLicenseInfo dependencyProject = 306 new ProjectLicenseInfo( depMavenProject.getGroupId(), depMavenProject.getArtifactId(), 307 depMavenProject.getVersion() ); 308 List<?> licenses = depMavenProject.getLicenses(); 309 for ( Object license : licenses ) 310 { 311 dependencyProject.addLicense( (License) license ); 312 } 313 return dependencyProject; 314 } 315 316 /** 317 * Determine filename to use for downloaded license file. The file name is based on the configured name of the 318 * license (if available) and the remote filename of the license. 319 * 320 * @param license the license 321 * @return A filename to be used for the downloaded license file 322 * @throws MalformedURLException if the license url is malformed 323 */ 324 private String getLicenseFileName( License license ) 325 throws MalformedURLException 326 { 327 URL licenseUrl = new URL( license.getUrl() ); 328 File licenseUrlFile = new File( licenseUrl.getPath() ); 329 String licenseFileName = licenseUrlFile.getName(); 330 331 if ( license.getName() != null ) 332 { 333 licenseFileName = license.getName() + " - " + licenseUrlFile.getName(); 334 } 335 336 // Check if the file has a valid file extention 337 final String DEFAULT_EXTENSION = ".txt"; 338 int extensionIndex = licenseFileName.lastIndexOf( "." ); 339 if ( extensionIndex == -1 || extensionIndex > ( licenseFileName.length() - 3 ) ) 340 { 341 // This means it isn't a valid file extension, so append the default 342 licenseFileName = licenseFileName + DEFAULT_EXTENSION; 343 } 344 345 // Force lower case so we don't end up with multiple copies of the same license 346 licenseFileName = licenseFileName.toLowerCase(); 347 348 return licenseFileName; 349 } 350 351 /** 352 * Download the licenses associated with this project 353 * 354 * @param depProject The project which generated the dependency 355 */ 356 private void downloadLicenses( ProjectLicenseInfo depProject ) 357 { 358 getLog().debug( "Downloading license(s) for project " + depProject ); 359 360 List<License> licenses = depProject.getLicenses(); 361 362 if ( depProject.getLicenses() == null || depProject.getLicenses().isEmpty() ) 363 { 364 if ( !quiet ) 365 { 366 getLog().warn( "No license information available for: " + depProject ); 367 } 368 return; 369 } 370 371 for ( License license : licenses ) 372 { 373 try 374 { 375 String licenseFileName = getLicenseFileName( license ); 376 377 File licenseOutputFile = new File( licensesOutputDirectory, licenseFileName ); 378 if ( licenseOutputFile.exists() ) 379 { 380 continue; 381 } 382 383 if ( !downloadedLicenseURLs.contains( license.getUrl() ) ) 384 { 385 LicenseDownloader.downloadLicense( license.getUrl(), licenseOutputFile ); 386 downloadedLicenseURLs.add( license.getUrl() ); 387 } 388 } 389 catch ( MalformedURLException e ) 390 { 391 if ( !quiet ) 392 { 393 getLog().warn( "POM for dependency " + depProject.toString() + " has an invalid license URL: " + 394 license.getUrl() ); 395 } 396 } 397 catch ( FileNotFoundException e ) 398 { 399 if ( !quiet ) 400 { 401 getLog().warn( "POM for dependency " + depProject.toString() + 402 " has a license URL that returns file not found: " + license.getUrl() ); 403 } 404 } 405 catch ( IOException e ) 406 { 407 getLog().warn( "Unable to retrieve license for dependency: " + depProject.toString() ); 408 getLog().warn( license.getUrl() ); 409 getLog().warn( e.getMessage() ); 410 } 411 412 } 413 414 } 415 416 public MavenProject getProject() 417 { 418 return project; 419 } 420 421 public ArtifactRepository getLocalRepository() 422 { 423 return localRepository; 424 } 425 426 public List getRemoteRepositories() 427 { 428 return remoteRepositories; 429 } 430 431 /** 432 * {@inheritDoc} 433 */ 434 public boolean isIncludeTransitiveDependencies() 435 { 436 return includeTransitiveDependencies; 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 public List<String> getExcludedScopes() 443 { 444 String[] split = excludedScopes == null ? new String[0] : excludedScopes.split( "," ); 445 return Arrays.asList( split ); 446 } 447 448 public void setExcludedScopes( String excludedScopes ) 449 { 450 this.excludedScopes = excludedScopes; 451 } 452 453 /** 454 * {@inheritDoc} 455 */ 456 public List<String> getIncludedScopes() 457 { 458 String[] split = includedScopes == null ? new String[0] : includedScopes.split( "," ); 459 return Arrays.asList( split ); 460 } 461 462 public void setIncludedScopes( String includedScopes ) 463 { 464 this.includedScopes = includedScopes; 465 } 466 467 // not used at the moment 468 469 /** 470 * {@inheritDoc} 471 */ 472 public String getIncludedArtifacts() 473 { 474 return null; 475 } 476 477 // not used at the moment 478 479 /** 480 * {@inheritDoc} 481 */ 482 public String getIncludedGroups() 483 { 484 return null; 485 } 486 487 // not used at the moment 488 489 /** 490 * {@inheritDoc} 491 */ 492 public String getExcludedGroups() 493 { 494 return null; 495 } 496 497 // not used at the moment 498 499 /** 500 * {@inheritDoc} 501 */ 502 public String getExcludedArtifacts() 503 { 504 return null; 505 } 506 507 /** 508 * {@inheritDoc} 509 */ 510 public boolean isVerbose() 511 { 512 return getLog().isDebugEnabled(); 513 } 514 }