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