/*
* #%L
* License Maven Plugin
*
* $Id$
* $HeadURL$
* %%
* Copyright (C) 2010 - 2011 CodeLutin, Codehaus, Tony Chemit
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.codehaus.mojo.license;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.model.License;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.codehaus.mojo.license.model.ProjectLicenseInfo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
/**
* Download the license files of all the current project's dependencies, and generate
* a summary file containing a list of all dependencies and their licenses.
*
* @author Paul Gier
* @version $Revision$
* @phase package
* @goal download-licenses
* @requiresDependencyResolution test
* @since 1.0
*/
public class DownloadLicensesMojo
extends AbstractMojo
implements MavenProjectDependenciesLoader
{
/**
* The Maven Project Object
*
* @parameter default-value="${project}"
* @readonly
* @since 1.0
*/
private MavenProject project;
/**
* Used to build a maven projects from artifacts in the remote repository.
*
* @component role="org.apache.maven.project.MavenProjectBuilder"
* @readonly
* @since 1.0
*/
private MavenProjectBuilder projectBuilder;
/**
* Location of the local repository.
*
* @parameter default-value="${localRepository}"
* @readonly
* @since 1.0
*/
private ArtifactRepository localRepository;
/**
* List of Remote Repositories used by the resolver
*
* @parameter default-value="${project.remoteArtifactRepositories}"
* @readonly
* @since 1.0
*/
private List remoteRepositories;
/**
* Input file containing a mapping between each dependency and it's license information.
*
* @parameter default-value="${project.basedir}/src/license/licenses.xml"
* @since 1.0
*/
private File licensesConfigFile;
/**
* The directory to which the dependency licenses should be written.
*
* @parameter default-value="${project.build.directory}/generated-resources/licenses"
* @since 1.0
*/
private File licensesOutputDirectory;
/**
* The output file containing a mapping between each dependency and it's license information.
*
* @parameter default-value="${project.build.directory}/generated-resources/licenses.xml"
* @since 1.0
*/
private File licensesOutputFile;
/**
* Don't show warnings about bad or missing license files.
*
* @parameter default-value="false"
* @since 1.0
*/
private boolean quiet;
/**
* Include transitive dependencies when downloading license files.
*
* @parameter default-value="true"
* @since 1.0
*/
private boolean includeTransitiveDependencies;
/**
* Keeps a collection of the URLs of the licenses that have been downlaoded. This helps the plugin to avoid
* downloading the same license multiple times.
*/
private Set<String> downloadedLicenseURLs = new HashSet<String>();
/**
* Main Maven plugin execution
*/
public void execute()
throws MojoExecutionException
{
initDirectories();
Map<String, ProjectLicenseInfo> configuredDepLicensesMap = new HashMap<String, ProjectLicenseInfo>();
// License info from previous build
loadLicenseInfo( configuredDepLicensesMap, licensesOutputFile, true );
// Manually configured license info, loaded second to override previously loaded info
loadLicenseInfo( configuredDepLicensesMap, licensesConfigFile, false );
SortedMap<String, MavenProject> dependencies = ArtifactHelper.loadProjectDependencies( this, getLog(), null );
// The resulting list of licenses after dependency resolution
List<ProjectLicenseInfo> depProjectLicenses = new ArrayList<ProjectLicenseInfo>();
for ( MavenProject project : dependencies.values() )
{
Artifact artifact = project.getArtifact();
getLog().debug( "Checking licenses for project " + artifact );
String artifactProjectId = getArtifactProjectId( artifact );
ProjectLicenseInfo depProject = null;
if ( configuredDepLicensesMap.containsKey( artifactProjectId ) )
{
depProject = configuredDepLicensesMap.get( artifactProjectId );
depProject.setVersion( artifact.getVersion() );
}
else
{
try
{
depProject = createDependencyProject( project );
}
catch ( ProjectBuildingException e )
{
getLog().warn( "Unable to build project: " + artifact );
getLog().warn( e );
}
}
downloadLicenses( depProject );
depProjectLicenses.add( depProject );
}
try
{
LicenseSummaryWriter.writeLicenseSummary( depProjectLicenses, licensesOutputFile );
}
catch ( Exception e )
{
throw new MojoExecutionException( "Unable to write license summary file.", e );
}
}
private void initDirectories()
throws MojoExecutionException
{
try
{
FileUtil.createDirectoryIfNecessary( licensesOutputDirectory );
FileUtil.createDirectoryIfNecessary( licensesOutputFile.getParentFile() );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to create a directory...", e );
}
}
/**
* Load the license information contained in a file if it exists. Will overwrite existing license information in the
* map for dependencies with the same id. If the config file does not exist, the method does nothing.
*
* @param configuredDepLicensesMap A map between the dependencyId and the license info
* @param licenseConfigFile The license configuration file to load
* @param previouslyDownloaded Whether these licenses were already downloaded
* @throws MojoExecutionException
*/
private void loadLicenseInfo( Map<String, ProjectLicenseInfo> configuredDepLicensesMap, File licenseConfigFile,
boolean previouslyDownloaded )
throws MojoExecutionException
{
// Check if we have already downloaded the licenses in a previous build
if ( licenseConfigFile.exists() )
{
FileInputStream fis = null;
try
{
fis = new FileInputStream( licenseConfigFile );
List<ProjectLicenseInfo> licensesList = LicenseSummaryReader.parseLicenseSummary( fis );
for ( ProjectLicenseInfo dep : licensesList )
{
configuredDepLicensesMap.put( dep.getId(), dep );
if ( previouslyDownloaded )
{
for ( License license : dep.getLicenses() )
{
downloadedLicenseURLs.add( license.getUrl() );
}
}
}
}
catch ( Exception e )
{
throw new MojoExecutionException( "Unable to parse license summary output file.", e );
}
finally
{
FileUtil.tryClose( fis );
}
}
}
/**
* Returns the project ID for the artifact
*
* @param artifact
* @return groupId:artifactId
*/
public String getArtifactProjectId( Artifact artifact )
{
return artifact.getGroupId() + ":" + artifact.getArtifactId();
}
// /**
// * Create a simple DependencyProject object containing the GAV and license info from the Maven Artifact
// *
// * @param artifact
// * @return DependencyProject with artifact and license info
// * @throws ProjectBuildingException
// */
// public ProjectLicenseInfo createDependencyProject( Artifact artifact )
// throws ProjectBuildingException
// {
// MavenProject depMavenProject =
// projectBuilder.buildFromRepository( artifact, remoteRepositories, localRepository );
//
// ProjectLicenseInfo dependencyProject =
// new ProjectLicenseInfo( depMavenProject.getGroupId(), depMavenProject.getArtifactId(),
// depMavenProject.getVersion() );
// List<License> licenses = depMavenProject.getLicenses();
// for ( License license : licenses )
// {
// dependencyProject.addLicense( license );
// }
// return dependencyProject;
// }
/**
* Create a simple DependencyProject object containing the GAV and license info from the Maven Artifact
*
* @param depMavenProject
* @return DependencyProject with artifact and license info
* @throws ProjectBuildingException
*/
public ProjectLicenseInfo createDependencyProject( MavenProject depMavenProject )
throws ProjectBuildingException
{
ProjectLicenseInfo dependencyProject =
new ProjectLicenseInfo( depMavenProject.getGroupId(), depMavenProject.getArtifactId(),
depMavenProject.getVersion() );
List<?> licenses = depMavenProject.getLicenses();
for ( Object license : licenses )
{
dependencyProject.addLicense( (License) license );
}
return dependencyProject;
}
/**
* Determine filename to use for downloaded license file. The file name is based on the configured name of the
* license (if available) and the remote filename of the license.
*
* @param license
* @return A filename to be used for the downloaded license file
* @throws MalformedURLException
*/
private String getLicenseFileName( License license )
throws MalformedURLException
{
URL licenseUrl = new URL( license.getUrl() );
File licenseUrlFile = new File( licenseUrl.getPath() );
String licenseFileName = licenseUrlFile.getName();
if ( license.getName() != null )
{
licenseFileName = license.getName() + " - " + licenseUrlFile.getName();
}
// Check if the file has a valid file extention
final String DEFAULT_EXTENSION = ".txt";
int extensionIndex = licenseFileName.lastIndexOf( "." );
if ( extensionIndex == -1 || extensionIndex > ( licenseFileName.length() - 3 ) )
{
// This means it isn't a valid file extension, so append the default
licenseFileName = licenseFileName + DEFAULT_EXTENSION;
}
// Force lower case so we don't end up with multiple copies of the same license
licenseFileName = licenseFileName.toLowerCase();
return licenseFileName;
}
/**
* Download the licenses associated with this project
*
* @param depProject The project which generated the dependency
*/
private void downloadLicenses( ProjectLicenseInfo depProject )
{
getLog().debug( "Downloading license(s) for project " + depProject );
List<License> licenses = depProject.getLicenses();
if ( depProject.getLicenses() == null || depProject.getLicenses().isEmpty() )
{
if ( !quiet )
{
getLog().warn( "No license information available for: " + depProject );
}
return;
}
for ( License license : licenses )
{
try
{
String licenseFileName = getLicenseFileName( license );
File licenseOutputFile = new File( licensesOutputDirectory, licenseFileName );
if ( licenseOutputFile.exists() )
{
continue;
}
if ( !downloadedLicenseURLs.contains( license.getUrl() ) )
{
LicenseDownloader.downloadLicense( license.getUrl(), licenseOutputFile );
downloadedLicenseURLs.add( license.getUrl() );
}
}
catch ( MalformedURLException e )
{
if ( !quiet )
{
getLog().warn( "POM for dependency " + depProject.toString() + " has an invalid license URL: " +
license.getUrl() );
}
}
catch ( FileNotFoundException e )
{
if ( !quiet )
{
getLog().warn( "POM for dependency " + depProject.toString() +
" has a license URL that returns file not found: " + license.getUrl() );
}
}
catch ( IOException e )
{
getLog().warn( "Unable to retrieve license for dependency: " + depProject.toString() );
getLog().warn( license.getUrl() );
getLog().warn( e.getMessage() );
}
}
}
public MavenProject getProject()
{
return project;
}
public MavenProjectBuilder getMavenProjectBuilder()
{
return projectBuilder;
}
public ArtifactRepository getLocalRepository()
{
return localRepository;
}
public List getRemoteRepositories()
{
return remoteRepositories;
}
public boolean isIncludeTransitiveDependencies()
{
return includeTransitiveDependencies;
}
// not used at the moment
public List<String> getExcludeScopes()
{
return Collections.emptyList();
}
// not used at the moment
public String getIncludedArtifacts()
{
return null;
}
// not used at the moment
public String getIncludedGroups()
{
return null;
}
// not used at the moment
public String getExcludedGroups()
{
return null;
}
// not used at the moment
public String getExcludedArtifacts()
{
return null;
}
public boolean isVerbose()
{
return !quiet;
}
}