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