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.util.HashSet;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.maven.api.services.Lookup;
32  import org.apache.maven.api.xml.XmlNode;
33  import org.apache.maven.internal.impl.DefaultLifecycleRegistry;
34  import org.apache.maven.lifecycle.DefaultLifecycles;
35  import org.apache.maven.lifecycle.LifeCyclePluginAnalyzer;
36  import org.apache.maven.lifecycle.Lifecycle;
37  import org.apache.maven.lifecycle.mapping.LifecycleMapping;
38  import org.apache.maven.lifecycle.mapping.LifecycleMojo;
39  import org.apache.maven.lifecycle.mapping.LifecyclePhase;
40  import org.apache.maven.model.InputLocation;
41  import org.apache.maven.model.InputSource;
42  import org.apache.maven.model.Plugin;
43  import org.apache.maven.model.PluginExecution;
44  import org.codehaus.plexus.util.xml.Xpp3Dom;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import static java.util.Objects.requireNonNull;
49  
50  /**
51   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
52   *
53   * @since 3.0
54   */
55  @Singleton
56  @Named
57  public class DefaultLifecyclePluginAnalyzer implements LifeCyclePluginAnalyzer {
58  
59      private final Logger logger = LoggerFactory.getLogger(getClass());
60  
61      private final Lookup lookup;
62  
63      private final DefaultLifecycles defaultLifeCycles;
64  
65      @Inject
66      public DefaultLifecyclePluginAnalyzer(Lookup lookup, DefaultLifecycles defaultLifeCycles) {
67          this.lookup = requireNonNull(lookup);
68          this.defaultLifeCycles = requireNonNull(defaultLifeCycles);
69      }
70  
71      // These methods deal with construction intact Plugin object that look like they come from a standard
72      // <plugin/> block in a Maven POM. We have to do some wiggling to pull the sources of information
73      // together and this really shows the problem of constructing a sensible default configuration but
74      // it's all encapsulated here so it appears normalized to the POM builder.
75  
76      // We are going to take the project packaging and find all plugins in the default lifecycle and create
77      // fully populated Plugin objects, including executions with goals and default configuration taken
78      // from the plugin.xml inside a plugin.
79      //
80  
81      @Override
82      public Set<Plugin> getPluginsBoundByDefaultToAllLifecycles(String packaging) {
83          if (logger.isDebugEnabled()) {
84              logger.debug("Looking up lifecycle mappings for packaging " + packaging + " from "
85                      + Thread.currentThread().getContextClassLoader());
86          }
87  
88          LifecycleMapping lifecycleMappingForPackaging = lookupLifecycleMapping(packaging);
89  
90          if (lifecycleMappingForPackaging == null) {
91              return null;
92          }
93  
94          Map<Plugin, Plugin> plugins = new LinkedHashMap<>();
95  
96          for (Lifecycle lifecycle : defaultLifeCycles.getLifeCycles()) {
97              org.apache.maven.lifecycle.mapping.Lifecycle lifecycleConfiguration =
98                      lifecycleMappingForPackaging.getLifecycles().get(lifecycle.getId());
99  
100             Map<String, LifecyclePhase> phaseToGoalMapping = null;
101 
102             if (lifecycleConfiguration != null) {
103                 phaseToGoalMapping = lifecycleConfiguration.getLifecyclePhases();
104             } else if (lifecycle.getDefaultLifecyclePhases() != null) {
105                 phaseToGoalMapping = lifecycle.getDefaultLifecyclePhases();
106             }
107 
108             if (phaseToGoalMapping != null) {
109                 for (Map.Entry<String, LifecyclePhase> goalsForLifecyclePhase : phaseToGoalMapping.entrySet()) {
110                     String phase = goalsForLifecyclePhase.getKey();
111                     LifecyclePhase goals = goalsForLifecyclePhase.getValue();
112                     if (goals != null) {
113                         parseLifecyclePhaseDefinitions(plugins, phase, goals);
114                     }
115                 }
116             }
117         }
118 
119         return plugins.keySet();
120     }
121 
122     /**
123      * Performs a lookup using Plexus API to make sure we can look up only "visible" (see Maven classloading) components
124      * from current module and for example not extensions coming from other modules.
125      */
126     private LifecycleMapping lookupLifecycleMapping(final String packaging) {
127         return lookup.lookupOptional(LifecycleMapping.class, packaging).orElse(null);
128     }
129 
130     private void parseLifecyclePhaseDefinitions(Map<Plugin, Plugin> plugins, String phase, LifecyclePhase goals) {
131         InputSource inputSource = new InputSource();
132         inputSource.setModelId(DefaultLifecycleRegistry.DEFAULT_LIFECYCLE_MODELID);
133         InputLocation location = new InputLocation(-1, -1, inputSource);
134         location.setLocation(0, location);
135 
136         List<LifecycleMojo> mojos = goals.getMojos();
137         if (mojos != null) {
138 
139             for (int i = 0; i < mojos.size(); i++) {
140                 LifecycleMojo mojo = mojos.get(i);
141 
142                 GoalSpec gs = parseGoalSpec(mojo.getGoal());
143 
144                 if (gs == null) {
145                     logger.warn(
146                             "Ignored invalid goal specification '{}' from lifecycle mapping for phase {}",
147                             mojo.getGoal(),
148                             phase);
149                     continue;
150                 }
151 
152                 Plugin plugin = new Plugin();
153                 plugin.setGroupId(gs.groupId);
154                 plugin.setArtifactId(gs.artifactId);
155                 plugin.setVersion(gs.version);
156 
157                 plugin.setLocation("", location);
158                 plugin.setLocation("groupId", location);
159                 plugin.setLocation("artifactId", location);
160                 plugin.setLocation("version", location);
161 
162                 Plugin existing = plugins.get(plugin);
163                 if (existing != null) {
164                     if (existing.getVersion() == null) {
165                         existing.setVersion(plugin.getVersion());
166                         existing.setLocation("version", location);
167                     }
168                     plugin = existing;
169                 } else {
170                     plugins.put(plugin, plugin);
171                 }
172 
173                 PluginExecution execution = new PluginExecution();
174                 execution.setId(getExecutionId(plugin, gs.goal));
175                 execution.setPhase(phase);
176                 execution.setPriority(i - mojos.size());
177                 execution.getGoals().add(gs.goal);
178 
179                 execution.setLocation("", location);
180                 execution.setLocation("id", location);
181                 execution.setLocation("phase", location);
182                 execution.setLocation("goals", location);
183 
184                 XmlNode lifecycleConfiguration = mojo.getConfiguration();
185                 if (lifecycleConfiguration != null) {
186                     execution.setConfiguration(new Xpp3Dom(lifecycleConfiguration));
187                 }
188 
189                 if (mojo.getDependencies() != null) {
190                     plugin.setDependencies(mojo.getDependencies());
191                 }
192                 plugin.getExecutions().add(execution);
193             }
194         }
195     }
196 
197     private GoalSpec parseGoalSpec(String goalSpec) {
198         GoalSpec gs = new GoalSpec();
199 
200         String[] p = goalSpec.trim().split(":");
201 
202         if (p.length == 3) {
203             // <groupId>:<artifactId>:<goal>
204             gs.groupId = p[0];
205             gs.artifactId = p[1];
206             gs.goal = p[2];
207         } else if (p.length == 4) {
208             // <groupId>:<artifactId>:<version>:<goal>
209             gs.groupId = p[0];
210             gs.artifactId = p[1];
211             gs.version = p[2];
212             gs.goal = p[3];
213         } else {
214             // invalid
215             gs = null;
216         }
217 
218         return gs;
219     }
220 
221     private String getExecutionId(Plugin plugin, String goal) {
222         Set<String> existingIds = new HashSet<>();
223         for (PluginExecution execution : plugin.getExecutions()) {
224             existingIds.add(execution.getId());
225         }
226 
227         String base = "default-" + goal;
228         String id = base;
229 
230         for (int index = 1; existingIds.contains(id); index++) {
231             id = base + '-' + index;
232         }
233 
234         return id;
235     }
236 
237     static class GoalSpec {
238 
239         String groupId;
240 
241         String artifactId;
242 
243         String version;
244 
245         String goal;
246     }
247 }