001package org.apache.maven.lifecycle.internal;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.maven.execution.MavenSession;
032import org.apache.maven.lifecycle.DefaultLifecycles;
033import org.apache.maven.lifecycle.Lifecycle;
034import org.apache.maven.lifecycle.LifecycleMappingDelegate;
035import org.apache.maven.lifecycle.LifecycleNotFoundException;
036import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
037import org.apache.maven.lifecycle.MavenExecutionPlan;
038import org.apache.maven.lifecycle.MojoExecutionConfigurator;
039import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
040import org.apache.maven.plugin.BuildPluginManager;
041import org.apache.maven.plugin.InvalidPluginDescriptorException;
042import org.apache.maven.plugin.MojoExecution;
043import org.apache.maven.plugin.MojoNotFoundException;
044import org.apache.maven.plugin.PluginDescriptorParsingException;
045import org.apache.maven.plugin.PluginNotFoundException;
046import org.apache.maven.plugin.PluginResolutionException;
047import org.apache.maven.plugin.descriptor.MojoDescriptor;
048import org.apache.maven.plugin.descriptor.Parameter;
049import org.apache.maven.plugin.descriptor.PluginDescriptor;
050import org.apache.maven.plugin.lifecycle.Execution;
051import org.apache.maven.plugin.lifecycle.Phase;
052import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
053import org.apache.maven.plugin.version.PluginVersionResolutionException;
054import org.apache.maven.plugin.version.PluginVersionResolver;
055import org.apache.maven.project.MavenProject;
056import org.codehaus.plexus.component.annotations.Component;
057import org.codehaus.plexus.component.annotations.Requirement;
058import org.codehaus.plexus.util.StringUtils;
059import org.codehaus.plexus.util.xml.Xpp3Dom;
060import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
061
062import com.google.common.collect.ImmutableMap;
063
064/**
065 * @since 3.0
066 * @author Benjamin Bentmann
067 * @author Kristian Rosenvold (Extract class)
068 *         <p/>
069 *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
070 */
071@Component( role = LifecycleExecutionPlanCalculator.class )
072public class DefaultLifecycleExecutionPlanCalculator
073    implements LifecycleExecutionPlanCalculator
074{
075    @Requirement
076    private PluginVersionResolver pluginVersionResolver;
077
078    @Requirement
079    private BuildPluginManager pluginManager;
080
081    @Requirement
082    private DefaultLifecycles defaultLifeCycles;
083
084    @Requirement
085    private MojoDescriptorCreator mojoDescriptorCreator;
086
087    @Requirement
088    private LifecyclePluginResolver lifecyclePluginResolver;
089
090    @Requirement( hint = DefaultLifecycleMappingDelegate.HINT )
091    private LifecycleMappingDelegate standardDelegate;
092
093    @Requirement
094    private Map<String, LifecycleMappingDelegate> delegates;
095
096    @Requirement
097    private Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators;
098
099    @SuppressWarnings( { "UnusedDeclaration" } )
100    public DefaultLifecycleExecutionPlanCalculator()
101    {
102    }
103
104    // Only used for testing
105    public DefaultLifecycleExecutionPlanCalculator( BuildPluginManager pluginManager,
106                                                    DefaultLifecycles defaultLifeCycles,
107                                                    MojoDescriptorCreator mojoDescriptorCreator,
108                                                    LifecyclePluginResolver lifecyclePluginResolver )
109    {
110        this.pluginManager = pluginManager;
111        this.defaultLifeCycles = defaultLifeCycles;
112        this.mojoDescriptorCreator = mojoDescriptorCreator;
113        this.lifecyclePluginResolver = lifecyclePluginResolver;
114        this.mojoExecutionConfigurators =
115            ImmutableMap.of( "default", (MojoExecutionConfigurator) new DefaultMojoExecutionConfigurator() );
116    }
117
118    @Override
119    public MavenExecutionPlan calculateExecutionPlan( MavenSession session, MavenProject project, List<Object> tasks,
120                                                      boolean setup )
121        throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
122        PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
123        NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException
124    {
125        lifecyclePluginResolver.resolveMissingPluginVersions( project, session );
126
127        final List<MojoExecution> executions = calculateMojoExecutions( session, project, tasks );
128
129        if ( setup )
130        {
131            setupMojoExecutions( session, project, executions );
132        }
133
134        final List<ExecutionPlanItem> planItem = ExecutionPlanItem.createExecutionPlanItems( project, executions );
135
136        return new MavenExecutionPlan( planItem, defaultLifeCycles );
137    }
138
139    @Override
140    public MavenExecutionPlan calculateExecutionPlan( MavenSession session, MavenProject project, List<Object> tasks )
141        throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
142        PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
143        NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException
144    {
145        return calculateExecutionPlan( session, project, tasks, true );
146    }
147
148    private void setupMojoExecutions( MavenSession session, MavenProject project, List<MojoExecution> mojoExecutions )
149        throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
150        MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
151        LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException
152    {
153        for ( MojoExecution mojoExecution : mojoExecutions )
154        {
155            setupMojoExecution( session, project, mojoExecution );
156        }
157    }
158
159    @Override
160    public void setupMojoExecution( MavenSession session, MavenProject project, MojoExecution mojoExecution )
161        throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
162        MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
163        LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException
164    {
165        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
166
167        if ( mojoDescriptor == null )
168        {
169            mojoDescriptor =
170                pluginManager.getMojoDescriptor( mojoExecution.getPlugin(), mojoExecution.getGoal(),
171                                                 project.getRemotePluginRepositories(),
172                                                 session.getRepositorySession() );
173
174            mojoExecution.setMojoDescriptor( mojoDescriptor );
175        }
176
177        mojoExecutionConfigurator( mojoExecution ).configure( project,
178                                                              mojoExecution,
179                                                        MojoExecution.Source.CLI.equals( mojoExecution.getSource() ) );
180
181        finalizeMojoConfiguration( mojoExecution );
182
183        calculateForkedExecutions( mojoExecution, session, project, new HashSet<MojoDescriptor>() );
184    }
185
186    public List<MojoExecution> calculateMojoExecutions( MavenSession session, MavenProject project, List<Object> tasks )
187        throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
188        MojoNotFoundException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
189        PluginVersionResolutionException, LifecyclePhaseNotFoundException
190    {
191        final List<MojoExecution> mojoExecutions = new ArrayList<>();
192
193        for ( Object task : tasks )
194        {
195            if ( task instanceof GoalTask )
196            {
197                String pluginGoal = ( (GoalTask) task ).pluginGoal;
198
199                String executionId = "default-cli";
200                int executionIdx = pluginGoal.indexOf( '@' );
201                if ( executionIdx > 0 )
202                {
203                    executionId = pluginGoal.substring( executionIdx + 1 );
204                }
205
206                MojoDescriptor mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor( pluginGoal, session, project );
207
208                MojoExecution mojoExecution = new MojoExecution( mojoDescriptor, executionId,
209                                                                 MojoExecution.Source.CLI );
210
211                mojoExecutions.add( mojoExecution );
212            }
213            else if ( task instanceof LifecycleTask )
214            {
215                String lifecyclePhase = ( (LifecycleTask) task ).getLifecyclePhase();
216
217                Map<String, List<MojoExecution>> phaseToMojoMapping =
218                    calculateLifecycleMappings( session, project, lifecyclePhase );
219
220                for ( List<MojoExecution> mojoExecutionsFromLifecycle : phaseToMojoMapping.values() )
221                {
222                    mojoExecutions.addAll( mojoExecutionsFromLifecycle );
223                }
224            }
225            else
226            {
227                throw new IllegalStateException( "unexpected task " + task );
228            }
229        }
230        return mojoExecutions;
231    }
232
233    private Map<String, List<MojoExecution>> calculateLifecycleMappings( MavenSession session, MavenProject project,
234                                                                         String lifecyclePhase )
235        throws LifecyclePhaseNotFoundException, PluginNotFoundException, PluginResolutionException,
236        PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException
237    {
238        /*
239         * Determine the lifecycle that corresponds to the given phase.
240         */
241
242        Lifecycle lifecycle = defaultLifeCycles.get( lifecyclePhase );
243
244        if ( lifecycle == null )
245        {
246            throw new LifecyclePhaseNotFoundException( "Unknown lifecycle phase \"" + lifecyclePhase
247                + "\". You must specify a valid lifecycle phase" + " or a goal in the format <plugin-prefix>:<goal> or"
248                + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
249                + defaultLifeCycles.getLifecyclePhaseList() + ".", lifecyclePhase );
250        }
251
252        LifecycleMappingDelegate delegate;
253        if ( Arrays.binarySearch( DefaultLifecycles.STANDARD_LIFECYCLES, lifecycle.getId() ) >= 0 )
254        {
255            delegate = standardDelegate;
256        }
257        else
258        {
259            delegate = delegates.get( lifecycle.getId() );
260            if ( delegate == null )
261            {
262                delegate = standardDelegate;
263            }
264        }
265
266        return delegate.calculateLifecycleMappings( session, project, lifecycle, lifecyclePhase );
267    }
268
269    /**
270     * Post-processes the effective configuration for the specified mojo execution. This step discards all parameters
271     * from the configuration that are not applicable to the mojo and injects the default values for any missing
272     * parameters.
273     *
274     * @param mojoExecution The mojo execution whose configuration should be finalized, must not be {@code null}.
275     */
276    private void finalizeMojoConfiguration( MojoExecution mojoExecution )
277    {
278        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
279
280        Xpp3Dom executionConfiguration = mojoExecution.getConfiguration();
281        if ( executionConfiguration == null )
282        {
283            executionConfiguration = new Xpp3Dom( "configuration" );
284        }
285
286        Xpp3Dom defaultConfiguration = getMojoConfiguration( mojoDescriptor );
287
288        Xpp3Dom finalConfiguration = new Xpp3Dom( "configuration" );
289
290        if ( mojoDescriptor.getParameters() != null )
291        {
292            for ( Parameter parameter : mojoDescriptor.getParameters() )
293            {
294                Xpp3Dom parameterConfiguration = executionConfiguration.getChild( parameter.getName() );
295
296                if ( parameterConfiguration == null )
297                {
298                    parameterConfiguration = executionConfiguration.getChild( parameter.getAlias() );
299                }
300
301                Xpp3Dom parameterDefaults = defaultConfiguration.getChild( parameter.getName() );
302
303                parameterConfiguration = Xpp3Dom.mergeXpp3Dom( parameterConfiguration, parameterDefaults,
304                                                               Boolean.TRUE );
305
306                if ( parameterConfiguration != null )
307                {
308                    parameterConfiguration = new Xpp3Dom( parameterConfiguration, parameter.getName() );
309
310                    if ( StringUtils.isEmpty( parameterConfiguration.getAttribute( "implementation" ) )
311                        && StringUtils.isNotEmpty( parameter.getImplementation() ) )
312                    {
313                        parameterConfiguration.setAttribute( "implementation", parameter.getImplementation() );
314                    }
315
316                    finalConfiguration.addChild( parameterConfiguration );
317                }
318            }
319        }
320
321        mojoExecution.setConfiguration( finalConfiguration );
322    }
323
324    private Xpp3Dom getMojoConfiguration( MojoDescriptor mojoDescriptor )
325    {
326        return MojoDescriptorCreator.convert( mojoDescriptor );
327    }
328
329    @Override
330    public void calculateForkedExecutions( MojoExecution mojoExecution, MavenSession session )
331        throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
332        PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
333        LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException
334    {
335        calculateForkedExecutions( mojoExecution, session, session.getCurrentProject(), new HashSet<MojoDescriptor>() );
336    }
337
338    private void calculateForkedExecutions( MojoExecution mojoExecution, MavenSession session, MavenProject project,
339                                            Collection<MojoDescriptor> alreadyForkedExecutions )
340        throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
341        PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
342        LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException
343    {
344        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
345
346        if ( !mojoDescriptor.isForking() )
347        {
348            return;
349        }
350
351        if ( !alreadyForkedExecutions.add( mojoDescriptor ) )
352        {
353            return;
354        }
355
356        List<MavenProject> forkedProjects =
357            LifecycleDependencyResolver.getProjects( project, session, mojoDescriptor.isAggregator() );
358
359        for ( MavenProject forkedProject : forkedProjects )
360        {
361            if ( forkedProject != project )
362            {
363                lifecyclePluginResolver.resolveMissingPluginVersions( forkedProject, session );
364            }
365
366            List<MojoExecution> forkedExecutions;
367
368            if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
369            {
370                forkedExecutions =
371                    calculateForkedLifecycle( mojoExecution, session, forkedProject, alreadyForkedExecutions );
372            }
373            else
374            {
375                forkedExecutions = calculateForkedGoal( mojoExecution, session, forkedProject,
376                                                        alreadyForkedExecutions );
377            }
378
379            mojoExecution.setForkedExecutions( BuilderCommon.getKey( forkedProject ), forkedExecutions );
380        }
381
382        alreadyForkedExecutions.remove( mojoDescriptor );
383    }
384
385    private List<MojoExecution> calculateForkedLifecycle( MojoExecution mojoExecution, MavenSession session,
386                                                          MavenProject project,
387                                                          Collection<MojoDescriptor> alreadyForkedExecutions )
388        throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
389        PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
390        LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException
391    {
392        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
393
394        String forkedPhase = mojoDescriptor.getExecutePhase();
395
396        Map<String, List<MojoExecution>> lifecycleMappings = calculateLifecycleMappings( session, project,
397                                                                                         forkedPhase );
398
399        for ( List<MojoExecution> forkedExecutions : lifecycleMappings.values() )
400        {
401            for ( MojoExecution forkedExecution : forkedExecutions )
402            {
403                if ( forkedExecution.getMojoDescriptor() == null )
404                {
405                    MojoDescriptor forkedMojoDescriptor =
406                        pluginManager.getMojoDescriptor( forkedExecution.getPlugin(), forkedExecution.getGoal(),
407                                                         project.getRemotePluginRepositories(),
408                                                         session.getRepositorySession() );
409
410                    forkedExecution.setMojoDescriptor( forkedMojoDescriptor );
411                }
412
413                mojoExecutionConfigurator( forkedExecution ).configure( project, forkedExecution, false );
414            }
415        }
416
417        injectLifecycleOverlay( lifecycleMappings, mojoExecution, session, project );
418
419        List<MojoExecution> mojoExecutions = new ArrayList<>();
420
421        for ( List<MojoExecution> forkedExecutions : lifecycleMappings.values() )
422        {
423            for ( MojoExecution forkedExecution : forkedExecutions )
424            {
425                if ( !alreadyForkedExecutions.contains( forkedExecution.getMojoDescriptor() ) )
426                {
427                    finalizeMojoConfiguration( forkedExecution );
428
429                    calculateForkedExecutions( forkedExecution, session, project, alreadyForkedExecutions );
430
431                    mojoExecutions.add( forkedExecution );
432                }
433            }
434        }
435
436        return mojoExecutions;
437    }
438
439    private void injectLifecycleOverlay( Map<String, List<MojoExecution>> lifecycleMappings,
440                                         MojoExecution mojoExecution, MavenSession session, MavenProject project )
441        throws PluginDescriptorParsingException, LifecycleNotFoundException, MojoNotFoundException,
442        PluginNotFoundException, PluginResolutionException, NoPluginFoundForPrefixException,
443        InvalidPluginDescriptorException, PluginVersionResolutionException
444    {
445        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
446
447        PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
448
449        String forkedLifecycle = mojoDescriptor.getExecuteLifecycle();
450
451        if ( StringUtils.isEmpty( forkedLifecycle ) )
452        {
453            return;
454        }
455
456        org.apache.maven.plugin.lifecycle.Lifecycle lifecycleOverlay;
457
458        try
459        {
460            lifecycleOverlay = pluginDescriptor.getLifecycleMapping( forkedLifecycle );
461        }
462        catch ( IOException | XmlPullParserException e )
463        {
464            throw new PluginDescriptorParsingException( pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e );
465        }
466
467        if ( lifecycleOverlay == null )
468        {
469            throw new LifecycleNotFoundException( forkedLifecycle );
470        }
471
472        for ( Phase phase : lifecycleOverlay.getPhases() )
473        {
474            List<MojoExecution> forkedExecutions = lifecycleMappings.get( phase.getId() );
475
476            if ( forkedExecutions != null )
477            {
478                for ( Execution execution : phase.getExecutions() )
479                {
480                    for ( String goal : execution.getGoals() )
481                    {
482                        MojoDescriptor forkedMojoDescriptor;
483
484                        if ( goal.indexOf( ':' ) < 0 )
485                        {
486                            forkedMojoDescriptor = pluginDescriptor.getMojo( goal );
487                            if ( forkedMojoDescriptor == null )
488                            {
489                                throw new MojoNotFoundException( goal, pluginDescriptor );
490                            }
491                        }
492                        else
493                        {
494                            forkedMojoDescriptor = mojoDescriptorCreator.getMojoDescriptor( goal, session, project );
495                        }
496
497                        MojoExecution forkedExecution =
498                            new MojoExecution( forkedMojoDescriptor, mojoExecution.getExecutionId() );
499
500                        Xpp3Dom forkedConfiguration = (Xpp3Dom) execution.getConfiguration();
501
502                        forkedExecution.setConfiguration( forkedConfiguration );
503
504                        mojoExecutionConfigurator( forkedExecution ).configure( project, forkedExecution, true );
505
506                        forkedExecutions.add( forkedExecution );
507                    }
508                }
509
510                Xpp3Dom phaseConfiguration = (Xpp3Dom) phase.getConfiguration();
511
512                if ( phaseConfiguration != null )
513                {
514                    for ( MojoExecution forkedExecution : forkedExecutions )
515                    {
516                        Xpp3Dom forkedConfiguration = forkedExecution.getConfiguration();
517
518                        forkedConfiguration = Xpp3Dom.mergeXpp3Dom( phaseConfiguration, forkedConfiguration );
519
520                        forkedExecution.setConfiguration( forkedConfiguration );
521                    }
522                }
523            }
524        }
525    }
526
527    // org.apache.maven.plugins:maven-remote-resources-plugin:1.0:process
528    // TODO: take repo mans into account as one may be aggregating prefixes of many
529    // TODO: collect at the root of the repository, read the one at the root, and fetch remote if something is missing
530    // or the user forces the issue
531
532    private List<MojoExecution> calculateForkedGoal( MojoExecution mojoExecution, MavenSession session,
533                                                     MavenProject project,
534                                                     Collection<MojoDescriptor> alreadyForkedExecutions )
535        throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
536        PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
537        LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException
538    {
539        MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
540
541        PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
542
543        String forkedGoal = mojoDescriptor.getExecuteGoal();
544
545        MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo( forkedGoal );
546        if ( forkedMojoDescriptor == null )
547        {
548            throw new MojoNotFoundException( forkedGoal, pluginDescriptor );
549        }
550
551        if ( alreadyForkedExecutions.contains( forkedMojoDescriptor ) )
552        {
553            return Collections.emptyList();
554        }
555
556        MojoExecution forkedExecution = new MojoExecution( forkedMojoDescriptor, forkedGoal );
557
558        mojoExecutionConfigurator( forkedExecution ).configure( project, forkedExecution, true );
559
560        finalizeMojoConfiguration( forkedExecution );
561
562        calculateForkedExecutions( forkedExecution, session, project, alreadyForkedExecutions );
563
564        return Collections.singletonList( forkedExecution );
565    }
566
567    private MojoExecutionConfigurator mojoExecutionConfigurator( MojoExecution mojoExecution )
568    {
569        String configuratorId = mojoExecution.getMojoDescriptor().getComponentConfigurator();
570        if ( configuratorId == null )
571        {
572            configuratorId = "default";
573        }
574
575        MojoExecutionConfigurator mojoExecutionConfigurator = mojoExecutionConfigurators.get( configuratorId );
576
577        if ( mojoExecutionConfigurator == null )
578        {
579            //
580            // The plugin has a custom component configurator but does not have a custom mojo execution configurator
581            // so fall back to the default mojo execution configurator.
582            //
583            mojoExecutionConfigurator = mojoExecutionConfigurators.get( "default" );
584        }
585        return mojoExecutionConfigurator;
586    }
587}