1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.impl.resolver.relocation;
20
21 import java.util.Arrays;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.function.Predicate;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27
28 import org.apache.maven.api.Constants;
29 import org.apache.maven.api.di.Named;
30 import org.apache.maven.api.di.Priority;
31 import org.apache.maven.api.di.Singleton;
32 import org.apache.maven.api.model.Model;
33 import org.apache.maven.internal.impl.resolver.MavenArtifactRelocationSource;
34 import org.apache.maven.internal.impl.resolver.RelocatedArtifact;
35 import org.eclipse.aether.RepositorySystemSession;
36 import org.eclipse.aether.artifact.Artifact;
37 import org.eclipse.aether.artifact.DefaultArtifact;
38 import org.eclipse.aether.resolution.ArtifactDescriptorException;
39 import org.eclipse.aether.resolution.ArtifactDescriptorResult;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43
44
45
46
47
48 @Singleton
49 @Named(UserPropertiesArtifactRelocationSource.NAME)
50 @Priority(50)
51 public final class UserPropertiesArtifactRelocationSource implements MavenArtifactRelocationSource {
52 public static final String NAME = "userProperties";
53 private static final Logger LOGGER = LoggerFactory.getLogger(UserPropertiesArtifactRelocationSource.class);
54
55 private static final Artifact SENTINEL = new DefaultArtifact("org.apache.maven.banned:user-relocation:1.0");
56
57 @Override
58 public Artifact relocatedTarget(
59 RepositorySystemSession session, ArtifactDescriptorResult artifactDescriptorResult, Model model)
60 throws ArtifactDescriptorException {
61 Relocations relocations = (Relocations) session.getData()
62 .computeIfAbsent(getClass().getName() + ".relocations", () -> parseRelocations(session));
63 if (relocations != null) {
64 Artifact original = artifactDescriptorResult.getRequest().getArtifact();
65 Relocation relocation = relocations.getRelocation(original);
66 if (relocation != null
67 && (isProjectContext(artifactDescriptorResult.getRequest().getRequestContext())
68 || relocation.global)) {
69 if (relocation.target == SENTINEL) {
70 String message = "The artifact " + original + " has been banned from resolution: "
71 + (relocation.global ? "User global ban" : "User project ban");
72 LOGGER.debug(message);
73 throw new ArtifactDescriptorException(artifactDescriptorResult, message);
74 }
75 Artifact result = new RelocatedArtifact(
76 original,
77 isAny(relocation.target.getGroupId()) ? null : relocation.target.getGroupId(),
78 isAny(relocation.target.getArtifactId()) ? null : relocation.target.getArtifactId(),
79 isAny(relocation.target.getClassifier()) ? null : relocation.target.getClassifier(),
80 isAny(relocation.target.getExtension()) ? null : relocation.target.getExtension(),
81 isAny(relocation.target.getVersion()) ? null : relocation.target.getVersion(),
82 relocation.global ? "User global relocation" : "User project relocation");
83 LOGGER.debug(
84 "The artifact {} has been relocated to {}: {}",
85 original,
86 result,
87 relocation.global ? "User global relocation" : "User project relocation");
88 return result;
89 }
90 }
91 return null;
92 }
93
94 private boolean isProjectContext(String context) {
95 return context != null && context.startsWith("project");
96 }
97
98 private static boolean isAny(String str) {
99 return "*".equals(str);
100 }
101
102 private static boolean matches(String pattern, String str) {
103 if (isAny(pattern)) {
104 return true;
105 } else if (pattern.endsWith("*")) {
106 return str.startsWith(pattern.substring(0, pattern.length() - 1));
107 } else {
108 return Objects.equals(pattern, str);
109 }
110 }
111
112 private static Predicate<Artifact> artifactPredicate(Artifact artifact) {
113 return a -> matches(artifact.getGroupId(), a.getGroupId())
114 && matches(artifact.getArtifactId(), a.getArtifactId())
115 && matches(artifact.getBaseVersion(), a.getBaseVersion())
116 && matches(artifact.getExtension(), a.getExtension())
117 && matches(artifact.getClassifier(), a.getClassifier());
118 }
119
120 private static class Relocation {
121 private final Predicate<Artifact> predicate;
122 private final boolean global;
123 private final Artifact source;
124 private final Artifact target;
125
126 private Relocation(boolean global, Artifact source, Artifact target) {
127 this.predicate = artifactPredicate(source);
128 this.global = global;
129 this.source = source;
130 this.target = target;
131 }
132
133 @Override
134 public String toString() {
135 return source + (global ? " >> " : " > ") + target;
136 }
137 }
138
139 private static class Relocations {
140 private final List<Relocation> relocations;
141
142 private Relocations(List<Relocation> relocations) {
143 this.relocations = relocations;
144 }
145
146 private Relocation getRelocation(Artifact artifact) {
147 return relocations.stream()
148 .filter(r -> r.predicate.test(artifact))
149 .findFirst()
150 .orElse(null);
151 }
152 }
153
154 private Relocations parseRelocations(RepositorySystemSession session) {
155 String relocationsEntries = (String) session.getConfigProperties().get(Constants.MAVEN_RELOCATIONS_ENTRIES);
156 if (relocationsEntries == null) {
157 return null;
158 }
159 String[] entries = relocationsEntries.split(",");
160 try (Stream<String> lines = Arrays.stream(entries)) {
161 List<Relocation> relocationList = lines.filter(
162 l -> l != null && !l.trim().isEmpty())
163 .map(l -> {
164 boolean global;
165 String splitExpr;
166 if (l.contains(">>")) {
167 global = true;
168 splitExpr = ">>";
169 } else if (l.contains(">")) {
170 global = false;
171 splitExpr = ">";
172 } else {
173 throw new IllegalArgumentException("Unrecognized entry: " + l);
174 }
175 String[] parts = l.split(splitExpr);
176 if (parts.length < 1) {
177 throw new IllegalArgumentException("Unrecognized entry: " + l);
178 }
179 Artifact s = parseArtifact(parts[0]);
180 Artifact t;
181 if (parts.length > 1) {
182 t = parseArtifact(parts[1]);
183 } else {
184 t = SENTINEL;
185 }
186 return new Relocation(global, s, t);
187 })
188 .collect(Collectors.toList());
189 LOGGER.info("Parsed {} user relocations", relocationList.size());
190 return new Relocations(relocationList);
191 }
192 }
193
194 private static Artifact parseArtifact(String coords) {
195 Artifact s;
196 String[] parts = coords.split(":");
197 switch (parts.length) {
198 case 3:
199 s = new DefaultArtifact(parts[0], parts[1], "*", "*", parts[2]);
200 break;
201 case 4:
202 s = new DefaultArtifact(parts[0], parts[1], "*", parts[2], parts[3]);
203 break;
204 case 5:
205 s = new DefaultArtifact(parts[0], parts[1], parts[2], parts[3], parts[4]);
206 break;
207 default:
208 throw new IllegalArgumentException("Bad artifact coordinates " + coords
209 + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
210 }
211 return s;
212 }
213 }