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