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