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