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