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