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