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 }