1 package org.apache.maven.reporting.exec;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import javax.inject.Inject;
23 import javax.inject.Named;
24 import javax.inject.Singleton;
25
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.Set;
33
34 import org.apache.maven.lifecycle.LifecycleExecutor;
35 import org.apache.maven.model.Build;
36 import org.apache.maven.model.Plugin;
37 import org.apache.maven.plugin.MavenPluginManager;
38 import org.apache.maven.plugin.Mojo;
39 import org.apache.maven.plugin.MojoExecution;
40 import org.apache.maven.plugin.MojoExecutionException;
41 import org.apache.maven.plugin.MojoNotFoundException;
42 import org.apache.maven.plugin.PluginConfigurationException;
43 import org.apache.maven.plugin.PluginContainerException;
44 import org.apache.maven.plugin.descriptor.MojoDescriptor;
45 import org.apache.maven.plugin.descriptor.PluginDescriptor;
46 import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
47 import org.apache.maven.plugin.version.PluginVersionRequest;
48 import org.apache.maven.plugin.version.PluginVersionResolutionException;
49 import org.apache.maven.plugin.version.PluginVersionResolver;
50 import org.apache.maven.plugin.version.PluginVersionResult;
51 import org.apache.maven.project.MavenProject;
52 import org.apache.maven.reporting.MavenReport;
53 import org.codehaus.plexus.configuration.PlexusConfiguration;
54 import org.codehaus.plexus.util.StringUtils;
55 import org.codehaus.plexus.util.xml.Xpp3Dom;
56 import org.codehaus.plexus.util.xml.Xpp3DomUtils;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 import static java.util.Objects.requireNonNull;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 @Singleton
103 @Named
104 public class DefaultMavenReportExecutor
105 implements MavenReportExecutor
106 {
107 private static final Logger LOGGER = LoggerFactory.getLogger( DefaultMavenReportExecutor.class );
108
109 private final MavenPluginManager mavenPluginManager;
110
111 private final MavenPluginManagerHelper mavenPluginManagerHelper;
112
113 private final LifecycleExecutor lifecycleExecutor;
114
115 private final PluginVersionResolver pluginVersionResolver;
116
117 private static final List<String> IMPORTS = Arrays.asList( "org.apache.maven.reporting.MavenReport",
118 "org.apache.maven.reporting.MavenMultiPageReport",
119 "org.apache.maven.doxia.siterenderer.Renderer",
120 "org.apache.maven.doxia.sink.SinkFactory",
121
122 "org.codehaus.doxia.sink.Sink",
123 "org.apache.maven.doxia.sink.Sink",
124 "org.apache.maven.doxia.sink.SinkEventAttributes",
125
126 "org.apache.maven.doxia.logging.LogEnabled",
127
128 "org.apache.maven.doxia.logging.Log" );
129
130 private static final List<String> EXCLUDES = Arrays.asList( "doxia-site-renderer", "doxia-sink-api",
131 "maven-reporting-api" );
132
133 @Inject
134 public DefaultMavenReportExecutor( MavenPluginManager mavenPluginManager,
135 MavenPluginManagerHelper mavenPluginManagerHelper,
136 LifecycleExecutor lifecycleExecutor,
137 PluginVersionResolver pluginVersionResolver )
138 {
139 this.mavenPluginManager = requireNonNull( mavenPluginManager );
140 this.mavenPluginManagerHelper = requireNonNull( mavenPluginManagerHelper );
141 this.lifecycleExecutor = requireNonNull( lifecycleExecutor );
142 this.pluginVersionResolver = requireNonNull( pluginVersionResolver );
143 }
144
145 @Override
146 public List<MavenReportExecution> buildMavenReports( MavenReportExecutorRequest mavenReportExecutorRequest )
147 throws MojoExecutionException
148 {
149 if ( mavenReportExecutorRequest.getReportPlugins() == null )
150 {
151 return Collections.emptyList();
152 }
153
154 Set<String> reportPluginKeys = new HashSet<>();
155 List<MavenReportExecution> reportExecutions = new ArrayList<>();
156
157 String pluginKey = "";
158 try
159 {
160 for ( ReportPlugin reportPlugin : mavenReportExecutorRequest.getReportPlugins() )
161 {
162 pluginKey = reportPlugin.getGroupId() + ':' + reportPlugin.getArtifactId();
163
164 if ( !reportPluginKeys.add( pluginKey ) )
165 {
166 LOGGER.info( "Plugin {} will be executed more than one time", pluginKey );
167 }
168
169 reportExecutions.addAll( buildReportPlugin( mavenReportExecutorRequest, reportPlugin ) );
170 }
171 }
172 catch ( Exception e )
173 {
174 throw new MojoExecutionException( "Failed to get report for " + pluginKey, e );
175 }
176
177 return reportExecutions;
178 }
179
180 protected List<MavenReportExecution> buildReportPlugin( MavenReportExecutorRequest mavenReportExecutorRequest,
181 ReportPlugin reportPlugin )
182 throws Exception
183 {
184
185 Plugin plugin = new Plugin();
186 plugin.setGroupId( reportPlugin.getGroupId() );
187 plugin.setArtifactId( reportPlugin.getArtifactId() );
188 plugin.setVersion( resolvePluginVersion( reportPlugin, mavenReportExecutorRequest ) );
189 LOGGER.info( "Configuring report plugin {}", plugin.getId() );
190
191 mergePluginToReportPlugin( mavenReportExecutorRequest, plugin, reportPlugin );
192
193 PluginDescriptor pluginDescriptor =
194 mavenPluginManagerHelper.getPluginDescriptor( plugin, mavenReportExecutorRequest.getMavenSession() );
195
196
197 List<GoalWithConf> goalsWithConfiguration = new ArrayList<>();
198 boolean hasUserDefinedReports = prepareGoals( reportPlugin, pluginDescriptor, goalsWithConfiguration );
199
200
201 List<MavenReportExecution> reports = new ArrayList<>( goalsWithConfiguration.size() );
202 for ( GoalWithConf report : goalsWithConfiguration )
203 {
204 MavenReportExecution mavenReportExecution =
205 prepareReportExecution( mavenReportExecutorRequest, report, hasUserDefinedReports );
206
207 if ( mavenReportExecution != null )
208 {
209
210 reports.add( mavenReportExecution );
211 }
212 }
213
214 if ( !reports.isEmpty() )
215 {
216
217 StringBuilder buff = new StringBuilder();
218 for ( MavenReportExecution mre : reports )
219 {
220 if ( buff.length() > 0 )
221 {
222 buff.append( ", " );
223 }
224 buff.append( mre.getGoal() );
225 }
226 LOGGER.info( "{} report{} {} for {}:{}: {}", reports.size(), ( reports.size() > 1 ? "s" : "" ),
227 ( hasUserDefinedReports ? "configured" : "detected" ), plugin.getArtifactId(),
228 plugin.getVersion(), buff );
229 }
230
231 return reports;
232 }
233
234 private boolean prepareGoals( ReportPlugin reportPlugin, PluginDescriptor pluginDescriptor,
235 List<GoalWithConf> goalsWithConfiguration )
236 {
237 if ( reportPlugin.getReportSets().isEmpty() && reportPlugin.getReports().isEmpty() )
238 {
239
240 List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
241 for ( MojoDescriptor mojoDescriptor : mojoDescriptors )
242 {
243 goalsWithConfiguration.add( new GoalWithConf( reportPlugin, pluginDescriptor, mojoDescriptor.getGoal(),
244 mojoDescriptor.getConfiguration() ) );
245 }
246
247 return false;
248 }
249
250 Set<String> goals = new HashSet<>();
251 for ( String report : reportPlugin.getReports() )
252 {
253 if ( goals.add( report ) )
254 {
255 goalsWithConfiguration.add( new GoalWithConf( reportPlugin, pluginDescriptor, report,
256 reportPlugin.getConfiguration() ) );
257 }
258 else
259 {
260 LOGGER.warn( "{} report is declared twice in default reports", report );
261 }
262 }
263
264 for ( ReportSet reportSet : reportPlugin.getReportSets() )
265 {
266 goals = new HashSet<>();
267 for ( String report : reportSet.getReports() )
268 {
269 if ( goals.add( report ) )
270 {
271 goalsWithConfiguration.add( new GoalWithConf( reportPlugin, pluginDescriptor, report,
272 reportSet.getConfiguration() ) );
273 }
274 else
275 {
276 LOGGER.warn( "{} report is declared twice in {} reportSet", report, reportSet.getId() );
277 }
278 }
279 }
280
281 return true;
282 }
283
284 private MavenReportExecution prepareReportExecution( MavenReportExecutorRequest mavenReportExecutorRequest,
285 GoalWithConf report, boolean hasUserDefinedReports )
286 throws Exception
287 {
288 ReportPlugin reportPlugin = report.getReportPlugin();
289 PluginDescriptor pluginDescriptor = report.getPluginDescriptor();
290
291 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( report.getGoal() );
292 if ( mojoDescriptor == null )
293 {
294 throw new MojoNotFoundException( report.getGoal(), pluginDescriptor );
295 }
296
297 MavenProject project = mavenReportExecutorRequest.getProject();
298 if ( !hasUserDefinedReports && mojoDescriptor.isAggregator() && !canAggregate( project ) )
299 {
300
301 return null;
302 }
303
304 MojoExecution mojoExecution = new MojoExecution( pluginDescriptor.getPlugin(), report.getGoal(), null );
305
306 mojoExecution.setMojoDescriptor( mojoDescriptor );
307
308 mavenPluginManagerHelper.setupPluginRealm( pluginDescriptor, mavenReportExecutorRequest.getMavenSession(),
309 Thread.currentThread().getContextClassLoader(), IMPORTS,
310 EXCLUDES );
311
312 if ( !isMavenReport( mojoExecution, pluginDescriptor ) )
313 {
314 if ( hasUserDefinedReports )
315 {
316
317 LOGGER.warn( "Ignoring {}:{}"
318 + " goal since it is not a report: should be removed from reporting configuration in POM",
319 mojoExecution.getPlugin().getId(), report.getGoal() );
320 }
321 return null;
322 }
323
324 Xpp3Dom pluginMgmtConfiguration = null;
325 if ( project.getBuild() != null && project.getBuild().getPluginManagement() != null )
326 {
327 Plugin pluginMgmt = find( reportPlugin, project.getBuild().getPluginManagement().getPlugins() );
328
329 if ( pluginMgmt != null )
330 {
331 pluginMgmtConfiguration = (Xpp3Dom) pluginMgmt.getConfiguration();
332 }
333 }
334
335 mojoExecution.setConfiguration( mergeConfiguration( mojoDescriptor.getMojoConfiguration(),
336 pluginMgmtConfiguration,
337 reportPlugin.getConfiguration(),
338 report.getConfiguration(),
339 mojoDescriptor.getParameterMap().keySet() ) );
340
341 MavenReport mavenReport =
342 getConfiguredMavenReport( mojoExecution, pluginDescriptor, mavenReportExecutorRequest );
343
344 MavenReportExecution mavenReportExecution =
345 new MavenReportExecution( report.getGoal(), mojoExecution.getPlugin(), mavenReport,
346 pluginDescriptor.getClassRealm() );
347
348 lifecycleExecutor.calculateForkedExecutions( mojoExecution,
349 mavenReportExecutorRequest.getMavenSession() );
350
351 if ( !mojoExecution.getForkedExecutions().isEmpty() )
352 {
353 String reportDescription = pluginDescriptor.getArtifactId() + ":" + report.getGoal() + " report";
354
355 String execution;
356 if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
357 {
358
359 execution = "'"
360 + ( StringUtils.isEmpty( mojoDescriptor.getExecuteLifecycle() ) ? ""
361 : ( '[' + mojoDescriptor.getExecuteLifecycle() + ']' ) )
362 + mojoDescriptor.getExecutePhase() + "' forked phase execution";
363 }
364 else
365 {
366
367 execution = "'" + mojoDescriptor.getExecuteGoal() + "' forked goal execution";
368 }
369
370 LOGGER.info( "Preparing {} requires {}", reportDescription, execution );
371
372 lifecycleExecutor.executeForkedExecutions( mojoExecution,
373 mavenReportExecutorRequest.getMavenSession() );
374
375 LOGGER.info( "{} for {} preparation done", execution, reportDescription );
376 }
377
378 return mavenReportExecution;
379 }
380
381 private boolean canAggregate( MavenProject project )
382 {
383 return project.isExecutionRoot() && "pom".equals( project.getPackaging() ) && ( project.getModules() != null )
384 && !project.getModules().isEmpty();
385 }
386
387 private MavenReport getConfiguredMavenReport( MojoExecution mojoExecution, PluginDescriptor pluginDescriptor,
388 MavenReportExecutorRequest mavenReportExecutorRequest )
389 throws PluginContainerException, PluginConfigurationException
390 {
391 try
392 {
393 Mojo mojo =
394 mavenPluginManager.getConfiguredMojo( Mojo.class, mavenReportExecutorRequest.getMavenSession(),
395 mojoExecution );
396
397 return (MavenReport) mojo;
398 }
399 catch ( ClassCastException e )
400 {
401 if ( LOGGER.isDebugEnabled() )
402 {
403 LOGGER.warn( "Skipping ClassCastException", e );
404 }
405 else
406 {
407 LOGGER.warn( "Skipping ClassCastException" );
408 }
409 return null;
410 }
411 catch ( PluginContainerException e )
412 {
413
414
415
416
417 if ( e.getCause() != null && e.getCause() instanceof NoClassDefFoundError
418 && e.getMessage().contains( "PluginRegistry" ) )
419 {
420 if ( LOGGER.isDebugEnabled() )
421 {
422 LOGGER.warn( "Skipping NoClassDefFoundError with PluginRegistry", e );
423 }
424 else
425 {
426 LOGGER.warn( "Skipping NoClassDefFoundError with PluginRegistry" );
427 }
428 return null;
429 }
430 throw e;
431 }
432 }
433
434 private boolean isMavenReport( MojoExecution mojoExecution, PluginDescriptor pluginDescriptor )
435 {
436 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
437
438
439 Class<?> mojoClass;
440 try
441 {
442 Thread.currentThread().setContextClassLoader( mojoExecution.getMojoDescriptor().getRealm() );
443
444 mojoClass =
445 pluginDescriptor.getClassRealm().loadClass( mojoExecution.getMojoDescriptor().getImplementation() );
446 }
447 catch ( ClassNotFoundException e )
448 {
449 if ( LOGGER.isDebugEnabled() )
450 {
451 LOGGER.warn( "Skipping ClassNotFoundException mojoExecution.goal {}", mojoExecution.getGoal(), e );
452 }
453 else
454 {
455 LOGGER.warn( "Skipping ClassNotFoundException mojoExecution.goal {}", mojoExecution.getGoal() );
456 }
457 return false;
458 }
459 finally
460 {
461 Thread.currentThread().setContextClassLoader( originalClassLoader );
462 }
463
464
465 try
466 {
467 Thread.currentThread().setContextClassLoader( mojoExecution.getMojoDescriptor().getRealm() );
468 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( mojoExecution.getGoal() );
469
470 boolean isMavenReport = MavenReport.class.isAssignableFrom( mojoClass );
471
472 if ( LOGGER.isDebugEnabled() )
473 {
474 if ( mojoDescriptor != null && mojoDescriptor.getImplementationClass() != null )
475 {
476 LOGGER.debug( "Class {} is MavenReport: ",
477 mojoDescriptor.getImplementationClass().getName(), isMavenReport );
478 }
479
480 if ( !isMavenReport )
481 {
482 LOGGER.debug( "Skipping non MavenReport {}", mojoExecution.getMojoDescriptor().getId() );
483 }
484 }
485
486 return isMavenReport;
487 }
488 catch ( LinkageError e )
489 {
490 if ( LOGGER.isDebugEnabled() )
491 {
492 LOGGER.warn( "Skipping LinkageError mojoExecution.goal {}", mojoExecution.getGoal(), e );
493 }
494 else
495 {
496 LOGGER.warn( "Skipping LinkageError mojoExecution.goal {}", mojoExecution.getGoal() );
497 }
498 return false;
499 }
500 finally
501 {
502 Thread.currentThread().setContextClassLoader( originalClassLoader );
503 }
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517 private Xpp3Dom mergeConfiguration( PlexusConfiguration mojoConf, Xpp3Dom pluginMgmtConfig,
518 PlexusConfiguration pluginConf, PlexusConfiguration reportSetConf,
519 Set<String> parameters )
520 {
521 Xpp3Dom mojoConfig = ( mojoConf != null ) ? convert( mojoConf ) : new Xpp3Dom( "configuration" );
522
523 if ( pluginMgmtConfig != null || pluginConf != null || reportSetConf != null )
524 {
525 Xpp3Dom pluginConfig = ( pluginConf == null ) ? new Xpp3Dom( "fake" ) : convert( pluginConf );
526
527
528 Xpp3Dom mergedConfig = Xpp3DomUtils.mergeXpp3Dom( convert( reportSetConf ), pluginConfig );
529
530 mergedConfig = Xpp3DomUtils.mergeXpp3Dom( mergedConfig, pluginMgmtConfig );
531
532 mergedConfig = Xpp3DomUtils.mergeXpp3Dom( mergedConfig, mojoConfig );
533
534
535 Xpp3Dom cleanedConfig = new Xpp3Dom( "configuration" );
536 if ( mergedConfig.getChildren() != null )
537 {
538 for ( Xpp3Dom parameter : mergedConfig.getChildren() )
539 {
540 if ( parameters.contains( parameter.getName() ) )
541 {
542 cleanedConfig.addChild( parameter );
543 }
544 }
545 }
546
547 mojoConfig = cleanedConfig;
548 }
549
550 return mojoConfig;
551 }
552
553 private Xpp3Dom convert( PlexusConfiguration config )
554 {
555 if ( config == null )
556 {
557 return null;
558 }
559
560 Xpp3Dom dom = new Xpp3Dom( config.getName() );
561 dom.setValue( config.getValue( null ) );
562
563 for ( String attrib : config.getAttributeNames() )
564 {
565 dom.setAttribute( attrib, config.getAttribute( attrib, null ) );
566 }
567
568 for ( int n = config.getChildCount(), i = 0; i < n; i++ )
569 {
570 dom.addChild( convert( config.getChild( i ) ) );
571 }
572
573 return dom;
574 }
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592 protected String resolvePluginVersion( ReportPlugin reportPlugin,
593 MavenReportExecutorRequest mavenReportExecutorRequest )
594 throws PluginVersionResolutionException
595 {
596 String reportPluginKey = reportPlugin.getGroupId() + ':' + reportPlugin.getArtifactId();
597 LOGGER.debug( "Resolving version for {}", reportPluginKey );
598
599
600 if ( reportPlugin.getVersion() != null )
601 {
602 LOGGER.debug( "Resolved {} version from the reporting.plugins section: {}",
603 reportPluginKey, reportPlugin.getVersion() );
604 return reportPlugin.getVersion();
605 }
606
607 MavenProject project = mavenReportExecutorRequest.getProject();
608
609
610 if ( project.getBuild() != null )
611 {
612 Plugin plugin = find( reportPlugin, project.getBuild().getPlugins() );
613
614 if ( plugin != null && plugin.getVersion() != null )
615 {
616 LOGGER.debug( "Resolved {} version from the build.plugins section: {}",
617 reportPluginKey, plugin.getVersion() );
618 return plugin.getVersion();
619 }
620 }
621
622
623 if ( project.getBuild() != null && project.getBuild().getPluginManagement() != null )
624 {
625 Plugin plugin = find( reportPlugin, project.getBuild().getPluginManagement().getPlugins() );
626
627 if ( plugin != null && plugin.getVersion() != null )
628 {
629 LOGGER.debug( "Resolved {} version from the build.pluginManagement.plugins section: {}",
630 reportPluginKey, plugin.getVersion() );
631 return plugin.getVersion();
632 }
633 }
634
635 LOGGER.warn( "Report plugin {} has an empty version.", reportPluginKey );
636 LOGGER.warn( "" );
637 LOGGER.warn( "It is highly recommended to fix these problems"
638 + " because they threaten the stability of your build." );
639 LOGGER.warn( "" );
640 LOGGER.warn( "For this reason, future Maven versions might no"
641 + " longer support building such malformed projects." );
642
643 Plugin plugin = new Plugin();
644 plugin.setGroupId( reportPlugin.getGroupId() );
645 plugin.setArtifactId( reportPlugin.getArtifactId() );
646
647 PluginVersionRequest pluginVersionRequest =
648 new DefaultPluginVersionRequest( plugin, mavenReportExecutorRequest.getMavenSession() );
649
650 PluginVersionResult result = pluginVersionResolver.resolve( pluginVersionRequest );
651 LOGGER.debug( "Resolved {} version from repository: {}", reportPluginKey, result.getVersion() );
652 return result.getVersion();
653 }
654
655
656
657
658
659
660
661
662 private Plugin find( ReportPlugin reportPlugin, List<Plugin> plugins )
663 {
664 if ( plugins == null )
665 {
666 return null;
667 }
668 for ( Plugin plugin : plugins )
669 {
670 if ( Objects.equals( plugin.getArtifactId(), reportPlugin.getArtifactId() )
671 && Objects.equals( plugin.getGroupId(), reportPlugin.getGroupId() ) )
672 {
673 return plugin;
674 }
675 }
676 return null;
677 }
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694 private void mergePluginToReportPlugin( MavenReportExecutorRequest mavenReportExecutorRequest, Plugin buildPlugin,
695 ReportPlugin reportPlugin )
696 {
697 Build build = mavenReportExecutorRequest.getProject().getBuild();
698 Plugin configuredPlugin = find( reportPlugin, build.getPlugins() );
699 if ( configuredPlugin == null && build.getPluginManagement() != null )
700 {
701 configuredPlugin = find( reportPlugin, build.getPluginManagement().getPlugins() );
702 }
703 if ( configuredPlugin != null )
704 {
705 if ( !configuredPlugin.getDependencies().isEmpty() )
706 {
707 buildPlugin.getDependencies().addAll( configuredPlugin.getDependencies() );
708 }
709 }
710 }
711
712 private static class GoalWithConf
713 {
714 private final String goal;
715
716 private final PlexusConfiguration configuration;
717
718 private final ReportPlugin reportPlugin;
719
720 private final PluginDescriptor pluginDescriptor;
721
722 GoalWithConf( ReportPlugin reportPlugin, PluginDescriptor pluginDescriptor, String goal,
723 PlexusConfiguration configuration )
724 {
725 this.reportPlugin = reportPlugin;
726 this.pluginDescriptor = pluginDescriptor;
727 this.goal = goal;
728 this.configuration = configuration;
729 }
730
731 public ReportPlugin getReportPlugin()
732 {
733 return reportPlugin;
734 }
735
736 public PluginDescriptor getPluginDescriptor()
737 {
738 return pluginDescriptor;
739 }
740
741 public String getGoal()
742 {
743 return goal;
744 }
745
746 public PlexusConfiguration getConfiguration()
747 {
748 return configuration;
749 }
750 }
751 }