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