org.wildfly.channelplugin.UpgradeComponentsMojo Maven / Gradle / Ivy
Show all versions of wildfly-channel-maven-plugin
package org.wildfly.channelplugin;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.execution.MavenSession;
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.repository.internal.MavenRepositorySystemUtils;
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.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.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.wildfly.channel.Channel;
import org.wildfly.channel.ChannelManifestCoordinate;
import org.wildfly.channel.ChannelMapper;
import org.wildfly.channel.ChannelSession;
import org.wildfly.channel.Repository;
import org.wildfly.channel.UnresolvedMavenArtifactException;
import org.wildfly.channel.VersionResult;
import org.wildfly.channel.maven.ChannelCoordinate;
import org.wildfly.channel.maven.VersionResolverFactory;
import org.wildfly.channelplugin.manipulation.PomManipulator;
import org.wildfly.channelplugin.utils.IOUtils;
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.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
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", requiresProject = true, requiresDirectInvocation = true)
public class UpgradeComponentsMojo extends AbstractChannelMojo {
/**
* Path to the channel definition file on a local filesystem.
*
* Exactly one of 'channelFile', 'manifestFile', 'channelGAV', 'manifestGAV' can be set.
*/
@Parameter(property = "channelFile")
String channelFile;
/**
* Path to the channel manifest file on a local filesystem.
*
* Exactly one of 'channelFile', 'manifestFile', 'channelGAV', 'manifestGAV' can be set.
*/
@Parameter(property = "manifestFile")
String manifestFile;
/**
* GAV of an artifact than contains the channel file.
*
* Exactly one of 'channelFile', 'manifestFile', 'channelGAV', 'manifestGAV' can be set.
*/
@Parameter(property = "channelGAV")
String channelGAV;
/**
* GAV of an artifact than contains the channel file.
*
* Exactly one of 'channelFile', 'manifestFile', 'channelGAV', 'manifestGAV' can be set.
*/
@Parameter(property = "manifestGAV")
String manifestGAV;
/**
* Comma separated list of remote repositories URLs, that should be used to resolve artifacts.
*/
@Parameter(property = "remoteRepositories")
List remoteRepositories;
/**
* Local repository path.
*/
@Parameter(property = "localRepository")
String localRepositoryPath;
/**
* 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;
/**
* Replace property reference in dependency version element with inlined version string, when it's not possible to
* override property value due to conflicting versions for different dependencies that use the property.
*/
@Parameter(property = "inlineVersionOnConflict", defaultValue = "true")
boolean inlineVersionOnConflict;
/**
* 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;
/**
* When injecting new dependency elements (to override transitive dependency versions), add exclussions according to given
* project submodule (given in G:A format).
*
* Effectively, this means that when a transitive dependency G:A:V is going to be injected to override the dependency version,
* the plugin will try to find this same dependency in a dependency tree of a given submodule and copy exclusions it finds
* there to the injected dependency element.
*
* If this parameter is unset, the dependency exclusions are taken from managed dependencies section of the root module
* effective POM.
*/
@Parameter(property = "copyExclusionsFrom")
String copyExclusionsFrom;
@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;
@Inject
DependencyGraphBuilder dependencyGraphBuilder;
@Inject
MavenSession mavenSession;
@Inject
ManipulationSession manipulationSession;
@Inject
RepositorySystem repositorySystem;
private List channels = new ArrayList<>();
private ChannelSession channelSession;
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 final HashMap, String> upgradedProperties = new HashMap<>();
private final Set declaredDependencies = new HashSet<>();
/**
* This includes pre-processing of input parameters.
*/
private void init() throws MojoExecutionException {
if (StringUtils.isBlank(localRepositoryPath)) {
try {
localRepositoryPath = IOUtils.createTemporaryCache();
} catch (IOException e) {
throw new MojoExecutionException("Cannot create local maven cache", e);
}
}
final DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils.newSession();
final LocalRepository localRepository = new LocalRepository(localRepositoryPath);
final LocalRepositoryManager localRepoManager = repositorySystem.newLocalRepositoryManager(repositorySystemSession,
localRepository);
repositorySystemSession.setLocalRepositoryManager(localRepoManager);
initChannel();
if (!remoteRepositories.isEmpty()) {
// TODO: Include remote repositories defined in the project pom.xml?
channels = overrideRemoteRepositories(channels, remoteRepositories);
}
channelSession = new ChannelSession(channels, new VersionResolverFactory(repositorySystem, repositorySystemSession));
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 = parsePmeProjects();
// 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);
}
if (injectTransitiveDependencies) {
injectTransitiveDependencies();
}
// if channel was given as an input, insert channel repositories into the parent pom
if (injectRepositories) {
Project rootProject = findRootProject(pmeProjects);
PomManipulator rootManipulator = manipulators.get(Pair.of(rootProject.getGroupId(), rootProject.getArtifactId()));
InjectRepositoriesMojo.insertRepositories(rootProject, rootManipulator, channels);
}
// 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 (Pair upgrade: dependenciesToUpgrade) {
String newVersion = upgrade.getRight();
Dependency locatedDependency = upgrade.getLeft();
@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())) { // 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).equals(newVersion)) {
// property has already been changed to different value
String propertyName = projectProperty.getRight();
String currentPropertyValue = upgradedProperties.get(projectProperty);
if (inlineVersionOnConflict) {
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);
} else {
getLog().warn(String.format(
"Can't upgrade %s:%s:%s to '%s', property '%s' was already upgraded to '%s'.",
d.getGroupId(), d.getArtifactId(), d.getVersion(), newVersion,
propertyName,
currentPropertyValue));
}
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.overrideDependencyVersion(toArtifactRef(locatedDependency), newVersion);
}
}
}
/**
* Injects all transitive dependencies that are present in channels into parent POM's dependencyManagement section.
*
* Call this method after all modules has been processed via the `processModule()` method.
*/
private void injectTransitiveDependencies() throws DependencyGraphBuilderException, XMLStreamException {
PomManipulator rootManipulator = manipulators.get(
Pair.of(mavenProject.getGroupId(), mavenProject.getArtifactId()));
// (dependency => exclusions list) map of undeclared dependencies
Map> undeclaredDependencies = collectUndeclaredDependencies();
List>> dependenciesToInject = undeclaredDependencies.entrySet().stream()
.sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
// filter only deps that have a stream defined in the channel
.filter(entry -> {
// verify if the artifact from the project dependency tree is modified by the channel
ArtifactRef a = entry.getKey();
try {
VersionResult versionResult = channelSession.findLatestMavenArtifactVersion(a.getGroupId(), a.getArtifactId(),
a.getType(), a.getClassifier(), a.getVersionString());
return !versionResult.getVersion().equals(a.getVersionString());
} catch (UnresolvedMavenArtifactException e) {
// no stream found -> no change
return false;
}
})
.filter(entry -> {
ArtifactRef a = entry.getKey();
boolean isIgnored = ignoredStreams.contains(a.asProjectRef())
|| ignoredStreams.contains(new SimpleProjectRef(a.getGroupId(), "*"));
boolean isUnignored = unignoredStreams.contains(a.asProjectRef());
return isUnignored || !isIgnored;
})
.collect(Collectors.toList());
for (Map.Entry> entry : dependenciesToInject) {
ArtifactRef a = entry.getKey();
try {
VersionResult versionResult = channelSession.findLatestMavenArtifactVersion(a.getGroupId(), a.getArtifactId(),
a.getType(), a.getClassifier(), a.getVersionString());
String newVersion = versionResult.getVersion();
if (!newVersion.equals(a.getVersionString())) {
SimpleArtifactRef newDependency = new SimpleArtifactRef(a.getGroupId(), a.getArtifactId(), newVersion,
a.getType(), a.getClassifier());
getLog().info(String.format("Injecting undeclared dependency: %s (original version was %s)", newDependency,
a.getVersionString()));
rootManipulator.injectManagedDependency(newDependency, entry.getValue());
}
} catch (UnresolvedMavenArtifactException e) {
getLog().error(String.format("Unable to resolve dependency %s", a));
}
}
}
/**
* 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 = findOverridenVersion(dependency);
if (overridenVersion.isPresent()) {
manipulator.overrideDependencyVersion(toArtifactRef(dependency), overridenVersion.get());
overriddenDependencies.add(dependency);
}
}
return overriddenDependencies;
}
private Optional findOverridenVersion(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 artifactRef = entry.getKey();
Dependency dependency = entry.getValue();
Objects.requireNonNull(artifactRef);
Objects.requireNonNull(dependency);
if (projectGavs.contains(artifactRef.asProjectVersionRef())) {
getLog().debug("Ignoring in-project dependency: "
+ artifactRef.asProjectVersionRef().toString());
continue;
}
if (!unignoredStreams.contains(artifactRef.asProjectRef())) {
if (ignoredStreams.contains(artifactRef.asProjectRef())) {
getLog().info("Skipping dependency (ignored stream): "
+ artifactRef.asProjectVersionRef().toString());
continue;
}
ProjectRef wildCardIgnoredProjectRef = new SimpleProjectRef(artifactRef.getGroupId(), "*");
if (ignoredStreams.contains(wildCardIgnoredProjectRef)) {
getLog().info("Skipping dependency (ignored stream): "
+ artifactRef.asProjectVersionRef().toString());
continue;
}
}
if (artifactRef.getVersionString() == null) {
// this is not expected to happen
getLog().error("Resolved dependency has null version: " + artifactRef);
continue;
}
if (VersionUtils.isProperty(artifactRef.getVersionString())) {
// hack: PME doesn't seem to resolve properties from external parent poms
Pair externalProperty = resolveExternalProperty(mavenProject,
VersionUtils.extractPropertyName(artifactRef.getVersionString()));
if (externalProperty != null) {
artifactRef = new SimpleArtifactRef(artifactRef.getGroupId(), artifactRef.getArtifactId(),
externalProperty.getRight(), artifactRef.getType(), artifactRef.getClassifier());
} else {
// didn't manage to resolve dependency version, this is not expected to happen
getLog().error("Resolved dependency has version with property: " + artifactRef);
continue;
}
}
if ("test".equals(dependency.getScope()) && ignoreTestDependencies) {
getLog().info("Skipping dependency (ignored scope): "
+ artifactRef.asProjectVersionRef().toString());
continue;
}
try {
VersionResult versionResult = channelSession.findLatestMavenArtifactVersion(artifactRef.getGroupId(),
artifactRef.getArtifactId(), artifactRef.getType(), artifactRef.getClassifier(),
artifactRef.getVersionString());
String channelVersion = versionResult.getVersion();
if (!channelVersion.equals(artifactRef.getVersionString())) {
getLog().info("Updating dependency " + artifactRef.getGroupId()
+ ":" + artifactRef.getArtifactId() + ":" + artifactRef.getVersionString()
+ " to version " + channelVersion);
}
dependenciesToUpgrade.add(Pair.of(dependency, 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: " + artifactRef, e);
}
}
return dependenciesToUpgrade;
}
private void initChannel() throws MojoExecutionException {
int numberOfSources = 0;
if (StringUtils.isNotBlank(channelFile)) numberOfSources++;
if (StringUtils.isNotBlank(channelGAV)) numberOfSources++;
if (StringUtils.isNotBlank(manifestFile)) numberOfSources++;
if (StringUtils.isNotBlank(manifestGAV)) numberOfSources++;
if (numberOfSources > 1) {
throw new MojoExecutionException("Exactly one of [channelFile, channelGAV, manifestFile, manifestGAV] has to be given.");
}
// Do not enforce this for now, repositories are also read from project pom.xml currently.
/*if ((StringUtils.isNotBlank(manifestFile) || StringUtils.isNotBlank(manifestGAV)) && remoteRepositories.isEmpty()) {
throw new MojoExecutionException("The remoteRepositories property is mandatory when manifest is given.");
}*/
try {
if (StringUtils.isNotBlank(channelFile)) {
String[] paths = channelFile.split(",");
for (String path: paths) {
Path channelFilePath = Path.of(path);
if (!channelFilePath.isAbsolute()) {
channelFilePath = Path.of(mavenSession.getExecutionRootDirectory()).resolve(channelFilePath);
}
getLog().info("Reading channel file " + channelFilePath);
channels.add(ChannelMapper.from(channelFilePath.toUri().toURL()));
}
} else if (StringUtils.isNotBlank(channelGAV)) {
String[] gavs = channelGAV.split(",");
for (String gav: gavs) {
channels.addAll(resolveChannelsFromGav(gav));
}
} else if (StringUtils.isNotBlank(manifestFile)) {
String[] paths = manifestFile.split(",");
for (String path: paths) {
URL manifestUrl = Path.of(path).toUri().toURL();
ChannelManifestCoordinate coordinate = new ChannelManifestCoordinate(manifestUrl);
channels.add(new Channel("a-channel", null, null, null, coordinate, null, null));
}
} else if (StringUtils.isNotBlank(manifestGAV)) {
String[] gavs = manifestGAV.split(",");
for (String gav: gavs) {
ChannelManifestCoordinate coordinate = toManifestCoordinate(gav);
// Compose list of repositories to look for the manifest as a union of the remoteRepositories property
// and repositories from the project pom.xml.
List repositories = mavenProject.getRemoteProjectRepositories().stream()
.map(rr -> new Repository(rr.getId(), rr.getUrl()))
.collect(Collectors.toList());
repositories.addAll(createRepositories(remoteRepositories));
channels.add(new Channel("a-channel", null, null, repositories, coordinate, null, null));
}
} else {
throw new MojoExecutionException("No channel or manifest specified.");
}
} catch (MalformedURLException e) {
throw new MojoExecutionException("Can't parse the channel or manifest file path", e);
}
}
private List resolveChannelsFromGav(String gavString) {
ChannelCoordinate channelCoordinate = toChannelCoordinate(gavString);
RepositorySystemSession repoSession = mavenSession.getRepositorySession();
// Collect repositories that should be used to locate the channel. Probably this should be a union of the
// repositories given in the `remoteRepositories` property and the repositories defined in the project pom.xml.
final List channelRepos = new ArrayList<>(mavenProject.getRemoteProjectRepositories());
int repoNumber = 0;
if (remoteRepositories != null && !remoteRepositories.isEmpty()) {
for (String repoUrl: remoteRepositories) {
RemoteRepository repo = new RemoteRepository.Builder("repo-" + repoNumber++, "default", repoUrl).build();
channelRepos.add(repo);
}
}
try (VersionResolverFactory versionResolverFactory = new VersionResolverFactory(repositorySystem, repoSession)) {
return versionResolverFactory.resolveChannels(List.of(channelCoordinate), channelRepos);
} catch (MalformedURLException e) {
// This should not happen here, URL coordinates are not supposed to be present.
throw new IllegalStateException("Couldn't resolve channel GAV", e);
}
}
private static ChannelCoordinate toChannelCoordinate(String gavString) {
String[] gavSegments = gavString.split(":");
ChannelCoordinate coordinate;
if (gavSegments.length == 2) {
coordinate = new ChannelCoordinate(gavSegments[0], gavSegments[1]);
} else if (gavSegments.length == 3) {
coordinate = new ChannelCoordinate(gavSegments[0], gavSegments[1], gavSegments[2]);
} else {
throw new IllegalArgumentException("Invalid GAV string, channel GAV has to have two or three segments separated with ':'. Given value was: " + gavString);
}
return coordinate;
}
private static ChannelManifestCoordinate toManifestCoordinate(String gavString) {
String[] gavSegments = gavString.split(":");
ChannelManifestCoordinate coordinate;
if (gavSegments.length == 2) {
coordinate = new ChannelManifestCoordinate(gavSegments[0], gavSegments[1]);
} else if (gavSegments.length == 3) {
coordinate = new ChannelManifestCoordinate(gavSegments[0], gavSegments[1], gavSegments[2]);
} else {
throw new IllegalArgumentException("Invalid GAV string, manifest GAV has to have two or three segments separated with ':'. Given value was: " + gavString);
}
return coordinate;
}
private static List overrideRemoteRepositories(List channels, List repositories) {
List updatedChannels = new ArrayList<>(channels.size());
for (Channel channel: channels) {
updatedChannels.add(new Channel(channel.getName(), channel.getDescription(), channel.getVendor(), createRepositories(repositories),
channel.getManifestCoordinate(), channel.getBlocklistCoordinate(), channel.getNoStreamStrategy()));
}
return updatedChannels;
}
private static List createRepositories(List userRepositories) {
HashMap result = new HashMap<>();
int idx = 0;
for (String input: userRepositories) {
String[] segments = input.split("::");
Repository previous;
String id;
if (segments.length == 1) {
id = "repo-" + idx++;
previous = result.put(id, new Repository(id, segments[0]));
} else if (segments.length == 2) {
id = segments[0];
previous = result.put(id, new Repository(id, segments[1]));
} else {
throw new IllegalArgumentException("Invalid remote repository entry: " + input);
}
if (previous != null) {
throw new IllegalArgumentException("Duplicate remote repository key: '" + id + "'");
}
}
return new ArrayList<>(result.values());
}
/**
* Collects transitive dependencies from all project's modules, which are not declared in the project.
*
* This has to be called after all submodules has been processed (so that all declared dependencies has been
* collected).
*
* @return a map containing a dependencies as keys and list of exclusions as values.
*/
private Map> collectUndeclaredDependencies() throws DependencyGraphBuilderException {
Map> artifactExclusions = new HashMap<>();
// First of all, if `copyExclusionsFrom` module has been set, we are going to remember exclusions from all dependencies
// of this module. These exclusions will be used for the newly injected dependency elements.
// First of all, we want to collect information about exclusions for the dependencies that are eventually going to be
// injected into the project. There are two possible strategies:
if (copyExclusionsFrom != null) {
// Either collect the exclusions from a dependency tree of a project submodule that a user specified.
ProjectRef exclusionsModule = SimpleProjectRef.parse(copyExclusionsFrom);
Optional exclusionsProject = mavenProject.getCollectedProjects().stream()
.filter(p -> exclusionsModule.getGroupId().equals(p.getGroupId())
&& exclusionsModule.getArtifactId().equals(p.getArtifactId()))
.findFirst();
if (exclusionsProject.isPresent()) {
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(mavenSession.getProjectBuildingRequest());
buildingRequest.setProject(exclusionsProject.get());
DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
rootNode.accept(visitor);
visitor.getNodes().forEach(node -> {
HashSet exclusionSet = new HashSet<>(toProjectRefs(node.getExclusions()));
Collection previousExclusions = artifactExclusions.put(toArtifactRef(node.getArtifact()), exclusionSet);
if (previousExclusions != null) {
exclusionSet.addAll(previousExclusions);
}
});
}
} else {
// Or else collect the exclusions from a dependency management section of an effective POM of the root module.
List managedDependencies = mavenProject.getModel().getDependencyManagement().getDependencies();
managedDependencies.forEach(d -> artifactExclusions.put(toArtifactRef(d), toProjectRefs(d.getExclusions())));
}
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> undeclaredDependencies = new HashMap<>();
for (MavenProject module: mavenProject.getCollectedProjects()) {
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());
Collection exclusions = artifactExclusions.get(artifact);
// 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;
}
undeclaredDependencies.put(artifact, exclusions);
});
}
return undeclaredDependencies;
}
/**
* 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);
}
}
}