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