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