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 }