1 package org.apache.maven.plugin.plugin;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileReader;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.ResourceBundle;
32
33 import org.apache.maven.artifact.repository.ArtifactRepository;
34 import org.apache.maven.doxia.sink.Sink;
35 import org.apache.maven.doxia.siterenderer.Renderer;
36 import org.apache.maven.model.Plugin;
37 import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
38 import org.apache.maven.plugin.descriptor.MojoDescriptor;
39 import org.apache.maven.plugin.descriptor.PluginDescriptor;
40 import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
41 import org.apache.maven.plugins.annotations.Component;
42 import org.apache.maven.plugins.annotations.Execute;
43 import org.apache.maven.plugins.annotations.LifecyclePhase;
44 import org.apache.maven.plugins.annotations.Mojo;
45 import org.apache.maven.plugins.annotations.Parameter;
46 import org.apache.maven.plugins.plugin.descriptor.MNG6109PluginDescriptorBuilder;
47 import org.apache.maven.project.MavenProject;
48 import org.apache.maven.reporting.AbstractMavenReport;
49 import org.apache.maven.reporting.AbstractMavenReportRenderer;
50 import org.apache.maven.reporting.MavenReportException;
51 import org.apache.maven.rtinfo.RuntimeInformation;
52 import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
53 import org.apache.maven.tools.plugin.PluginToolsRequest;
54 import org.apache.maven.tools.plugin.extractor.ExtractionException;
55 import org.apache.maven.tools.plugin.generator.GeneratorException;
56 import org.apache.maven.tools.plugin.generator.GeneratorUtils;
57 import org.apache.maven.tools.plugin.generator.PluginXdocGenerator;
58 import org.apache.maven.tools.plugin.scanner.MojoScanner;
59 import org.apache.maven.tools.plugin.util.PluginUtils;
60 import org.codehaus.plexus.component.repository.ComponentDependency;
61 import org.codehaus.plexus.configuration.PlexusConfigurationException;
62 import org.codehaus.plexus.util.StringUtils;
63 import org.codehaus.plexus.util.xml.Xpp3Dom;
64
65
66
67
68
69
70
71
72
73 @Mojo( name = "report", threadSafe = true )
74 @Execute( phase = LifecyclePhase.PROCESS_CLASSES )
75 public class PluginReport
76 extends AbstractMavenReport
77 {
78
79
80
81 @Parameter( defaultValue = "${project.build.directory}/generated-site/xdoc" )
82 private File outputDirectory;
83
84
85
86
87 @Component
88 private Renderer siteRenderer;
89
90
91
92
93 @Parameter( defaultValue = "${project}", readonly = true )
94 private MavenProject project;
95
96
97
98
99 @Component
100 protected MojoScanner mojoScanner;
101
102
103
104
105
106
107 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
108 private String encoding;
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 @Parameter
133 private Requirements requirements;
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 @Parameter( property = "goalPrefix" )
155 protected String goalPrefix;
156
157
158
159
160
161
162 @Parameter( defaultValue = "false", property = "maven.plugin.skip" )
163 private boolean skip;
164
165
166
167
168
169
170 @Parameter( defaultValue = "false", property = "maven.plugin.report.skip" )
171 private boolean skipReport;
172
173
174
175
176
177
178 @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
179 protected List<ArtifactRepository> remoteRepos;
180
181
182
183
184
185
186 @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
187 protected ArtifactRepository local;
188
189
190
191
192 @Component
193 private RuntimeInformation rtInfo;
194
195
196
197
198
199
200 @Parameter( defaultValue = "${project.build.outputDirectory}/META-INF/maven/plugin.xml", required = true,
201 readonly = true )
202 private File pluginXmlFile;
203
204
205
206
207 @Override
208 protected Renderer getSiteRenderer()
209 {
210 return siteRenderer;
211 }
212
213
214
215
216 @Override
217 protected String getOutputDirectory()
218 {
219
220 return project.getReporting().getOutputDirectory();
221 }
222
223
224
225
226 @Override
227 protected MavenProject getProject()
228 {
229 return project;
230 }
231
232
233
234
235 @Override
236 public boolean canGenerateReport()
237 {
238 return pluginXmlFile != null && pluginXmlFile.isFile() && pluginXmlFile.canRead();
239 }
240
241
242
243
244 @Override
245 protected void executeReport( Locale locale )
246 throws MavenReportException
247 {
248 if ( !canGenerateReport() )
249 {
250 return;
251 }
252 if ( skip || skipReport )
253 {
254 getLog().info( "Maven Plugin Plugin Report generation skipped." );
255 return;
256 }
257
258 PluginDescriptor pluginDescriptor = extractPluginDescriptor();
259
260
261 generateMojosDocumentation( pluginDescriptor, locale );
262
263
264 PluginOverviewRenderer r =
265 new PluginOverviewRenderer( project, requirements, getSink(), pluginDescriptor, locale );
266 r.render();
267 }
268
269 private PluginDescriptor extractPluginDescriptor()
270 throws MavenReportException
271 {
272 PluginDescriptorBuilder builder = getPluginDescriptorBuilder();
273
274 try
275 {
276 return builder.build( new FileReader( pluginXmlFile ) );
277 }
278 catch ( FileNotFoundException | PlexusConfigurationException e )
279 {
280 getLog().debug( "Failed to read " + pluginXmlFile + ", fall back to mojoScanner" );
281 }
282
283
284 String defaultGoalPrefix = PluginDescriptor.getGoalPrefixFromArtifactId( project.getArtifactId() );
285 if ( goalPrefix == null )
286 {
287 goalPrefix = defaultGoalPrefix;
288 }
289 else
290 {
291 getLog().warn( "\n\nGoal prefix is specified as: '" + goalPrefix + "'. Maven currently expects it to be '"
292 + defaultGoalPrefix + "'.\n" );
293 }
294
295
296 PluginDescriptor pluginDescriptor = new PluginDescriptor();
297
298 pluginDescriptor.setGroupId( project.getGroupId() );
299
300 pluginDescriptor.setArtifactId( project.getArtifactId() );
301
302 pluginDescriptor.setVersion( project.getVersion() );
303
304 pluginDescriptor.setGoalPrefix( goalPrefix );
305
306 try
307 {
308 List<ComponentDependency> deps = GeneratorUtils.toComponentDependencies( project.getArtifacts() );
309 pluginDescriptor.setDependencies( deps );
310
311 PluginToolsRequest request = new DefaultPluginToolsRequest( project, pluginDescriptor );
312 request.setEncoding( encoding );
313 request.setSkipErrorNoDescriptorsFound( true );
314 request.setDependencies( new LinkedHashSet<>( project.getArtifacts() ) );
315 request.setLocal( this.local );
316 request.setRemoteRepos( this.remoteRepos );
317
318 try
319 {
320 mojoScanner.populatePluginDescriptor( request );
321 }
322 catch ( InvalidPluginDescriptorException e )
323 {
324
325 getLog().debug( "Plugin without mojos.", e );
326 }
327 }
328 catch ( ExtractionException e )
329 {
330 throw new MavenReportException( "Error extracting plugin descriptor: \'" + e.getLocalizedMessage() + "\'",
331 e );
332 }
333 return pluginDescriptor;
334 }
335
336
337
338
339
340
341
342
343
344
345 private PluginDescriptorBuilder getPluginDescriptorBuilder()
346 {
347 PluginDescriptorBuilder pluginDescriptorBuilder;
348
349 if ( rtInfo.isMavenVersion( "(3.3.9,)" ) )
350 {
351 pluginDescriptorBuilder = new PluginDescriptorBuilder();
352 }
353 else
354 {
355 pluginDescriptorBuilder = new MNG6109PluginDescriptorBuilder();
356 }
357
358 return pluginDescriptorBuilder;
359 }
360
361
362
363
364 @Override
365 public String getDescription( Locale locale )
366 {
367 return getBundle( locale ).getString( "report.plugin.description" );
368 }
369
370
371
372
373 @Override
374 public String getName( Locale locale )
375 {
376 return getBundle( locale ).getString( "report.plugin.name" );
377 }
378
379
380
381
382 @Override
383 public String getOutputName()
384 {
385 return "plugin-info";
386 }
387
388
389
390
391
392
393
394
395 private void generateMojosDocumentation( PluginDescriptor pluginDescriptor, Locale locale )
396 throws MavenReportException
397 {
398 try
399 {
400 File outputDir = outputDirectory;
401 outputDir.mkdirs();
402
403 PluginXdocGenerator generator = new PluginXdocGenerator( project, locale );
404 PluginToolsRequest pluginToolsRequest = new DefaultPluginToolsRequest( project, pluginDescriptor );
405 generator.execute( outputDir, pluginToolsRequest );
406 }
407 catch ( GeneratorException e )
408 {
409 throw new MavenReportException( "Error writing plugin documentation", e );
410 }
411
412 }
413
414
415
416
417
418 protected static ResourceBundle getBundle( Locale locale )
419 {
420 return ResourceBundle.getBundle( "plugin-report", locale, PluginReport.class.getClassLoader() );
421 }
422
423
424
425
426
427 static class PluginOverviewRenderer
428 extends AbstractMavenReportRenderer
429 {
430 private final MavenProject project;
431
432 private final Requirements requirements;
433
434 private final PluginDescriptor pluginDescriptor;
435
436 private final Locale locale;
437
438
439
440
441
442
443
444
445 PluginOverviewRenderer( MavenProject project, Requirements requirements, Sink sink,
446 PluginDescriptor pluginDescriptor, Locale locale )
447 {
448 super( sink );
449
450 this.project = project;
451
452 this.requirements = ( requirements == null ? new Requirements() : requirements );
453
454 this.pluginDescriptor = pluginDescriptor;
455
456 this.locale = locale;
457 }
458
459
460
461
462 @Override
463 public String getTitle()
464 {
465 return getBundle( locale ).getString( "report.plugin.title" );
466 }
467
468
469
470
471 @Override
472 @SuppressWarnings( { "unchecked", "rawtypes" } )
473 public void renderBody()
474 {
475 startSection( getTitle() );
476
477 if ( !( pluginDescriptor.getMojos() != null && pluginDescriptor.getMojos().size() > 0 ) )
478 {
479 paragraph( getBundle( locale ).getString( "report.plugin.goals.nogoal" ) );
480 endSection();
481 return;
482 }
483
484 paragraph( getBundle( locale ).getString( "report.plugin.goals.intro" ) );
485
486 boolean hasMavenReport = false;
487 for ( Iterator<MojoDescriptor> i = pluginDescriptor.getMojos().iterator(); i.hasNext(); )
488 {
489 MojoDescriptor mojo = i.next();
490
491 if ( GeneratorUtils.isMavenReport( mojo.getImplementation(), project ) )
492 {
493 hasMavenReport = true;
494 }
495 }
496
497 startTable();
498
499 String goalColumnName = getBundle( locale ).getString( "report.plugin.goals.column.goal" );
500 String isMavenReport = getBundle( locale ).getString( "report.plugin.goals.column.isMavenReport" );
501 String descriptionColumnName = getBundle( locale ).getString( "report.plugin.goals.column.description" );
502 if ( hasMavenReport )
503 {
504 tableHeader( new String[]{ goalColumnName, isMavenReport, descriptionColumnName } );
505 }
506 else
507 {
508 tableHeader( new String[]{ goalColumnName, descriptionColumnName } );
509 }
510
511 List<MojoDescriptor> mojos = new ArrayList<>();
512 mojos.addAll( pluginDescriptor.getMojos() );
513 PluginUtils.sortMojos( mojos );
514 for ( MojoDescriptor mojo : mojos )
515 {
516 String goalName = mojo.getFullGoalName();
517
518
519
520
521
522 String goalDocumentationLink = "./" + mojo.getGoal() + "-mojo.html";
523
524 String description;
525 if ( StringUtils.isNotEmpty( mojo.getDeprecated() ) )
526 {
527 description =
528 "<strong>" + getBundle( locale ).getString( "report.plugin.goal.deprecated" ) + "</strong> "
529 + GeneratorUtils.makeHtmlValid( mojo.getDeprecated() );
530 }
531 else if ( StringUtils.isNotEmpty( mojo.getDescription() ) )
532 {
533 description = GeneratorUtils.makeHtmlValid( mojo.getDescription() );
534 }
535 else
536 {
537 description = getBundle( locale ).getString( "report.plugin.goal.nodescription" );
538 }
539
540 sink.tableRow();
541 tableCell( createLinkPatternedText( goalName, goalDocumentationLink ) );
542 if ( hasMavenReport )
543 {
544 if ( GeneratorUtils.isMavenReport( mojo.getImplementation(), project ) )
545 {
546 sink.tableCell();
547 sink.text( getBundle( locale ).getString( "report.plugin.isReport" ) );
548 sink.tableCell_();
549 }
550 else
551 {
552 sink.tableCell();
553 sink.text( getBundle( locale ).getString( "report.plugin.isNotReport" ) );
554 sink.tableCell_();
555 }
556 }
557 tableCell( description, true );
558 sink.tableRow_();
559 }
560
561 endTable();
562
563 startSection( getBundle( locale ).getString( "report.plugin.systemrequirements" ) );
564
565 paragraph( getBundle( locale ).getString( "report.plugin.systemrequirements.intro" ) );
566
567 startTable();
568
569 String maven = discoverMavenRequirement( project, requirements );
570 sink.tableRow();
571 tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.maven" ) );
572 tableCell( ( maven != null
573 ? maven
574 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
575 sink.tableRow_();
576
577 String jdk = discoverJdkRequirement( project, requirements );
578 sink.tableRow();
579 tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.jdk" ) );
580 tableCell(
581 ( jdk != null ? jdk : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
582 sink.tableRow_();
583
584 sink.tableRow();
585 tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.memory" ) );
586 tableCell( ( StringUtils.isNotEmpty( requirements.getMemory() )
587 ? requirements.getMemory()
588 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
589 sink.tableRow_();
590
591 sink.tableRow();
592 tableCell( getBundle( locale ).getString( "report.plugin.systemrequirements.diskspace" ) );
593 tableCell( ( StringUtils.isNotEmpty( requirements.getDiskSpace() )
594 ? requirements.getDiskSpace()
595 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
596 sink.tableRow_();
597
598 if ( requirements.getOthers() != null && requirements.getOthers().size() > 0 )
599 {
600 for ( Iterator it = requirements.getOthers().keySet().iterator(); it.hasNext(); )
601 {
602 String key = it.next().toString();
603
604 sink.tableRow();
605 tableCell( key );
606 tableCell( ( StringUtils.isNotEmpty( requirements.getOthers().getProperty( key ) )
607 ? requirements.getOthers().getProperty( key )
608 : getBundle( locale ).getString( "report.plugin.systemrequirements.nominimum" ) ) );
609 sink.tableRow_();
610 }
611 }
612 endTable();
613
614 endSection();
615
616 renderUsageSection( hasMavenReport );
617
618 endSection();
619 }
620
621
622
623
624
625
626 private void renderUsageSection( boolean hasMavenReport )
627 {
628 startSection( getBundle( locale ).getString( "report.plugin.usage" ) );
629
630
631 sink.paragraph();
632 text( getBundle( locale ).getString( "report.plugin.usage.intro" ) );
633 sink.paragraph_();
634
635 StringBuilder sb = new StringBuilder();
636 sb.append( "<project>" ).append( '\n' );
637 sb.append( " ..." ).append( '\n' );
638 sb.append( " <build>" ).append( '\n' );
639 sb.append(
640 " <!-- " + getBundle( locale ).getString( "report.plugin.usage.pluginManagement" ) + " -->" ).append(
641 '\n' );
642 sb.append( " <pluginManagement>" ).append( '\n' );
643 sb.append( " <plugins>" ).append( '\n' );
644 sb.append( " <plugin>" ).append( '\n' );
645 sb.append( " <groupId>" ).append( pluginDescriptor.getGroupId() ).append( "</groupId>" ).append(
646 '\n' );
647 sb.append( " <artifactId>" ).append( pluginDescriptor.getArtifactId() ).append(
648 "</artifactId>" ).append( '\n' );
649 sb.append( " <version>" ).append( pluginDescriptor.getVersion() ).append( "</version>" ).append(
650 '\n' );
651 sb.append( " </plugin>" ).append( '\n' );
652 sb.append( " ..." ).append( '\n' );
653 sb.append( " </plugins>" ).append( '\n' );
654 sb.append( " </pluginManagement>" ).append( '\n' );
655 sb.append( " <!-- " + getBundle( locale ).getString( "report.plugin.usage.plugins" ) + " -->" ).append(
656 '\n' );
657 sb.append( " <plugins>" ).append( '\n' );
658 sb.append( " <plugin>" ).append( '\n' );
659 sb.append( " <groupId>" ).append( pluginDescriptor.getGroupId() ).append( "</groupId>" ).append(
660 '\n' );
661 sb.append( " <artifactId>" ).append( pluginDescriptor.getArtifactId() ).append(
662 "</artifactId>" ).append( '\n' );
663 sb.append( " <version>" ).append( pluginDescriptor.getVersion() ).append( "</version>" ).append(
664 '\n' );
665 sb.append( " </plugin>" ).append( '\n' );
666 sb.append( " ..." ).append( '\n' );
667 sb.append( " </plugins>" ).append( '\n' );
668 sb.append( " </build>" ).append( '\n' );
669
670 if ( hasMavenReport )
671 {
672 sb.append( " ..." ).append( '\n' );
673 sb.append(
674 " <!-- " + getBundle( locale ).getString( "report.plugin.usage.reporting" ) + " -->" ).append(
675 '\n' );
676 sb.append( " <reporting>" ).append( '\n' );
677 sb.append( " <plugins>" ).append( '\n' );
678 sb.append( " <plugin>" ).append( '\n' );
679 sb.append( " <groupId>" ).append( pluginDescriptor.getGroupId() ).append( "</groupId>" ).append(
680 '\n' );
681 sb.append( " <artifactId>" ).append( pluginDescriptor.getArtifactId() ).append(
682 "</artifactId>" ).append( '\n' );
683 sb.append( " <version>" ).append( pluginDescriptor.getVersion() ).append( "</version>" ).append(
684 '\n' );
685 sb.append( " </plugin>" ).append( '\n' );
686 sb.append( " ..." ).append( '\n' );
687 sb.append( " </plugins>" ).append( '\n' );
688 sb.append( " </reporting>" ).append( '\n' );
689 }
690
691 sb.append( " ..." ).append( '\n' );
692 sb.append( "</project>" ).append( '\n' );
693
694 verbatimText( sb.toString() );
695
696 sink.paragraph();
697 linkPatternedText( getBundle( locale ).getString( "report.plugin.configuration.end" ) );
698 sink.paragraph_();
699
700 endSection();
701 }
702
703
704
705
706
707
708
709
710
711 private static String discoverMavenRequirement( MavenProject project, Requirements requirements )
712 {
713 String maven = requirements.getMaven();
714 if ( maven == null )
715 {
716 maven = ( project.getPrerequisites() != null ? project.getPrerequisites().getMaven() : null );
717 }
718 if ( maven == null )
719 {
720 maven = "2.0";
721 }
722
723 return maven;
724 }
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739 private static String discoverJdkRequirement( MavenProject project, Requirements requirements )
740 {
741 String jdk = requirements.getJdk();
742
743 if ( jdk != null )
744 {
745 return jdk;
746 }
747
748 Plugin compiler = getCompilerPlugin( project.getBuild().getPluginsAsMap() );
749 if ( compiler == null )
750 {
751 compiler = getCompilerPlugin( project.getPluginManagement().getPluginsAsMap() );
752 }
753
754 jdk = getPluginParameter( compiler, "target" );
755 if ( jdk != null )
756 {
757 return jdk;
758 }
759
760 jdk = getPluginParameter( compiler, "release" );
761 if ( jdk != null )
762 {
763 return jdk;
764 }
765
766
767 jdk = project.getProperties().getProperty( "maven.compiler.target" );
768 if ( jdk != null )
769 {
770 return jdk;
771 }
772
773
774
775 String version = ( compiler == null ) ? null : compiler.getVersion();
776
777 if ( version != null )
778 {
779 return "Default target for maven-compiler-plugin version " + version;
780 }
781
782 return "Unknown";
783 }
784
785 private static Plugin getCompilerPlugin( Map<String, Plugin> pluginsAsMap )
786 {
787 return pluginsAsMap.get( "org.apache.maven.plugins:maven-compiler-plugin" );
788 }
789
790 private static String getPluginParameter( Plugin plugin, String parameter )
791 {
792 if ( plugin != null )
793 {
794 Xpp3Dom pluginConf = (Xpp3Dom) plugin.getConfiguration();
795
796 if ( pluginConf != null )
797 {
798 Xpp3Dom target = pluginConf.getChild( parameter );
799
800 if ( target != null )
801 {
802 return target.getValue();
803 }
804 }
805 }
806
807 return null;
808 }
809 }
810 }