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