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