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