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