org.commonjava.maven.ext.manip.impl.DependencyManipulator.java Source code

Java tutorial

Introduction

Here is the source code for org.commonjava.maven.ext.manip.impl.DependencyManipulator.java

Source

/*
 * Copyright (C) 2012 Red Hat, Inc. (jcasey@redhat.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.commonjava.maven.ext.manip.impl;

import org.apache.commons.lang.StringUtils;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Model;
import org.apache.maven.model.Profile;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.commonjava.maven.atlas.ident.ref.ArtifactRef;
import org.commonjava.maven.atlas.ident.ref.InvalidRefException;
import org.commonjava.maven.atlas.ident.ref.ProjectRef;
import org.commonjava.maven.atlas.ident.ref.ProjectVersionRef;
import org.commonjava.maven.atlas.ident.ref.SimpleArtifactRef;
import org.commonjava.maven.atlas.ident.ref.SimpleProjectRef;
import org.commonjava.maven.ext.manip.ManipulationException;
import org.commonjava.maven.ext.manip.ManipulationSession;
import org.commonjava.maven.ext.manip.io.ModelIO;
import org.commonjava.maven.ext.manip.model.Project;
import org.commonjava.maven.ext.manip.state.DependencyState;
import org.commonjava.maven.ext.manip.util.ProfileUtils;
import org.commonjava.maven.ext.manip.util.PropertiesUtils;
import org.commonjava.maven.ext.manip.util.WildcardMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.join;
import static org.commonjava.maven.ext.manip.util.IdUtils.ga;
import static org.commonjava.maven.ext.manip.util.IdUtils.gav;

/**
 * {@link Manipulator} implementation that can alter dependency (and dependency management) sections in a project's pom file.
 * Configuration is stored in a {@link DependencyState} instance, which is in turn stored in the {@link ManipulationSession}.
 */
@Component(role = Manipulator.class, hint = "project-dependency-manipulator")
public class DependencyManipulator implements Manipulator {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Used to store mappings of old property to new version for explicit overrides.
     */
    private final HashMap<String, String> explicitVersionPropertyUpdateMap = new HashMap<>();
    /**
     * Used to store mappings of old property to new version.
     */
    private final HashMap<String, String> versionPropertyUpdateMap = new HashMap<>();

    @Requirement
    private ModelIO effectiveModelBuilder;

    /**
     * Initialize the {@link DependencyState} state holder in the {@link ManipulationSession}. This state holder detects
     * version-change configuration from the Maven user properties (-D properties from the CLI) and makes it available for
     * later invocations of {@link Manipulator#scan(List, ManipulationSession)} and the apply* methods.
     */
    @Override
    public void init(final ManipulationSession session) throws ManipulationException {
        final Properties userProps = session.getUserProperties();
        session.setState(new DependencyState(userProps));
    }

    /**
     * No prescanning required for BOM manipulation.
     */
    @Override
    public void scan(final List<Project> projects, final ManipulationSession session) throws ManipulationException {
    }

    /**
     * Apply the alignment changes to the list of {@link Project}'s given.
     */
    @Override
    public Set<Project> applyChanges(final List<Project> projects, final ManipulationSession session)
            throws ManipulationException {
        final DependencyState state = session.getState(DependencyState.class);

        if (!session.isEnabled() || !state.isEnabled()) {
            logger.debug(getClass().getSimpleName() + ": Nothing to do!");
            return Collections.emptySet();
        }
        return internalApplyChanges(projects, session, loadRemoteOverrides(state));
    }

    /**
     * This will load the remote overrides. It will first try to load any overrides that might have
     * been prepopulated by the REST scanner, failing that it will load from a remote POM file.
     *
     * @param state the dependency state
     * @return the loaded overrides
     * @throws ManipulationException if an error occurs.
     */
    private Map<ArtifactRef, String> loadRemoteOverrides(final DependencyState state) throws ManipulationException {
        Map<ArtifactRef, String> overrides = state.getRemoteRESTOverrides();

        if (overrides == null) {
            overrides = new LinkedHashMap<>();
            final List<ProjectVersionRef> gavs = state.getRemoteBOMDepMgmt();

            if (gavs == null || gavs.isEmpty()) {
                return overrides;
            }

            final ListIterator<ProjectVersionRef> iter = gavs.listIterator(gavs.size());
            // Iterate in reverse order so that the first GAV in the list overwrites the last
            while (iter.hasPrevious()) {
                final ProjectVersionRef ref = iter.previous();
                overrides.putAll(effectiveModelBuilder.getRemoteDependencyVersionOverrides(ref));
            }
        }
        return overrides;
    }

