All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kloeckner.maven.plugin.VersionRange Maven / Gradle / Ivy

The newest version!
package org.kloeckner.maven.plugin;

/*
 * Copyright 2013 mk.
 *
 * 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 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 = "]++)|(?:\"[^\"]*+\")|(?:'[^\']*+')|(?:"
						+ 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 reactorProjects = new ArrayList();
		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 reactorProjects, Object result, boolean simulate)
			throws MojoExecutionException {
		Namespace namespace = rootElement.getNamespace();

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

		Map mappedVersions = getNextVersionMap(eagerArtifactsStrings);
		Map 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 roots = new ArrayList();
		roots.add(rootElement);
		List 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 getOriginalVersionMap(List
	// reactorProjects) {
	private Map getOriginalVersionMap(MavenProject projects) {
		HashMap hashMap = new HashMap();

		// 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 buildVersionsMap(List dependencies) {
		Map hashMap = new HashMap();
		for (Dependency dep : dependencies) {
			String currentVersion = dep.getVersion();
			String versionlessKey = ArtifactUtils.versionlessKey(
					dep.getGroupId(), dep.getArtifactId());
			hashMap.put(versionlessKey, currentVersion);
		}
		return hashMap;
	}

	private Map getNextVersionMap(List eagerArtifacts)
			throws MojoExecutionException {
		HashMap hashMap = new HashMap();
		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 elements,
			Map mappedVersions,
			Map 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");
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy