1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.filter;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.IOException;
26 import java.io.UncheckedIOException;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.TreeSet;
35 import java.util.concurrent.ConcurrentHashMap;
36 import java.util.concurrent.ConcurrentMap;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40
41 import org.eclipse.aether.MultiRuntimeException;
42 import org.eclipse.aether.RepositorySystemSession;
43 import org.eclipse.aether.artifact.Artifact;
44 import org.eclipse.aether.impl.RepositorySystemLifecycle;
45 import org.eclipse.aether.internal.impl.filter.ruletree.GroupTree;
46 import org.eclipse.aether.metadata.Metadata;
47 import org.eclipse.aether.repository.RemoteRepository;
48 import org.eclipse.aether.resolution.ArtifactResult;
49 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
50 import org.eclipse.aether.spi.io.PathProcessor;
51 import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
52 import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
53 import org.eclipse.aether.util.ConfigUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 import static java.util.Objects.requireNonNull;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 @Singleton
77 @Named(GroupIdRemoteRepositoryFilterSource.NAME)
78 public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryFilterSourceSupport
79 implements ArtifactResolverPostProcessor {
80 public static final String NAME = "groupId";
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 public static final String CONFIG_PROP_ENABLED = RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME;
107
108 public static final boolean DEFAULT_ENABLED = true;
109
110
111
112
113
114
115
116
117
118
119
120 public static final String CONFIG_PROP_SKIPPED =
121 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".skipped";
122
123 public static final boolean DEFAULT_SKIPPED = false;
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 public static final String CONFIG_PROP_NO_INPUT_OUTCOME =
139 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".noInputOutcome";
140
141 public static final boolean DEFAULT_NO_INPUT_OUTCOME = true;
142
143
144
145
146
147
148
149
150 public static final String CONFIG_PROP_BASEDIR =
151 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".basedir";
152
153 public static final String LOCAL_REPO_PREFIX_DIR = ".remoteRepositoryFilters";
154
155
156
157
158
159
160
161
162 public static final String CONFIG_PROP_RECORD =
163 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".record";
164
165 static final String GROUP_ID_FILE_PREFIX = "groupId-";
166
167 static final String GROUP_ID_FILE_SUFFIX = ".txt";
168
169 private final Logger logger = LoggerFactory.getLogger(GroupIdRemoteRepositoryFilterSource.class);
170
171 private final RepositorySystemLifecycle repositorySystemLifecycle;
172
173 private final PathProcessor pathProcessor;
174
175 @Inject
176 public GroupIdRemoteRepositoryFilterSource(
177 RepositoryKeyFunctionFactory repositoryKeyFunctionFactory,
178 RepositorySystemLifecycle repositorySystemLifecycle,
179 PathProcessor pathProcessor) {
180 super(repositoryKeyFunctionFactory);
181 this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle);
182 this.pathProcessor = requireNonNull(pathProcessor);
183 }
184
185 @SuppressWarnings("unchecked")
186 private ConcurrentMap<RemoteRepository, GroupTree> rules(RepositorySystemSession session) {
187 return (ConcurrentMap<RemoteRepository, GroupTree>)
188 session.getData().computeIfAbsent(getClass().getName() + ".rules", ConcurrentHashMap::new);
189 }
190
191 @SuppressWarnings("unchecked")
192 private ConcurrentMap<RemoteRepository, Path> ruleFiles(RepositorySystemSession session) {
193 return (ConcurrentMap<RemoteRepository, Path>)
194 session.getData().computeIfAbsent(getClass().getName() + ".ruleFiles", ConcurrentHashMap::new);
195 }
196
197 @SuppressWarnings("unchecked")
198 private ConcurrentMap<RemoteRepository, Set<String>> recordedRules(RepositorySystemSession session) {
199 return (ConcurrentMap<RemoteRepository, Set<String>>)
200 session.getData().computeIfAbsent(getClass().getName() + ".recordedRules", ConcurrentHashMap::new);
201 }
202
203 private AtomicBoolean onShutdownHandlerRegistered(RepositorySystemSession session) {
204 return (AtomicBoolean) session.getData()
205 .computeIfAbsent(getClass().getName() + ".onShutdownHandlerRegistered", AtomicBoolean::new);
206 }
207
208 @Override
209 protected boolean isEnabled(RepositorySystemSession session) {
210 return ConfigUtils.getBoolean(session, DEFAULT_ENABLED, CONFIG_PROP_ENABLED)
211 && !ConfigUtils.getBoolean(session, DEFAULT_SKIPPED, CONFIG_PROP_SKIPPED);
212 }
213
214 private boolean isRepositoryFilteringEnabled(RepositorySystemSession session, RemoteRepository remoteRepository) {
215 if (isEnabled(session)) {
216 return ConfigUtils.getBoolean(
217 session,
218 DEFAULT_ENABLED,
219 CONFIG_PROP_ENABLED + "." + remoteRepository.getId(),
220 CONFIG_PROP_ENABLED + ".*")
221 && !ConfigUtils.getBoolean(
222 session,
223 DEFAULT_SKIPPED,
224 CONFIG_PROP_SKIPPED + "." + remoteRepository.getId(),
225 CONFIG_PROP_SKIPPED + ".*");
226 }
227 return false;
228 }
229
230 @Override
231 public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
232 if (isEnabled(session) && !isRecord(session)) {
233 return new GroupIdFilter(session);
234 }
235 return null;
236 }
237
238 @Override
239 public void postProcess(RepositorySystemSession session, List<ArtifactResult> artifactResults) {
240 if (isEnabled(session) && isRecord(session)) {
241 if (onShutdownHandlerRegistered(session).compareAndSet(false, true)) {
242 repositorySystemLifecycle.addOnSystemEndedHandler(() -> saveRecordedLines(session));
243 }
244 for (ArtifactResult artifactResult : artifactResults) {
245 if (artifactResult.isResolved() && artifactResult.getRepository() instanceof RemoteRepository) {
246 RemoteRepository remoteRepository = (RemoteRepository) artifactResult.getRepository();
247 if (isRepositoryFilteringEnabled(session, remoteRepository)) {
248 ruleFile(session, remoteRepository);
249 String line = "=" + artifactResult.getArtifact().getGroupId();
250 RemoteRepository normalized = normalizeRemoteRepository(session, remoteRepository);
251 recordedRules(session)
252 .computeIfAbsent(normalized, k -> new TreeSet<>())
253 .add(line);
254 rules(session)
255 .compute(normalized, (k, v) -> {
256 if (v == null || v == DISABLED || v == ENABLED_NO_INPUT) {
257 v = GroupTree.create("record");
258 }
259 return v;
260 })
261 .loadNode(line);
262 }
263 }
264 }
265 }
266 }
267
268 private Path ruleFile(RepositorySystemSession session, RemoteRepository remoteRepository) {
269 return ruleFiles(session).computeIfAbsent(normalizeRemoteRepository(session, remoteRepository), r -> getBasedir(
270 session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false)
271 .resolve(GROUP_ID_FILE_PREFIX + repositoryKey(session, remoteRepository) + GROUP_ID_FILE_SUFFIX));
272 }
273
274 private GroupTree cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
275 return rules(session)
276 .computeIfAbsent(
277 normalizeRemoteRepository(session, remoteRepository), r -> loadRepositoryRules(session, r));
278 }
279
280 private static final GroupTree DISABLED = GroupTree.create("disabled");
281 private static final GroupTree ENABLED_NO_INPUT = GroupTree.create("enabled-no-input");
282
283 private GroupTree loadRepositoryRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
284 if (isRepositoryFilteringEnabled(session, remoteRepository)) {
285 Path filePath = ruleFile(session, remoteRepository);
286 if (Files.isReadable(filePath)) {
287 try (Stream<String> lines = Files.lines(filePath, StandardCharsets.UTF_8)) {
288 GroupTree groupTree =
289 GroupTree.create(filePath.getFileName().toString());
290 int rules = groupTree.loadNodes(lines);
291 logger.info("Loaded {} group rules for remote repository {}", rules, remoteRepository.getId());
292 if (logger.isDebugEnabled()) {
293 groupTree.dump("");
294 }
295 return groupTree;
296 } catch (IOException e) {
297 throw new UncheckedIOException(e);
298 }
299 }
300 logger.debug("Group rules file for remote repository {} not available", remoteRepository);
301 return ENABLED_NO_INPUT;
302 }
303 logger.debug("Group rules file for remote repository {} disabled", remoteRepository);
304 return DISABLED;
305 }
306
307 private class GroupIdFilter implements RemoteRepositoryFilter {
308 private final RepositorySystemSession session;
309
310 private GroupIdFilter(RepositorySystemSession session) {
311 this.session = session;
312 }
313
314 @Override
315 public Result acceptArtifact(RemoteRepository repository, Artifact artifact) {
316 return acceptGroupId(repository, artifact.getGroupId());
317 }
318
319 @Override
320 public Result acceptMetadata(RemoteRepository repository, Metadata metadata) {
321 return acceptGroupId(repository, metadata.getGroupId());
322 }
323
324 private Result acceptGroupId(RemoteRepository repository, String groupId) {
325 GroupTree groupTree = cacheRules(session, repository);
326 if (groupTree == DISABLED) {
327 return result(true, NAME, "Disabled");
328 } else if (groupTree == ENABLED_NO_INPUT) {
329 return result(
330 ConfigUtils.getBoolean(
331 session,
332 DEFAULT_NO_INPUT_OUTCOME,
333 CONFIG_PROP_NO_INPUT_OUTCOME + "." + repository.getId(),
334 CONFIG_PROP_NO_INPUT_OUTCOME),
335 NAME,
336 "No input available");
337 }
338
339 boolean accepted = groupTree.acceptedGroupId(groupId);
340 return result(
341 accepted,
342 NAME,
343 accepted
344 ? "G:" + groupId + " allowed from " + repository.getId()
345 : "G:" + groupId + " NOT allowed from " + repository.getId());
346 }
347 }
348
349
350
351
352 private boolean isRecord(RepositorySystemSession session) {
353 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_RECORD);
354 }
355
356
357
358
359 private void saveRecordedLines(RepositorySystemSession session) {
360 ArrayList<Exception> exceptions = new ArrayList<>();
361 for (Map.Entry<RemoteRepository, Path> entry : ruleFiles(session).entrySet()) {
362 Set<String> recorded = recordedRules(session).get(entry.getKey());
363 if (recorded != null && !recorded.isEmpty()) {
364 try {
365 ArrayList<String> result = new ArrayList<>();
366 if (Files.isReadable(entry.getValue())) {
367 result.addAll(Files.readAllLines(entry.getValue()));
368 }
369 result.add("# Recorded entries");
370 result.addAll(recorded);
371 logger.info("Saving {} groupIds to '{}'", result.size(), entry.getValue());
372 pathProcessor.writeWithBackup(
373 entry.getValue(), result.stream().collect(Collectors.joining(System.lineSeparator())));
374 } catch (IOException e) {
375 exceptions.add(e);
376 }
377 }
378 }
379 MultiRuntimeException.mayThrow("session save groupIds failure", exceptions);
380 }
381 }