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.version.PluginVersionNotFoundException;
067import org.apache.maven.plugin.version.PluginVersionResolutionException;
068import org.apache.maven.project.MavenProject;
069import org.apache.maven.rtinfo.RuntimeInformation;
070import org.apache.maven.settings.Settings;
071import org.codehaus.plexus.PlexusContainer;
072import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
073import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
074import org.codehaus.plexus.util.StringUtils;
075import org.eclipse.aether.RepositorySystem;
076import org.eclipse.aether.resolution.ArtifactRequest;
077import org.eclipse.aether.resolution.ArtifactResolutionException;
078
079import static java.util.Optional.ofNullable;
080
081/**
082 * This rule will enforce that all plugins specified in the poms have a version declared.
083 *
084 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
085 */
086@Named("requirePluginVersions")
087public final class RequirePluginVersions extends AbstractStandardEnforcerRule {
088
089    /**
090     * Don't allow the LATEST identifier.
091     */
092    private boolean banLatest = true;
093
094    /**
095     * Don't allow the RELEASE identifier.
096     */
097    private boolean banRelease = true;
098
099    /**
100     * Don't allow snapshot plugins.
101     */
102    private boolean banSnapshots = true;
103
104    /**
105     * Don't allow timestamp snapshot plugins.
106     */
107    private boolean banTimestamps = true;
108
109    /**
110     * @since 3.0.0
111     */
112    private boolean banMavenDefaults = true;
113
114    /**
115     * The comma separated list of phases that should be used to find lifecycle plugin bindings. The default value is
116     * "clean,deploy,site".
117     */
118    private String phases = "clean,deploy,site";
119
120    /**
121     * Additional plugins to enforce have versions. These are plugins that may not be in the poms but are used anyway,
122     * like help, eclipse etc. <br>
123     * The plugins should be specified in the form: <code>group:artifactId</code>.
124     */
125    private List<String> additionalPlugins;
126
127    /**
128     * Plugins to skip for version enforcement. The plugins should be specified in the form:
129     * <code>group:artifactId</code>. NOTE: This is deprecated, use unCheckedPluginList instead.
130     */
131    private List<String> unCheckedPlugins;
132
133    /**
134     * Same as unCheckedPlugins but as a comma list to better support properties. Sample form:
135     * <code>group:artifactId,group2:artifactId2</code>
136     *
137     * @since 1.0-beta-1
138     */
139    private String unCheckedPluginList;
140
141    /** The phase to lifecycle map. */
142    private Map<String, Lifecycle> phaseToLifecycleMap;
143
144    /** The lifecycles. */
145    private Collection<Lifecycle> lifecycles;
146
147    /** The plugin manager. */
148    private final PluginManager pluginManager;
149
150    /** The factory. */
151    private final ArtifactFactory factory;
152
153    private final RepositorySystem repositorySystem;
154
155    /** The session. */
156    private final MavenSession session;
157
158    /** The utils. */
159    private final EnforcerRuleUtils utils;
160
161    private final RuntimeInformation runtimeInformation;
162
163    private final DefaultLifecycles defaultLifeCycles;
164
165    private final MavenProject project;
166
167    private final ExpressionEvaluator evaluator;
168
169    private final PlexusContainer container;
170
171    @SuppressWarnings("checkstyle:ParameterNumber")
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 plugins
367     * @param additional the additional plugins
368     * @return the additional and existing plugins
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            if (existing == null) {
374                existing = new HashSet<>();
375            }
376            for (String pluginString : additional) {
377                Plugin plugin = parsePluginString(pluginString, "AdditionalPlugins");
378                existing.add(plugin);
379            }
380        }
381        return existing;
382    }
383
384    /**
385     * Helper method to parse and inject a Plugin.
386     *
387     * @param pluginString a plugin description to parse
388     * @param field        a source of pluginString
389     * @return the prepared plugin
390     */
391    private Plugin parsePluginString(String pluginString, String field) throws EnforcerRuleError {
392        if (pluginString != null) {
393            String[] pluginStrings = pluginString.split(":");
394            if (pluginStrings.length == 2) {
395                Plugin plugin = new Plugin();
396                plugin.setGroupId(StringUtils.strip(pluginStrings[0]));
397                plugin.setArtifactId(StringUtils.strip(pluginStrings[1]));
398
399                return plugin;
400            } else {
401                throw new EnforcerRuleError("Invalid " + field + " string: " + pluginString);
402            }
403        } else {
404            throw new EnforcerRuleError("Invalid " + field + " null plugin string.");
405        }
406    }
407
408    /**
409     * Finds the plugins that are listed in active profiles.
410     *
411     * @param project the project
412     * @return the profile plugins
413     */
414    public Set<Plugin> getProfilePlugins(MavenProject project) {
415        Set<Plugin> result = new HashSet<>();
416        List<Profile> profiles = project.getActiveProfiles();
417        if (profiles != null && !profiles.isEmpty()) {
418            for (Profile p : profiles) {
419                BuildBase b = p.getBuild();
420                if (b != null) {
421                    List<Plugin> plugins = b.getPlugins();
422                    if (plugins != null) {
423                        result.addAll(plugins);
424                    }
425                }
426            }
427        }
428        return result;
429    }
430
431    /**
432     * Given a plugin, this will retrieve the matching plugin artifact from the model.
433     *
434     * @param plugin  plugin to lookup
435     * @param project project to search
436     * @return matching plugin, <code>null</code> if not found.
437     */
438    private Plugin findCurrentPlugin(Plugin plugin, MavenProject project) throws EnforcerRuleException {
439        Plugin found = null;
440        try {
441            Model model = project.getModel();
442            Map<String, Plugin> plugins = model.getBuild().getPluginsAsMap();
443            found = plugins.get(plugin.getKey());
444        } catch (NullPointerException e) {
445            // nothing to do here
446        }
447
448        if (found == null) {
449            Artifact artifact = factory.createPluginArtifact(
450                    plugin.getGroupId(), plugin.getArtifactId(), VersionRange.createFromVersion("LATEST"));
451
452            try {
453                repositorySystem.resolveArtifact(
454                        session.getRepositorySession(),
455                        new ArtifactRequest(
456                                RepositoryUtils.toArtifact(artifact),
457                                session.getCurrentProject().getRemotePluginRepositories(),
458                                "resolvePlugin"));
459            } catch (ArtifactResolutionException e) {
460                throw new EnforcerRuleException("Unable to resolve the plugin " + artifact.getArtifactId(), e);
461            }
462            plugin.setVersion(artifact.getVersion());
463
464            found = plugin;
465        }
466
467        return found;
468    }
469
470    /**
471     * Gets the plugins that are bound to the defined phases. This does not find plugins bound in the pom to a phase
472     * later than the plugin is executing.
473     *
474     * @param project   the project
475     * @param phases the phases
476     * @return the bound plugins
477     * @throws PluginNotFoundException     the plugin not found exception
478     * @throws LifecycleExecutionException the lifecycle execution exception
479     */
480    private Set<Plugin> getBoundPlugins(MavenProject project, String phases)
481            throws PluginNotFoundException, LifecycleExecutionException {
482
483        Set<Plugin> allPlugins = new HashSet<>();
484
485        // lookup the bindings for all the passed in phases
486        String[] lifecyclePhases = phases.split(",");
487        for (int i = 0; i < lifecyclePhases.length; i++) {
488            String lifecyclePhase = lifecyclePhases[i];
489            if (lifecyclePhase != null && !lifecyclePhase.isEmpty()) {
490                try {
491                    Lifecycle lifecycle = getLifecycleForPhase(lifecyclePhase);
492                    getLog().debug("getBoundPlugins(): " + project.getId() + " " + lifecyclePhase + " "
493                            + lifecycle.getId());
494                    allPlugins.addAll(getAllPlugins(project, lifecycle));
495                } catch (BuildFailureException e) {
496                    // swallow this because the
497                    // user may have declared a phase that
498                    // doesn't exist for every module.
499                }
500            }
501        }
502        return allPlugins;
503    }
504
505    /**
506     * Checks for valid version specified. Checks to see if the version is specified for the plugin. Can optionally ban
507     * "RELEASE" or "LATEST" even if specified.
508     *
509     * @param source         the source
510     * @param pluginWrappers the plugins
511     * @return true, if successful
512     */
513    public boolean hasValidVersionSpecified(Plugin source, List<PluginWrapper> pluginWrappers) {
514        boolean found = false;
515        boolean status = false;
516        for (PluginWrapper plugin : pluginWrappers) {
517            // find the matching plugin entry
518            if (isMatchingPlugin(source, plugin)) {
519                found = true;
520                // found the entry. now see if the version is specified
521                String version = plugin.getVersion();
522                try {
523                    version = (String) evaluator.evaluate(version);
524                } catch (ExpressionEvaluationException e) {
525                    return false;
526                }
527
528                if (isValidVersion(version)) {
529                    getLog().debug("checking for notEmpty and notIsWhitespace(): " + version);
530                    if (banRelease && version.equals("RELEASE")) {
531                        return false;
532                    }
533
534                    if (banLatest && version.equals("LATEST")) {
535                        return false;
536                    }
537
538                    if (banSnapshots && isSnapshot(version)) {
539                        return false;
540                    }
541                    // the version was specified and not
542                    // banned. It's ok. Keep looking through the list to make
543                    // sure it's not using a banned version somewhere else.
544
545                    status = true;
546
547                    if (!banRelease && !banLatest && !banSnapshots) {
548                        // no need to keep looking
549                        break;
550                    }
551                }
552            }
553        }
554        if (!found) {
555            getLog().debug("plugin " + source.getGroupId() + ":" + source.getArtifactId() + " not found");
556        }
557        return status;
558    }
559
560    private boolean isValidVersion(String version) {
561        return (version != null && !version.isEmpty()) && !StringUtils.isWhitespace(version);
562    }
563
564    private boolean isMatchingPlugin(Plugin source, PluginWrapper plugin) {
565        return source.getArtifactId().equals(plugin.getArtifactId())
566                && source.getGroupId().equals(plugin.getGroupId());
567    }
568
569    /**
570     * Checks if is snapshot.
571     *
572     * @param baseVersion the base version
573     * @return true, if is snapshot
574     */
575    private boolean isSnapshot(String baseVersion) {
576        if (banTimestamps) {
577            return Artifact.VERSION_FILE_PATTERN.matcher(baseVersion).matches()
578                    || baseVersion.endsWith(Artifact.SNAPSHOT_VERSION);
579        } else {
580            return baseVersion.endsWith(Artifact.SNAPSHOT_VERSION);
581        }
582    }
583
584    /*
585     * Uses borrowed lifecycle code to get a list of all plugins bound to the lifecycle.
586     */
587
588    /**
589     * Gets the all plugins.
590     *
591     * @param project   the project
592     * @param lifecycle the lifecycle
593     * @return the all plugins
594     * @throws PluginNotFoundException     the plugin not found exception
595     * @throws LifecycleExecutionException the lifecycle execution exception
596     */
597    private Set<Plugin> getAllPlugins(MavenProject project, Lifecycle lifecycle)
598            throws PluginNotFoundException, LifecycleExecutionException {
599
600        getLog().debug("RequirePluginVersions.getAllPlugins:");
601
602        Set<Plugin> plugins = new HashSet<>();
603        // first, bind those associated with the packaging
604        Map<String, String> mappings = findMappingsForLifecycle(project, lifecycle);
605
606        for (Map.Entry<String, String> entry : mappings.entrySet()) {
607            getLog().debug("  lifecycleMapping = " + entry.getKey());
608            String pluginsForLifecycle = entry.getValue();
609            getLog().debug("  plugins = " + pluginsForLifecycle);
610            if (pluginsForLifecycle != null && !pluginsForLifecycle.isEmpty()) {
611                String pluginList[] = pluginsForLifecycle.split(",");
612                for (String plugin : pluginList) {
613                    plugin = StringUtils.strip(plugin);
614                    getLog().debug("    plugin = " + plugin);
615                    String tokens[] = plugin.split(":");
616                    getLog().debug("    GAV = " + Arrays.asList(tokens));
617
618                    Plugin p = new Plugin();
619                    p.setGroupId(tokens[0]);
620                    p.setArtifactId(tokens[1]);
621                    plugins.add(p);
622                }
623            }
624        }
625
626        plugins.addAll(project.getBuildPlugins());
627
628        return plugins;
629    }
630
631    /*
632     * NOTE: All the code following this point was scooped from the DefaultLifecycleExecutor. There must be a better way
633     * but for now it should work.
634     */
635
636    /**
637     * Gets the phase to lifecycle map.
638     *
639     * @return the phase to lifecycle map
640     * @throws LifecycleExecutionException the lifecycle execution exception
641     */
642    public Map<String, Lifecycle> getPhaseToLifecycleMap() throws LifecycleExecutionException {
643        if (phaseToLifecycleMap == null) {
644            phaseToLifecycleMap = new HashMap<>();
645
646            for (Lifecycle lifecycle : lifecycles) {
647                List<String> phases = lifecycle.getPhases();
648                for (String phase : phases) {
649                    getLog().debug("getPhaseToLifecycleMap(): phase: " + phase);
650                    if (phaseToLifecycleMap.containsKey(phase)) {
651                        Lifecycle prevLifecycle = phaseToLifecycleMap.get(phase);
652                        throw new LifecycleExecutionException("Phase '" + phase
653                                + "' is defined in more than one lifecycle: '" + lifecycle.getId() + "' and '"
654                                + prevLifecycle.getId() + "'");
655                    } else {
656                        phaseToLifecycleMap.put(phase, lifecycle);
657                    }
658                }
659            }
660        }
661        return phaseToLifecycleMap;
662    }
663
664    /**
665     * Gets the lifecycle for phase.
666     *
667     * @param phase the phase
668     * @return the lifecycle for phase
669     * @throws BuildFailureException       the build failure exception
670     * @throws LifecycleExecutionException the lifecycle execution exception
671     */
672    private Lifecycle getLifecycleForPhase(String phase) throws BuildFailureException, LifecycleExecutionException {
673        Lifecycle lifecycle = getPhaseToLifecycleMap().get(phase);
674
675        if (lifecycle == null) {
676            throw new BuildFailureException("Unable to find lifecycle for phase '" + phase + "'");
677        }
678        return lifecycle;
679    }
680
681    /**
682     * Find mappings for lifecycle.
683     *
684     * @param project   the project
685     * @param lifecycle the lifecycle
686     * @return the map
687     * @throws LifecycleExecutionException the lifecycle execution exception
688     * @throws PluginNotFoundException     the plugin not found exception
689     */
690    private Map<String, String> findMappingsForLifecycle(MavenProject project, Lifecycle lifecycle)
691            throws LifecycleExecutionException, PluginNotFoundException {
692        String packaging = project.getPackaging();
693        Map<String, String> mappings = null;
694
695        LifecycleMapping m = (LifecycleMapping) findExtension(
696                project, LifecycleMapping.ROLE, packaging, session.getSettings(), session.getLocalRepository());
697        if (m != null) {
698            mappings = m.getPhases(lifecycle.getId());
699        }
700
701        Map<String, String> defaultMappings = lifecycle.getDefaultPhases();
702
703        if (mappings == null) {
704            try {
705                m = container.lookup(LifecycleMapping.class, packaging);
706                mappings = m.getPhases(lifecycle.getId());
707            } catch (ComponentLookupException e) {
708                if (defaultMappings == null) {
709                    throw new LifecycleExecutionException(
710                            "Cannot find lifecycle mapping for packaging: '" + packaging + "'.", e);
711                }
712            }
713        }
714
715        if (mappings == null) {
716            if (defaultMappings == null) {
717                throw new LifecycleExecutionException(
718                        "Cannot find lifecycle mapping for packaging: '" + packaging + "', and there is no default");
719            } else {
720                mappings = defaultMappings;
721            }
722        }
723
724        return mappings;
725    }
726
727    /**
728     * Find extension.
729     *
730     * @param project         the project
731     * @param role            the role
732     * @param roleHint        the role hint
733     * @param settings        the settings
734     * @param localRepository the local repository
735     * @return the object
736     * @throws LifecycleExecutionException the lifecycle execution exception
737     * @throws PluginNotFoundException     the plugin not found exception
738     */
739    private Object findExtension(
740            MavenProject project, String role, String roleHint, Settings settings, ArtifactRepository localRepository)
741            throws LifecycleExecutionException, PluginNotFoundException {
742        Object pluginComponent = null;
743
744        List<Plugin> buildPlugins = project.getBuildPlugins();
745        for (Plugin plugin : buildPlugins) {
746            if (plugin.isExtensions()) {
747                verifyPlugin(plugin, project, settings, localRepository);
748
749                // TODO: if moved to the plugin manager we
750                // already have the descriptor from above
751                // and so do can lookup the container
752                // directly
753                try {
754                    pluginComponent = pluginManager.getPluginComponent(plugin, role, roleHint);
755
756                    if (pluginComponent != null) {
757                        break;
758                    }
759                } catch (ComponentLookupException e) {
760                    getLog().debug("Unable to find the lifecycle component in the extension " + e.getMessage());
761                } catch (PluginManagerException e) {
762                    throw new LifecycleExecutionException(
763                            "Error getting extensions from the plugin '" + plugin.getKey() + "': " + e.getMessage(), e);
764                }
765            }
766        }
767        return pluginComponent;
768    }
769
770    /**
771     * Verify plugin.
772     *
773     * @param plugin          the plugin
774     * @param project         the project
775     * @param settings        the settings
776     * @param localRepository the local repository
777     * @return the plugin descriptor
778     * @throws LifecycleExecutionException the lifecycle execution exception
779     * @throws PluginNotFoundException     the plugin not found exception
780     */
781    private void verifyPlugin(
782            Plugin plugin, MavenProject project, Settings settings, ArtifactRepository localRepository)
783            throws LifecycleExecutionException, PluginNotFoundException {
784        try {
785            pluginManager.verifyPlugin(plugin, project, settings, localRepository);
786        } catch (PluginManagerException e) {
787            throw new LifecycleExecutionException(
788                    "Internal error in the plugin manager getting plugin '" + plugin.getKey() + "': " + e.getMessage(),
789                    e);
790        } catch (PluginVersionResolutionException
791                | InvalidVersionSpecificationException
792                | InvalidPluginException
793                | PluginVersionNotFoundException
794                | org.apache.maven.artifact.resolver.ArtifactResolutionException
795                | ArtifactNotFoundException e) {
796            throw new LifecycleExecutionException(e.getMessage(), e);
797        }
798    }
799
800    /**
801     * Gets all plugin entries in build.plugins, build.pluginManagement.plugins, profile.build.plugins, reporting and
802     * profile.reporting in this project and all parents
803     *
804     * @param project the project
805     * @return the all plugin entries wrapped in a PluginWrapper Object
806     */
807    private List<PluginWrapper> getAllPluginEntries(MavenProject project) {
808        List<PluginWrapper> plugins = new ArrayList<>();
809        // now find all the plugin entries, either in
810        // build.plugins or build.pluginManagement.plugins, profiles.plugins and reporting
811
812        getPlugins(plugins, project.getModel());
813        getReportingPlugins(plugins, project.getModel());
814        getPluginManagementPlugins(plugins, project.getModel());
815        addPluginsInProfiles(plugins, project.getModel());
816
817        return plugins;
818    }
819
820    private void addPluginsInProfiles(List<PluginWrapper> plugins, Model model) {
821        List<Profile> profiles = ofNullable(model).map(Model::getProfiles).orElseGet(Collections::emptyList);
822        for (Profile profile : profiles) {
823            getProfilePlugins(plugins, profile);
824            getProfileReportingPlugins(plugins, profile);
825            getProfilePluginManagementPlugins(plugins, profile);
826        }
827    }
828
829    private void getProfilePluginManagementPlugins(List<PluginWrapper> plugins, Profile profile) {
830        List<Plugin> modelPlugins = ofNullable(profile)
831                .map(Profile::getBuild)
832                .map(PluginConfiguration::getPluginManagement)
833                .map(PluginContainer::getPlugins)
834                .orElseGet(Collections::emptyList);
835        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
836    }
837
838    private void getProfileReportingPlugins(List<PluginWrapper> plugins, Profile profile) {
839        List<ReportPlugin> modelReportPlugins = ofNullable(profile)
840                .map(ModelBase::getReporting)
841                .map(Reporting::getPlugins)
842                .orElseGet(Collections::emptyList);
843        // add the reporting plugins
844        plugins.addAll(PluginWrapper.addAll(utils.resolveReportPlugins(modelReportPlugins), banMavenDefaults));
845    }
846
847    private void getProfilePlugins(List<PluginWrapper> plugins, Profile profile) {
848        List<Plugin> modelPlugins = ofNullable(profile)
849                .map(Profile::getBuild)
850                .map(PluginContainer::getPlugins)
851                .orElseGet(Collections::emptyList);
852        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
853    }
854
855    private void getPlugins(List<PluginWrapper> plugins, Model model) {
856        List<Plugin> modelPlugins = ofNullable(model)
857                .map(Model::getBuild)
858                .map(PluginContainer::getPlugins)
859                .orElseGet(Collections::emptyList);
860        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
861    }
862
863    private void getPluginManagementPlugins(List<PluginWrapper> plugins, Model model) {
864        List<Plugin> modelPlugins = ofNullable(model)
865                .map(Model::getBuild)
866                .map(PluginConfiguration::getPluginManagement)
867                .map(PluginContainer::getPlugins)
868                .orElseGet(Collections::emptyList);
869        plugins.addAll(PluginWrapper.addAll(utils.resolvePlugins(modelPlugins), banMavenDefaults));
870    }
871
872    private void getReportingPlugins(List<PluginWrapper> plugins, Model model) {
873        List<ReportPlugin> modelReportPlugins = ofNullable(model)
874                .map(ModelBase::getReporting)
875                .map(Reporting::getPlugins)
876                .orElseGet(Collections::emptyList);
877        // add the reporting plugins
878        plugins.addAll(PluginWrapper.addAll(utils.resolveReportPlugins(modelReportPlugins), banMavenDefaults));
879    }
880
881    /**
882     * Sets the ban latest.
883     *
884     * @param banLatest the banLatest to set
885     */
886    public void setBanLatest(boolean banLatest) {
887        this.banLatest = banLatest;
888    }
889
890    /**
891     * Sets the ban release.
892     *
893     * @param banRelease the banRelease to set
894     */
895    public void setBanRelease(boolean banRelease) {
896        this.banRelease = banRelease;
897    }
898
899    /**
900     * Checks if is ban snapshots.
901     *
902     * @return the banSnapshots
903     */
904    public boolean isBanSnapshots() {
905        return this.banSnapshots;
906    }
907
908    /**
909     * Sets the ban snapshots.
910     *
911     * @param banSnapshots the banSnapshots to set
912     */
913    public void setBanSnapshots(boolean banSnapshots) {
914        this.banSnapshots = banSnapshots;
915    }
916
917    /**
918     * Sets the ban timestamps.
919     *
920     * @param banTimestamps the banTimestamps to set
921     */
922    public void setBanTimestamps(boolean banTimestamps) {
923        this.banTimestamps = banTimestamps;
924    }
925
926    @Override
927    public String toString() {
928        return String.format(
929                "RequirePluginVersions[message=%s, banLatest=%b, banRelease=%b, banSnapshots=%b, banTimestamps=%b, phases=%s, additionalPlugins=%s, unCheckedPluginList=%s, unCheckedPlugins=%s]",
930                getMessage(),
931                banLatest,
932                banRelease,
933                banSnapshots,
934                banTimestamps,
935                phases,
936                additionalPlugins,
937                unCheckedPluginList,
938                unCheckedPlugins);
939    }
940}