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