org.kloeckner.maven.plugin.VersionRange.java Source code

Java tutorial

Introduction

Here is the source code for org.kloeckner.maven.plugin.VersionRange.java

Source

package org.kloeckner.maven.plugin;

/*
 * 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.
 */

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Profile;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.kloeckner.maven.plugin.util.VersionRangeUtils;

/**
 * Goal updates the configured dependencies within the specified ranges.
 * 
 * @goal use-latest-versions
 * 
 * @phase process-sources
 */
public class VersionRange extends AbstractMojo {

    /** @parameter default-value="${project}" */
    private MavenProject mavenProject;

    /**
     * The entry point to Aether, i.e. the component doing all the work.
     * 
     * @component
     */
    private RepositorySystem repoSystem;

    /**
     * The current repository/network configuration of Maven.
     * 
     * @parameter default-value="${repositorySystemSession}"
     * @readonly
     */
    private RepositorySystemSession repoSession;

    /**
     * The project's remote repositories to use for the resolution of project
     * dependencies.
     * 
     * @parameter default-value="${project.remoteProjectRepositories}"
     * @readonly
     */
    private List<RemoteRepository> remoteRepos;

    /**
     * @parameter default-value="."
     */
    private String dependencyVersionRangePath = ".";

    /**
     * @parameter default-value="version-range-maven-plugin.properties"
     */
    private String dependencyVersionRangeFile = "version-range-maven-plugin.properties";

    public void execute() throws MojoExecutionException {
        readWritePom(mavenProject);
    }

    private void readWritePom(MavenProject project) throws MojoExecutionException {

        Document document;
        String intro = null;
        String outtro = null;
        try {
            String content = VersionRangeUtils.readXmlFile(VersionRangeUtils.getStandardPom(project),
                    VersionRangeUtils.LS);
            // we need to eliminate any extra whitespace inside elements, as
            // JDOM will nuke it
            content = content.replaceAll("<([^!][^>]*?)\\s{2,}([^>]*?)>", "<$1 $2>");
            content = content.replaceAll("(\\s{2,}|[^\\s])/>", "$1 />");

            SAXBuilder builder = new SAXBuilder();
            document = builder.build(new StringReader(content));

            // Normalize line endings to platform's style (XML processors like
            // JDOM normalize line endings to "\n" as
            // per section 2.11 of the XML spec)
            VersionRangeUtils.normaliseLineEndings(document);

            // rewrite DOM as a string to find differences, since text outside
            // the root element is not tracked
            StringWriter w = new StringWriter();
            Format format = Format.getRawFormat();
            format.setLineSeparator(VersionRangeUtils.LS);
            XMLOutputter out = new XMLOutputter(format);
            out.output(document.getRootElement(), w);

            int index = content.indexOf(w.toString());
            if (index >= 0) {
                intro = content.substring(0, index);
                outtro = content.substring(index + w.toString().length());
            } else {
                /*
                 * NOTE: Due to whitespace, attribute reordering or entity
                 * expansion the above indexOf test can easily fail. So let's
                 * try harder. Maybe some day, when JDOM offers a StaxBuilder
                 * and this builder employes
                 * XMLInputFactory2.P_REPORT_PROLOG_WHITESPACE, this whole mess
                 * can be avoided.
                 */
                final String SPACE = "\\s++";
                final String XML = "<\\?(?:(?:[^\"'>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+>";
                final String INTSUB = "\\[(?:(?:[^\"'\\]]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+\\]";
                final String DOCTYPE = "<!DOCTYPE(?:(?:[^\"'\\[>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+')|(?:" + INTSUB
                        + "))*+>";
                final String PI = XML;
                final String COMMENT = "<!--(?:[^-]|(?:-[^-]))*+-->";

                final String INTRO = "(?:(?:" + SPACE + ")|(?:" + XML + ")|(?:" + DOCTYPE + ")|(?:" + COMMENT
                        + ")|(?:" + PI + "))*";
                final String OUTRO = "(?:(?:" + SPACE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
                final String POM = "(?s)(" + INTRO + ")(.*?)(" + OUTRO + ")";

                Matcher matcher = Pattern.compile(POM).matcher(content);
                if (matcher.matches()) {
                    intro = matcher.group(1);
                    outtro = matcher.group(matcher.groupCount());
                }
            }
        } catch (JDOMException e) {
            throw new MojoExecutionException("Error reading POM: " + e.getMessage(), e);
        } catch (IOException e) {
            throw new MojoExecutionException("Error reading POM: " + e.getMessage(), e);
        }

        List<MavenProject> reactorProjects = new ArrayList<MavenProject>();
        Object result = new Object();
        transformDocument(project, document.getRootElement(), reactorProjects, result, false);

        // for overwriting:
        File pomFile = VersionRangeUtils.getStandardPom(project);
        // File pomFile = new File(project.getBasedir(), "newpom.xml");

        // if (simulate) {
        // File outputFile = new File(pomFile.getParentFile(), pomFile.getName()
        // + "." + pomSuffix);
        // writePom(outputFile, document, releaseDescriptor,
        // project.getModelVersion(), intro, outtro);
        // } else {

        VersionRangeUtils.writePom(pomFile, document, project.getModelVersion(), intro, outtro);
        // }

    }

