001package org.apache.maven.lifecycle.internal;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.lifecycle.DefaultLifecycles;
023import org.apache.maven.lifecycle.LifeCyclePluginAnalyzer;
024import org.apache.maven.lifecycle.Lifecycle;
025import org.apache.maven.lifecycle.mapping.LifecycleMapping;
026import org.apache.maven.lifecycle.mapping.LifecycleMojo;
027import org.apache.maven.lifecycle.mapping.LifecyclePhase;
028import org.apache.maven.model.Plugin;
029import org.apache.maven.model.PluginExecution;
030import org.codehaus.plexus.component.annotations.Component;
031import org.codehaus.plexus.component.annotations.Requirement;
032import org.codehaus.plexus.logging.Logger;
033import org.codehaus.plexus.util.StringUtils;
034
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.Comparator;
038import java.util.HashSet;
039import java.util.LinkedHashMap;
040import java.util.List;
041import java.util.Map;
042import java.util.Set;
043
044/**
045 * @since 3.0
046 * @author Benjamin Bentmann
047 * @author Jason van Zyl
048 * @author jdcasey
049 * @author Kristian Rosenvold (extracted class only)
050 *         <p/>
051 *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
052 */
053@Component( role = LifeCyclePluginAnalyzer.class )
054public class DefaultLifecyclePluginAnalyzer
055    implements LifeCyclePluginAnalyzer
056{
057
058    @Requirement( role = LifecycleMapping.class )
059    private Map<String, LifecycleMapping> lifecycleMappings;
060
061    @Requirement
062    private DefaultLifecycles defaultLifeCycles;
063
064    @Requirement
065    private Logger logger;
066
067    public DefaultLifecyclePluginAnalyzer()
068    {
069    }
070
071    // These methods deal with construction intact Plugin object that look like they come from a standard
072    // <plugin/> block in a Maven POM. We have to do some wiggling to pull the sources of information
073    // together and this really shows the problem of constructing a sensible default configuration but
074    // it's all encapsulated here so it appears normalized to the POM builder.
075
076    // We are going to take the project packaging and find all plugin in the default lifecycle and create
077    // fully populated Plugin objects, including executions with goals and default configuration taken
078    // from the plugin.xml inside a plugin.
079    //
080
081    public Set<Plugin> getPluginsBoundByDefaultToAllLifecycles( String packaging )
082    {
083        if ( logger.isDebugEnabled() )
084        {
085            logger.debug( "Looking up lifecyle mappings for packaging " + packaging + " from "
086                + Thread.currentThread().getContextClassLoader() );
087        }
088
089        LifecycleMapping lifecycleMappingForPackaging = lifecycleMappings.get( packaging );
090
091        if ( lifecycleMappingForPackaging == null )
092        {
093            return null;
094        }
095
096        Map<Plugin, Plugin> plugins = new LinkedHashMap<>();
097
098        for ( Lifecycle lifecycle : getOrderedLifecycles() )
099        {
100            org.apache.maven.lifecycle.mapping.Lifecycle lifecycleConfiguration =
101                lifecycleMappingForPackaging.getLifecycles().get( lifecycle.getId() );
102
103            Map<String, LifecyclePhase> phaseToGoalMapping = null;
104
105            if ( lifecycleConfiguration != null )
106            {
107                phaseToGoalMapping = lifecycleConfiguration.getLifecyclePhases();
108            }
109            else if ( lifecycle.getDefaultLifecyclePhases() != null )
110            {
111                phaseToGoalMapping = lifecycle.getDefaultLifecyclePhases();
112            }
113
114            if ( phaseToGoalMapping != null )
115            {
116                for ( Map.Entry<String, LifecyclePhase> goalsForLifecyclePhase : phaseToGoalMapping.entrySet() )
117                {
118                    String phase = goalsForLifecyclePhase.getKey();
119                    LifecyclePhase goals = goalsForLifecyclePhase.getValue();
120                    if ( goals != null )
121                    {
122                        parseLifecyclePhaseDefinitions( plugins, phase, goals );
123                    }
124                }
125            }
126        }
127
128        return plugins.keySet();
129    }
130
131    private List<Lifecycle> getOrderedLifecycles()
132    {
133        // NOTE: The lifecycle order can affect implied execution ids so we better be deterministic.
134
135        List<Lifecycle> lifecycles = new ArrayList<>( defaultLifeCycles.getLifeCycles() );
136
137        Collections.sort( lifecycles, new Comparator<Lifecycle>()
138        {
139
140            public int compare( Lifecycle l1, Lifecycle l2 )
141            {
142                return l1.getId().compareTo( l2.getId() );
143            }
144
145        } );
146
147        return lifecycles;
148    }
149
150    private void parseLifecyclePhaseDefinitions( Map<Plugin, Plugin> plugins, String phase, LifecyclePhase goals )
151    {
152        List<LifecycleMojo> mojos = goals.getMojos();
153        if ( mojos != null )
154        {
155            
156            for ( int i = 0; i < mojos.size(); i++ )
157            {
158                LifecycleMojo mojo = mojos.get( i );
159                
160                GoalSpec gs = parseGoalSpec( mojo.getGoal() );
161    
162                if ( gs == null )
163                {
164                    logger.warn( "Ignored invalid goal specification '" + mojo.getGoal()
165                            + "' from lifecycle mapping for phase " + phase );
166                    continue;
167                }
168    
169                Plugin plugin = new Plugin();
170                plugin.setGroupId( gs.groupId );
171                plugin.setArtifactId( gs.artifactId );
172                plugin.setVersion( gs.version );
173    
174                Plugin existing = plugins.get( plugin );
175                if ( existing != null )
176                {
177                    if ( existing.getVersion() == null )
178                    {
179                        existing.setVersion( plugin.getVersion() );
180                    }
181                    plugin = existing;
182                }
183                else
184                {
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                execution.setConfiguration( mojo.getConfiguration() );
194                
195                plugin.setDependencies( mojo.getDependencies() );
196                plugin.getExecutions().add( execution );
197            }
198        }
199    }
200
201    private GoalSpec parseGoalSpec( String goalSpec )
202    {
203        GoalSpec gs = new GoalSpec();
204
205        String[] p = StringUtils.split( goalSpec.trim(), ":" );
206
207        if ( p.length == 3 )
208        {
209            // <groupId>:<artifactId>:<goal>
210            gs.groupId = p[0];
211            gs.artifactId = p[1];
212            gs.goal = p[2];
213        }
214        else if ( p.length == 4 )
215        {
216            // <groupId>:<artifactId>:<version>:<goal>
217            gs.groupId = p[0];
218            gs.artifactId = p[1];
219            gs.version = p[2];
220            gs.goal = p[3];
221        }
222        else
223        {
224            // invalid
225            gs = null;
226        }
227
228        return gs;
229    }
230
231    private String getExecutionId( Plugin plugin, String goal )
232    {
233        Set<String> existingIds = new HashSet<>();
234        for ( PluginExecution execution : plugin.getExecutions() )
235        {
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        {
244            id = base + '-' + index;
245        }
246
247        return id;
248    }
249
250    static class GoalSpec
251    {
252
253        String groupId;
254
255        String artifactId;
256
257        String version;
258
259        String goal;
260
261    }
262
263}