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