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