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