1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
118 "org.apache.maven.doxia.siterenderer.Renderer",
119 "org.apache.maven.doxia.siterenderer.SiteRenderer",
120 "org.apache.maven.doxia.sink.SinkFactory",
121
122 "org.codehaus.doxia.sink.Sink",
123 "org.apache.maven.doxia.sink.Sink",
124 "org.apache.maven.doxia.sink.SinkEventAttributes",
125
126 "org.apache.maven.doxia.logging.LogEnabled",
127
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
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
188 List<GoalWithConf> goalsWithConfiguration = new ArrayList<>();
189 boolean hasUserDefinedReports = prepareGoals(reportPlugin, pluginDescriptor, goalsWithConfiguration);
190
191
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
199 reports.add(mavenReportExecution);
200 }
201 }
202
203 if (!reports.isEmpty()) {
204
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
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
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
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
349 execution = "'"
350 + (StringUtils.isEmpty(mojoDescriptor.getExecuteLifecycle())
351 ? ""
352 : ('[' + mojoDescriptor.getExecuteLifecycle() + ']'))
353 + mojoDescriptor.getExecutePhase() + "' forked phase execution";
354 } else {
355
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
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
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
457
458
459
460
461
462
463
464
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
478 Xpp3Dom mergedConfig = Xpp3DomUtils.mergeXpp3Dom(convert(reportSetConf), pluginConfig);
479
480 mergedConfig = Xpp3DomUtils.mergeXpp3Dom(mergedConfig, pluginMgmtConfig);
481
482 mergedConfig = Xpp3DomUtils.mergeXpp3Dom(mergedConfig, mojoConfig);
483
484
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
521
522
523
524
525
526
527
528
529
530
531
532
533
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
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
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
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
599
600
601
602
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
619
620
621
622
623
624
625
626
627
628
629
630
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 }