org.jboss.maven.extension.dependency.modelmodifier.versionoverride.DepVersionOverrider.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.maven.extension.dependency.modelmodifier.versionoverride.DepVersionOverrider.java

Source

/**
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * 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.jboss.maven.extension.dependency.modelmodifier.versionoverride;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.MavenExecutionException;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.ModelBuildingException;
import org.jboss.maven.extension.dependency.resolver.EffectiveModelBuilder;
import org.jboss.maven.extension.dependency.util.Log;
import org.jboss.maven.extension.dependency.util.MavenUtil;
import org.jboss.maven.extension.dependency.util.VersionPropertyReader;
import org.sonatype.aether.resolution.ArtifactDescriptorException;
import org.sonatype.aether.resolution.ArtifactResolutionException;

/**
 * Overrides dependency versions in a model
 */
public class DepVersionOverrider extends AbstractVersionOverrider {
    /**
     * The String that needs to be prepended a system property to make it a version override. <br />
     * ex: -Dversion:junit:junit=4.10
     */
    private static final String DEPENDENCY_VERSION_OVERRIDE_PREFIX = "version:";

    /**
     * The name of the property that specifies whether or not to override transitive dependencies in the build. This
     * causes non-matching dependencies to be added to the dependency management section of the pom. Default is true. <br />
     * ex: -overrideTransitive=true
     */
    private static final String OVERRIDE_TRANSITIVE = "overrideTransitive";

    /**
     * The name of the property which contains the GAV of the remote pom from which to retrieve dependency management
     * information. <br />
     * ex: -DdependencyManagement:org.foo:bar-dep-mgmt:1.0
     */
    private static final String DEPENDENCY_MANAGEMENT_POM_PROPERTY = "dependencyManagement";

    /**
     * A short description of the thing being overridden
     */
    private static final String OVERRIDE_NAME = "dependency";

    /**
     * Cache for override properties. Null until getVersionOverrides() is called.
     */
    private Map<String, String> dependencyVersionOverrides;

    /**
     * The set of projects currently in the reactor. The versions of these projects should not be overridden.
     */
    private Set<String> reactorProjects;

    /**
     * Modify model's dependency management and direct dependencies.
     */
    @Override
    public boolean updateModel(Model model) throws MavenExecutionException {
        Map<String, String> versionOverrides = getVersionOverrides();
        if (versionOverrides.size() == 0) {
            return false;
        }

        versionOverrides = removeReactorGAs(versionOverrides);

        String projectGA = model.getGroupId() + ":" + model.getArtifactId();

        versionOverrides = applyModuleVersionOverrides(projectGA, versionOverrides);

        // Add/override a property to the build for each override
        addVersionOverrideProperties(versionOverrides, model.getProperties());

        // 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);
            Log.getLog().debug("Added <DependencyManagement/> for current project");
        }

        // Apply overrides to project dependency management
        List<Dependency> dependencies = dependencyManagement.getDependencies();
        Map<String, String> nonMatchingVersionOverrides = applyOverrides(dependencies, versionOverrides);
        if (overrideTransitive()) {
            // Add dependencies to Dependency Management which did not match any existing dependency
            for (String groupIdArtifactId : nonMatchingVersionOverrides.keySet()) {
                String[] groupIdArtifactIdParts = groupIdArtifactId.split(":");

                Dependency newDependency = new Dependency();
                newDependency.setGroupId(groupIdArtifactIdParts[0]);
                newDependency.setArtifactId(groupIdArtifactIdParts[1]);

                String artifactVersion = nonMatchingVersionOverrides.get(groupIdArtifactId);
                newDependency.setVersion(artifactVersion);

                dependencyManagement.getDependencies().add(newDependency);
                Log.getLog().debug("New entry added to <DependencyManagement/> - " + groupIdArtifactId + ":"
                        + artifactVersion);
            }
        } else {
            Log.getLog().debug("Non-matching dependencies ignored.");
        }

        // Apply overrides to project direct dependencies
        List<Dependency> projectDependencies = model.getDependencies();
        applyOverrides(projectDependencies, versionOverrides);

        // Include the overrides in the built files for repeatability
        writeOverrideMap(model, getName(), versionOverrides);

