org.wildfly.channelplugin.UpgradeComponentsMojo Maven / Gradle / Ivy
Show all versions of wildfly-channel-maven-plugin
package org.wildfly.channelplugin;
import groovy.lang.Tuple;
import groovy.lang.Tuple3;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
import org.codehaus.mojo.versions.ordering.MavenVersionComparator;
import org.codehaus.mojo.versions.ordering.VersionComparator;
import org.commonjava.maven.atlas.ident.ref.ArtifactRef;
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.atlas.ident.ref.SimpleProjectVersionRef;
import org.commonjava.maven.ext.common.ManipulationException;
import org.commonjava.maven.ext.common.model.Project;
import org.commonjava.maven.ext.core.ManipulationSession;
import org.wildfly.channel.NoStreamFoundException;
import org.wildfly.channel.Repository;
import org.wildfly.channel.UnresolvedMavenArtifactException;
import org.wildfly.channel.VersionResult;
import org.wildfly.channelplugin.manipulation.PomManipulator;
import org.wildfly.channelplugin.utils.PMEUtils;
import org.wildfly.channeltools.util.VersionUtils;
import javax.inject.Inject;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import static org.wildfly.channeltools.util.ConversionUtils.toArtifactRef;
import static org.wildfly.channeltools.util.ConversionUtils.toProjectRefs;
/**
* This tasks overrides dependencies versions according to provided channel file.
*
* One of following properties needs to be set:
*
`channelFile` to use a channel file located on a local file system,
* `channelGAV` to lookup an artifact containing the channel file.
*/
@Mojo(name = "upgrade", requiresDirectInvocation = true)
public class UpgradeComponentsMojo extends AbstractChannelMojo {
private final static VersionComparator VERSION_COMPARATOR = new MavenVersionComparator();
/**
* Comma separated list of dependency G:As that should not be upgraded.
*/
@Parameter(property = "ignoreStreams")
List ignoreStreams;
/**
* Takes precedence over the ignoreStreams settings. One can use it to set "-DignoreStreams=org.wildfly.core:*" and
* "-DdontIgnoreStreams=org.wildfly.core:wildfly-core-parent" which would together ignore all org.wildfly.core:*
* streams except for org.wildfly.core:wildfly-core-parent.
*/
@Parameter(property = "dontIgnoreStreams")
List dontIgnoreStreams;
/**
* Comma separated list of module G:As that should not be processed.
*/
@Parameter(property = "ignoreModules")
List ignoreModules;
/**
* Comma separated list of property names. Project properties that match one of these names will not get
* overridden.
*/
@Parameter(property = "ignoreProperties")
List ignoreProperties;
/**
* Comma separated list of property names prefixes. Project properties that match one of these prefixes will not get
* overridden.
*/
@Parameter(property = "ignorePropertiesPrefixedWith")
List ignorePropertiesPrefixedWith;
/**
* Comma separated list of propertyName=propertyValue. Given properties will be overridden to given values.
*
* This setting takes precedence over channel streams.
*/
@Parameter(property = "overrideProperties")
List overrideProperties;
/**
* Comma separated list of groupId:artifactId:version triplets. All existing dependencies in the project with given
* groupId and artifactId will be set to given version.
*
* This setting takes precedence over channel streams.
*/
@Parameter(property = "overrideDependencies")
List overrideDependencies;
/**
* If true, upgraded versions would be inlined in the dependency version element, possibly replacing a property
* reference if it was originally used. If false, the property would be updated instead (less robust option).
*/
@Parameter(property = "inlineUpgradedVersions", defaultValue = "false")
boolean inlineUpgradedVersions;
/**
* If true, transitive dependencies of the project that are also declared in the channel will be injected into root
* POM's dependencyManagement section.
*/
@Parameter(property = "injectTransitiveDependencies", defaultValue = "true")
boolean injectTransitiveDependencies;
@Parameter(property = "ignoreTestDependencies", defaultValue = "true")
boolean ignoreTestDependencies;
/**
* When a dependency is defined with version string referencing a property, and that property is defined in a parent
* pom outside the project, the property would be injected into a pom where the dependency is defined, if this
* parameter is set to true (default).
*/
@Parameter(property = "injectExternalProperties", defaultValue = "true")
boolean injectExternalProperties;
/**
* Should the remote maven repositories (specified via -DremoteRepositories or in input channels) be injected into
* the parent project POM, to ensure that project is buildable?
*/
@Parameter(property = "injectRepositories", defaultValue = "true")
boolean injectRepositories;
/**
* If set to true, the plugin will not downgrade versions.
*/
@Parameter(property = "doNotDowngrade", defaultValue = "false")
boolean doNotDowngrade;
@Inject
DependencyGraphBuilder dependencyGraphBuilder;
@Inject
ManipulationSession manipulationSession;
private final List ignoredStreams = new ArrayList<>();
private final List unignoredStreams = new ArrayList<>();
private final List ignoredModules = new ArrayList<>();
private Set projectGavs;
private final HashMap, PomManipulator> manipulators = new HashMap<>();
private PomManipulator rootManipulator;
private final HashMap, String> upgradedProperties = new HashMap<>();
private final Set declaredDependencies = new HashSet<>();
/**
* This includes pre-processing of input parameters.
*/
private void init() throws MojoExecutionException {
initChannelSession();
ignoreStreams.forEach(ga -> ignoredStreams.add(SimpleProjectRef.parse(ga)));
dontIgnoreStreams.forEach(ga -> unignoredStreams.add(SimpleProjectRef.parse(ga)));
ignoreModules.forEach(ga -> ignoredModules.add(SimpleProjectRef.parse(ga)));
}
/**
* This updates the configuration according to a config file living in the project root. Should be called before the
* {@link #init()} method.
*/
private void reconfigure() throws MojoExecutionException {
File configFile = new File(mavenSession.getExecutionRootDirectory(), MojoConfigurator.DEFAULT_CONFIGURATION_FILE);
try {
MojoConfigurator configurator = new MojoConfigurator(configFile);
configurator.configureProperties(this);
} catch (IOException e) {
throw new MojoExecutionException("Unable to read plugin configuration from " + MojoConfigurator.DEFAULT_CONFIGURATION_FILE, e);
}
}
@Override
public void execute() throws MojoExecutionException {
if (!mavenSession.getCurrentProject().isExecutionRoot()) {
// do not perform any work in submodules
return;
}
reconfigure();
init();
try {
List pmeProjects = PMEUtils.parsePmeProjects(pomIO, mavenProject);
// collect GAVs of in-project modules, these are not going to be upgraded
projectGavs = pmeProjects.stream()
.map(p -> new SimpleProjectVersionRef(p.getGroupId(), p.getArtifactId(), p.getVersion()))
.collect(Collectors.toSet());
// process project modules
for (Project project: pmeProjects) {
ProjectRef moduleGA = project.getKey().asProjectRef();
if (ignoredModules.contains(moduleGA)) {
getLog().info(String.format("Skipping module %s:%s", project.getGroupId(), project.getArtifactId()));
continue;
}
getLog().info(String.format("Processing module %s:%s", project.getGroupId(), project.getArtifactId()));
// create manipulator for given module
PomManipulator manipulator = new PomManipulator(project);
manipulators.put(Pair.of(project.getGroupId(), project.getArtifactId()), manipulator);
processModule(project, manipulator);
}
Project rootProject = PMEUtils.findRootProject(pmeProjects);
rootManipulator = manipulators.get(Pair.of(rootProject.getGroupId(), rootProject.getArtifactId()));
if (injectTransitiveDependencies) {
injectTransitiveDependencies();
}
// if channel was given as an input, insert channel repositories into the parent pom
if (injectRepositories) {
Map repositoriesToInject = channels.stream().flatMap(c -> c.getRepositories().stream()).distinct()
.collect(Collectors.toMap(Repository::getId, Repository::getUrl));
InjectRepositoriesMojo.insertRepositories(rootProject, rootManipulator, repositoriesToInject);
}
// override modified poms
for (PomManipulator manipulator: manipulators.values()) {
manipulator.writePom();
}
} catch (ManipulationException | XMLStreamException e) {
throw new MojoExecutionException("Project parsing failed", e);
} catch (DependencyGraphBuilderException e) {
throw new MojoExecutionException("Dependency collector error", e);
}
}
/**
* Processes single project module:
* collects all declared dependencies,
* performs hard overrides of properties and dependency versions in the module,
* upgrades dependencies according to channel definition.
*/
private void processModule(Project pmeProject, PomManipulator manipulator)
throws ManipulationException, XMLStreamException {
Map resolvedProjectDependencies = collectResolvedProjectDependencies(pmeProject);
resolvedProjectDependencies.keySet().forEach(a -> declaredDependencies.add(a.asProjectRef()));
List overriddenProperties = performHardPropertyOverrides(manipulator);
List overriddenDependencies = performHardDependencyOverrides(resolvedProjectDependencies, manipulator);
List> dependenciesToUpgrade = findDependenciesToUpgrade(resolvedProjectDependencies);
for (Tuple3 upgrade: dependenciesToUpgrade) {
String newVersion = upgrade.getV3();
Dependency locatedDependency = upgrade.getV1();
ArtifactRef resolvedDependency = upgrade.getV2();
@SuppressWarnings("UnnecessaryLocalVariable")
Dependency d = locatedDependency;
if (overriddenDependencies.contains(locatedDependency)) {
// if there was a hard version override, the dependency is not processed again
continue;
}
if (VersionUtils.isProperty(locatedDependency.getVersion()) && !inlineUpgradedVersions) { // dependency version is set from a property
String originalVersionString = locatedDependency.getVersion();
String versionPropertyName = VersionUtils.extractPropertyName(originalVersionString);
if (overriddenProperties.contains(versionPropertyName)) {
// this property has been overridden based on `overrideProperties` parameter, do not process again
continue;
}
Pair projectProperty = followProperties(pmeProject, versionPropertyName);
if (projectProperty == null) {
Pair externalProperty = resolveExternalProperty(mavenProject, versionPropertyName);
if (externalProperty != null) {
projectProperty = Pair.of(null, externalProperty.getLeft());
}
}
if (projectProperty == null) {
ChannelPluginLogger.LOGGER.errorf(
"Unable to upgrade %s:%s:%s to '%s', can't locate property '%s' in the project",
d.getGroupId(), d.getArtifactId(), d.getVersion(), newVersion,
versionPropertyName);
continue;
}
Project targetProject = projectProperty.getLeft();
String targetPropertyName = projectProperty.getRight();
if (isIgnoredProperty(targetPropertyName)) {
getLog().info(String.format("Ignoring property '%s'", targetPropertyName));
continue;
}
if (upgradedProperties.containsKey(projectProperty)) {
if (!upgradedProperties.get(projectProperty).trim().equals(newVersion.trim())) {
// property has already been changed to different value
String propertyName = projectProperty.getRight();
String currentPropertyValue = upgradedProperties.get(projectProperty);
getLog().warn(String.format("Inlining version string for %s:%s:%s, new version '%s'. " +
"The original version property '%s' has already been modified to '%s'.",
d.getGroupId(), d.getArtifactId(), d.getVersion(), newVersion, propertyName,
currentPropertyValue));
manipulator.overrideDependencyVersion(d.getGroupId(), d.getArtifactId(),
originalVersionString, newVersion);
continue; // do not override the property again
}
}
// get manipulator for the module where the target property is located
upgradedProperties.put(projectProperty, newVersion);
if (targetProject != null) {
// property has been located in some project module
// => override the located property in the module where it has been located
PomManipulator targetManipulator = manipulators.get(
Pair.of(targetProject.getGroupId(), targetProject.getArtifactId()));
targetManipulator.overrideProperty(targetPropertyName, newVersion);
} else if (injectExternalProperties) {
// property has been located in external parent pom
// => inject the property into current module
PomManipulator targetManipulator = manipulators.get(
Pair.of(pmeProject.getGroupId(), pmeProject.getArtifactId()));
targetManipulator.injectProperty(targetPropertyName, newVersion);
} else {
getLog().warn(String.format("Can't upgrade %s:%s:%s to %s, property %s is not defined in the " +
"scope of the project (consider enabling the injectExternalProperties parameter).",
d.getGroupId(), d.getArtifactId(), d.getVersion(), newVersion, targetPropertyName));
}
} else { // dependency version is inlined in version element, can be directly overwritten
manipulator.overrideDependencyVersionWithComment(resolvedDependency, newVersion);
}
}
}
/**
* Performs hard property overrides based on the `overrideProperties` parameter input.
*
* @param manipulator manipulator for current module
* @return list of overridden properties
*/
private List performHardPropertyOverrides(PomManipulator manipulator) throws XMLStreamException {
ArrayList overriddenProperties = new ArrayList<>();
for (String nameValue: overrideProperties) {
String[] split = nameValue.split("=");
if (split.length != 2) {
getLog().error(String.format("Can't interpret property to override settings: '%s'", nameValue));
continue;
}
String propertyName = split[0];
String propertyValue = split[1];
if (manipulator.overrideProperty(propertyName, propertyValue)) {
getLog().info(String.format("Property '%s' overridden to '%s'", propertyName, propertyValue));
overriddenProperties.add(propertyName);
}
}
return overriddenProperties;
}
/**
* Performs hard dependency versions overrides based on the `overrideDependencies` parameter input. These versions
* are inlined into the version elements, version properties are not followed.
*
* @param resolvedProjectDependencies collection of all resolved dependencies in the module
* @param manipulator manipulator for current module
* @return list of updated dependencies
*/
private List performHardDependencyOverrides(Map resolvedProjectDependencies,
PomManipulator manipulator) throws XMLStreamException {
List overriddenDependencies = new ArrayList<>();
for (Dependency dependency: resolvedProjectDependencies.values()) {
Optional overridenVersion = findOverriddenVersion(dependency);
if (overridenVersion.isPresent()) {
manipulator.overrideDependencyVersion(toArtifactRef(dependency), overridenVersion.get());
overriddenDependencies.add(dependency);
}
}
return overriddenDependencies;
}
private Optional findOverriddenVersion(Dependency dependency) {
for (String gav: overrideDependencies) {
String[] split = gav.split(":");
if (split.length != 3) {
continue;
}
String g = split[0];
String a = split[1];
String v = split[2];
if (dependency.getGroupId().equals(g) && dependency.getArtifactId().equals(a)) {
return Optional.of(v);
}
}
return Optional.empty();
}
private boolean isIgnoredProperty(String propertyName) {
if (ignoreProperties.contains(propertyName)) {
return true;
}
for (String prefix: ignorePropertiesPrefixedWith) {
if (propertyName.startsWith(prefix)) {
return true;
}
}
return false;
}
private Map collectResolvedProjectDependencies(Project pmeProject)
throws ManipulationException {
Map projectDependencies = new HashMap<>();
projectDependencies.putAll(pmeProject.getResolvedManagedDependencies(manipulationSession));
projectDependencies.putAll(pmeProject.getResolvedDependencies(manipulationSession));
if (projectDependencies.isEmpty()) {
getLog().debug("No dependencies found in " + pmeProject.getArtifactId());
}
return projectDependencies;
}
private List> findDependenciesToUpgrade(
Map resolvedProjectDependencies) {
List> dependenciesToUpgrade = new ArrayList<>();
for (Map.Entry entry : resolvedProjectDependencies.entrySet()) {
ArtifactRef resolvedArtifact = entry.getKey();
Dependency dependency = entry.getValue();
Objects.requireNonNull(resolvedArtifact);
Objects.requireNonNull(dependency);
if (projectGavs.contains(resolvedArtifact.asProjectVersionRef())) {
getLog().debug("Ignoring in-project dependency: "
+ resolvedArtifact.asProjectVersionRef().toString());
continue;
}
if (!unignoredStreams.contains(resolvedArtifact.asProjectRef())) {
if (ignoredStreams.contains(resolvedArtifact.asProjectRef())) {
getLog().info("Skipping dependency (ignored stream): "
+ resolvedArtifact.asProjectVersionRef().toString());
continue;
}
ProjectRef wildCardIgnoredProjectRef = new SimpleProjectRef(resolvedArtifact.getGroupId(), "*");
if (ignoredStreams.contains(wildCardIgnoredProjectRef)) {
getLog().info("Skipping dependency (ignored stream): "
+ resolvedArtifact.asProjectVersionRef().toString());
continue;
}
}
if (resolvedArtifact.getVersionString() == null) {
// this is not expected to happen
getLog().error("Resolved dependency has null version: " + resolvedArtifact);
continue;
}
if (VersionUtils.isProperty(resolvedArtifact.getVersionString())) {
// hack: PME doesn't seem to resolve properties from external parent poms
Pair externalProperty = resolveExternalProperty(mavenProject,
VersionUtils.extractPropertyName(resolvedArtifact.getVersionString()));
if (externalProperty != null) {
resolvedArtifact = new SimpleArtifactRef(resolvedArtifact.getGroupId(), resolvedArtifact.getArtifactId(),
externalProperty.getRight(), resolvedArtifact.getType(), resolvedArtifact.getClassifier());
} else {
// didn't manage to resolve dependency version, this is not expected to happen
getLog().error("Resolved dependency has version with property: " + resolvedArtifact);
continue;
}
}
if ("test".equals(dependency.getScope()) && ignoreTestDependencies) {
getLog().info("Skipping dependency (ignored scope): "
+ resolvedArtifact.asProjectVersionRef().toString());
continue;
}
try {
VersionResult versionResult = channelSession.findLatestMavenArtifactVersion(resolvedArtifact.getGroupId(),
resolvedArtifact.getArtifactId(), resolvedArtifact.getType(), resolvedArtifact.getClassifier(),
resolvedArtifact.getVersionString());
String channelVersion = versionResult.getVersion();
int comparison = compareVersions(channelVersion, resolvedArtifact.getVersionString());
if (comparison > 0 || (!doNotDowngrade && comparison < 0)) {
getLog().info("Updating dependency " + resolvedArtifact.getGroupId()
+ ":" + resolvedArtifact.getArtifactId() + ":" + resolvedArtifact.getVersionString()
+ " to version " + channelVersion);
dependenciesToUpgrade.add(Tuple.tuple(dependency, resolvedArtifact, channelVersion));
}
} catch (UnresolvedMavenArtifactException e) {
// this produces a lot of noise due to many of e.g. test artifacts not being managed by channels, so keep it
// at the debug level
getLog().debug("Can't resolve artifact: " + resolvedArtifact, e);
}
}
return dependenciesToUpgrade;
}
/**
* Overrides versions of transitive dependencies in all project's modules.
*
* This has to be called after all submodules has been processed (so that all declared dependencies has been
* collected).
*/
private void injectTransitiveDependencies() throws DependencyGraphBuilderException {
final List projectGAs = projectGavs.stream().map(ProjectRef::asProjectRef)
.collect(Collectors.toList());
// This performs a traversal of a dependency tree of all submodules in the project. All discovered dependencies
// that are not directly declared in the project are considered transitive dependencies.
Map> dependenciesToInject = new HashMap<>();
ArrayList projects = new ArrayList<>();
projects.add(mavenProject);
projects.addAll(mavenProject.getCollectedProjects());
for (MavenProject module: projects) {
// Collect exclusions from the effective POM
Map> artifactExclusions = new HashMap<>();
List managedDependencies = Collections.emptyList();
if (module.getModel().getDependencyManagement() != null) {
managedDependencies = module.getModel().getDependencyManagement().getDependencies();
}
managedDependencies.forEach(dep -> artifactExclusions.put(toArtifactRef(dep), toProjectRefs(dep.getExclusions())));
ProjectBuildingRequest buildingRequest =
new DefaultProjectBuildingRequest(mavenSession.getProjectBuildingRequest());
buildingRequest.setProject(module);
DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
rootNode.accept(visitor);
visitor.getNodes().forEach(node -> {
ArtifactRef artifact = toArtifactRef(node.getArtifact());
// Project modules should not be counted into undeclared dependencies.
if (projectGAs.contains(artifact.asProjectRef())) {
return;
}
// Declared project dependencies should not be counted as undeclared.
if (declaredDependencies.contains(artifact.asProjectRef())) {
return;
}
// Ignore test scope dependencies.
if ("test".equals(node.getArtifact().getScope()) && ignoreTestDependencies) {
// Ignore test scope undeclared dependencies entirely.
return;
}
// Check if the dependency channel stream is configured as ignored.
boolean isIgnored = ignoredStreams.contains(artifact.asProjectRef())
|| ignoredStreams.contains(new SimpleProjectRef(artifact.getGroupId(), "*"));
boolean isUnignored = unignoredStreams.contains(artifact.asProjectRef());
if (isIgnored && !isUnignored) {
return;
}
Collection exclusions = artifactExclusions.getOrDefault(artifact, Collections.emptyList());
Collection existingExclusions = dependenciesToInject.computeIfAbsent(artifact, a -> new HashSet<>());
existingExclusions.addAll(exclusions);
});
}
dependenciesToInject.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEachOrdered(entry -> {
ArtifactRef artifact = entry.getKey();
Collection exclusions = entry.getValue();
String newVersion;
try {
// Check if the dependency is updated by the channel.
VersionResult versionResult = channelSession.findLatestMavenArtifactVersion(artifact.getGroupId(),
artifact.getArtifactId(), artifact.getType(), artifact.getClassifier(), artifact.getVersionString());
newVersion = versionResult.getVersion();
int comparison = compareVersions(newVersion, artifact.getVersionString());
if ((doNotDowngrade && comparison < 0) || comparison == 0) {
return;
}
} catch (NoStreamFoundException e) {
// No stream found -> dependency remains the same.
return;
}
SimpleArtifactRef newDependency = new SimpleArtifactRef(artifact.getGroupId(), artifact.getArtifactId(),
newVersion, artifact.getType(), artifact.getClassifier());
getLog().info(String.format("Injecting undeclared dependency: %s (original version was %s)", newDependency,
artifact.getVersionString()));
try {
rootManipulator.injectManagedDependency(newDependency, exclusions, artifact.getVersionString());
} catch (XMLStreamException e) {
throw new RuntimeException("Failed to inject a managed dependency", e);
}
});
}
/**
* If a property references another property (possibly recursively), this method returns the final referenced
* property name and the module where the property is defined.
*
* This method doesn't support cases when a property value is a composition of multiple properties, or a composition
* of properties and strings.
*/
static Pair followProperties(Project pmeProject, String propertyName) {
Properties properties = pmeProject.getModel().getProperties();
if (!properties.containsKey(propertyName)) {
// property not present in current module, look into parent module
Project parentProject = pmeProject.getProjectParent();
if (parentProject == null) {
return null;
} else {
return followProperties(parentProject, propertyName);
}
} else {
// property is defined in this module
String propertyValue = (String) properties.get(propertyName);
if (VersionUtils.isProperty(propertyValue)) {
// the property value is also a property reference -> follow the chain
String newPropertyName = VersionUtils.extractPropertyName(propertyValue);
Pair targetProperty = followProperties(pmeProject, newPropertyName);
if (targetProperty != null) {
return targetProperty;
}
}
return Pair.of(pmeProject, propertyName);
}
}
/**
* Resolves a property from external parent pom. If given property references another property, this method
* tries to traverse the property chain.
*
* @return pair [property, value], where the property is the last property name in the traversal chain
*/
static Pair resolveExternalProperty(MavenProject mavenProject, String propertyName) {
if (mavenProject == null) {
return null;
}
Properties properties = mavenProject.getModel().getProperties();
if (!properties.containsKey(propertyName)) {
return resolveExternalProperty(mavenProject.getParent(), propertyName);
} else {
// property is defined in this module
String propertyValue = (String) properties.get(propertyName);
if (VersionUtils.isProperty(propertyValue)) {
// the property value is also a property reference -> follow the chain
String newPropertyName = VersionUtils.extractPropertyName(propertyValue);
Pair targetProperty = resolveExternalProperty(mavenProject, newPropertyName);
if (targetProperty != null) {
return targetProperty;
}
}
return Pair.of(propertyName, propertyValue);
}
}
private static int compareVersions(String v1, String v2) {
return VERSION_COMPARATOR.compare(new DefaultArtifactVersion(v1.trim()), new DefaultArtifactVersion(v2.trim()));
}
}