    private void transformDocument(MavenProject project, Element rootElement, List<MavenProject> reactorProjects,
            Object result, boolean simulate) throws MojoExecutionException {
        Namespace namespace = rootElement.getNamespace();

        List<String> eagerArtifactsStrings = VersionRangeUtils.loadEagerDependencies(dependencyVersionRangePath,
                dependencyVersionRangeFile);

        Map<String, String> mappedVersions = getNextVersionMap(eagerArtifactsStrings);
        Map<String, String> originalVersions = getOriginalVersionMap(project);

        getLog().debug("mapped Versions (newer Versions):" + mappedVersions);
        getLog().debug("original Versions: " + originalVersions);

        Model model = project.getModel();
        Element properties = rootElement.getChild("properties", namespace);

        // String parentVersion = EagerUpdateUtils.rewriteParent(project,
        // rootElement, namespace, mappedVersions, originalVersions);
        VersionRangeUtils.rewriteParent(project, rootElement, namespace, mappedVersions, originalVersions);

        List<Element> roots = new ArrayList<Element>();
        roots.add(rootElement);
        List<Element> profileElements = VersionRangeUtils.getChildren(rootElement, "profiles", "profile");
        getLog().debug("got profiles: " + profileElements);
        roots.addAll(profileElements);

        for (Element root : roots) {
            rewriteArtifactVersions(VersionRangeUtils.getChildren(root, "parent"), mappedVersions, originalVersions,
                    model, properties, result);
            rewriteArtifactVersions(VersionRangeUtils.getChildren(root, "dependencies", "dependency"),
                    mappedVersions, originalVersions, model, properties, result);
            rewriteArtifactVersions(
                    VersionRangeUtils.getChildren(root, "dependencyManagement", "dependencies", "dependency"),
                    mappedVersions, originalVersions, model, properties, result);
        }
    }

    // private Map<String, String> getOriginalVersionMap(List<MavenProject>
    // reactorProjects) {
    private Map<String, String> getOriginalVersionMap(MavenProject projects) {
        HashMap<String, String> hashMap = new HashMap<String, String>();

        // TODO depmgmt, parent ...
        if (projects.getParentArtifact() != null) {
            hashMap.put(
                    ArtifactUtils.versionlessKey(projects.getParentArtifact().getGroupId(),
                            projects.getParentArtifact().getArtifactId()),
                    projects.getParentArtifact().getArtifactId());
        }
        hashMap.putAll(buildVersionsMap(projects.getDependencies()));
        if (projects.getDependencyManagement() != null) {
            hashMap.putAll(buildVersionsMap(projects.getDependencyManagement().getDependencies()));
        }

        for (Profile profile : projects.getActiveProfiles()) {
            hashMap.putAll(buildVersionsMap(profile.getDependencies()));
            if (profile.getDependencyManagement() != null) {
                hashMap.putAll(buildVersionsMap(profile.getDependencyManagement().getDependencies()));
            }
        }

        return hashMap;
    }

    private Map<String, String> buildVersionsMap(List<Dependency> dependencies) {
        Map<String, String> hashMap = new HashMap<String, String>();
        for (Dependency dep : dependencies) {
            String currentVersion = dep.getVersion();
            String versionlessKey = ArtifactUtils.versionlessKey(dep.getGroupId(), dep.getArtifactId());
            hashMap.put(versionlessKey, currentVersion);
        }
        return hashMap;
    }

    private Map<String, String> getNextVersionMap(List<String> eagerArtifacts) throws MojoExecutionException {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        for (String s : eagerArtifacts) {
            DefaultArtifact artifact = new DefaultArtifact(s);
            Version newVersion = resolveNewVersion(artifact);
            String versionlessKey = ArtifactUtils.versionlessKey(artifact.getGroupId(), artifact.getArtifactId());
            hashMap.put(versionlessKey, newVersion.toString());
        }
        return hashMap;
    }

