1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.report.projectinfo;
20
21 import javax.inject.Inject;
22
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Properties;
31
32 import org.apache.maven.doxia.sink.Sink;
33 import org.apache.maven.model.Contributor;
34 import org.apache.maven.model.Developer;
35 import org.apache.maven.plugins.annotations.Mojo;
36 import org.apache.maven.plugins.annotations.Parameter;
37 import org.apache.maven.project.MavenProject;
38 import org.apache.maven.project.ProjectBuilder;
39 import org.apache.maven.report.projectinfo.avatars.AvatarsProvider;
40 import org.apache.maven.reporting.MavenReportException;
41 import org.apache.maven.repository.RepositorySystem;
42 import org.codehaus.plexus.i18n.I18N;
43 import org.codehaus.plexus.util.StringUtils;
44
45
46
47
48
49
50
51 @Mojo(name = "team")
52 public class TeamReport extends AbstractProjectInfoReport {
53
54
55
56
57
58
59 @Parameter(property = "teamlist.showAvatarImages", defaultValue = "true")
60 private boolean showAvatarImages;
61
62
63
64
65
66
67
68
69
70 @Parameter(property = "teamlist.externalAvatarImages", defaultValue = "true")
71 private boolean externalAvatarImages;
72
73
74
75
76
77
78 @Parameter(property = "teamlist.avatarBaseUrl", defaultValue = "https://www.gravatar.com/avatar/")
79 private String avatarBaseUrl;
80
81
82
83
84
85
86
87
88 @Parameter(property = "teamlist.avatarProviderName", defaultValue = "gravatar")
89 private String avatarProviderName;
90
91 private final Map<String, AvatarsProvider> avatarsProviders;
92
93 @Inject
94 public TeamReport(
95 RepositorySystem repositorySystem,
96 I18N i18n,
97 ProjectBuilder projectBuilder,
98 Map<String, AvatarsProvider> avatarsProviders) {
99 super(repositorySystem, i18n, projectBuilder);
100 this.avatarsProviders = avatarsProviders;
101 }
102
103
104
105
106
107 @Override
108 public boolean canGenerateReport() throws MavenReportException {
109 boolean result = super.canGenerateReport();
110 if (result && skipEmptyReport) {
111 result = !isEmpty(getProject().getModel().getDevelopers())
112 || !isEmpty(getProject().getModel().getContributors());
113 }
114
115 return result;
116 }
117
118 @Override
119 public void executeReport(Locale locale) throws MavenReportException {
120
121 Map<Contributor, String> avatarImages = prepareAvatars();
122
123 ProjectTeamRenderer renderer =
124 new ProjectTeamRenderer(getSink(), project, getI18N(locale), locale, showAvatarImages, avatarImages);
125 renderer.render();
126 }
127
128 private Map<Contributor, String> prepareAvatars() throws MavenReportException {
129
130 if (!showAvatarImages) {
131 return Collections.emptyMap();
132 }
133
134 AvatarsProvider avatarsProvider = avatarsProviders.get(avatarProviderName);
135 if (avatarsProvider == null) {
136 throw new MavenReportException("No AvatarsProvider found for name " + avatarProviderName);
137 }
138 avatarsProvider.setBaseUrl(avatarBaseUrl);
139 avatarsProvider.setOutputDirectory(getReportOutputDirectory());
140
141 Map<Contributor, String> result = new HashMap<>();
142 try {
143 prepareContributorAvatars(result, avatarsProvider, project.getDevelopers());
144 prepareContributorAvatars(result, avatarsProvider, project.getContributors());
145 } catch (IOException e) {
146 throw new MavenReportException("Unable to load avatar images", e);
147 }
148 return result;
149 }
150
151 private void prepareContributorAvatars(
152 Map<Contributor, String> avatarImages,
153 AvatarsProvider avatarsProvider,
154 List<? extends Contributor> contributors)
155 throws IOException {
156
157 for (Contributor contributor : contributors) {
158
159 String picSource = contributor.getProperties().getProperty("picUrl");
160 if (picSource == null || picSource.isEmpty()) {
161 picSource = externalAvatarImages
162 ? avatarsProvider.getAvatarUrl(contributor.getEmail())
163 : avatarsProvider.getLocalAvatarPath(contributor.getEmail());
164 }
165
166 avatarImages.put(contributor, picSource);
167 }
168 }
169
170
171
172
173 @Override
174 public String getOutputName() {
175 return "team";
176 }
177
178 @Override
179 protected String getI18Nsection() {
180 return "team";
181 }
182
183
184
185
186
187
188
189
190 private static class ProjectTeamRenderer extends AbstractProjectInfoRenderer {
191 private static final String PROPERTIES = "properties";
192
193 private static final String TIME_ZONE = "timeZone";
194
195 private static final String ROLES = "roles";
196
197 private static final String ORGANIZATION_URL = "organizationUrl";
198
199 private static final String ORGANIZATION = "organization";
200
201 private static final String URL = "url";
202
203 private static final String EMAIL = "email";
204
205 private static final String NAME = "name";
206
207 private static final String IMAGE = "image";
208
209 private static final String ID = "id";
210
211 private final MavenProject mavenProject;
212
213 private final boolean showAvatarImages;
214
215 private final Map<Contributor, String> avatarImages;
216
217 ProjectTeamRenderer(
218 Sink sink,
219 MavenProject mavenProject,
220 I18N i18n,
221 Locale locale,
222 boolean showAvatarImages,
223 Map<Contributor, String> avatarImages) {
224 super(sink, i18n, locale);
225
226 this.mavenProject = mavenProject;
227 this.showAvatarImages = showAvatarImages;
228 this.avatarImages = avatarImages;
229 }
230
231 @Override
232 protected String getI18Nsection() {
233 return "team";
234 }
235
236 @Override
237 protected void renderBody() {
238 startSection(getI18nString("intro.title"));
239
240
241 paragraph(getI18nString("intro.description1"));
242 paragraph(getI18nString("intro.description2"));
243
244
245 List<Developer> developers = mavenProject.getDevelopers();
246
247 startSection(getI18nString("developers.title"));
248
249 if (developers.isEmpty()) {
250 paragraph(getI18nString("nodeveloper"));
251 } else {
252 paragraph(getI18nString("developers.intro"));
253
254 startTable();
255
256
257 Map<String, Boolean> headersMap = checkRequiredHeaders(developers);
258 String[] requiredHeaders = getRequiredDevHeaderArray(headersMap);
259
260 tableHeader(requiredHeaders);
261
262 for (Developer developer : developers) {
263 renderTeamMember(developer, headersMap);
264 }
265
266 endTable();
267 }
268
269 endSection();
270
271
272 List<Contributor> contributors = mavenProject.getContributors();
273
274 startSection(getI18nString("contributors.title"));
275
276 if (contributors.isEmpty()) {
277 paragraph(getI18nString("nocontributor"));
278 } else {
279 paragraph(getI18nString("contributors.intro"));
280
281 startTable();
282
283 Map<String, Boolean> headersMap = checkRequiredHeaders(contributors);
284 String[] requiredHeaders = getRequiredContrHeaderArray(headersMap);
285
286 tableHeader(requiredHeaders);
287
288 for (Contributor contributor : contributors) {
289 renderTeamMember(contributor, headersMap);
290 }
291
292 endTable();
293 }
294
295 endSection();
296
297 endSection();
298 }
299
300 private void renderTeamMember(Contributor member, Map<String, Boolean> headersMap) {
301 sink.tableRow();
302
303 if (headersMap.get(IMAGE) == Boolean.TRUE && showAvatarImages) {
304 sink.tableCell();
305 sink.figure();
306 sink.figureGraphics(avatarImages.get(member));
307 sink.figure_();
308 sink.tableCell_();
309 }
310 if (member instanceof Developer) {
311 if (headersMap.get(ID) == Boolean.TRUE) {
312 String id = ((Developer) member).getId();
313 if (id == null) {
314 tableCell(null);
315 } else {
316 tableCell("<a id=\"" + id + "\"></a>" + id, true);
317 }
318 }
319 }
320 if (headersMap.get(NAME) == Boolean.TRUE) {
321 tableCell(member.getName());
322 }
323 if (headersMap.get(EMAIL) == Boolean.TRUE) {
324 final String link = String.format("mailto:%s", member.getEmail());
325 tableCell(createLinkPatternedText(member.getEmail(), link));
326 }
327 if (headersMap.get(URL) == Boolean.TRUE) {
328 tableCellForUrl(member.getUrl());
329 }
330 if (headersMap.get(ORGANIZATION) == Boolean.TRUE) {
331 tableCell(member.getOrganization());
332 }
333 if (headersMap.get(ORGANIZATION_URL) == Boolean.TRUE) {
334 tableCellForUrl(member.getOrganizationUrl());
335 }
336 if (headersMap.get(ROLES) == Boolean.TRUE) {
337 if (member.getRoles() != null) {
338
339 List<String> var = member.getRoles();
340 tableCell(StringUtils.join(var.toArray(new String[var.size()]), ", "));
341 } else {
342 tableCell(null);
343 }
344 }
345 if (headersMap.get(TIME_ZONE) == Boolean.TRUE) {
346 tableCell(member.getTimezone());
347 }
348
349 if (headersMap.get(PROPERTIES) == Boolean.TRUE) {
350 Properties props = member.getProperties();
351 if (props != null) {
352 tableCell(propertiesToString(props));
353 } else {
354 tableCell(null);
355 }
356 }
357
358 sink.tableRow_();
359 }
360
361 private String[] getRequiredContrHeaderArray(Map<String, Boolean> requiredHeaders) {
362 List<String> requiredArray = new ArrayList<>();
363 String image = getI18nString("contributors.image");
364 String name = getI18nString("contributors.name");
365 String email = getI18nString("contributors.email");
366 String url = getI18nString("contributors.url");
367 String organization = getI18nString("contributors.organization");
368 String organizationUrl = getI18nString("contributors.organizationurl");
369 String roles = getI18nString("contributors.roles");
370 String timeZone = getI18nString("contributors.timezone");
371 String properties = getI18nString("contributors.properties");
372 if (requiredHeaders.get(IMAGE) == Boolean.TRUE && showAvatarImages) {
373 requiredArray.add(image);
374 }
375 setRequiredArray(
376 requiredHeaders,
377 requiredArray,
378 name,
379 email,
380 url,
381 organization,
382 organizationUrl,
383 roles,
384 timeZone,
385 properties);
386
387 return requiredArray.toArray(new String[requiredArray.size()]);
388 }
389
390 private String[] getRequiredDevHeaderArray(Map<String, Boolean> requiredHeaders) {
391 List<String> requiredArray = new ArrayList<>();
392
393 String image = getI18nString("developers.image");
394 String id = getI18nString("developers.id");
395 String name = getI18nString("developers.name");
396 String email = getI18nString("developers.email");
397 String url = getI18nString("developers.url");
398 String organization = getI18nString("developers.organization");
399 String organizationUrl = getI18nString("developers.organizationurl");
400 String roles = getI18nString("developers.roles");
401 String timeZone = getI18nString("developers.timezone");
402 String properties = getI18nString("developers.properties");
403
404 if (requiredHeaders.get(IMAGE) == Boolean.TRUE && showAvatarImages) {
405 requiredArray.add(image);
406 }
407 if (requiredHeaders.get(ID) == Boolean.TRUE) {
408 requiredArray.add(id);
409 }
410
411 setRequiredArray(
412 requiredHeaders,
413 requiredArray,
414 name,
415 email,
416 url,
417 organization,
418 organizationUrl,
419 roles,
420 timeZone,
421 properties);
422
423 return requiredArray.toArray(new String[0]);
424 }
425
426 private static void setRequiredArray(
427 Map<String, Boolean> requiredHeaders,
428 List<String> requiredArray,
429 String name,
430 String email,
431 String url,
432 String organization,
433 String organizationUrl,
434 String roles,
435 String timeZone,
436 String properties) {
437 if (requiredHeaders.get(NAME) == Boolean.TRUE) {
438 requiredArray.add(name);
439 }
440 if (requiredHeaders.get(EMAIL) == Boolean.TRUE) {
441 requiredArray.add(email);
442 }
443 if (requiredHeaders.get(URL) == Boolean.TRUE) {
444 requiredArray.add(url);
445 }
446 if (requiredHeaders.get(ORGANIZATION) == Boolean.TRUE) {
447 requiredArray.add(organization);
448 }
449 if (requiredHeaders.get(ORGANIZATION_URL) == Boolean.TRUE) {
450 requiredArray.add(organizationUrl);
451 }
452 if (requiredHeaders.get(ROLES) == Boolean.TRUE) {
453 requiredArray.add(roles);
454 }
455 if (requiredHeaders.get(TIME_ZONE) == Boolean.TRUE) {
456 requiredArray.add(timeZone);
457 }
458
459 if (requiredHeaders.get(PROPERTIES) == Boolean.TRUE) {
460 requiredArray.add(properties);
461 }
462 }
463
464
465
466
467
468 private static Map<String, Boolean> checkRequiredHeaders(List<? extends Contributor> units) {
469 Map<String, Boolean> requiredHeaders = new HashMap<>();
470
471 requiredHeaders.put(IMAGE, Boolean.FALSE);
472 requiredHeaders.put(ID, Boolean.FALSE);
473 requiredHeaders.put(NAME, Boolean.FALSE);
474 requiredHeaders.put(EMAIL, Boolean.FALSE);
475 requiredHeaders.put(URL, Boolean.FALSE);
476 requiredHeaders.put(ORGANIZATION, Boolean.FALSE);
477 requiredHeaders.put(ORGANIZATION_URL, Boolean.FALSE);
478 requiredHeaders.put(ROLES, Boolean.FALSE);
479 requiredHeaders.put(TIME_ZONE, Boolean.FALSE);
480 requiredHeaders.put(PROPERTIES, Boolean.FALSE);
481
482 for (Contributor unit : units) {
483 if (unit instanceof Developer) {
484 Developer developer = (Developer) unit;
485 if (StringUtils.isNotEmpty(developer.getId())) {
486 requiredHeaders.put(ID, Boolean.TRUE);
487 }
488 }
489 if (StringUtils.isNotEmpty(unit.getName())) {
490 requiredHeaders.put(NAME, Boolean.TRUE);
491 }
492 if (StringUtils.isNotEmpty(unit.getEmail())) {
493 requiredHeaders.put(EMAIL, Boolean.TRUE);
494 requiredHeaders.put(IMAGE, Boolean.TRUE);
495 }
496 if (StringUtils.isNotEmpty(unit.getUrl())) {
497 requiredHeaders.put(URL, Boolean.TRUE);
498 }
499 if (StringUtils.isNotEmpty(unit.getOrganization())) {
500 requiredHeaders.put(ORGANIZATION, Boolean.TRUE);
501 }
502 if (StringUtils.isNotEmpty(unit.getOrganizationUrl())) {
503 requiredHeaders.put(ORGANIZATION_URL, Boolean.TRUE);
504 }
505 if (!unit.getRoles().isEmpty()) {
506 requiredHeaders.put(ROLES, Boolean.TRUE);
507 }
508 if (StringUtils.isNotEmpty(unit.getTimezone())) {
509 requiredHeaders.put(TIME_ZONE, Boolean.TRUE);
510 }
511 Properties properties = unit.getProperties();
512 boolean hasPicUrl = properties.containsKey("picUrl");
513 if (hasPicUrl) {
514 requiredHeaders.put(IMAGE, Boolean.TRUE);
515 }
516 boolean isJustAnImageProperty = properties.size() == 1 && hasPicUrl;
517 if (!isJustAnImageProperty && !properties.isEmpty()) {
518 requiredHeaders.put(PROPERTIES, Boolean.TRUE);
519 }
520 }
521 return requiredHeaders;
522 }
523
524
525
526
527 private void tableCellForUrl(String url) {
528 sink.tableCell();
529
530 if (url == null || url.isEmpty()) {
531 text(url);
532 } else {
533 link(url, url);
534 }
535
536 sink.tableCell_();
537 }
538 }
539 }