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
31 import org.apache.maven.doxia.sink.Sink;
32 import org.apache.maven.model.Contributor;
33 import org.apache.maven.model.Developer;
34 import org.apache.maven.model.Model;
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 @Mojo( name = "team" )
47 public class TeamReport
48 extends AbstractProjectInfoReport
49 {
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 ProjectTeamRenderer r =
84 new ProjectTeamRenderer( getSink(), project.getModel(), getI18N( locale ), locale, showAvatarImages );
85 r.render();
86 }
87
88
89
90
91 @Override
92 public String getOutputName()
93 {
94 return "team";
95 }
96
97 @Override
98 protected String getI18Nsection()
99 {
100 return "team";
101 }
102
103
104
105
106
107
108
109
110 private static class ProjectTeamRenderer
111 extends AbstractProjectInfoRenderer
112 {
113 private static final String PROPERTIES = "properties";
114
115 private static final String TIME_ZONE = "timeZone";
116
117 private static final String ROLES = "roles";
118
119 private static final String ORGANIZATION_URL = "organizationUrl";
120
121 private static final String ORGANIZATION = "organization";
122
123 private static final String URL = "url";
124
125 private static final String EMAIL = "email";
126
127 private static final String NAME = "name";
128
129 private static final String IMAGE = "image";
130
131 private static final String ID = "id";
132
133 private final Model model;
134
135 private final boolean showAvatarImages;
136
137 private final String protocol;
138
139 ProjectTeamRenderer( Sink sink, Model model, I18N i18n, Locale locale, boolean showAvatarImages )
140 {
141 super( sink, i18n, locale );
142
143 this.model = model;
144 this.showAvatarImages = showAvatarImages;
145
146
147 if ( model.getUrl() != null && model.getUrl().startsWith( "https://" ) )
148 {
149 this.protocol = "https";
150 }
151 else
152 {
153 this.protocol = "http";
154 }
155 }
156
157 @Override
158 protected String getI18Nsection()
159 {
160 return "team";
161 }
162
163 @Override
164 public void renderBody()
165 {
166 startSection( getI18nString( "intro.title" ) );
167
168
169 paragraph( getI18nString( "intro.description1" ) );
170 paragraph( getI18nString( "intro.description2" ) );
171
172
173 List<Developer> developers = model.getDevelopers();
174
175 startSection( getI18nString( "developers.title" ) );
176
177 if ( isEmpty( developers ) )
178 {
179 paragraph( getI18nString( "nodeveloper" ) );
180 }
181 else
182 {
183 paragraph( getI18nString( "developers.intro" ) );
184
185 startTable();
186
187
188 Map<String, Boolean> headersMap = checkRequiredHeaders( developers );
189 String[] requiredHeaders = getRequiredDevHeaderArray( headersMap );
190
191 tableHeader( requiredHeaders );
192
193 for ( Developer developer : developers )
194 {
195 renderTeamMember( developer, headersMap );
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 for ( Contributor contributor : contributors )
224 {
225 renderTeamMember( contributor, headersMap );
226 }
227
228 endTable();
229 }
230
231 endSection();
232
233 endSection();
234 }
235
236 private void renderTeamMember( Contributor member, Map<String, Boolean> headersMap )
237 {
238 sink.tableRow();
239
240 if ( headersMap.get( IMAGE ) == Boolean.TRUE && showAvatarImages )
241 {
242 Properties properties = member.getProperties();
243 String picUrl = properties.getProperty( "picUrl" );
244 if ( StringUtils.isEmpty( picUrl ) )
245 {
246 picUrl = getGravatarUrl( member.getEmail() );
247 }
248 if ( StringUtils.isEmpty( picUrl ) )
249 {
250 picUrl = getSpacerGravatarUrl();
251 }
252 sink.tableCell();
253 sink.figure();
254 sink.figureGraphics( picUrl );
255 sink.figure_();
256 sink.tableCell_();
257 }
258 if ( member instanceof Developer )
259 {
260 if ( headersMap.get( ID ) == Boolean.TRUE )
261 {
262 String id = ( (Developer) member ).getId();
263 if ( id == null )
264 {
265 tableCell( null );
266 }
267 else
268 {
269 tableCell( "<a name=\"" + id + "\"></a>" + id, true );
270 }
271 }
272 }
273 if ( headersMap.get( NAME ) == Boolean.TRUE )
274 {
275 tableCell( member.getName() );
276 }
277 if ( headersMap.get( EMAIL ) == Boolean.TRUE )
278 {
279 final String link = String.format( "mailto:%s", member.getEmail() );
280 tableCell( createLinkPatternedText( member.getEmail(), link ) );
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 static 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 static 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 static boolean isEmpty( List<?> list )
570 {
571 return ( list == null ) || list.isEmpty();
572 }
573 }
574 }