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