    private Version resolveNewVersion(Artifact artifact) throws MojoExecutionException {
        VersionRangeRequest request = new VersionRangeRequest();
        request.setArtifact(artifact);
        request.setRepositories(remoteRepos);

        getLog().debug("Resolving artifact " + artifact + " from " + remoteRepos);

        VersionRangeResult rangeResult;
        try {
            rangeResult = repoSystem.resolveVersionRange(repoSession, request);
        } catch (VersionRangeResolutionException e) {
            e.printStackTrace();
            throw new MojoExecutionException("unable to resolve versions for: " + artifact, e);
        }
        getLog().debug("artifactId: " + artifact.getArtifactId() + " - " + rangeResult.getVersions());
        Version newestVersion = rangeResult.getHighestVersion();
        return newestVersion;
    }

    private void rewriteArtifactVersions(Collection<Element> elements, Map<String, String> mappedVersions,
            Map<String, String> originalVersions, Model projectModel, Element properties, Object result)
            throws MojoExecutionException {
        if (elements == null) {
            return;
        }

        String projectId = ArtifactUtils.versionlessKey(projectModel.getGroupId(), projectModel.getArtifactId());
        for (Element element : elements) {
            Element versionElement = element.getChild("version", element.getNamespace());
            if (versionElement == null) {
                // managed dependency or unversioned plugin
                continue;
            }
            String rawVersion = versionElement.getTextTrim();
            Element groupIdElement = element.getChild("groupId", element.getNamespace());
            if (groupIdElement == null) {
                if ("plugin".equals(element.getName())) {
                    groupIdElement = new Element("groupId", element.getNamespace());
                    groupIdElement.setText("org.apache.maven.plugins");
                } else {
                    // incomplete dependency
                    continue;
                }
            }
            String groupId = VersionRangeUtils.interpolate(groupIdElement.getTextTrim(), projectModel);

            Element artifactIdElement = element.getChild("artifactId", element.getNamespace());
            if (artifactIdElement == null) {
                // incomplete element
                continue;
            }
            String artifactId = VersionRangeUtils.interpolate(artifactIdElement.getTextTrim(), projectModel);

            String key = ArtifactUtils.versionlessKey(groupId, artifactId);
            String mappedVersion = mappedVersions.get(key);
            String originalVersion = originalVersions.get(key);

            if (mappedVersion != null) {
                if (rawVersion.equals(originalVersion)) {
                    getLog().info("Updating " + artifactId + " to " + mappedVersion);
                    VersionRangeUtils.rewriteValue(versionElement, mappedVersion);
                } else if (rawVersion.matches("\\$\\{.+\\}")) {
                    String expression = rawVersion.substring(2, rawVersion.length() - 1);

                    if (expression.startsWith("project.") || expression.startsWith("pom.")
                            || "version".equals(expression)) {
                        if (!mappedVersion.equals(mappedVersions.get(projectId))) {
                            getLog().info("Updating " + artifactId + " to " + mappedVersion);
                            VersionRangeUtils.rewriteValue(versionElement, mappedVersion);
                        } else {
                            getLog().info("Ignoring artifact version update for expression " + rawVersion);
                        }
                    } else if (properties != null) {
                        // version is an expression, check for properties to
                        // update instead
                        Element property = properties.getChild(expression, properties.getNamespace());
                        if (property != null) {
                            String propertyValue = property.getTextTrim();

                            if (propertyValue.equals(originalVersion)) {
                                getLog().info("Updating " + rawVersion + " to " + mappedVersion);
                                // change the property only if the property is
                                // the same as what's in the reactor
                                VersionRangeUtils.rewriteValue(property, mappedVersion);
                            } else if (mappedVersion.equals(propertyValue)) {
                                // this property may have been updated during
                                // processing a sibling.
                                getLog().info("Ignoring artifact version update for expression " + rawVersion
                                        + " because it is already updated");
                            } else if (!mappedVersion.equals(rawVersion)) {
                                if (mappedVersion.matches("\\$\\{project.+\\}")
                                        || mappedVersion.matches("\\$\\{pom.+\\}")
                                        || "${version}".equals(mappedVersion)) {
                                    getLog().info(
                                            "Ignoring artifact version update for expression " + mappedVersion);
                                    // ignore... we cannot update this
                                    // expression
                                } else {
                                    // the value of the expression conflicts
                                    // with what the user wanted to release
                                    throw new MojoExecutionException("The artifact (" + key + ") requires a "
                                            + "different version (" + mappedVersion + ") than what is found ("
                                            + propertyValue + ") for the expression (" + expression + ") in the "
                                            + "project (" + projectId + ").");
                                }
                            }
                        } else {
                            // the expression used to define the version of this
                            // artifact may be inherited
                            // TODO needs a better error message, what pom? what
                            // dependency?
                            throw new MojoExecutionException("The version could not be updated: " + rawVersion);
                        }
                    }
                } else {
                    // different/previous version not related to current release
                    getLog().debug("different/previous version not related to current release");
                }
            } else {
                // artifact not related to current release
                getLog().debug("artifact not related to current release");
            }
        }
    }
}