    @Override
    public int getExecutionIndex() {
        return 40;
    }

    private Set<Project> internalApplyChanges(final List<Project> projects, final ManipulationSession session,
            Map<ArtifactRef, String> overrides) throws ManipulationException {
        final DependencyState state = session.getState(DependencyState.class);
        final Set<Project> result = new HashSet<>();

        for (final Project project : projects) {
            final Model model = project.getModel();

            if (!overrides.isEmpty() || !state.getDependencyExclusions().isEmpty()) {
                apply(session, project, model, overrides);

                result.add(project);
            }
        }

        // If we've changed something now update any old properties with the new values.
        if (!result.isEmpty()) {
            logger.info("Iterating for standard overrides...");
            for (final Map.Entry<String, String> entry : versionPropertyUpdateMap.entrySet()) {
                PropertiesUtils.PropertyUpdate found = PropertiesUtils.updateProperties(session, result, false,
                        entry.getKey(), entry.getValue());

                if (found == PropertiesUtils.PropertyUpdate.NOTFOUND) {
                    // Problem in this scenario is that we know we have a property update map but we have not found a
                    // property to update. Its possible this property has been inherited from a parent. Override in the
                    // top pom for safety.
                    logger.info("Unable to find a property for {} to update", entry.getKey());
                    for (final Project p : result) {
                        if (p.isInheritanceRoot()) {
                            logger.info("Adding property {} with {} ", entry.getKey(), entry.getValue());
                            p.getModel().getProperties().setProperty(entry.getKey(), entry.getValue());
                        }
                    }
                }
            }
            logger.info("Iterating for explicit overrides...");
            for (final Map.Entry<String, String> entry : explicitVersionPropertyUpdateMap.entrySet()) {
                PropertiesUtils.PropertyUpdate found = PropertiesUtils.updateProperties(session, result, true,
                        entry.getKey(), entry.getValue());

                if (found == PropertiesUtils.PropertyUpdate.NOTFOUND) {
                    // Problem in this scenario is that we know we have a property update map but we have not found a
                    // property to update. Its possible this property has been inherited from a parent. Override in the
                    // top pom for safety.
                    logger.info("Unable to find a property for {} to update for explicit overrides",
                            entry.getKey());
                    for (final Project p : result) {
                        if (p.isInheritanceRoot()) {
                            logger.info("Adding property {} with {} ", entry.getKey(), entry.getValue());
                            p.getModel().getProperties().setProperty(entry.getKey(), entry.getValue());
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * Applies dependency overrides to the project.
     */
    private void apply(final ManipulationSession session, final Project project, final Model model,
            final Map<ArtifactRef, String> overrides) throws ManipulationException {
        // Map of Group : Map of artifactId [ may be wildcard ] : value
        final WildcardMap<String> explicitOverrides = new WildcardMap<>();
        final String projectGA = ga(project);
        final DependencyState state = session.getState(DependencyState.class);

        Map<ArtifactRef, String> moduleOverrides = new LinkedHashMap<>(overrides);
        moduleOverrides = removeReactorGAs(session, moduleOverrides);

        try {
            moduleOverrides = applyModuleVersionOverrides(projectGA, state.getDependencyExclusions(),
                    moduleOverrides, explicitOverrides);
            logger.debug("Module overrides are:\n{}", moduleOverrides);
            logger.debug("Explicit overrides are:\n{}", explicitOverrides);
        } catch (InvalidRefException e) {
            logger.error("Invalid module exclusion override {} : {} ", moduleOverrides, explicitOverrides);
            throw e;
        }

        if (project.isInheritanceRoot()) {
            // Handle the situation where the top level parent refers to a prior build that is in the BOM.
            if (project.getParent() != null) {
                for (Map.Entry<ArtifactRef, String> entry : moduleOverrides.entrySet()) {
                    String oldValue = project.getParent().getVersion();
                    String newValue = entry.getValue();

                    if (entry.getKey().asProjectRef().equals(SimpleProjectRef.parse(ga(project.getParent())))) {
                        if (state.getStrict()) {
                            if (!PropertiesUtils.checkStrictValue(session, oldValue, newValue)) {
                                if (state.getFailOnStrictViolation()) {
                                    throw new ManipulationException(
                                            "Parent reference {} replacement: {} of original version: {} violates the strict version-alignment rule!",
                                            ga(project.getParent()), newValue, oldValue);
                                } else {
                                    logger.warn(
                                            "Parent reference {} replacement: {} of original version: {} violates the strict version-alignment rule!",
                                            ga(project.getParent()), newValue, oldValue);
                                    // Ignore the dependency override. As found has been set to true it won't inject
                                    // a new property either.
                                    continue;
                                }
                            }
                        }

                        logger.debug(" Modifying parent reference from {} to {} for {} ",
                                model.getParent().getVersion(), newValue, ga(project.getParent()));
                        model.getParent().setVersion(newValue);
                        break;
                    }
                }

                // Apply any explicit overrides to the top level parent. Convert it to a simulated
                // dependency so we can reuse applyExplicitOverrides.
                ArrayList<Dependency> pDeps = new ArrayList<>();
                Dependency d = new Dependency();
                d.setGroupId(project.getParent().getGroupId());
                d.setArtifactId(project.getParent().getArtifactId());
                d.setVersion(project.getParent().getVersion());
                pDeps.add(d);
                applyExplicitOverrides(explicitVersionPropertyUpdateMap, explicitOverrides, pDeps);
                project.getParent().setVersion(d.getVersion());
            }

            if (session.getState(DependencyState.class).getOverrideDependencies()) {
                // If the model doesn't have any Dependency Management set by default, create one for it
                DependencyManagement dependencyManagement = model.getDependencyManagement();
                if (dependencyManagement == null) {
                    dependencyManagement = new DependencyManagement();
                    model.setDependencyManagement(dependencyManagement);
                    logger.debug("Added <DependencyManagement/> for current project");
                }

                // Apply overrides to project dependency management
                final List<Dependency> dependencies = dependencyManagement.getDependencies();

                logger.debug("Applying overrides to managed dependencies for top-pom: {}", projectGA);

                final Map<ArtifactRef, String> nonMatchingVersionOverrides = applyOverrides(session, project,
                        dependencies, moduleOverrides, explicitOverrides);

                final Map<ArtifactRef, String> matchedOverrides = new LinkedHashMap<>(moduleOverrides);
                matchedOverrides.keySet().removeAll(nonMatchingVersionOverrides.keySet());

                applyExplicitOverrides(explicitVersionPropertyUpdateMap, explicitOverrides, dependencies);

                if (session.getState(DependencyState.class).getOverrideTransitive()) {
                    final List<Dependency> extraDeps = new ArrayList<>();

                    // Add dependencies to Dependency Management which did not match any existing dependency
                    for (final ArtifactRef var : overrides.keySet()) {
                        if (!nonMatchingVersionOverrides.containsKey(var)) {
                            // This one in the remote pom was already dealt with ; continue.
                            continue;
                        }

                        final Dependency newDependency = new Dependency();
                        newDependency.setGroupId(var.getGroupId());
                        newDependency.setArtifactId(var.getArtifactId());
                        newDependency.setType(var.getType());
                        newDependency.setClassifier(var.getClassifier());

                        final String artifactVersion = moduleOverrides.get(var);
                        newDependency.setVersion(artifactVersion);

                        extraDeps.add(newDependency);
                        logger.debug("New entry added to <DependencyManagement/> - {} : {} ", var, artifactVersion);
                    }

                    dependencyManagement.getDependencies().addAll(0, extraDeps);
                } else {
                    logger.debug("Non-matching dependencies ignored.");
                }
            } else {
                logger.debug("NOT applying overrides to managed dependencies for top-pom: {}", projectGA);
            }
        } else {
            // If a child module has a depMgmt section we'll change that as well.
            final DependencyManagement dependencyManagement = model.getDependencyManagement();
            if (session.getState(DependencyState.class).getOverrideDependencies() && dependencyManagement != null) {
                logger.debug("Applying overrides to managed dependencies for: {}", projectGA);
                applyOverrides(session, project, dependencyManagement.getDependencies(), moduleOverrides,
                        explicitOverrides);
                applyExplicitOverrides(explicitVersionPropertyUpdateMap, explicitOverrides,
                        dependencyManagement.getDependencies());
            } else {
                logger.debug("NOT applying overrides to managed dependencies for: {}", projectGA);
            }
        }

        if (session.getState(DependencyState.class).getOverrideDependencies()) {
            logger.debug("Applying overrides to concrete dependencies for: {}", projectGA);
            // Apply overrides to project direct dependencies
            final List<Dependency> projectDependencies = model.getDependencies();
            applyOverrides(session, project, projectDependencies, moduleOverrides, explicitOverrides);
            applyExplicitOverrides(explicitVersionPropertyUpdateMap, explicitOverrides, projectDependencies);

            // Now check all possible profiles and update them.
            List<Profile> profiles = ProfileUtils.getProfiles(session, project.getModel());
            if (profiles != null) {
                for (Profile p : profiles) {
                    if (p.getDependencyManagement() != null) {
                        applyOverrides(session, project, p.getDependencyManagement().getDependencies(),
                                moduleOverrides, explicitOverrides);
                        applyExplicitOverrides(explicitVersionPropertyUpdateMap, explicitOverrides,
                                p.getDependencyManagement().getDependencies());
                    }
                    final List<Dependency> profileDependencies = p.getDependencies();
                    applyOverrides(session, project, profileDependencies, moduleOverrides, explicitOverrides);
                    applyExplicitOverrides(explicitVersionPropertyUpdateMap, explicitOverrides,
                            profileDependencies);
                }
            }
        } else {
            logger.debug("NOT applying overrides to concrete dependencies for: {}", projectGA);
        }
    }

    /**
     * Apply explicit overrides to a set of dependencies from a project. The explicit overrides come from
     * dependencyExclusion. However they have to be separated out from standard overrides so we can easily
     * ignore any property references (and overwrite them).
     *
     * @param versionPropertyUpdateMap properties to update
     * @param explicitOverrides a custom map to handle wildcard overrides
     * @param dependencies dependencies to check
     * @throws ManipulationException if an error occurs
     */
    private void applyExplicitOverrides(final Map<String, String> versionPropertyUpdateMap,
            final WildcardMap<String> explicitOverrides, final List<Dependency> dependencies)
            throws ManipulationException {
        // Apply matching overrides to dependencies
        for (final Dependency dependency : dependencies) {
            final ProjectRef groupIdArtifactId = new SimpleProjectRef(dependency.getGroupId(),
                    dependency.getArtifactId());

            if (explicitOverrides.containsKey(groupIdArtifactId)) {
                final String overrideVersion = explicitOverrides.get(groupIdArtifactId);
                final String oldVersion = dependency.getVersion();

                if (isEmpty(overrideVersion) || isEmpty(oldVersion)) {
                    if (isEmpty(oldVersion)) {
                        logger.warn("Unable to force align as no existing version field to update for "
                                + groupIdArtifactId + "; ignoring");
                    } else {
                        logger.warn("Unable to force align as override version is empty for " + groupIdArtifactId
                                + "; ignoring");
                    }
                } else {
                    for (String target : overrideVersion.split(",")) {
                        if (target.startsWith("+")) {
                            logger.info("Adding dependency exclusion {} to dependency {} ", target.substring(1),
                                    dependency);
                            Exclusion e = new Exclusion();
                            e.setGroupId(target.substring(1).split(":")[0]);
                            e.setArtifactId(target.split(":")[1]);
                            dependency.addExclusion(e);
                        } else {
                            logger.info("Explicit overrides : force aligning {} to {}.", groupIdArtifactId, target);

                            if (!PropertiesUtils.cacheProperty(versionPropertyUpdateMap, oldVersion, target,
                                    dependency, true)) {
                                if (oldVersion.contains("${")) {
                                    logger.warn(
                                            "Overriding version with {} when old version contained a property {} ",
                                            target, oldVersion);
                                    // TODO: Should this throw an exception?
                                }
                                // Not checking strict version alignment here as explicit overrides take priority.
                                dependency.setVersion(target);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Apply a set of version overrides to a list of dependencies. Return a set of the overrides which were not applied.
     *
     * @param session The ManipulationSession
     * @param project The current Project
     * @param dependencies The list of dependencies
     * @param overrides The map of dependency version overrides
     * @param explicitOverrides Any explicitOverrides to track for ignoring    @return The map of overrides that were not matched in the dependencies
     * @throws ManipulationException if an error occurs
     */
    private Map<ArtifactRef, String> applyOverrides(final ManipulationSession session, Project project,
            final List<Dependency> dependencies, final Map<ArtifactRef, String> overrides,
            WildcardMap<String> explicitOverrides) throws ManipulationException {
        // Duplicate the override map so unused overrides can be easily recorded
        final Map<ArtifactRef, String> unmatchedVersionOverrides = new LinkedHashMap<>();
        unmatchedVersionOverrides.putAll(overrides);

        if (dependencies == null) {
            return unmatchedVersionOverrides;
        }

        final DependencyState state = session.getState(DependencyState.class);
        final boolean strict = state.getStrict();

        // Apply matching overrides to dependencies
        for (final Dependency dependency : dependencies) {
            ProjectRef depPr = new SimpleProjectRef(dependency.getGroupId(), dependency.getArtifactId());

            // We might have junit:junit:3.8.2 and junit:junit:4.1 for differing override scenarios within the
            // overrides list. If strict mode alignment is enabled, using multiple overrides will work with
            // different modules. It is currently undefined what will happen if non-strict mode is enabled and
            // multiple versions are in the remote override list (be it from a bom or rest call). Actually, what
            // will most likely happen is last-wins.
            for (final Map.Entry<ArtifactRef, String> entry : overrides.entrySet()) {
                ProjectRef groupIdArtifactId = entry.getKey().asProjectRef();

                if (depPr.equals(groupIdArtifactId)) {
                    final String oldVersion = dependency.getVersion();
                    final String overrideVersion = entry.getValue();
                    final String resolvedValue = PropertiesUtils.resolveProperties(session.getProjects(),
                            oldVersion);

                    if (isEmpty(overrideVersion)) {
                        logger.warn("Unable to align with an empty override version for " + groupIdArtifactId
                                + "; ignoring");
                    } else if (isEmpty(oldVersion)) {
                        logger.debug("Dependency is a managed version for " + groupIdArtifactId + "; ignoring");
                    }
                    // If we have an explicitOverride, this will always override the dependency changes made here.
                    // By avoiding the potential duplicate work it also avoids a possible property clash problem.
                    else if (explicitOverrides.containsKey(depPr)) {
                        logger.debug(
                                "Dependency {} matches known explicit override so not performing initial override pass.",
                                depPr);
                        unmatchedVersionOverrides.remove(entry.getKey());
                    }
                    // If we're doing strict matching with properties, then the original parts should match.
                    // i.e. assuming original resolved value is 1.2 and potential new value is 1.2.rebuild-1
                    // then this is fine to continue. If the original is 1.2 and potential new value is 1.3.rebuild-1
                    // then don't bother to attempt to cache the property as the strict check would fail.
                    // This extra check avoids an erroneous "Property replacement clash" error.

                    // Can't blindly compare resolvedValue [original] against ar as ar / overrideVersion is the new GAV. We don't
                    // have immediate access to the original property so the closest that is feasible is verify strict matching.
                    else if (strict && oldVersion.contains("$")
                            && !PropertiesUtils.checkStrictValue(session, resolvedValue, overrideVersion)) {
                        logger.debug(
                                "Original fully resolved version {} of {} does not match override version {} -> {} so ignoring",
                                resolvedValue, dependency, entry.getKey(), overrideVersion);
                        if (state.getFailOnStrictViolation()) {
                            throw new ManipulationException(
                                    "For {} replacing original property version {} (fully resolved: {} ) with new version {} for {} violates the strict version-alignment rule!",
                                    depPr.toString(), dependency.getVersion(), resolvedValue,
                                    entry.getKey().getVersionString(), entry.getKey().asProjectRef().toString());
                        } else {
                            logger.warn(
                                    "Replacing original property version {} with new version {} for {} violates the strict version-alignment rule!",
                                    resolvedValue, overrideVersion, dependency.getVersion());
                        }
                    } else {
                        // Too much spurious logging with project.version.
                        if (!oldVersion.equals("${project.version}")) {
                            logger.info("Updating version {} for dependency {} from {}.", overrideVersion,
                                    dependency, project.getPom());
                        }

                        if (!PropertiesUtils.cacheProperty(versionPropertyUpdateMap, oldVersion, overrideVersion,
                                entry.getKey(), false)) {
                            if (oldVersion.equals("${project.version}")) {
                                logger.debug("For dependency {} ; version is built in {} so skipping inlining {}",
                                        groupIdArtifactId, oldVersion, overrideVersion);
                            } else if (strict
                                    && !PropertiesUtils.checkStrictValue(session, resolvedValue, overrideVersion)) {
                                if (state.getFailOnStrictViolation()) {
                                    throw new ManipulationException(
                                            "Replacing original version {} in dependency {} with new version {} violates the strict version-alignment rule!",
                                            oldVersion, groupIdArtifactId.toString(), overrideVersion);
                                } else {
                                    logger.warn(
                                            "Replacing original version {} in dependency {} with new version {} violates the strict version-alignment rule!",
                                            oldVersion, groupIdArtifactId, overrideVersion);
                                }
                            } else {
                                logger.debug("Altered dependency {} : {} -> {}", groupIdArtifactId, oldVersion,
                                        overrideVersion);

                                if (oldVersion.contains("${")) {
                                    String suffix = PropertiesUtils.getSuffix(session);
                                    String replaceVersion;

                                    if (state.getStrictIgnoreSuffix() && oldVersion.contains(suffix)) {
                                        replaceVersion = StringUtils.substringBefore(oldVersion, suffix);
                                        replaceVersion += suffix
                                                + StringUtils.substringAfter(overrideVersion, suffix);
                                    } else {
                                        replaceVersion = oldVersion
                                                + StringUtils.removeStart(overrideVersion, resolvedValue);
                                    }
                                    logger.debug("Resolved value is {} and replacement version is {} ",
                                            resolvedValue, replaceVersion);

                                    // In this case the previous value couldn't be cached even though it contained a property
                                    // as it was either multiple properties or a property combined with a hardcoded value. Therefore
                                    // just append the suffix.
                                    dependency.setVersion(replaceVersion);
                                } else {
                                    dependency.setVersion(overrideVersion);
                                }
                            }
                        }
                        unmatchedVersionOverrides.remove(entry.getKey());
                    }
                }
            }
        }

        return unmatchedVersionOverrides;
    }

    /**
     * Remove version overrides which refer to projects in the current reactor.
     * Projects in the reactor include things like inter-module dependencies
     * which should never be overridden.
     * @param session the ManipulationSession
     * @param versionOverrides current set of ArtifactRef:newVersion overrides.
     * @return A new Map with the reactor GAs removed.
     */
    private Map<ArtifactRef, String> removeReactorGAs(final ManipulationSession session,
            final Map<ArtifactRef, String> versionOverrides) {
        final Map<ArtifactRef, String> reducedVersionOverrides = new LinkedHashMap<>(versionOverrides);
        for (final Project project : session.getProjects()) {
            final String reactorGA = gav(project.getModel());
            reducedVersionOverrides.remove(SimpleArtifactRef.parse(reactorGA));
        }
        return reducedVersionOverrides;
    }

    /**
     * Remove module overrides which do not apply to the current module. Searches the full list of version overrides
     * for any keys which contain the '@' symbol.  Removes these from the version overrides list, and add them back
     * without the '@' symbol only if they apply to the current module.
     *
     * @param projectGA the current project group : artifact
     * @param originalOverrides The full list of version overrides, both global and module specific
     * @param moduleOverrides are individual overrides e.g. group:artifact@groupId:artifactId :: value
     * @param explicitOverrides a custom map to handle wildcard overrides
     * @return The map of global and module specific overrides which apply to the given module
     * @throws ManipulationException if an error occurs
     */
    private Map<ArtifactRef, String> applyModuleVersionOverrides(final String projectGA,
            final Map<String, String> moduleOverrides, Map<ArtifactRef, String> originalOverrides,
            final WildcardMap explicitOverrides) throws ManipulationException {
        final Map<ArtifactRef, String> remainingOverrides = new LinkedHashMap<>(originalOverrides);

        logger.debug("Calculating module-specific version overrides. Starting with:\n  {}",
                join(remainingOverrides.entrySet(), "\n  "));

        // These modes correspond to two different kinds of passes over the available override properties:
        // 1. Module-specific: Don't process wildcard overrides here, allow module-specific settings to take precedence.
        // 2. Wildcards: Add these IF there is no corresponding module-specific override.
        final boolean wildcardMode[] = { false, true };
        for (boolean aWildcardMode : wildcardMode) {
            for (final String currentKey : new HashSet<>(moduleOverrides.keySet())) {
                final String currentValue = moduleOverrides.get(currentKey);

                logger.debug("Processing key {} for override with value {}", currentKey, currentValue);

                if (!currentKey.contains("@")) {
                    logger.debug("Not an override. Skip.");
                    continue;
                }

                final boolean isWildcard = currentKey.endsWith("@*");
                logger.debug("Is wildcard? {}", isWildcard);

                // process module-specific overrides (first)
                if (!aWildcardMode) {
                    // skip wildcard overrides in this mode
                    if (isWildcard) {
                        logger.debug("Not currently in wildcard mode. Skip.");
                        continue;
                    }

                    final String[] artifactAndModule = currentKey.split("@");
                    if (artifactAndModule.length != 2) {
                        throw new ManipulationException("Invalid format for exclusion key " + currentKey);
                    }
                    final String artifactGA = artifactAndModule[0];
                    final String moduleGA = artifactAndModule[1];

                    logger.debug("For artifact override: {}, comparing parsed module: {} to current project: {}",
                            artifactGA, moduleGA, projectGA);

                    if (moduleGA.equals(projectGA)) {
                        if (currentValue != null && !currentValue.isEmpty()) {
                            explicitOverrides.put(SimpleProjectRef.parse(artifactGA), currentValue);
                            logger.debug("Overriding module dependency for {} with {} : {}", moduleGA, artifactGA,
                                    currentValue);
                        } else {
                            removeGA(remainingOverrides, SimpleProjectRef.parse(artifactGA));
                            logger.debug("Ignoring module dependency override for {} " + moduleGA);
                        }
                    }
                }
                // process wildcard overrides (second)
                else {
                    // skip module-specific overrides in this mode
                    if (!isWildcard) {
                        logger.debug("Currently in wildcard mode. Skip.");
                        continue;
                    }

                    final String artifactGA = currentKey.substring(0, currentKey.length() - 2);
                    logger.debug(
                            "For artifact override: {}, checking if current overrides already contain a module-specific version.",
                            artifactGA);

                    if (explicitOverrides.containsKey(SimpleProjectRef.parse(artifactGA))) {
                        logger.debug(
                                "For artifact override: {}, current overrides already contain a module-specific version. Skip.",
                                artifactGA);
                        continue;
                    }

                    // I think this is only used for e.g. dependencyExclusion.groupId:artifactId@*=<explicitVersion>
                    if (currentValue != null && !currentValue.isEmpty()) {
                        logger.debug("Overriding module dependency for {} with {} : {}", projectGA, artifactGA,
                                currentValue);
                        explicitOverrides.put(SimpleProjectRef.parse(artifactGA), currentValue);
                    } else {
                        // If we have a wildcard artifact we want to replace any prior explicit overrides
                        // with this one i.e. this takes precedence.
                        if (artifactGA.endsWith(":*")) {
                            final ProjectRef artifactGAPr = SimpleProjectRef.parse(artifactGA);
                            final Iterator<ArtifactRef> it = remainingOverrides.keySet().iterator();
                            while (it.hasNext()) {
                                final ArtifactRef pr = it.next();
                                if (artifactGAPr.getGroupId().equals(pr.getGroupId())) {
                                    logger.debug("Removing artifactGA " + pr + " from overrides");
                                    it.remove();
                                }
                            }
                        } else {
                            removeGA(remainingOverrides, SimpleProjectRef.parse(artifactGA));
                            logger.debug("Removing artifactGA " + artifactGA + " from overrides");
                        }
                        logger.debug("Ignoring module dependency override for {} " + projectGA);
                    }
                }
            }
        }

        return remainingOverrides;
    }

    private void removeGA(Map<ArtifactRef, String> map, ProjectRef ref) {
        Iterator<ArtifactRef> it = map.keySet().iterator();

        while (it.hasNext()) {
            ArtifactRef a = it.next();

            if (a.asProjectRef().equals(ref)) {
                it.remove();
            }
        }
    }

}