UserPropertiesArtifactRelocationSource.java
/*
* 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;
/**
* Relocation source from user properties.
*
* @since 4.0.0
* @deprecated since 4.0.0, use {@code maven-api-impl} jar instead
*/
@Singleton
@Named(UserPropertiesArtifactRelocationSource.NAME)
@Priority(50)
@Deprecated(since = "4.0.0")
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) {
Artifact s;
String[] parts = coords.split(":");
s = switch (parts.length) {
case 3 -> new DefaultArtifact(parts[0], parts[1], "*", "*", parts[2]);
case 4 -> new DefaultArtifact(parts[0], parts[1], "*", parts[2], parts[3]);
case 5 -> 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>");
};
return s;
}
}