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.lifecycle.internal;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Map;
32  
33  import org.apache.maven.execution.MavenSession;
34  import org.apache.maven.lifecycle.DefaultLifecycles;
35  import org.apache.maven.lifecycle.Lifecycle;
36  import org.apache.maven.lifecycle.LifecycleMappingDelegate;
37  import org.apache.maven.lifecycle.LifecycleNotFoundException;
38  import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
39  import org.apache.maven.lifecycle.MavenExecutionPlan;
40  import org.apache.maven.lifecycle.MojoExecutionConfigurator;
41  import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
42  import org.apache.maven.plugin.BuildPluginManager;
43  import org.apache.maven.plugin.InvalidPluginDescriptorException;
44  import org.apache.maven.plugin.MojoExecution;
45  import org.apache.maven.plugin.MojoNotFoundException;
46  import org.apache.maven.plugin.PluginDescriptorParsingException;
47  import org.apache.maven.plugin.PluginNotFoundException;
48  import org.apache.maven.plugin.PluginResolutionException;
49  import org.apache.maven.plugin.descriptor.MojoDescriptor;
50  import org.apache.maven.plugin.descriptor.Parameter;
51  import org.apache.maven.plugin.descriptor.PluginDescriptor;
52  import org.apache.maven.plugin.lifecycle.Execution;
53  import org.apache.maven.plugin.lifecycle.Phase;
54  import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
55  import org.apache.maven.plugin.version.PluginVersionResolutionException;
56  import org.apache.maven.plugin.version.PluginVersionResolver;
57  import org.apache.maven.project.MavenProject;
58  import org.codehaus.plexus.util.StringUtils;
59  import org.codehaus.plexus.util.xml.Xpp3Dom;
60  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
61  
62  /**
63   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
64   *
65   * @since 3.0
66   * @author Benjamin Bentmann
67   * @author Kristian Rosenvold (Extract class)
68   */
69  @Singleton
70  @Named
71  public class DefaultLifecycleExecutionPlanCalculator implements LifecycleExecutionPlanCalculator {
72      @Inject
73      private PluginVersionResolver pluginVersionResolver;
74  
75      @Inject
76      private BuildPluginManager pluginManager;
77  
78      @Inject
79      private DefaultLifecycles defaultLifeCycles;
80  
81      @Inject
82      private MojoDescriptorCreator mojoDescriptorCreator;
83  
84      @Inject
85      private LifecyclePluginResolver lifecyclePluginResolver;
86  
87      @Inject
88      private LifecycleMappingDelegate standardDelegate;
89  
90      @Inject
91      private Map<String, LifecycleMappingDelegate> delegates;
92  
93      @Inject
94      private Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators;
95  
96      @SuppressWarnings({"UnusedDeclaration"})
97      public DefaultLifecycleExecutionPlanCalculator() {}
98  
99      // Only used for testing
100     public DefaultLifecycleExecutionPlanCalculator(
101             BuildPluginManager pluginManager,
102             DefaultLifecycles defaultLifeCycles,
103             MojoDescriptorCreator mojoDescriptorCreator,
104             LifecyclePluginResolver lifecyclePluginResolver) {
105         this.pluginManager = pluginManager;
106         this.defaultLifeCycles = defaultLifeCycles;
107         this.mojoDescriptorCreator = mojoDescriptorCreator;
108         this.lifecyclePluginResolver = lifecyclePluginResolver;
109         this.mojoExecutionConfigurators =
110                 Collections.singletonMap("default", (MojoExecutionConfigurator) new DefaultMojoExecutionConfigurator());
111     }
112 
113     @Override
114     public MavenExecutionPlan calculateExecutionPlan(
115             MavenSession session, MavenProject project, List<Object> tasks, boolean setup)
116             throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
117                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
118                     NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException {
119         lifecyclePluginResolver.resolveMissingPluginVersions(project, session);
120 
121         final List<MojoExecution> executions = calculateMojoExecutions(session, project, tasks);
122 
123         if (setup) {
124             setupMojoExecutions(session, project, executions);
125         }
126 
127         final List<ExecutionPlanItem> planItem = ExecutionPlanItem.createExecutionPlanItems(project, executions);
128 
129         return new MavenExecutionPlan(planItem, defaultLifeCycles);
130     }
131 
132     @Override
133     public MavenExecutionPlan calculateExecutionPlan(MavenSession session, MavenProject project, List<Object> tasks)
134             throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException,
135                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException,
136                     NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException {
137         return calculateExecutionPlan(session, project, tasks, true);
138     }
139 
140     private void setupMojoExecutions(MavenSession session, MavenProject project, List<MojoExecution> mojoExecutions)
141             throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
142                     MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
143                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
144         for (MojoExecution mojoExecution : mojoExecutions) {
145             setupMojoExecution(session, project, mojoExecution);
146         }
147     }
148 
149     @Override
150     public void setupMojoExecution(MavenSession session, MavenProject project, MojoExecution mojoExecution)
151             throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
152                     MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException,
153                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
154         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
155 
156         if (mojoDescriptor == null) {
157             mojoDescriptor = pluginManager.getMojoDescriptor(
158                     mojoExecution.getPlugin(),
159                     mojoExecution.getGoal(),
160                     project.getRemotePluginRepositories(),
161                     session.getRepositorySession());
162 
163             mojoExecution.setMojoDescriptor(mojoDescriptor);
164         }
165 
166         mojoExecutionConfigurator(mojoExecution)
167                 .configure(project, mojoExecution, MojoExecution.Source.CLI.equals(mojoExecution.getSource()));
168 
169         finalizeMojoConfiguration(mojoExecution);
170 
171         calculateForkedExecutions(mojoExecution, session, project, new HashSet<MojoDescriptor>());
172     }
173 
174     public List<MojoExecution> calculateMojoExecutions(MavenSession session, MavenProject project, List<Object> tasks)
175             throws PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
176                     MojoNotFoundException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
177                     PluginVersionResolutionException, LifecyclePhaseNotFoundException {
178         final List<MojoExecution> mojoExecutions = new ArrayList<>();
179 
180         for (Object task : tasks) {
181             if (task instanceof GoalTask) {
182                 String pluginGoal = ((GoalTask) task).pluginGoal;
183 
184                 String executionId = "default-cli";
185                 int executionIdx = pluginGoal.indexOf('@');
186                 if (executionIdx > 0) {
187                     executionId = pluginGoal.substring(executionIdx + 1);
188                 }
189 
190                 MojoDescriptor mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor(pluginGoal, session, project);
191 
192                 MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, executionId, MojoExecution.Source.CLI);
193 
194                 mojoExecutions.add(mojoExecution);
195             } else if (task instanceof LifecycleTask) {
196                 String lifecyclePhase = ((LifecycleTask) task).getLifecyclePhase();
197 
198                 Map<String, List<MojoExecution>> phaseToMojoMapping =
199                         calculateLifecycleMappings(session, project, lifecyclePhase);
200 
201                 for (List<MojoExecution> mojoExecutionsFromLifecycle : phaseToMojoMapping.values()) {
202                     mojoExecutions.addAll(mojoExecutionsFromLifecycle);
203                 }
204             } else {
205                 throw new IllegalStateException("unexpected task " + task);
206             }
207         }
208         return mojoExecutions;
209     }
210 
211     private Map<String, List<MojoExecution>> calculateLifecycleMappings(
212             MavenSession session, MavenProject project, String lifecyclePhase)
213             throws LifecyclePhaseNotFoundException, PluginNotFoundException, PluginResolutionException,
214                     PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException {
215         /*
216          * Determine the lifecycle that corresponds to the given phase.
217          */
218 
219         Lifecycle lifecycle = defaultLifeCycles.get(lifecyclePhase);
220 
221         if (lifecycle == null) {
222             throw new LifecyclePhaseNotFoundException(
223                     "Unknown lifecycle phase \"" + lifecyclePhase
224                             + "\". You must specify a valid lifecycle phase"
225                             + " or a goal in the format <plugin-prefix>:<goal> or"
226                             + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
227                             + defaultLifeCycles.getLifecyclePhaseList() + ".",
228                     lifecyclePhase);
229         }
230 
231         LifecycleMappingDelegate delegate;
232         if (defaultLifeCycles.getStandardLifecycles().contains(lifecycle.getId())) {
233             delegate = standardDelegate;
234         } else {
235             delegate = delegates.get(lifecycle.getId());
236             if (delegate == null) {
237                 delegate = standardDelegate;
238             }
239         }
240 
241         return delegate.calculateLifecycleMappings(session, project, lifecycle, lifecyclePhase);
242     }
243 
244     /**
245      * Post-processes the effective configuration for the specified mojo execution. This step discards all parameters
246      * from the configuration that are not applicable to the mojo and injects the default values for any missing
247      * parameters.
248      *
249      * @param mojoExecution The mojo execution whose configuration should be finalized, must not be {@code null}.
250      */
251     private void finalizeMojoConfiguration(MojoExecution mojoExecution) {
252         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
253 
254         Xpp3Dom executionConfiguration = mojoExecution.getConfiguration();
255         if (executionConfiguration == null) {
256             executionConfiguration = new Xpp3Dom("configuration");
257         }
258 
259         Xpp3Dom defaultConfiguration = getMojoConfiguration(mojoDescriptor);
260 
261         Xpp3Dom finalConfiguration = new Xpp3Dom("configuration");
262 
263         if (mojoDescriptor.getParameters() != null) {
264             for (Parameter parameter : mojoDescriptor.getParameters()) {
265                 Xpp3Dom parameterConfiguration = executionConfiguration.getChild(parameter.getName());
266 
267                 if (parameterConfiguration == null) {
268                     parameterConfiguration = executionConfiguration.getChild(parameter.getAlias());
269                 }
270 
271                 Xpp3Dom parameterDefaults = defaultConfiguration.getChild(parameter.getName());
272 
273                 parameterConfiguration = Xpp3Dom.mergeXpp3Dom(parameterConfiguration, parameterDefaults, Boolean.TRUE);
274 
275                 if (parameterConfiguration != null) {
276                     parameterConfiguration = new Xpp3Dom(parameterConfiguration, parameter.getName());
277 
278                     if (StringUtils.isEmpty(parameterConfiguration.getAttribute("implementation"))
279                             && StringUtils.isNotEmpty(parameter.getImplementation())) {
280                         parameterConfiguration.setAttribute("implementation", parameter.getImplementation());
281                     }
282 
283                     finalConfiguration.addChild(parameterConfiguration);
284                 }
285             }
286         }
287 
288         mojoExecution.setConfiguration(finalConfiguration);
289     }
290 
291     private Xpp3Dom getMojoConfiguration(MojoDescriptor mojoDescriptor) {
292         return MojoDescriptorCreator.convert(mojoDescriptor);
293     }
294 
295     @Override
296     public void calculateForkedExecutions(MojoExecution mojoExecution, MavenSession session)
297             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
298                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
299                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
300         calculateForkedExecutions(mojoExecution, session, session.getCurrentProject(), new HashSet<MojoDescriptor>());
301     }
302 
303     private void calculateForkedExecutions(
304             MojoExecution mojoExecution,
305             MavenSession session,
306             MavenProject project,
307             Collection<MojoDescriptor> alreadyForkedExecutions)
308             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
309                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
310                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
311         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
312 
313         if (!mojoDescriptor.isForking()) {
314             return;
315         }
316 
317         if (!alreadyForkedExecutions.add(mojoDescriptor)) {
318             return;
319         }
320 
321         List<MavenProject> forkedProjects =
322                 LifecycleDependencyResolver.getProjects(project, session, mojoDescriptor.isAggregator());
323 
324         for (MavenProject forkedProject : forkedProjects) {
325             if (forkedProject != project) {
326                 lifecyclePluginResolver.resolveMissingPluginVersions(forkedProject, session);
327             }
328 
329             List<MojoExecution> forkedExecutions;
330 
331             if (StringUtils.isNotEmpty(mojoDescriptor.getExecutePhase())) {
332                 forkedExecutions =
333                         calculateForkedLifecycle(mojoExecution, session, forkedProject, alreadyForkedExecutions);
334             } else {
335                 forkedExecutions = calculateForkedGoal(mojoExecution, session, forkedProject, alreadyForkedExecutions);
336             }
337 
338             mojoExecution.setForkedExecutions(BuilderCommon.getKey(forkedProject), forkedExecutions);
339         }
340 
341         alreadyForkedExecutions.remove(mojoDescriptor);
342     }
343 
344     private List<MojoExecution> calculateForkedLifecycle(
345             MojoExecution mojoExecution,
346             MavenSession session,
347             MavenProject project,
348             Collection<MojoDescriptor> alreadyForkedExecutions)
349             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
350                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
351                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
352         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
353 
354         String forkedPhase = mojoDescriptor.getExecutePhase();
355 
356         Map<String, List<MojoExecution>> lifecycleMappings = calculateLifecycleMappings(session, project, forkedPhase);
357 
358         for (List<MojoExecution> forkedExecutions : lifecycleMappings.values()) {
359             for (MojoExecution forkedExecution : forkedExecutions) {
360                 if (forkedExecution.getMojoDescriptor() == null) {
361                     MojoDescriptor forkedMojoDescriptor = pluginManager.getMojoDescriptor(
362                             forkedExecution.getPlugin(),
363                             forkedExecution.getGoal(),
364                             project.getRemotePluginRepositories(),
365                             session.getRepositorySession());
366 
367                     forkedExecution.setMojoDescriptor(forkedMojoDescriptor);
368                 }
369 
370                 mojoExecutionConfigurator(forkedExecution).configure(project, forkedExecution, false);
371             }
372         }
373 
374         injectLifecycleOverlay(lifecycleMappings, mojoExecution, session, project);
375 
376         List<MojoExecution> mojoExecutions = new ArrayList<>();
377 
378         for (List<MojoExecution> forkedExecutions : lifecycleMappings.values()) {
379             for (MojoExecution forkedExecution : forkedExecutions) {
380                 if (!alreadyForkedExecutions.contains(forkedExecution.getMojoDescriptor())) {
381                     finalizeMojoConfiguration(forkedExecution);
382 
383                     calculateForkedExecutions(forkedExecution, session, project, alreadyForkedExecutions);
384 
385                     mojoExecutions.add(forkedExecution);
386                 }
387             }
388         }
389 
390         return mojoExecutions;
391     }
392 
393     private void injectLifecycleOverlay(
394             Map<String, List<MojoExecution>> lifecycleMappings,
395             MojoExecution mojoExecution,
396             MavenSession session,
397             MavenProject project)
398             throws PluginDescriptorParsingException, LifecycleNotFoundException, MojoNotFoundException,
399                     PluginNotFoundException, PluginResolutionException, NoPluginFoundForPrefixException,
400                     InvalidPluginDescriptorException, PluginVersionResolutionException {
401         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
402 
403         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
404 
405         String forkedLifecycle = mojoDescriptor.getExecuteLifecycle();
406 
407         if (StringUtils.isEmpty(forkedLifecycle)) {
408             return;
409         }
410 
411         org.apache.maven.plugin.lifecycle.Lifecycle lifecycleOverlay;
412 
413         try {
414             lifecycleOverlay = pluginDescriptor.getLifecycleMapping(forkedLifecycle);
415         } catch (IOException | XmlPullParserException e) {
416             throw new PluginDescriptorParsingException(pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e);
417         }
418 
419         if (lifecycleOverlay == null) {
420             throw new LifecycleNotFoundException(forkedLifecycle);
421         }
422 
423         for (Phase phase : lifecycleOverlay.getPhases()) {
424             List<MojoExecution> forkedExecutions = lifecycleMappings.get(phase.getId());
425 
426             if (forkedExecutions != null) {
427                 for (Execution execution : phase.getExecutions()) {
428                     for (String goal : execution.getGoals()) {
429                         MojoDescriptor forkedMojoDescriptor;
430 
431                         if (goal.indexOf(':') < 0) {
432                             forkedMojoDescriptor = pluginDescriptor.getMojo(goal);
433                             if (forkedMojoDescriptor == null) {
434                                 throw new MojoNotFoundException(goal, pluginDescriptor);
435                             }
436                         } else {
437                             forkedMojoDescriptor = mojoDescriptorCreator.getMojoDescriptor(goal, session, project);
438                         }
439 
440                         MojoExecution forkedExecution =
441                                 new MojoExecution(forkedMojoDescriptor, mojoExecution.getExecutionId());
442 
443                         Xpp3Dom forkedConfiguration = (Xpp3Dom) execution.getConfiguration();
444 
445                         forkedExecution.setConfiguration(forkedConfiguration);
446 
447                         mojoExecutionConfigurator(forkedExecution).configure(project, forkedExecution, true);
448 
449                         forkedExecutions.add(forkedExecution);
450                     }
451                 }
452 
453                 Xpp3Dom phaseConfiguration = (Xpp3Dom) phase.getConfiguration();
454 
455                 if (phaseConfiguration != null) {
456                     for (MojoExecution forkedExecution : forkedExecutions) {
457                         Xpp3Dom forkedConfiguration = forkedExecution.getConfiguration();
458 
459                         forkedConfiguration = Xpp3Dom.mergeXpp3Dom(phaseConfiguration, forkedConfiguration);
460 
461                         forkedExecution.setConfiguration(forkedConfiguration);
462                     }
463                 }
464             }
465         }
466     }
467 
468     // org.apache.maven.plugins:maven-remote-resources-plugin:1.0:process
469     // TODO take repo mans into account as one may be aggregating prefixes of many
470     // TODO collect at the root of the repository, read the one at the root, and fetch remote if something is missing
471     // or the user forces the issue
472 
473     private List<MojoExecution> calculateForkedGoal(
474             MojoExecution mojoExecution,
475             MavenSession session,
476             MavenProject project,
477             Collection<MojoDescriptor> alreadyForkedExecutions)
478             throws MojoNotFoundException, PluginNotFoundException, PluginResolutionException,
479                     PluginDescriptorParsingException, NoPluginFoundForPrefixException, InvalidPluginDescriptorException,
480                     LifecyclePhaseNotFoundException, LifecycleNotFoundException, PluginVersionResolutionException {
481         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
482 
483         PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
484 
485         String forkedGoal = mojoDescriptor.getExecuteGoal();
486 
487         MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo(forkedGoal);
488         if (forkedMojoDescriptor == null) {
489             throw new MojoNotFoundException(forkedGoal, pluginDescriptor);
490         }
491 
492         if (alreadyForkedExecutions.contains(forkedMojoDescriptor)) {
493             return Collections.emptyList();
494         }
495 
496         MojoExecution forkedExecution = new MojoExecution(forkedMojoDescriptor, forkedGoal);
497 
498         mojoExecutionConfigurator(forkedExecution).configure(project, forkedExecution, true);
499 
500         finalizeMojoConfiguration(forkedExecution);
501 
502         calculateForkedExecutions(forkedExecution, session, project, alreadyForkedExecutions);
503 
504         return Collections.singletonList(forkedExecution);
505     }
506 
507     private MojoExecutionConfigurator mojoExecutionConfigurator(MojoExecution mojoExecution) {
508         String configuratorId = mojoExecution.getMojoDescriptor().getComponentConfigurator();
509         if (configuratorId == null) {
510             configuratorId = "default";
511         }
512 
513         MojoExecutionConfigurator mojoExecutionConfigurator = mojoExecutionConfigurators.get(configuratorId);
514 
515         if (mojoExecutionConfigurator == null) {
516             //
517             // The plugin has a custom component configurator but does not have a custom mojo execution configurator
518             // so fall back to the default mojo execution configurator.
519             //
520             mojoExecutionConfigurator = mojoExecutionConfigurators.get("default");
521         }
522         return mojoExecutionConfigurator;
523     }
524 }