You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2026/04/13 18:41:18 UTC
(maven) branch maven-3.10.x updated: Feat: user relocations backport (#11937)
This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch maven-3.10.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-3.10.x by this push:
new f8f5335ac5 Feat: user relocations backport (#11937)
f8f5335ac5 is described below
commit f8f5335ac510454016fd0b27604efca6f91ceed6
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Mon Apr 13 20:41:12 2026 +0200
Feat: user relocations backport (#11937)
Backport of user relocations.
Fixes #11917
---
.../internal/DefaultArtifactDescriptorReader.java | 90 +++++++--
.../internal/MavenArtifactRelocationSource.java | 45 +++++
.../repository/internal/RelocatedArtifact.java | 62 ++++--
...ributionManagementArtifactRelocationSource.java | 76 +++++++
.../UserPropertiesArtifactRelocationSource.java | 225 +++++++++++++++++++++
5 files changed, 458 insertions(+), 40 deletions(-)
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java
index bfea91cb6c..761c4f30ab 100644
--- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java
@@ -22,6 +22,7 @@
import javax.inject.Named;
import javax.inject.Singleton;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -29,9 +30,7 @@
import java.util.Properties;
import java.util.Set;
-import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Model;
-import org.apache.maven.model.Relocation;
import org.apache.maven.model.building.DefaultModelBuilderFactory;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.FileModelSource;
@@ -42,6 +41,8 @@
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.building.ModelProblemUtils;
import org.apache.maven.model.resolution.UnresolvableModelException;
+import org.apache.maven.repository.internal.relocation.DistributionManagementArtifactRelocationSource;
+import org.apache.maven.repository.internal.relocation.UserPropertiesArtifactRelocationSource;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
@@ -94,6 +95,8 @@ public class DefaultArtifactDescriptorReader implements ArtifactDescriptorReader
private ModelCacheFactory modelCacheFactory;
+ private Map<String, MavenArtifactRelocationSource> artifactRelocationSources;
+
private final ArtifactDescriptorReaderDelegate artifactDescriptorReaderDelegate =
new ArtifactDescriptorReaderDelegate();
@@ -104,7 +107,10 @@ public DefaultArtifactDescriptorReader() {
// enable no-arg constructor
}
- @Inject
+ /**
+ * Just here for "static" resolver providers.
+ */
+ @Deprecated
public DefaultArtifactDescriptorReader(
RemoteRepositoryManager remoteRepositoryManager,
VersionResolver versionResolver,
@@ -120,6 +126,34 @@ public DefaultArtifactDescriptorReader(
setModelBuilder(modelBuilder);
setRepositoryEventDispatcher(repositoryEventDispatcher);
setModelCacheFactory(modelCacheFactory);
+ Map<String, MavenArtifactRelocationSource> artifactRelocationSources = new LinkedHashMap<>(); // order!
+ artifactRelocationSources.put(
+ UserPropertiesArtifactRelocationSource.NAME, new UserPropertiesArtifactRelocationSource());
+ artifactRelocationSources.put(
+ DistributionManagementArtifactRelocationSource.NAME,
+ new DistributionManagementArtifactRelocationSource());
+ setArtifactRelocationSources(artifactRelocationSources);
+ }
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ @Inject
+ public DefaultArtifactDescriptorReader(
+ RemoteRepositoryManager remoteRepositoryManager,
+ VersionResolver versionResolver,
+ VersionRangeResolver versionRangeResolver,
+ ArtifactResolver artifactResolver,
+ ModelBuilder modelBuilder,
+ RepositoryEventDispatcher repositoryEventDispatcher,
+ ModelCacheFactory modelCacheFactory,
+ Map<String, MavenArtifactRelocationSource> artifactRelocationSources) {
+ setRemoteRepositoryManager(remoteRepositoryManager);
+ setVersionResolver(versionResolver);
+ setVersionRangeResolver(versionRangeResolver);
+ setArtifactResolver(artifactResolver);
+ setModelBuilder(modelBuilder);
+ setRepositoryEventDispatcher(repositoryEventDispatcher);
+ setModelCacheFactory(modelCacheFactory);
+ setArtifactRelocationSources(artifactRelocationSources);
}
@Deprecated
@@ -175,6 +209,13 @@ public DefaultArtifactDescriptorReader setModelCacheFactory(ModelCacheFactory mo
return this;
}
+ public DefaultArtifactDescriptorReader setArtifactRelocationSources(
+ Map<String, MavenArtifactRelocationSource> artifactRelocationSources) {
+ this.artifactRelocationSources =
+ Objects.requireNonNull(artifactRelocationSources, "artifactRelocationSources cannot be null");
+ return this;
+ }
+
@Override
public ArtifactDescriptorResult readArtifactDescriptor(
RepositorySystemSession session, ArtifactDescriptorRequest request) throws ArtifactDescriptorException {
@@ -336,23 +377,28 @@ private Model loadPom(
throw new ArtifactDescriptorException(result);
}
- Relocation relocation = getRelocation(model);
-
- if (relocation != null) {
- result.addRelocation(a);
- a = new RelocatedArtifact(
- a,
- relocation.getGroupId(),
- relocation.getArtifactId(),
- relocation.getVersion(),
- relocation.getMessage());
- result.setArtifact(a);
+ Artifact relocatedArtifact = getRelocation(session, result, model);
+ if (relocatedArtifact != null) {
+ if (withinSameGav(relocatedArtifact, a)) {
+ result.setArtifact(relocatedArtifact);
+ return model; // they share same model
+ } else {
+ result.addRelocation(a);
+ a = relocatedArtifact;
+ result.setArtifact(a);
+ }
} else {
return model;
}
}
}
+ private boolean withinSameGav(Artifact a1, Artifact a2) {
+ return Objects.equals(a1.getGroupId(), a2.getGroupId())
+ && Objects.equals(a1.getArtifactId(), a2.getArtifactId())
+ && Objects.equals(a1.getVersion(), a2.getVersion());
+ }
+
private Properties toProperties(Map<String, String> dominant, Map<String, String> recessive) {
Properties props = new Properties();
if (recessive != null) {
@@ -364,13 +410,17 @@ private Properties toProperties(Map<String, String> dominant, Map<String, String
return props;
}
- private Relocation getRelocation(Model model) {
- Relocation relocation = null;
- DistributionManagement distMgmt = model.getDistributionManagement();
- if (distMgmt != null) {
- relocation = distMgmt.getRelocation();
+ private Artifact getRelocation(
+ RepositorySystemSession session, ArtifactDescriptorResult artifactDescriptorResult, Model model)
+ throws ArtifactDescriptorException {
+ Artifact result = null;
+ for (MavenArtifactRelocationSource source : artifactRelocationSources.values()) {
+ result = source.relocatedTarget(session, artifactDescriptorResult, model);
+ if (result != null) {
+ break;
+ }
}
- return relocation;
+ return result;
}
private void missingDescriptor(
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenArtifactRelocationSource.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenArtifactRelocationSource.java
new file mode 100644
index 0000000000..13d4880c61
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenArtifactRelocationSource.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.repository.internal;
+
+import org.apache.maven.model.Model;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+
+/**
+ * Maven relocation source.
+ * Note: implementations of this component should avoid the "default" name (has special meaning in Eclipse Sisu) and
+ * explicitly order implementations using Sisu priorities.
+ *
+ * @since 3.10.0
+ */
+public interface MavenArtifactRelocationSource {
+ /**
+ * Returns {@link Artifact} instance where to relocate to, or {@code null}.
+ *
+ * @param session The session, never {@code null}.
+ * @param result The artifact descriptor result, never {@code null}.
+ * @param model The artifact model, never {@code null}.
+ * @return The {@link Artifact} to relocate to, or {@code null} if no relocation wanted.
+ */
+ Artifact relocatedTarget(RepositorySystemSession session, ArtifactDescriptorResult result, Model model)
+ throws ArtifactDescriptorException;
+}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RelocatedArtifact.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RelocatedArtifact.java
index 1053ef983d..44d48b07fc 100644
--- a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RelocatedArtifact.java
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RelocatedArtifact.java
@@ -36,17 +36,29 @@ public final class RelocatedArtifact extends AbstractArtifact {
private final String artifactId;
+ private final String classifier;
+
+ private final String extension;
+
private final String version;
private final String message;
- RelocatedArtifact(Artifact artifact, String groupId, String artifactId, String version, String message) {
+ public RelocatedArtifact(
+ Artifact artifact,
+ String groupId,
+ String artifactId,
+ String classifier,
+ String extension,
+ String version,
+ String message) {
this.artifact = Objects.requireNonNull(artifact, "artifact cannot be null");
- // TODO Use StringUtils here
- this.groupId = (groupId != null && groupId.length() > 0) ? groupId : null;
- this.artifactId = (artifactId != null && artifactId.length() > 0) ? artifactId : null;
- this.version = (version != null && version.length() > 0) ? version : null;
- this.message = (message != null && message.length() > 0) ? message : null;
+ this.groupId = (groupId != null && !groupId.isEmpty()) ? groupId : null;
+ this.artifactId = (artifactId != null && !artifactId.isEmpty()) ? artifactId : null;
+ this.classifier = (classifier != null && !classifier.isEmpty()) ? classifier : null;
+ this.extension = (extension != null && !extension.isEmpty()) ? extension : null;
+ this.version = (version != null && !version.isEmpty()) ? version : null;
+ this.message = (message != null && !message.isEmpty()) ? message : null;
}
@Override
@@ -67,6 +79,24 @@ public String getArtifactId() {
}
}
+ @Override
+ public String getClassifier() {
+ if (classifier != null) {
+ return classifier;
+ } else {
+ return artifact.getClassifier();
+ }
+ }
+
+ @Override
+ public String getExtension() {
+ if (extension != null) {
+ return extension;
+ } else {
+ return artifact.getExtension();
+ }
+ }
+
@Override
public String getVersion() {
if (version != null) {
@@ -80,10 +110,10 @@ public String getVersion() {
@Override
public Artifact setVersion(String version) {
String current = getVersion();
- if (current.equals(version) || (version == null && current.length() <= 0)) {
+ if (current.equals(version) || (version == null && current.isEmpty())) {
return this;
}
- return new RelocatedArtifact(artifact, groupId, artifactId, version, message);
+ return new RelocatedArtifact(artifact, groupId, artifactId, classifier, extension, version, message);
}
@Override
@@ -92,7 +122,8 @@ public Artifact setFile(File file) {
if (Objects.equals(current, file)) {
return this;
}
- return new RelocatedArtifact(artifact.setFile(file), groupId, artifactId, version, message);
+ return new RelocatedArtifact(
+ artifact.setFile(file), groupId, artifactId, classifier, extension, version, message);
}
@Override
@@ -101,17 +132,8 @@ public Artifact setProperties(Map<String, String> properties) {
if (current.equals(properties) || (properties == null && current.isEmpty())) {
return this;
}
- return new RelocatedArtifact(artifact.setProperties(properties), groupId, artifactId, version, message);
- }
-
- @Override
- public String getClassifier() {
- return artifact.getClassifier();
- }
-
- @Override
- public String getExtension() {
- return artifact.getExtension();
+ return new RelocatedArtifact(
+ artifact.setProperties(properties), groupId, artifactId, classifier, extension, version, message);
}
@Override
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/DistributionManagementArtifactRelocationSource.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/DistributionManagementArtifactRelocationSource.java
new file mode 100644
index 0000000000..790e85280a
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/DistributionManagementArtifactRelocationSource.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.repository.internal.relocation;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.model.DistributionManagement;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Relocation;
+import org.apache.maven.repository.internal.MavenArtifactRelocationSource;
+import org.apache.maven.repository.internal.RelocatedArtifact;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.eclipse.sisu.Priority;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Relocation source from standard distribution management. This is the "one and only" relocation implementation that
+ * existed in Maven 3 land, uses POM distributionManagement/relocation.
+ * <p>
+ * Note: this component should kick-in last regarding relocations.
+ *
+ * @since 3.10.0
+ */
+@Singleton
+@Named(DistributionManagementArtifactRelocationSource.NAME)
+@Priority(5)
+public final class DistributionManagementArtifactRelocationSource implements MavenArtifactRelocationSource {
+ public static final String NAME = "distributionManagement";
+ private static final Logger LOGGER = LoggerFactory.getLogger(DistributionManagementArtifactRelocationSource.class);
+
+ @Override
+ public Artifact relocatedTarget(
+ RepositorySystemSession session, ArtifactDescriptorResult artifactDescriptorResult, Model model) {
+ DistributionManagement distMgmt = model.getDistributionManagement();
+ if (distMgmt != null) {
+ Relocation relocation = distMgmt.getRelocation();
+ if (relocation != null) {
+ Artifact result = new RelocatedArtifact(
+ artifactDescriptorResult.getRequest().getArtifact(),
+ relocation.getGroupId(),
+ relocation.getArtifactId(),
+ null,
+ null,
+ relocation.getVersion(),
+ relocation.getMessage());
+ LOGGER.debug(
+ "The artifact {} has been relocated to {}: {}",
+ artifactDescriptorResult.getRequest().getArtifact(),
+ result,
+ relocation.getMessage());
+ return result;
+ }
+ }
+ return null;
+ }
+}
diff --git a/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java
new file mode 100644
index 0000000000..cb4cc0ac17
--- /dev/null
+++ b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.repository.internal.relocation;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.repository.internal.MavenArtifactRelocationSource;
+import org.apache.maven.repository.internal.RelocatedArtifact;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.eclipse.sisu.Priority;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * User controlled relocations.
+ * This property is a comma separated list of entries with the syntax <code>GAV>GAV</code>.
+ * The first <code>GAV</code> can contain <code>*</code> for any elem (so <code>*:*:*</code> would mean ALL, something
+ * you don't want). The second <code>GAV</code> is either fully specified, or also can contain <code>*</code>,
+ * then it behaves as "ordinary relocation": the coordinate is preserved from relocated artifact.
+ * Finally, if right hand <code>GAV</code> is absent (line looks like <code>GAV></code>), the left hand matching
+ * <code>GAV</code> is banned fully (from resolving).
+ * <br/>
+ * Note: the <code>></code> means project level, while <code>>></code> means global (whole session level,
+ * so even plugins will get relocated artifacts) relocation.
+ * <br/>
+ * For example,
+ * <pre>maven.relocations.entries = org.foo:*:*>, \\<br/> org.here:*:*>org.there:*:*, \\<br/> javax.inject:javax.inject:1>>jakarta.inject:jakarta.inject:1.0.5</pre>
+ * means: 3 entries, ban <code>org.foo group</code> (exactly, so <code>org.foo.bar</code> is allowed),
+ * relocate <code>org.here</code> to <code>org.there</code> and finally globally relocate (see <code>>></code> above)
+ * <code>javax.inject:javax.inject:1</code> to <code>jakarta.inject:jakarta.inject:1.0.5</code>.
+ *
+ * @since 3.10.0
+ */
+@Singleton
+@Named(UserPropertiesArtifactRelocationSource.NAME)
+@Priority(50)
+public final class UserPropertiesArtifactRelocationSource implements MavenArtifactRelocationSource {
+ public static final String NAME = "userProperties";
+ private static final Logger LOGGER = LoggerFactory.getLogger(UserPropertiesArtifactRelocationSource.class);
+
+ private static final String CONFIG_PROP_RELOCATIONS_ENTRIES = "maven.relocations.entries";
+
+ private static final Artifact SENTINEL = new DefaultArtifact("org.apache.maven.banned:user-relocation:1.0");
+
+ @Override
+ public Artifact relocatedTarget(
+ RepositorySystemSession session, ArtifactDescriptorResult artifactDescriptorResult, Model model)
+ throws ArtifactDescriptorException {
+ Relocations relocations = (Relocations) session.getData()
+ .computeIfAbsent(getClass().getName() + ".relocations", () -> parseRelocations(session));
+ if (relocations != null) {
+ Artifact original = artifactDescriptorResult.getRequest().getArtifact();
+ Relocation relocation = relocations.getRelocation(original);
+ if (relocation != null
+ && (isProjectContext(artifactDescriptorResult.getRequest().getRequestContext())
+ || relocation.global)) {
+ if (relocation.target == SENTINEL) {
+ String message = "The artifact " + original + " has been banned from resolution: "
+ + (relocation.global ? "User global ban" : "User project ban");
+ LOGGER.debug(message);
+ throw new ArtifactDescriptorException(artifactDescriptorResult, message);
+ }
+ Artifact result = new RelocatedArtifact(
+ original,
+ isAny(relocation.target.getGroupId()) ? null : relocation.target.getGroupId(),
+ isAny(relocation.target.getArtifactId()) ? null : relocation.target.getArtifactId(),
+ isAny(relocation.target.getClassifier()) ? null : relocation.target.getClassifier(),
+ isAny(relocation.target.getExtension()) ? null : relocation.target.getExtension(),
+ isAny(relocation.target.getVersion()) ? null : relocation.target.getVersion(),
+ relocation.global ? "User global relocation" : "User project relocation");
+ LOGGER.debug(
+ "The artifact {} has been relocated to {}: {}",
+ original,
+ result,
+ relocation.global ? "User global relocation" : "User project relocation");
+ return result;
+ }
+ }
+ return null;
+ }
+
+ private boolean isProjectContext(String context) {
+ return context != null && context.startsWith("project");
+ }
+
+ private static boolean isAny(String str) {
+ return "*".equals(str);
+ }
+
+ private static boolean matches(String pattern, String str) {
+ if (isAny(pattern)) {
+ return true;
+ } else if (pattern.endsWith("*")) {
+ return str.startsWith(pattern.substring(0, pattern.length() - 1));
+ } else {
+ return Objects.equals(pattern, str);
+ }
+ }
+
+ private static Predicate<Artifact> artifactPredicate(Artifact artifact) {
+ return a -> matches(artifact.getGroupId(), a.getGroupId())
+ && matches(artifact.getArtifactId(), a.getArtifactId())
+ && matches(artifact.getBaseVersion(), a.getBaseVersion())
+ && matches(artifact.getExtension(), a.getExtension())
+ && matches(artifact.getClassifier(), a.getClassifier());
+ }
+
+ private static class Relocation {
+ private final Predicate<Artifact> predicate;
+ private final boolean global;
+ private final Artifact source;
+ private final Artifact target;
+
+ private Relocation(boolean global, Artifact source, Artifact target) {
+ this.predicate = artifactPredicate(source);
+ this.global = global;
+ this.source = source;
+ this.target = target;
+ }
+
+ @Override
+ public String toString() {
+ return source + (global ? " >> " : " > ") + target;
+ }
+ }
+
+ private static class Relocations {
+ private final List<Relocation> relocations;
+
+ private Relocations(List<Relocation> relocations) {
+ this.relocations = relocations;
+ }
+
+ private Relocation getRelocation(Artifact artifact) {
+ return relocations.stream()
+ .filter(r -> r.predicate.test(artifact))
+ .findFirst()
+ .orElse(null);
+ }
+ }
+
+ private Relocations parseRelocations(RepositorySystemSession session) {
+ String relocationsEntries = (String) session.getConfigProperties().get(CONFIG_PROP_RELOCATIONS_ENTRIES);
+ if (relocationsEntries == null) {
+ return null;
+ }
+ String[] entries = relocationsEntries.split(",");
+ try (Stream<String> lines = Arrays.stream(entries)) {
+ List<Relocation> relocationList = lines.filter(
+ l -> l != null && !l.trim().isEmpty())
+ .map(l -> {
+ boolean global;
+ String splitExpr;
+ if (l.contains(">>")) {
+ global = true;
+ splitExpr = ">>";
+ } else if (l.contains(">")) {
+ global = false;
+ splitExpr = ">";
+ } else {
+ throw new IllegalArgumentException("Unrecognized entry: " + l);
+ }
+ String[] parts = l.split(splitExpr);
+ if (parts.length < 1) {
+ throw new IllegalArgumentException("Unrecognized entry: " + l);
+ }
+ Artifact s = parseArtifact(parts[0]);
+ Artifact t;
+ if (parts.length > 1) {
+ t = parseArtifact(parts[1]);
+ } else {
+ t = SENTINEL;
+ }
+ return new Relocation(global, s, t);
+ })
+ .collect(Collectors.toList());
+ LOGGER.info("Parsed {} user relocations", relocationList.size());
+ return new Relocations(relocationList);
+ }
+ }
+
+ private static Artifact parseArtifact(String coords) {
+ String[] parts = coords.split(":");
+ switch (parts.length) {
+ case 3:
+ return new DefaultArtifact(parts[0], parts[1], "*", "*", parts[2]);
+ case 4:
+ return new DefaultArtifact(parts[0], parts[1], "*", parts[2], parts[3]);
+ case 5:
+ return new DefaultArtifact(parts[0], parts[1], parts[2], parts[3], parts[4]);
+ default:
+ throw new IllegalArgumentException("Bad artifact coordinates " + coords
+ + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
+ }
+ }
+}