001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.enforcer.rules;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Objects;
033import java.util.Set;
034
035import org.apache.maven.BuildFailureException;
036import org.apache.maven.RepositoryUtils;
037import org.apache.maven.artifact.Artifact;
038import org.apache.maven.artifact.factory.ArtifactFactory;
039import org.apache.maven.artifact.repository.ArtifactRepository;
040import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
041import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
042import org.apache.maven.artifact.versioning.VersionRange;
043import org.apache.maven.enforcer.rule.api.EnforcerRuleError;
044import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
045import org.apache.maven.enforcer.rules.utils.EnforcerRuleUtils;
046import org.apache.maven.enforcer.rules.utils.ExpressionEvaluator;
047import org.apache.maven.enforcer.rules.utils.PluginWrapper;
048import org.apache.maven.execution.MavenSession;
049import org.apache.maven.lifecycle.DefaultLifecycles;
050import org.apache.maven.lifecycle.Lifecycle;
051import org.apache.maven.lifecycle.LifecycleExecutionException;
052import org.apache.maven.lifecycle.mapping.LifecycleMapping;
053import org.apache.maven.model.BuildBase;
054import org.apache.maven.model.Model;
055import org.apache.maven.model.ModelBase;
056import org.apache.maven.model.Plugin;
057import org.apache.maven.model.PluginConfiguration;
058import org.apache.maven.model.PluginContainer;
059import org.apache.maven.model.Profile;
060import org.apache.maven.model.ReportPlugin;
061import org.apache.maven.model.Reporting;
062import org.apache.maven.plugin.InvalidPluginException;
063import org.apache.maven.plugin.PluginManager;
064import org.apache.maven.plugin.PluginManagerException;
065import org.apache.maven.plugin.PluginNotFoundException;
066import org.apache.maven.plugin.descriptor.PluginDescriptor;
067import org.apache.maven.plugin.version.PluginVersionNotFoundException;
068import org.apache.maven.plugin.version.PluginVersionResolutionException;
069import org.apache.maven.project.MavenProject;
070import org.apache.maven.rtinfo.RuntimeInformation;
071import org.apache.maven.settings.Settings;
072import org.codehaus.plexus.PlexusContainer;
073import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
074import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
075import org.codehaus.plexus.util.StringUtils;
076import org.eclipse.aether.RepositorySystem;
077import org.eclipse.aether.resolution.ArtifactRequest;
078import org.eclipse.aether.resolution.ArtifactResolutionException;
079
080import static java.util.Optional.ofNullable;
081
082/**
083 * This rule will enforce that all plugins specified in the poms have a version declared.
084 *
085 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
086 */
087@Named("requirePluginVersions")
088public final class RequirePluginVersions extends AbstractStandardEnforcerRule {
089
090    /**
091     * Don't allow the LATEST identifier.
092     */
093    private boolean banLatest = true;
094
095    /**
096     * Don't allow the RELEASE identifier.
097     */
098    private boolean banRelease = true;
099
100    /**
101     * Don't allow snapshot plugins.
102     */
103    private boolean banSnapshots = true;
104
105    /**
106     * Don't allow timestamp snapshot plugins.
107     */
108    private boolean banTimestamps = true;
109
110    /**
111     * @since 3.0.0
112     */
113    private boolean banMavenDefaults = true;
114
115    /**
116     * The comma separated list of phases that should be used to find lifecycle plugin bindings. The default value is
117     * "clean,deploy,site".
118     */
119    private String phases = "clean,deploy,site";
120
121    /**
122     * Additional plugins to enforce have versions. These are plugins that may not be in the poms but are used anyway,
123     * like help, eclipse etc. <br>
124     * The plugins should be specified in the form: <code>group:artifactId</code>.
125     */
126    private List<String> additionalPlugins;
127
128    /**
129     * Plugins to skip for version enforcement. The plugins should be specified in the form:
130     * <code>group:artifactId</code>. NOTE: This is deprecated, use unCheckedPluginList instead.
131     */
132    private List<String> unCheckedPlugins;
133
134    /**
135     * Same as unCheckedPlugins but as a comma list to better support properties. Sample form:
136     * <code>group:artifactId,group2:artifactId2</code>
137     *
138     * @since 1.0-beta-1
139     */
140    private String unCheckedPluginList;
141
142    /** The phase to lifecycle map. */
143    private Map<String, Lifecycle> phaseToLifecycleMap;
144
145    /** The lifecycles. */
146    private Collection<Lifecycle> lifecycles;
147
148    /** The plugin manager. */
149    private final PluginManager pluginManager;
150
151    /** The factory. */
152    private final ArtifactFactory factory;
153
154    private final RepositorySystem repositorySystem;
155
156    /** The session. */
157    private final MavenSession session;
158
159    /** The utils. */
160    private final EnforcerRuleUtils utils;
161
162    private final RuntimeInformation runtimeInformation;
163
164    private final DefaultLifecycles defaultLifeCycles;
165
166    private final MavenProject project;
167
168    private final ExpressionEvaluator evaluator;
169
170    private final PlexusContainer container;
171
172    @Inject
173    public RequirePluginVersions(
174            PluginManager pluginManager,
175            ArtifactFactory factory,
176            RepositorySystem repositorySystem,
177            MavenSession session,
178            EnforcerRuleUtils utils,
179            RuntimeInformation runtimeInformation,
180            DefaultLifecycles defaultLifeCycles,
181            MavenProject project,
182            ExpressionEvaluator evaluator,
183            PlexusContainer container) {
184        this.pluginManager = Objects.requireNonNull(pluginManager);
185        this.factory = Objects.requireNonNull(factory);
186        this.repositorySystem = Objects.requireNonNull(repositorySystem);
187        this.session = Objects.requireNonNull(session);
188        this.utils = Objects.requireNonNull(utils);
189        this.runtimeInformation = Objects.requireNonNull(runtimeInformation);
190        this.defaultLifeCycles = Objects.requireNonNull(defaultLifeCycles);
191        this.project = Objects.requireNonNull(project);
192        this.evaluator = Objects.requireNonNull(evaluator);
193        this.container = Objects.requireNonNull(container);
194    }
195
196    @Override
197    public void execute() throws EnforcerRuleException {
198
199        try {
200            // get the various expressions out of the helper.
201
202            lifecycles = defaultLifeCycles.getLifeCycles();
203
204            // get all the plugins that are bound to the specified lifecycles
205            Set<Plugin> allPlugins = getBoundPlugins(project, phases);
206
207            // insert any additional plugins specified by the user.
208            allPlugins = addAdditionalPlugins(allPlugins, additionalPlugins);
209            allPlugins.addAll(getProfilePlugins(project));
210
211            // pull out any we should skip
212            allPlugins =
213                    removeUncheckedPlugins(combineUncheckedPlugins(unCheckedPlugins, unCheckedPluginList), allPlugins);
214
215            // there's nothing to do here
216            if (allPlugins.isEmpty()) {
217                getLog().info("No plugin bindings found.");
218                return;
219            } else {
220                getLog().debug("All Plugins in use: " + allPlugins);
221            }
222
223            // get all the plugins that are mentioned in the pom (and parents)
224            List<PluginWrapper> pluginWrappers = getAllPluginEntries(project);
225
226            for (PluginWrapper pluginWrapper : pluginWrappers) {
227                getLog().debug("pluginWrappers: " + pluginWrapper.getGroupId() + ":" + pluginWrapper.getArtifactId()
228                        + ":" + pluginWrapper.getVersion() + " source: " + pluginWrapper.getSource());
229            }
230            // now look for the versions that aren't valid and add to a list.
231            List<Plugin> failures = new ArrayList<>();
232
233            for (Plugin plugin : allPlugins) {
234                if (!hasValidVersionSpecified(plugin, pluginWrappers)) {
235                    failures.add(plugin);
236                }
237            }
238
239            // if anything was found, log it then append the optional message.
240            if (!failures.isEmpty()) {
241                handleMessagesToTheUser(project, failures);
242            }
243        } catch (PluginNotFoundException | LifecycleExecutionException e) {
244            throw new EnforcerRuleException(e.getLocalizedMessage(), e);
245        }
246    }
247
248    private void handleMessagesToTheUser(MavenProject project, List<Plugin> failures) throws EnforcerRuleException {
249        StringBuilder newMsg = new StringBuilder();
250        newMsg.append("Some plugins are missing valid versions or depend on Maven ");
251        newMsg.append(runtimeInformation.getMavenVersion());
252        newMsg.append(" defaults");
253        handleBanMessages(newMsg);
254        newMsg.append(System.lineSeparator());
255        for (Plugin plugin : failures) {
256            newMsg.append("   ");
257            newMsg.append(plugin.getGroupId());
258            newMsg.append(":");
259            newMsg.append(plugin.getArtifactId());
260
261            try {
262                newMsg.append(". \tThe version currently in use is ");
263
264                Plugin currentPlugin = findCurrentPlugin(plugin, project);
265
266                if (currentPlugin == null) {
267                    newMsg.append("unknown");
268                } else {
269                    newMsg.append(currentPlugin.getVersion());
270
271                    if (PluginWrapper.isVersionFromDefaultLifecycleBindings(currentPlugin)
272                            .orElse(false)) {
273                        newMsg.append(" via default lifecycle bindings");
274                    } else {
275                        String msg = PluginWrapper.isVersionFromSuperpom(currentPlugin)
276                                .filter(b -> b)
277                                .map(t -> " via super POM")
278                                // for Maven 3.6.0 or before (MNG-6593 / MNG-6600)
279                                .orElse(" via super POM or default lifecycle bindings");
280                        newMsg.append(msg);
281                    }
282                }
283            } catch (Exception e) {
284                // lots can go wrong here. Don't allow any issues trying to
285                // determine the issue stop me
286                getLog().debug("Exception while determining plugin Version " + e.getMessage());
287                newMsg.append(". Unable to determine the plugin version.");
288            }
289            newMsg.append(System.lineSeparator());
290        }
291        String message = getMessage();
292        if (message != null && !message.isEmpty()) {
293            newMsg.append(message);
294        }
295
296        throw new EnforcerRuleException(newMsg.toString());
297    }
298
299    private void handleBanMessages(StringBuilder newMsg) {
300        if (banLatest || banRelease || banSnapshots || banTimestamps) {
301            List<String> banList = new ArrayList<>();
302            if (banLatest) {
303                banList.add("LATEST");
304            }
305            if (banRelease) {
306                banList.add("RELEASE");
307            }
308            if (banSnapshots) {
309                banList.add("SNAPSHOT");
310                if (banTimestamps) {
311                    banList.add("TIMESTAMP SNAPSHOT");
312                }
313            }
314            if (!banList.isEmpty()) {
315                newMsg.append(" (");
316                newMsg.append(String.join(", ", banList));
317                newMsg.append(" as plugin version are not allowed)");
318            }
319        }
320    }
321
322    /**
323     * Remove the plugins that the user doesn't want to check.
324     *
325     * @param uncheckedPlugins
326     * @param plugins
327     * @return The plugins which have been removed.
328     */
329    Set<Plugin> removeUncheckedPlugins(Collection<String> uncheckedPlugins, Set<Plugin> plugins)
330            throws EnforcerRuleError {
331        if (uncheckedPlugins != null && !uncheckedPlugins.isEmpty()) {
332            for (String pluginKey : uncheckedPlugins) {
333                Plugin plugin = parsePluginString(pluginKey, "UncheckedPlugins");
334                plugins.remove(plugin);
335            }
336        }
337        return plugins;
338    }
339
340    /**
341     * Combines the old Collection with the new comma separated list.
342     *
343     * @param uncheckedPlugins     a new collections
344     * @param uncheckedPluginsList a list to merge
345     * @return List of unchecked plugins.
346     */
347    public Collection<String> combineUncheckedPlugins(
348            Collection<String> uncheckedPlugins, String uncheckedPluginsList) {
349        // if the comma list is empty, then there's nothing to do here.
350        if (uncheckedPluginsList != null && !uncheckedPluginsList.isEmpty()) {
351            // make sure there is a collection to add to.
352            if (uncheckedPlugins == null) {
353                uncheckedPlugins = new HashSet<>();
354            } else if (!uncheckedPlugins.isEmpty()) {
355                getLog().warn("The parameter 'unCheckedPlugins' is deprecated. Use 'unCheckedPluginList' instead");
356            }
357
358            uncheckedPlugins.addAll(Arrays.asList(uncheckedPluginsList.split(",")));
359        }
360        return uncheckedPlugins;
361    }
362
363    /**
364     * Add the additional plugins if they don't exist yet.
365     *
366     * @param existing   the existing
367     * @param additional the additional
368     * @return the sets the
369     * @throws EnforcerRuleError the enforcer error
370     */
371    public Set<Plugin> addAdditionalPlugins(Set<Plugin> existing, List<String> additional) throws EnforcerRuleError {
372        if (additional != null) {
373            for (String pluginString : additional) {
374                Plugin plugin = parsePluginString(pluginString, "AdditionalPlugins");
375
376                if (existing == null) {
377                    existing = new HashSet<>();
378                    existing.add(plugin);
379                } else if (!existing.contains(plugin)) {
380                    existing.add(plugin);
381                }
382            }
383        }
384        return existing;
385    }
386
387    /**
388     * Helper method to parse and inject a Plugin.
389     *
390     * @param pluginString a plugin description to parse
391     * @param field        a source of pluginString
392     * @return the prepared plugin
393     */
394    private Plugin parsePluginString(String pluginString, String field) throws EnforcerRuleError {
395        if (pluginString != null) {
396            String[] pluginStrings = pluginString.split(":");
397            if (pluginStrings.length == 2) {
398                Plugin plugin = new Plugin();
399                plugin.setGroupId(StringUtils.strip(pluginStrings[0]));
400                plugin.setArtifactId(StringUtils.strip(pluginStrings[1]));
401
402                return plugin;
403            } else {
404                throw new EnforcerRuleError("Invalid " + field + " string: " + pluginString);
405            }
406        } else {
407            throw new EnforcerRuleError("Invalid " + field + " string: " + pluginString);
408        }
409    }
410
411    /**
412     * Finds the plugins that are listed in active profiles.
413     *
414     * @param project the project
415     * @return the profile plugins
416     */
417    public Set<Plugin> getProfilePlugins(MavenProject project) {
418        Set<Plugin> result = new HashSet<>();
419        List<Profile> profiles = project.getActiveProfiles();
420        if (profiles != null && !profiles.isEmpty()) {
421            for (Profile p : profiles) {
422                BuildBase b = p.getBuild();
423                if (b != null) {
424                    List<Plugin> plugins = b.getPlugins();
425                    if (plugins != null) {
426                        result.addAll(plugins);
427                    }
428                }
429            }
430        }
431        return result;
432    }
433
434    /**
435     * Given a plugin, this will retrieve the matching plugin artifact from the model.
436     *
437     * @param plugin  plugin to lookup
438     * @param project project to search
439     * @return matching plugin, <code>null</code> if not found.
440     */
441    private Plugin findCurrentPlugin(Plugin plugin, MavenProject project) throws EnforcerRuleException {
442        Plugin found = null;
443        try {
444            Model model = project.getModel();
445            Map<String, Plugin> plugins = model.getBuild().getPluginsAsMap();
446            found = plugins.get(plugin.getKey());
447        } catch (NullPointerException e) {
448            // nothing to do here
449        }
450
451        if (found == null) {
452            Artifact artifact = factory.createPluginArtifact(
453                    plugin.getGroupId(), plugin.getArtifactId(), VersionRange.createFromVersion("LATEST"));
454
455            try {
456                repositorySystem.resolveArtifact(
457                        session.getRepositorySession(),
458                        new ArtifactRequest(
459                                RepositoryUtils.toArtifact(artifact),
460                                session.getCurrentProject().getRemotePluginRepositories(),
461                                "resolvePlugin"));
462            } catch (ArtifactResolutionException e) {
463                throw new EnforcerRuleException("Unable to resolve the plugin " + artifact.getArtifactId(), e);
464            }
465            plugin.setVersion(artifact.getVersion());
466
467            found = plugin;
468        }
469
470        return found;
471    }
472
473    /**
474     * Gets the plugins that are bound to the defined phases. This does not find plugins bound in the pom to a phase
475     * later than the plugin is executing.
476     *
477     * @param project   the project
478     * @param thePhases the phases
479     * @return the bound plugins
480     * @throws PluginNotFoundException     the plugin not found exception
481     * @throws LifecycleExecutionException the lifecycle execution exception
482     */
483    private Set<Plugin> getBoundPlugins(MavenProject project, String thePhases)
484            throws PluginNotFoundException, LifecycleExecutionException {
485
486        Set<Plugin> allPlugins = new HashSet<>();
487
488        // lookup the bindings for all the passed in phases
489        String[] lifecyclePhases = thePhases.split(",");
490        for (int i = 0; i < lifecyclePhases.length; i++) {
491            String lifecyclePhase = lifecyclePhases[i];
492            if (lifecyclePhase != null && !lifecyclePhase.isEmpty()) {
493                try {
494                    Lifecycle lifecycle = getLifecycleForPhase(lifecyclePhase);
495                    getLog().debug("getBoundPlugins(): " + project.getId() + " " + lifecyclePhase + " "
496                            + lifecycle.getId());
497                    allPlugins.addAll(getAllPlugins(project, lifecycle));
498                } catch (BuildFailureException e) {
499                    // i'm going to swallow this because the
500                    // user may have declared a phase that
501                    // doesn't exist for every module.
502                }
503            }
504        }
505        return allPlugins;
506    }
507
508    /**
509     * Checks for valid version specified. Checks to see if the version is specified for the plugin. Can optionally ban
510     * "RELEASE" or "LATEST" even if specified.
511     *
512     * @param source         the source
513     * @param pluginWrappers the plugins
514     * @return true, if successful
515     */
516    public boolean hasValidVersionSpecified(Plugin source, List<PluginWrapper> pluginWrappers) {
517        boolean found = false;
518        boolean status = false;
519        for (PluginWrapper plugin : pluginWrappers) {
520            // find the matching plugin entry
521            if (isMatchingPlugin(source, plugin)) {
522                found = true;
523                // found the entry. now see if the version is specified
524                String version = plugin.getVersion();
525                try {
526                    version = (String) evaluator.evaluate(version);
527                } catch (ExpressionEvaluationException e) {
528                    return false;
529                }
530
531                if (isValidVersion(version)) {
532                    getLog().debug("checking for notEmpty and notIsWhitespace(): " + version);
533                    if (banRelease && version.equals("RELEASE")) {
534                        return false;
535                    }
536
537                    if (banLatest && version.equals("LATEST")) {
538                        return false;
539                    }
540
541                    if (banSnapshots && isSnapshot(version)) {
542                        return false;
543                    }
544                    // the version was specified and not
545                    // banned. It's ok. Keep looking through the list to make
546                    // sure it's not using a banned version somewhere else.
547
548                    status = true;
549
550                    if (!banRelease && !banLatest && !banSnapshots) {
551                        // no need to keep looking
552                        break;
553                    }
554                }
555            }
556        }
557        if (!found) {
558            getLog().debug("plugin " + source.getGroupId() + ":" + source.getArtifactId() + " not found");
559        }
560        return status;
561    }
562
563    private boolean isValidVersion(String version) {
564        return (version != null && !version.isEmpty()) && !StringUtils.isWhitespace(version);
565    }
566
567    private boolean isMatchingPlugin(Plugin source, PluginWrapper plugin) {
568        return source.getArtifactId().equals(plugin.getArtifactId())
569                && source.getGroupId().equals(plugin.getGroupId());
570    }
571
572    /**
573     * Checks if is snapshot.
574     *
575     * @param baseVersion the base version
576     * @return true, if is snapshot
577     */
578    private boolean isSnapshot(String baseVersion) {
579        if (banTimestamps) {
580            return Artifact.VERSION_FILE_PATTERN.matcher(baseVersion).matches()
581                    || baseVersion.endsWith(Artifact.SNAPSHOT_VERSION);
582        } else {
583            return baseVersion.endsWith(Artifact.SNAPSHOT_VERSION);
584        }
585    }
586
587    /*
588     * Uses borrowed lifecycle code to get a list of all plugins bound to the lifecycle.
589     */
590
591    /**
592     * Gets the all plugins.
593     *
594     * @param project   the project
595     * @param lifecycle the lifecycle
596     * @return the all plugins
597     * @throws PluginNotFoundException     the plugin not found exception
598     * @throws LifecycleExecutionException the lifecycle execution exception
599     */
600    private Set<Plugin> getAllPlugins(MavenProject project, Lifecycle lifecycle)
601            throws PluginNotFoundException, LifecycleExecutionException {
602
603        getLog().debug("RequirePluginVersions.getAllPlugins:");
604
605        Set<Plugin> plugins = new HashSet<>();
606        // first, bind those associated with the packaging
607        Map<String, String> mappings = findMappingsForLifecycle(project, lifecycle);
608
609        for (Map.Entry<String, String> entry : mappings.entrySet()) {
610            getLog().debug("  lifecycleMapping = " + entry.getKey());
611            String pluginsForLifecycle = (String) entry.getValue();
612            getLog().debug("  plugins = " + pluginsForLifecycle);
613            if (pluginsForLifecycle != null && !pluginsForLifecycle.isEmpty()) {
614                String pluginList[] = pluginsForLifecycle.split(",");
615                for (String plugin : pluginList) {
616                    plugin = StringUtils.strip(plugin);
617                    getLog().debug("    plugin = " + plugin);
618                    String tokens[] = plugin.split(":");
619                    getLog().debug("    GAV = " + Arrays.asList(tokens));
620
621                    Plugin p = new Plugin();
622                    p.setGroupId(tokens[0]);
623                    p.setArtifactId(tokens[1]);
624                    plugins.add(p);
625                }
626            }
627        }
628
629        plugins.addAll(project.getBuildPlugins());
630
631        return plugins;
632    }
633
634    /*
635     * NOTE: All the code following this point was scooped from the DefaultLifecycleExecutor. There must be a better way
636     * but for now it should work.
637     */
638
639    /**
640     * Gets the phase to lifecycle map.
641     *
642     * @return the phase to lifecycle map
643     * @throws LifecycleExecutionException the lifecycle execution exception
644     */
645    public Map<String, Lifecycle> getPhaseToLifecycleMap() throws LifecycleExecutionException {
646        if (phaseToLifecycleMap == null) {
647            phaseToLifecycleMap = new HashMap<>();
648
649            for (Lifecycle lifecycle : lifecycles) {
650                List<String> phases = lifecycle.getPhases();
651                for (String phase : phases) {
652                    getLog().debug("getPhaseToLifecycleMap(): phase: " + phase);
653                    if (phaseToLifecycleMap.containsKey(phase)) {
654                        Lifecycle prevLifecycle = (Lifecycle) phaseToLifecycleMap.get(phase);
655                        throw new LifecycleExecutionException("Phase '" + phase
656                                + "' is defined in more than one lifecycle: '" + lifecycle.getId() + "' and '"
657                                + prevLifecycle.getId() + "'");
658                    } else {
659                        phaseToLifecycleMap.put(phase, lifecycle);
660                    }
661                }
662            }
663        }
664        return phaseToLifecycleMap;
665    }
666
667    /**
668     * Gets the lifecycle for phase.
669     *
670     * @param phase the phase
671     * @return the lifecycle for phase
672     * @throws BuildFailureException       the build failure exception
673     * @throws LifecycleExecutionException the lifecycle execution exception
674     */
675    private Lifecycle getLifecycleForPhase(String phase) throws BuildFailureException, LifecycleExecutionException {
676        Lifecycle lifecycle = getPhaseToLifecycleMap().get(phase);
677
678        if (lifecycle == null) {
679            throw new BuildFailureException("Unable to find lifecycle for phase '" + phase + "'");
680        }
681        return lifecycle;
682    }
683
684    /**
685     * Find mappings for lifecycle.
686     *
687     * @param project   the project
688     * @param lifecycle the lifecycle
689     * @return the map
690     * @throws LifecycleExecutionException the lifecycle execution exception
691     * @throws PluginNotFoundException     the plugin not found exception
692     */
693    private Map<String, String> findMappingsForLifecycle(MavenProject project, Lifecycle lifecycle)
694            throws LifecycleExecutionException, PluginNotFoundException {
695        String packaging = project.getPackaging();
696        Map<String, String> mappings = null;
697
698        LifecycleMapping m = (LifecycleMapping) findExtension(
699                project, LifecycleMapping.ROLE, packaging, session.getSettings(), session.getLocalRepository());
700        if (m != null) {
701            mappings = m.getPhases(lifecycle.getId());
702        }
703
704        Map<String, String> defaultMappings = lifecycle.getDefaultPhases();
705
706        if (mappings == null) {
707            try {
708                m = container.lookup(LifecycleMapping.class, packaging);
709                mappings = m.getPhases(lifecycle.getId());
710            } catch (ComponentLookupException e) {
711                if (defaultMappings == null) {
712                    throw new LifecycleExecutionException(
713                            "Cannot find lifecycle mapping for packaging: \'" + packaging + "\'.", e);
714                }
715            }
716        }
717
718        if (mappings == null) {
719            if (defaultMappings == null) {
720                throw new LifecycleExecutionException(
721                        "Cannot find lifecycle mapping for packaging: \'" + packaging + "\', and there is no default");
722            } else {
723                mappings = defaultMappings;
724            }
725        }
726
727        return mappings;
728    }
729
730    /**
731     * Find extension.
732     *
733     * @param project         the project
734     * @param role            the role
735     * @param roleHint        the role hint
736     * @param settings        the settings
737     * @param localRepository the local repository
738     * @return the object
739     * @throws LifecycleExecutionException the lifecycle execution exception
740     * @throws PluginNotFoundException     the plugin not found exception
741     */
742    private Object findExtension(
743            MavenProject project, String role, String roleHint, Settings settings, ArtifactRepository localRepository)
744            throws LifecycleExecutionException, PluginNotFoundException {
745        Object pluginComponent = null;
746
747        List<Plugin> buildPlugins = project.getBuildPlugins();
748        for (Plugin plugin : buildPlugins) {
749            if (plugin.isExtensions()) {
750                verifyPlugin(plugin, project, settings, localRepository);
751
752                // TODO: if moved to the plugin manager we
753                // already have the descriptor from above
754                // and so do can lookup the container
755                // directly
756                try {
757                    pluginComponent = pluginManager.getPluginComponent(plugin, role, roleHint);
758
759                    if (pluginComponent != null) {
760                        break;
761                    }
762                } catch (ComponentLookupException e) {
763                    getLog().debug("Unable to find the lifecycle component in the extension " + e.getMessage());
764                } catch (PluginManagerException e) {
765                    throw new LifecycleExecutionException(
766                            "Error getting extensions from the plugin '" + plugin.getKey() + "': " + e.getMessage(), e);
767                }
768            }
769        }
770        return pluginComponent;
771    }
772
773    /**
774     * Verify plugin.
775     *
776     * @param plugin          the plugin
777     * @param project         the project
778     * @param settings        the settings
779     * @param localRepository the local repository
780     * @return the plugin descriptor
781     * @throws LifecycleExecutionException the lifecycle execution exception
782     * @throws PluginNotFoundException     the plugin not found exception
783     */
784    private PluginDescriptor verifyPlugin(
785            Plugin plugin, MavenProject project, Settings settings, ArtifactRepository localRepository)
786            throws LifecycleExecutionException, PluginNotFoundException {
787        PluginDescriptor pluginDescriptor;
788        try {
789            pluginDescriptor = pluginManager.verifyPlugin(plugin, project, settings, localRepository);
790        } catch (PluginManagerException e) {
791            throw new LifecycleExecutionException(
792                    "Internal error in the plugin manager getting plugin '" + plugin.getKey() + "': " + e.getMessage(),
793                    e);
794        } catch (PluginVersionResolutionException
795                | InvalidVersionSpecificationException
796                | InvalidPluginException
797                | PluginVersionNotFoundException
798                | org.apache.maven.artifact.resolver.ArtifactResolutionException
799                | ArtifactNotFoundException e) {
800            throw new LifecycleExecutionException(e.getMessage(), e);
801        }
802        return pluginDescriptor;
803    }
804
805    /**
806     * Gets all plugin entries in build.plugins, build.pluginManagement.plugins, profile.build.plugins, reporting and
807     * profile.reporting in this project and all parents
808     *
809     * @param project the project
810     * @return the all plugin entries wrapped in a PluginWrapper Object
811     */
812    private List<PluginWrapper> getAllPluginEntries(MavenProject project) {
813        List<PluginWrapper> plugins = new ArrayList<>();
814        // now find all the plugin entries, either in
815        // build.plugins or build.pluginManagement.plugins, profiles.plugins and reporting
816
817        getPlugins(plugins, project.getModel());
818        getReportingPlugins(plugins, project.getModel());
819        getPluginManagementPlugins(plugins, project.getModel());
820        addPluginsInProfiles(plugins, project.getModel());
821
822        return plugins;
823    }
824
825    private void addPluginsInProfiles(List<PluginWrapper> plugins, Model model) {
826        List<Profile> profiles = ofNullable(model).map(Model::getProfiles).orElseGet(Collections::emptyList);
827        for (Profile profile : profiles) {
828            getProfilePlugins(plugins, profile);
829            getProfileReportingPlugins(plugins, profile);
830            getProfilePluginManagementPlugins(plugins, profile);
831        }
832    }
833
834    private void getProfilePluginManagementPlugins(List<PluginWrapper> plugins, Profile profile) {
835        List<Plugin> modelPlugins = ofNullable(profile)
836                .map(Profile::getBuild)
837                .map(PluginConfiguration::getPluginManagement)
838                .map(PluginContainer::getPlugins)
839                .orElseGet(Collections::emptyList);
840        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
841    }
842
843    private void getProfileReportingPlugins(List<PluginWrapper> plugins, Profile profile) {
844        List<ReportPlugin> modelReportPlugins = ofNullable(profile)
845                .map(ModelBase::getReporting)
846                .map(Reporting::getPlugins)
847                .orElseGet(Collections::emptyList);
848        // add the reporting plugins
849        plugins.addAll(PluginWrapper.addAll(utils.resolveReportPlugins(modelReportPlugins), banMavenDefaults));
850    }
851
852    private void getProfilePlugins(List<PluginWrapper> plugins, Profile profile) {
853        List<Plugin> modelPlugins = ofNullable(profile)
854                .map(Profile::getBuild)
855                .map(PluginContainer::getPlugins)
856                .orElseGet(Collections::emptyList);
857        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
858    }
859
860    private void getPlugins(List<PluginWrapper> plugins, Model model) {
861        List<Plugin> modelPlugins = ofNullable(model)
862                .map(Model::getBuild)
863                .map(PluginContainer::getPlugins)
864                .orElseGet(Collections::emptyList);
865        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
866    }
867
868    private void getPluginManagementPlugins(List<PluginWrapper> plugins, Model model) {
869        List<Plugin> modelPlugins = ofNullable(model)
870                .map(Model::getBuild)
871                .map(PluginConfiguration::getPluginManagement)
872                .map(PluginContainer::getPlugins)
873                .orElseGet(Collections::emptyList);
874        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
875    }
876
877    private void getReportingPlugins(List<PluginWrapper> plugins, Model model) {
878        List<ReportPlugin> modelReportPlugins = ofNullable(model)
879                .map(ModelBase::getReporting)
880                .map(Reporting::getPlugins)
881                .orElseGet(Collections::emptyList);
882        // add the reporting plugins
883        plugins.addAll(PluginWrapper.addAll(utils.resolveReportPlugins(modelReportPlugins), banMavenDefaults));
884    }
885
886    /**
887     * Sets the ban latest.
888     *
889     * @param theBanLatest the banLatest to set
890     */
891    public void setBanLatest(boolean theBanLatest) {
892        this.banLatest = theBanLatest;
893    }
894
895    /**
896     * Sets the ban release.
897     *
898     * @param theBanRelease the banRelease to set
899     */
900    public void setBanRelease(boolean theBanRelease) {
901        this.banRelease = theBanRelease;
902    }
903
904    /**
905     * Checks if is ban snapshots.
906     *
907     * @return the banSnapshots
908     */
909    public boolean isBanSnapshots() {
910        return this.banSnapshots;
911    }
912
913    /**
914     * Sets the ban snapshots.
915     *
916     * @param theBanSnapshots the banSnapshots to set
917     */
918    public void setBanSnapshots(boolean theBanSnapshots) {
919        this.banSnapshots = theBanSnapshots;
920    }
921
922    /**
923     * Sets the ban timestamps.
924     *
925     * @param theBanTimestamps the banTimestamps to set
926     */
927    public void setBanTimestamps(boolean theBanTimestamps) {
928        this.banTimestamps = theBanTimestamps;
929    }
930
931    @Override
932    public String toString() {
933        return String.format(
934                "RequirePluginVersions[message=%s, banLatest=%b, banRelease=%b, banSnapshots=%b, banTimestamps=%b, phases=%s, additionalPlugins=%s, unCheckedPluginList=%s, unCheckedPlugins=%s]",
935                getMessage(),
936                banLatest,
937                banRelease,
938                banSnapshots,
939                banTimestamps,
940                phases,
941                additionalPlugins,
942                unCheckedPluginList,
943                unCheckedPlugins);
944    }
945}