View Javadoc

1   /*
2    * #%L
3    * License Maven Plugin
4    *
5    * $Id: DownloadLicensesMojo.java 14414 2011-08-10 23:10:24Z tchemit $
6    * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/DownloadLicensesMojo.java $
7    * %%
8    * Copyright (C) 2010 - 2011 CodeLutin, Codehaus, Tony Chemit
9    * %%
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as 
12   * published by the Free Software Foundation, either version 3 of the 
13   * License, or (at your option) any later version.
14   * 
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   * 
20   * You should have received a copy of the GNU General Lesser Public 
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
23   * #L%
24   */
25  package org.codehaus.mojo.license;
26  
27  import org.apache.maven.artifact.Artifact;
28  import org.apache.maven.artifact.repository.ArtifactRepository;
29  import org.apache.maven.model.License;
30  import org.apache.maven.plugin.AbstractMojo;
31  import org.apache.maven.plugin.MojoExecutionException;
32  import org.apache.maven.project.MavenProject;
33  import org.codehaus.mojo.license.model.ProjectLicenseInfo;
34  
35  import java.io.File;
36  import java.io.FileInputStream;
37  import java.io.FileNotFoundException;
38  import java.io.IOException;
39  import java.net.MalformedURLException;
40  import java.net.URL;
41  import java.util.*;
42  
43  /**
44   * Download the license files of all the current project's dependencies, and generate a summary file containing a list
45   * of all dependencies and their licenses.
46   *
47   * @author Paul Gier
48   * @version $Revision: 14414 $
49   * @phase package
50   * @goal download-licenses
51   * @requiresDependencyResolution test
52   * @since 1.0
53   */
54  public class DownloadLicensesMojo
55      extends AbstractMojo
56      implements MavenProjectDependenciesConfigurator
57  {
58  
59      /**
60       * The Maven Project Object
61       *
62       * @parameter default-value="${project}"
63       * @readonly
64       * @since 1.0
65       */
66      private MavenProject project;
67  
68      /**
69       * Location of the local repository.
70       *
71       * @parameter default-value="${localRepository}"
72       * @readonly
73       * @since 1.0
74       */
75      private ArtifactRepository localRepository;
76  
77      /**
78       * List of Remote Repositories used by the resolver
79       *
80       * @parameter default-value="${project.remoteArtifactRepositories}"
81       * @readonly
82       * @since 1.0
83       */
84      private List remoteRepositories;
85  
86      /**
87       * Input file containing a mapping between each dependency and it's license information.
88       *
89       * @parameter default-value="${project.basedir}/src/license/licenses.xml" expression="${licensesConfigFile}"
90       * @since 1.0
91       */
92      private File licensesConfigFile;
93  
94      /**
95       * The directory to which the dependency licenses should be written.
96       *
97       * @parameter default-value="${project.build.directory}/generated-resources/licenses" expression="${licensesOutputDirectory}
98       * @since 1.0
99       */
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 }