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