View Javadoc
1   package org.apache.maven.report.projectinfo.dependencies.renderer;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
31  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
32  import org.apache.maven.artifact.versioning.ArtifactVersion;
33  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
34  import org.apache.maven.artifact.versioning.VersionRange;
35  import org.apache.maven.doxia.sink.Sink;
36  import org.apache.maven.model.Dependency;
37  import org.apache.maven.model.License;
38  import org.apache.maven.plugin.logging.Log;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.project.ProjectBuilder;
41  import org.apache.maven.project.ProjectBuildingException;
42  import org.apache.maven.project.ProjectBuildingRequest;
43  import org.apache.maven.report.projectinfo.AbstractProjectInfoRenderer;
44  import org.apache.maven.report.projectinfo.LicenseMapping;
45  import org.apache.maven.report.projectinfo.ProjectInfoReportUtils;
46  import org.apache.maven.report.projectinfo.dependencies.ManagementDependencies;
47  import org.apache.maven.report.projectinfo.dependencies.RepositoryUtils;
48  import org.apache.maven.repository.RepositorySystem;
49  import org.codehaus.plexus.i18n.I18N;
50  import org.codehaus.plexus.util.StringUtils;
51  
52  /**
53   * @author Nick Stolwijk
54   * @since 2.1
55   */
56  public class DependencyManagementRenderer
57      extends AbstractProjectInfoRenderer
58  {
59      private final ManagementDependencies dependencies;
60  
61      private final Log log;
62  
63      private final ArtifactMetadataSource artifactMetadataSource;
64  
65      private final RepositorySystem repositorySystem;
66  
67      private final ProjectBuilder projectBuilder;
68  
69      private final ProjectBuildingRequest buildingRequest;
70  
71      private final RepositoryUtils repoUtils;
72  
73      private final Map<String, String> licenseMappings;
74  
75      /**
76       * Default constructor
77       *
78       * @param sink {@link Sink}
79       * @param locale {@link Locale}
80       * @param i18n {@link I18N}
81       * @param log {@link Log}
82       * @param dependencies {@link ManagementDependencies}
83       * @param artifactMetadataSource {@link ArtifactMetadataSource}
84       * @param repositorySystem {@link RepositorySystem}
85       * @param projectBuilder {@link ProjectBuilder}
86       * @param buildingRequest {@link ProjectBuildingRequest}
87       * @param repoUtils {@link RepositoryUtils}
88       * @param licenseMappings {@link LicenseMapping}
89       */
90      public DependencyManagementRenderer( Sink sink, Locale locale, I18N i18n, Log log,
91                                           ManagementDependencies dependencies,
92                                           ArtifactMetadataSource artifactMetadataSource,
93                                           RepositorySystem repositorySystem, ProjectBuilder projectBuilder,
94                                           ProjectBuildingRequest buildingRequest, RepositoryUtils repoUtils,
95                                           Map<String, String> licenseMappings )
96      {
97          super( sink, i18n, locale );
98  
99          this.log = log;
100         this.dependencies = dependencies;
101         this.artifactMetadataSource = artifactMetadataSource;
102         this.repositorySystem = repositorySystem;
103         this.projectBuilder = projectBuilder;
104         this.buildingRequest = buildingRequest;
105         this.repoUtils = repoUtils;
106         this.licenseMappings = licenseMappings;
107     }
108 
109     // ----------------------------------------------------------------------
110     // Public methods
111     // ----------------------------------------------------------------------
112 
113     @Override
114     protected String getI18Nsection()
115     {
116         return "dependency-management";
117     }
118 
119     @Override
120     public void renderBody()
121     {
122         // Dependencies report
123 
124         if ( !dependencies.hasDependencies() )
125         {
126             startSection( getTitle() );
127 
128             paragraph( getI18nString( "nolist" ) );
129 
130             endSection();
131 
132             return;
133         }
134 
135         // === Section: Project Dependencies.
136         renderSectionProjectDependencies();
137     }
138 
139     // ----------------------------------------------------------------------
140     // Private methods
141     // ----------------------------------------------------------------------
142 
143     private void renderSectionProjectDependencies()
144     {
145         startSection( getTitle() );
146 
147         // collect dependencies by scope
148         Map<String, List<Dependency>> dependenciesByScope = dependencies.getManagementDependenciesByScope();
149 
150         renderDependenciesForAllScopes( dependenciesByScope );
151 
152         endSection();
153     }
154 
155     private void renderDependenciesForAllScopes( Map<String, List<Dependency>> dependenciesByScope )
156     {
157         renderDependenciesForScope( Artifact.SCOPE_COMPILE, dependenciesByScope.get( Artifact.SCOPE_COMPILE ) );
158         renderDependenciesForScope( Artifact.SCOPE_RUNTIME, dependenciesByScope.get( Artifact.SCOPE_RUNTIME ) );
159         renderDependenciesForScope( Artifact.SCOPE_TEST, dependenciesByScope.get( Artifact.SCOPE_TEST ) );
160         renderDependenciesForScope( Artifact.SCOPE_PROVIDED, dependenciesByScope.get( Artifact.SCOPE_PROVIDED ) );
161         renderDependenciesForScope( Artifact.SCOPE_SYSTEM, dependenciesByScope.get( Artifact.SCOPE_SYSTEM ) );
162     }
163 
164     private String[] getDependencyTableHeader( boolean hasClassifier )
165     {
166         String groupId = getI18nString( "column.groupId" );
167         String artifactId = getI18nString( "column.artifactId" );
168         String version = getI18nString( "column.version" );
169         String classifier = getI18nString( "column.classifier" );
170         String type = getI18nString( "column.type" );
171         String license = getI18nString( "column.license" );
172 
173         if ( hasClassifier )
174         {
175             return new String[] { groupId, artifactId, version, classifier, type, license };
176         }
177 
178         return new String[] { groupId, artifactId, version, type, license };
179     }
180 
181     private void renderDependenciesForScope( String scope, List<Dependency> artifacts )
182     {
183         if ( artifacts != null )
184         {
185             // can't use straight artifact comparison because we want optional last
186             Collections.sort( artifacts, getDependencyComparator() );
187 
188             startSection( scope );
189 
190             paragraph( getI18nString( "intro." + scope ) );
191             startTable();
192 
193             boolean hasClassifier = false;
194             for ( Dependency dependency : artifacts )
195             {
196                 if ( StringUtils.isNotEmpty( dependency.getClassifier() ) )
197                 {
198                     hasClassifier = true;
199                     break;
200                 }
201             }
202 
203             String[] tableHeader = getDependencyTableHeader( hasClassifier );
204             tableHeader( tableHeader );
205 
206             for ( Dependency dependency : artifacts )
207             {
208                 tableRow( getDependencyRow( dependency, hasClassifier ) );
209             }
210             endTable();
211 
212             endSection();
213         }
214     }
215 
216     @SuppressWarnings( "unchecked" )
217     private String[] getDependencyRow( Dependency dependency, boolean hasClassifier )
218     {
219         Artifact artifact =
220             repositorySystem.createArtifact( dependency.getGroupId(), dependency.getArtifactId(),
221                                              dependency.getVersion(), dependency.getScope(), dependency.getType() );
222 
223         StringBuilder licensesBuffer = new StringBuilder();
224         String url = null;
225         try
226         {
227             VersionRange range = VersionRange.createFromVersionSpec( dependency.getVersion() );
228 
229             if ( range.getRecommendedVersion() == null )
230             {
231                 // MPIR-216: no direct version but version range: need to choose one precise version
232                 log.debug( "Resolving range for DependencyManagement on " + artifact.getId() );
233 
234                 List<ArtifactVersion> versions =
235                     artifactMetadataSource.retrieveAvailableVersions( artifact, buildingRequest.getLocalRepository(),
236                                                                       buildingRequest.getRemoteRepositories() );
237 
238                 // only use versions from range
239                 for ( Iterator<ArtifactVersion> iter = versions.iterator(); iter.hasNext(); )
240                 {
241                     if ( ! range.containsVersion( iter.next() ) )
242                     {
243                         iter.remove();
244                     }
245                 }
246 
247                 // select latest, assuming pom information will be the most accurate
248                 if ( !versions.isEmpty() )
249                 {
250                     ArtifactVersion maxArtifactVersion = Collections.max( versions );
251 
252                     artifact.setVersion( maxArtifactVersion.toString() );
253                     log.debug( "DependencyManagement resolved: " + artifact.getId() );
254                 }
255             }
256 
257             url = ProjectInfoReportUtils.getArtifactUrl( repositorySystem, artifact, projectBuilder, buildingRequest );
258 
259             MavenProject artifactProject = repoUtils.getMavenProjectFromRepository( artifact );
260 
261             List<License> licenses = artifactProject.getLicenses();
262             for ( License license : licenses )
263             {
264                 String name = license.getName();
265                 if ( licenseMappings != null && licenseMappings.containsKey( name ) )
266                 {
267                     name = licenseMappings.get( name );
268                 }
269                 String licenseCell = ProjectInfoReportUtils.getArtifactIdCell( name, license.getUrl() );
270                 if ( licensesBuffer.length() > 0 )
271                 {
272                     licensesBuffer.append( ", " );
273                 }
274                 licensesBuffer.append( licenseCell );
275             }
276         }
277         catch ( InvalidVersionSpecificationException e )
278         {
279             log.warn( "Unable to parse version for " + artifact.getId(), e );
280         }
281         catch ( ArtifactMetadataRetrievalException e )
282         {
283             log.warn( "Unable to retrieve versions for " + artifact.getId() + " from repository.", e );
284         }
285         catch ( ProjectBuildingException e )
286         {
287             if ( log.isDebugEnabled() )
288             {
289                 log.warn( "Unable to create Maven project for " + artifact.getId() + " from repository.", e );
290             }
291             else
292             {
293                 log.warn( "Unable to create Maven project for " + artifact.getId() + " from repository." );
294             }
295         }
296 
297         String artifactIdCell = ProjectInfoReportUtils.getArtifactIdCell( artifact.getArtifactId(), url );
298 
299         if ( hasClassifier )
300         {
301             return new String[] { dependency.getGroupId(), artifactIdCell, dependency.getVersion(),
302                 dependency.getClassifier(), dependency.getType(), licensesBuffer.toString() };
303         }
304 
305         return new String[] { dependency.getGroupId(), artifactIdCell, dependency.getVersion(),
306             dependency.getType(), licensesBuffer.toString() };
307     }
308 
309     private Comparator<Dependency> getDependencyComparator()
310     {
311         return new Comparator<Dependency>()
312         {
313             public int compare( Dependency a1, Dependency a2 )
314             {
315                 int result = a1.getGroupId().compareTo( a2.getGroupId() );
316                 if ( result != 0 )
317                 {
318                     return result;
319                 }
320 
321                 result = a1.getArtifactId().compareTo( a2.getArtifactId() );
322                 if ( result != 0 )
323                 {
324                     return result;
325                 }
326 
327                 result = a1.getType().compareTo( a2.getType() );
328                 if ( result != 0 )
329                 {
330                     return result;
331                 }
332 
333                 if ( a1.getClassifier() == null )
334                 {
335                     if ( a2.getClassifier() != null )
336                     {
337                         return 1;
338                     }
339                 }
340                 else
341                 {
342                     if ( a2.getClassifier() != null )
343                     {
344                         result = a1.getClassifier().compareTo( a2.getClassifier() );
345                     }
346                     else
347                     {
348                         return -1;
349                     }
350                 }
351 
352                 if ( result != 0 )
353                 {
354                     return result;
355                 }
356 
357                 // We don't consider the version range in the comparison, just the resolved version
358                 return a1.getVersion().compareTo( a2.getVersion() );
359             }
360         };
361     }
362 }