        // Assuming the Model changed since overrides were given
        return true;
    }

    private Set<String> getReactorProjects() {
        if (reactorProjects == null || reactorProjects.size() == 0) {
            String[] reactorProjectGAs = System.getProperty("reactorProjectGAs").split(",");
            reactorProjects = new HashSet<String>(Arrays.asList(reactorProjectGAs));
        }
        return reactorProjects;
    }

    @Override
    public String getName() {
        return OVERRIDE_NAME;
    }

    /**
     * Whether to override unmanaged transitive dependencies in the build. Has the effect of adding (or not) new entries
     * to dependency management when no matching dependency is found in the pom. Defaults to true.
     *
     * @return
     */
    private boolean overrideTransitive() {
        String overrideTransitive = System.getProperties().getProperty(OVERRIDE_TRANSITIVE, "true");
        return overrideTransitive.equals("true");
    }

    /**
     * Get the set of versions which will be used to override local dependency versions. This is the full set of version
     * overrides from system properties and remote poms.
     *
     * The format of the key is "groupId:artifactId[@moduleGroupId:moduleArtifactId]"
     * The value is the version string
     */
    private Map<String, String> getVersionOverrides() throws MavenExecutionException {
        if (dependencyVersionOverrides == null) {
            dependencyVersionOverrides = new HashMap<String, String>();

            Map<String, String> remoteDepOverrides = loadRemoteDepVersionOverrides();
            dependencyVersionOverrides.putAll(remoteDepOverrides);

            Map<String, String> propDepOverrides = VersionPropertyReader
                    .getPropertiesByPrefix(DEPENDENCY_VERSION_OVERRIDE_PREFIX);
            dependencyVersionOverrides.putAll(propDepOverrides);

        }
        return dependencyVersionOverrides;
    }

    /**
     * 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 versionOverrides
     * @return A new Map with the reactor GAs removed.
     */
    private Map<String, String> removeReactorGAs(Map<String, String> versionOverrides) {
        Map<String, String> reducedVersionOverrides = new HashMap<String, String>(versionOverrides);
        Set<String> reactorProjects = getReactorProjects();
        for (String reactorGA : reactorProjects) {
            reducedVersionOverrides.remove(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 versionOverides The full list of version overrides, both global and module specific
     * @return The map of global and module specific overrides which apply to the given module
     */
    private Map<String, String> applyModuleVersionOverrides(String projectGA,
            Map<String, String> versionOverrides) {
        Map<String, String> moduleVersionOverrides = new HashMap<String, String>(versionOverrides);
        for (String currentKey : versionOverrides.keySet()) {
            if (currentKey.contains("@")) {
                moduleVersionOverrides.remove(currentKey);
                String[] artifactAndModule = currentKey.split("@");
                String artifactGA = artifactAndModule[0];
                String moduleGA = artifactAndModule[1];
                if (moduleGA.equals(projectGA)) {
                    if (versionOverrides.get(currentKey) != null && versionOverrides.get(currentKey).length() > 0) {
                        moduleVersionOverrides.put(artifactGA, versionOverrides.get(currentKey));
                    } else {
                        moduleVersionOverrides.remove(artifactGA);
                        Log.getLog().debug("Ignoring module dependency override for " + moduleGA);
                    }
                }
            }
        }
        return moduleVersionOverrides;
    }

    /***
     * Add properties to the build which match the version overrides.
     * The property names are in the format
     */
    private void addVersionOverrideProperties(Map<String, String> overrides, Properties props) {
        String propPrefix = getVersionPropertyPrefix();
        String gaSeparator = getGASeparator();
        String propSuffix = getVersionPropertySuffix();

        for (String currentGA : overrides.keySet()) {
            String versionPropName = propPrefix + currentGA.replace(":", gaSeparator) + propSuffix;
            props.setProperty(versionPropName, overrides.get(currentGA));
        }
    }

    /**
     * Get the prefix that should be used for version property names
     * @return The prefix set in the system properties or the defult DEPENDENCY_VERSION_OVERRIDE_PREFIX
     */
    private String getVersionPropertyPrefix() {
        return System.getProperty("versionPropertyPrefix", DEPENDENCY_VERSION_OVERRIDE_PREFIX);
    }

    /**
     * Get the groupId/artifactId separator
     * @return The separator set in the system properties, or ":" by default
     */
    private String getGASeparator() {
        return System.getProperty("versionPropertyGASeparator", ":");
    }

    /**
     * Get the suffix that should be used for version property names
     * @return The suffix set in the system properties or the default empty string
     */
    private String getVersionPropertySuffix() {
        return System.getProperty("versionPropertySuffix", "");
    }

    /**
     * Apply a set of version overrides to a list of dependencies. Return a set of the overrides which were not applied.
     *
     * @param dependencies The list of dependencies
     * @param overrides The map of dependency version overrides
     * @return The map of overrides that were not matched in the dependencies
     */
    private static Map<String, String> applyOverrides(List<Dependency> dependencies,
            Map<String, String> overrides) {
        Set<String> excludes = new HashSet<String>();
        return applyOverrides(dependencies, overrides, excludes);
    }

    /**
     * Apply a set of version overrides to a list of dependencies. Return a set of the overrides which were not applied.
     *
     * @param dependencies The list of dependencies
     * @param overrides The map of dependency version overrides
     * @param excludes A set of GAs to ignore when overridding dep versions
     * @return The map of overrides that were not matched in the dependencies
     */
    private static Map<String, String> applyOverrides(List<Dependency> dependencies, Map<String, String> overrides,
            Set<String> excludes) {
        // Duplicate the override map so unused overrides can be easily recorded
        Map<String, String> unmatchedVersionOverrides = new HashMap<String, String>();
        unmatchedVersionOverrides.putAll(overrides);

        // Apply matching overrides to dependencies
        for (Dependency dependency : dependencies) {
            String groupIdArtifactId = dependency.getGroupId() + GAV_SEPERATOR + dependency.getArtifactId();
            if (overrides.containsKey(groupIdArtifactId) && !excludes.contains(groupIdArtifactId)) {
                String oldVersion = dependency.getVersion();
                String overrideVersion = overrides.get(groupIdArtifactId);

                if (overrideVersion == null || overrideVersion.length() == 0) {
                    Log.getLog()
                            .warn("Unable to align to an empty version for " + groupIdArtifactId + "; ignoring");
                } else {
                    dependency.setVersion(overrideVersion);
                    Log.getLog().debug(
                            "Altered dependency " + groupIdArtifactId + " " + oldVersion + "->" + overrideVersion);
                    unmatchedVersionOverrides.remove(groupIdArtifactId);
                }
            }
        }

        return unmatchedVersionOverrides;
    }

    /**
     * Get dependency management version properties from a remote POM
     *
     * @return Map between the GA of the dependency and the version of the dependency. If the property is not set,
     *         returns an empty map
     */
    private static Map<String, String> loadRemoteDepVersionOverrides() throws MavenExecutionException {
        Properties systemProperties = System.getProperties();
        String depMgmtPomCSV = systemProperties.getProperty(DEPENDENCY_MANAGEMENT_POM_PROPERTY);

        Map<String, String> versionOverrides = new HashMap<String, String>(0);

        if (depMgmtPomCSV == null) {
            return versionOverrides;
        }

        String[] depMgmtPomGAVs = depMgmtPomCSV.split(",");

        // Iterate in reverse order so that the first GAV in the list overwrites the last
        for (int i = (depMgmtPomGAVs.length - 1); i > -1; --i) {
            String nextGAV = depMgmtPomGAVs[i];
            if (!MavenUtil.validGav(nextGAV)) {
                Log.getLog().warn("Skipping invalid dependency management GAV: " + nextGAV);
                continue;
            }
            try {
                EffectiveModelBuilder resolver = EffectiveModelBuilder.getInstance();
                versionOverrides.putAll(resolver.getRemoteDependencyVersionOverrides(nextGAV));
            } catch (ArtifactResolutionException e) {
                Log.getLog().error("Unable to resolve remote pom: " + e);
                throw new MavenExecutionException("Unable to resolve remote pom", e);
            } catch (ArtifactDescriptorException e) {
                Log.getLog().error("Unable to resolve remote pom: " + e);
                throw new MavenExecutionException("Unable to resolve remote pom", e);
            } catch (ModelBuildingException e) {
                Log.getLog().error("Unable to resolve remote pom: " + e);
                throw new MavenExecutionException("Unable to resolve remote pom", e);
            }
        }

        return versionOverrides;
    }
}