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 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   * <p>
64   * This component will build some {@link MavenReportExecution} from {@link MavenReportExecutorRequest}. If a
65   * {@link MavenReport} needs to fork a lifecycle, this fork is executed here. It will ask the core to get some
66   * informations in order to correctly setup {@link MavenReport}.
67   * </p>
68   * <p>
69   * <b>Note</b> if no version is defined in the report plugin, the version will be searched with
70   * {@link #resolvePluginVersion(ReportPlugin, MavenReportExecutorRequest) resolvePluginVersion(...)} method:
71   * </p>
72   * <ol>
73   * <li>use the one defined in the reportPlugin configuration,</li>
74   * <li>search similar (same groupId and artifactId) plugin in the build/plugins section of the pom,</li>
75   * <li>search similar (same groupId and artifactId) plugin in the build/pluginManagement section of the pom,</li>
76   * <li>ask {@link PluginVersionResolver} to get a fallback version (display a warning as it's not a recommended use).
77   * </li>
78   * </ol>
79   * <p>
80   * Following steps are done:
81   * </p>
82   * <ul>
83   * <li>get {@link PluginDescriptor} from the {@link MavenPluginManager} (through
84   * {@link MavenPluginManagerHelper#getPluginDescriptor(Plugin, org.apache.maven.execution.MavenSession)
85   * MavenPluginManagerHelper.getPluginDescriptor(...)} to protect from core API change)</li>
86   * <li>setup a {@link ClassLoader}, with the Site plugin classloader as parent for the report execution. <br>
87   * Notice that some classes are imported from the current Site plugin ClassRealm: see {@link #IMPORTS}. Corresponding
88   * artifacts are excluded from the artifact resolution: <code>doxia-site-renderer</code>, <code>doxia-sink-api</code>
89   *  and <code>maven-reporting-api</code>.<br>
90   * Work is done using {@link MavenPluginManager} (through
91   * {@link MavenPluginManagerHelper#setupPluginRealm(PluginDescriptor, MavenSession, ClassLoader, List, List)
92   * MavenPluginManagerHelper.setupPluginRealm(...)} to protect from core API change)</li>
93   * <li>setup the mojo using {@link MavenPluginManager#getConfiguredMojo(Class, MavenSession, MojoExecution)
94   * MavenPluginManager.getConfiguredMojo(...)}</li>
95   * <li>verify with {@link LifecycleExecutor#calculateForkedExecutions(MojoExecution, MavenSession)
96   * LifecycleExecutor.calculateForkedExecutions(...)} if any forked execution is needed: if yes, execute the forked
97   * execution here</li>
98   * </ul>
99   *
100  * @author Olivier Lamy
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                                                                // TODO Will be removed with Doxia 2.0.0-M1
122                                                                "org.codehaus.doxia.sink.Sink",
123                                                                "org.apache.maven.doxia.sink.Sink",
124                                                                "org.apache.maven.doxia.sink.SinkEventAttributes",
125                                                                // TODO Will be removed with Doxia 2.0.0-M1
126                                                                "org.apache.maven.doxia.logging.LogEnabled",
127                                                                // TODO Will be removed with Doxia 2.0.0-M1
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         // step 1: prepare the plugin
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         // step 2: prepare the goals
197         List<GoalWithConf> goalsWithConfiguration = new ArrayList<>();
198         boolean hasUserDefinedReports = prepareGoals( reportPlugin, pluginDescriptor, goalsWithConfiguration );
199 
200         // step 3: prepare the reports
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                 // ok, report is ready to generate
210                 reports.add( mavenReportExecution );
211             }
212         }
213 
214         if ( !reports.isEmpty() )
215         {
216             // log reports, either configured or detected
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             // by default, use every goal, which will be filtered later to only keep reporting goals
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             // aggregator mojos automatically added from plugin are only run at execution root
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                 // reports were explicitly written in the POM
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                 // forked phase
359                 execution = "'"
360                     + ( StringUtils.isEmpty( mojoDescriptor.getExecuteLifecycle() ) ? ""
361                                     : ( '[' + mojoDescriptor.getExecuteLifecycle() + ']' ) )
362                     + mojoDescriptor.getExecutePhase() + "' forked phase execution";
363             }
364             else
365             {
366                 // forked goal
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              * ignore old plugin which are using removed PluginRegistry [INFO] Caused by:
415              * java.lang.NoClassDefFoundError: org/apache/maven/plugin/registry/PluginRegistry
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         // get the plugin's goal Mojo class
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         // check if it is a report
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      * Merge plugin configuration and reportset configuration to mojo configuration to get effective
508      * mojo configuration.
509      *
510      * @param mojoConf configuration done at mojo descriptor level
511      * @param pluginMgmtConfig configuration done at build.pluginManagement level
512      * @param pluginConf configuration done at reporting plugin level
513      * @param reportSetConf configuration done at reportSet level
514      * @param parameters set of supported parameters: any other parameter will be removed
515      * @return the effective configuration to be used
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             // merge pluginConf into reportSetConf
528             Xpp3Dom mergedConfig = Xpp3DomUtils.mergeXpp3Dom( convert( reportSetConf ), pluginConfig );
529             // then merge pluginMgmtConfig
530             mergedConfig = Xpp3DomUtils.mergeXpp3Dom( mergedConfig, pluginMgmtConfig );
531             // then merge mojoConf
532             mergedConfig = Xpp3DomUtils.mergeXpp3Dom( mergedConfig, mojoConfig );
533 
534             // clean result
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      * Resolve report plugin version. Steps to find a plugin version stop after each step if a non <code>null</code>
578      * value has been found:
579      * <ol>
580      * <li>use the one defined in the reportPlugin configuration,</li>
581      * <li>search similar (same groupId and artifactId) mojo in the build/plugins section of the pom,</li>
582      * <li>search similar (same groupId and artifactId) mojo in the build/pluginManagement section of the pom,</li>
583      * <li>ask {@link PluginVersionResolver} to get a fallback version and display a warning as it's not a recommended
584      * use.</li>
585      * </ol>
586      *
587      * @param reportPlugin the report plugin to resolve the version
588      * @param mavenReportExecutorRequest the current report execution context
589      * @return the report plugin version
590      * @throws PluginVersionResolutionException on plugin version resolution issue
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         // look for version defined in the reportPlugin configuration
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         // search in the build section
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         // search in pluginManagement section
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      * Search similar (same groupId and artifactId) plugin as a given report plugin.
657      *
658      * @param reportPlugin the report plugin to search for a similar plugin
659      * @param plugins the candidate plugins
660      * @return the first similar plugin
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      * TODO other stuff to merge ?
681      * <p>
682      * this method will "merge" some part of the plugin declaration existing in the build section to the fake plugin
683      * build for report execution:
684      * <ul>
685      * <li>dependencies</li>
686      * </ul>
687      * </p>
688      * The plugin could only be present in the dependency management section.
689      *
690      * @param mavenReportExecutorRequest
691      * @param buildPlugin
692      * @param reportPlugin
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 }