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.net.URI;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.function.Supplier;
33
34 import org.eclipse.aether.DefaultRepositorySystemSession;
35 import org.eclipse.aether.RepositorySystemSession;
36 import org.eclipse.aether.artifact.Artifact;
37 import org.eclipse.aether.impl.MetadataResolver;
38 import org.eclipse.aether.impl.RemoteRepositoryManager;
39 import org.eclipse.aether.internal.impl.filter.prefixes.PrefixesSource;
40 import org.eclipse.aether.internal.impl.filter.ruletree.PrefixTree;
41 import org.eclipse.aether.metadata.DefaultMetadata;
42 import org.eclipse.aether.metadata.Metadata;
43 import org.eclipse.aether.repository.RemoteRepository;
44 import org.eclipse.aether.resolution.MetadataRequest;
45 import org.eclipse.aether.resolution.MetadataResult;
46 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
47 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
48 import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
49 import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider;
50 import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
51 import org.eclipse.aether.transfer.NoRepositoryLayoutException;
52 import org.eclipse.aether.util.ConfigUtils;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import static java.util.Objects.requireNonNull;
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 @Singleton
82 @Named(PrefixesRemoteRepositoryFilterSource.NAME)
83 public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepositoryFilterSourceSupport {
84 public static final String NAME = "prefixes";
85
86 static final String PREFIX_FILE_TYPE = ".meta/prefixes.txt";
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 public static final String CONFIG_PROP_ENABLED = RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME;
117
118 public static final boolean DEFAULT_ENABLED = true;
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public static final String CONFIG_PROP_SKIPPED =
133 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".skipped";
134
135 public static final boolean DEFAULT_SKIPPED = false;
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 public static final String CONFIG_PROP_NO_INPUT_OUTCOME =
151 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".noInputOutcome";
152
153 public static final boolean DEFAULT_NO_INPUT_OUTCOME = true;
154
155
156
157
158
159
160
161
162
163
164
165 public static final String CONFIG_PROP_RESOLVE_PREFIX_FILES =
166 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".resolvePrefixFiles";
167
168 public static final boolean DEFAULT_RESOLVE_PREFIX_FILES = true;
169
170
171
172
173
174
175
176
177
178
179
180
181
182 public static final String CONFIG_PROP_USE_MIRRORED_REPOSITORIES =
183 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".useMirroredRepositories";
184
185 public static final boolean DEFAULT_USE_MIRRORED_REPOSITORIES = false;
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200 public static final String CONFIG_PROP_USE_REPOSITORY_MANAGERS =
201 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".useRepositoryManagers";
202
203 public static final boolean DEFAULT_USE_REPOSITORY_MANAGERS = false;
204
205
206
207
208
209
210
211
212 public static final String CONFIG_PROP_BASEDIR =
213 RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".basedir";
214
215 public static final String LOCAL_REPO_PREFIX_DIR = ".remoteRepositoryFilters";
216
217 static final String PREFIXES_FILE_PREFIX = "prefixes-";
218
219 static final String PREFIXES_FILE_SUFFIX = ".txt";
220
221 private final Logger logger = LoggerFactory.getLogger(PrefixesRemoteRepositoryFilterSource.class);
222
223 private final Supplier<MetadataResolver> metadataResolver;
224
225 private final Supplier<RemoteRepositoryManager> remoteRepositoryManager;
226
227 private final RepositoryLayoutProvider repositoryLayoutProvider;
228
229 @Inject
230 public PrefixesRemoteRepositoryFilterSource(
231 RepositoryKeyFunctionFactory repositoryKeyFunctionFactory,
232 Supplier<MetadataResolver> metadataResolver,
233 Supplier<RemoteRepositoryManager> remoteRepositoryManager,
234 RepositoryLayoutProvider repositoryLayoutProvider) {
235 super(repositoryKeyFunctionFactory);
236 this.metadataResolver = requireNonNull(metadataResolver);
237 this.remoteRepositoryManager = requireNonNull(remoteRepositoryManager);
238 this.repositoryLayoutProvider = requireNonNull(repositoryLayoutProvider);
239 }
240
241 @SuppressWarnings("unchecked")
242 private ConcurrentMap<RemoteRepository, PrefixTree> prefixes(RepositorySystemSession session) {
243 return (ConcurrentMap<RemoteRepository, PrefixTree>)
244 session.getData().computeIfAbsent(getClass().getName() + ".prefixes", ConcurrentHashMap::new);
245 }
246
247 @SuppressWarnings("unchecked")
248 private ConcurrentMap<RemoteRepository, RepositoryLayout> layouts(RepositorySystemSession session) {
249 return (ConcurrentMap<RemoteRepository, RepositoryLayout>)
250 session.getData().computeIfAbsent(getClass().getName() + ".layouts", ConcurrentHashMap::new);
251 }
252
253 @Override
254 protected boolean isEnabled(RepositorySystemSession session) {
255 return ConfigUtils.getBoolean(session, DEFAULT_ENABLED, CONFIG_PROP_ENABLED)
256 && !ConfigUtils.getBoolean(session, DEFAULT_SKIPPED, CONFIG_PROP_SKIPPED);
257 }
258
259 private boolean isRepositoryFilteringEnabled(RepositorySystemSession session, RemoteRepository remoteRepository) {
260 if (isEnabled(session)) {
261 return ConfigUtils.getBoolean(
262 session,
263 DEFAULT_ENABLED,
264 CONFIG_PROP_ENABLED + "." + remoteRepository.getId(),
265 CONFIG_PROP_ENABLED + ".*")
266 && !ConfigUtils.getBoolean(
267 session,
268 DEFAULT_SKIPPED,
269 CONFIG_PROP_SKIPPED + "." + remoteRepository.getId(),
270 CONFIG_PROP_SKIPPED + ".*");
271 }
272 return false;
273 }
274
275 @Override
276 public RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession session) {
277 if (isEnabled(session)) {
278 return new PrefixesFilter(session, getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false));
279 }
280 return null;
281 }
282
283
284
285
286
287
288 private RepositoryLayout cacheLayout(RepositorySystemSession session, RemoteRepository remoteRepository) {
289 return layouts(session).computeIfAbsent(normalizeRemoteRepository(session, remoteRepository), r -> {
290 try {
291 return repositoryLayoutProvider.newRepositoryLayout(session, remoteRepository);
292 } catch (NoRepositoryLayoutException e) {
293 return NOT_SUPPORTED;
294 }
295 });
296 }
297
298 private PrefixTree cachePrefixTree(
299 RepositorySystemSession session, Path basedir, RemoteRepository remoteRepository) {
300 return prefixes(session)
301 .computeIfAbsent(
302 normalizeRemoteRepository(session, remoteRepository),
303 r -> loadPrefixTree(session, basedir, remoteRepository));
304 }
305
306 private static final PrefixTree DISABLED = new PrefixTree("disabled");
307 private static final PrefixTree ENABLED_NO_INPUT = new PrefixTree("enabled-no-input");
308
309 private PrefixTree loadPrefixTree(
310 RepositorySystemSession session, Path baseDir, RemoteRepository remoteRepository) {
311 if (isRepositoryFilteringEnabled(session, remoteRepository)) {
312 String origin = "user-provided";
313 Path filePath = resolvePrefixesFromLocalConfiguration(session, baseDir, remoteRepository);
314 if (filePath == null) {
315 if (!supportedResolvePrefixesForRemoteRepository(session, remoteRepository)) {
316 origin = "unsupported";
317 } else {
318 origin = "auto-discovered";
319 filePath = resolvePrefixesFromRemoteRepository(session, remoteRepository);
320 }
321 }
322 if (filePath != null) {
323 PrefixesSource prefixesSource = PrefixesSource.of(remoteRepository, filePath);
324 if (prefixesSource.valid()) {
325 logger.debug(
326 "Loaded prefixes for remote repository {} from {} file '{}'",
327 prefixesSource.origin().getId(),
328 origin,
329 prefixesSource.path());
330 PrefixTree prefixTree = new PrefixTree("");
331 int rules = prefixTree.loadNodes(prefixesSource.entries().stream());
332 logger.info(
333 "Loaded {} {} prefixes for remote repository {} ({})",
334 rules,
335 origin,
336 prefixesSource.origin().getId(),
337 prefixesSource.path().getFileName());
338 return prefixTree;
339 } else {
340 logger.info(
341 "Rejected {} prefixes for remote repository {} ({}): {}",
342 origin,
343 prefixesSource.origin().getId(),
344 prefixesSource.path().getFileName(),
345 prefixesSource.message());
346 }
347 }
348 logger.debug("Prefix file for remote repository {} not available", remoteRepository);
349 return ENABLED_NO_INPUT;
350 }
351 logger.debug("Prefix file for remote repository {} disabled", remoteRepository);
352 return DISABLED;
353 }
354
355 private Path resolvePrefixesFromLocalConfiguration(
356 RepositorySystemSession session, Path baseDir, RemoteRepository remoteRepository) {
357 Path filePath =
358 baseDir.resolve(PREFIXES_FILE_PREFIX + repositoryKey(session, remoteRepository) + PREFIXES_FILE_SUFFIX);
359 if (Files.isReadable(filePath)) {
360 return filePath;
361 } else {
362 return null;
363 }
364 }
365
366 private boolean supportedResolvePrefixesForRemoteRepository(
367 RepositorySystemSession session, RemoteRepository remoteRepository) {
368 if (!ConfigUtils.getBoolean(
369 session,
370 DEFAULT_RESOLVE_PREFIX_FILES,
371 CONFIG_PROP_RESOLVE_PREFIX_FILES + "." + remoteRepository.getId(),
372 CONFIG_PROP_RESOLVE_PREFIX_FILES)) {
373 return false;
374 }
375 if (remoteRepository.isRepositoryManager()) {
376 return ConfigUtils.getBoolean(
377 session, DEFAULT_USE_REPOSITORY_MANAGERS, CONFIG_PROP_USE_REPOSITORY_MANAGERS);
378 } else {
379 return remoteRepository.getMirroredRepositories().isEmpty()
380 || ConfigUtils.getBoolean(
381 session, DEFAULT_USE_MIRRORED_REPOSITORIES, CONFIG_PROP_USE_MIRRORED_REPOSITORIES);
382 }
383 }
384
385 private Path resolvePrefixesFromRemoteRepository(
386 RepositorySystemSession session, RemoteRepository remoteRepository) {
387 MetadataResolver mr = metadataResolver.get();
388 RemoteRepositoryManager rm = remoteRepositoryManager.get();
389 if (mr != null && rm != null) {
390
391 RemoteRepository prepared = rm.aggregateRepositories(
392 session, Collections.emptyList(), Collections.singletonList(remoteRepository), true)
393 .get(0);
394
395 MetadataResult result = mr.resolveMetadata(
396 new DefaultRepositorySystemSession(session)
397 .setTransferListener(null)
398 .setConfigProperty(CONFIG_PROP_SKIPPED, Boolean.TRUE.toString()),
399 Collections.singleton(new MetadataRequest(
400 new DefaultMetadata(PREFIX_FILE_TYPE, Metadata.Nature.RELEASE_OR_SNAPSHOT))
401 .setRepository(prepared)
402 .setDeleteLocalCopyIfMissing(true)
403 .setFavorLocalRepository(true)))
404 .get(0);
405 if (result.isResolved()) {
406 return result.getMetadata().getPath();
407 } else {
408 return null;
409 }
410 }
411 return null;
412 }
413
414 private class PrefixesFilter implements RemoteRepositoryFilter {
415 private final RepositorySystemSession session;
416 private final Path basedir;
417
418 private PrefixesFilter(RepositorySystemSession session, Path basedir) {
419 this.session = session;
420 this.basedir = basedir;
421 }
422
423 @Override
424 public Result acceptArtifact(RemoteRepository remoteRepository, Artifact artifact) {
425 RepositoryLayout repositoryLayout = cacheLayout(session, remoteRepository);
426 if (repositoryLayout == NOT_SUPPORTED) {
427 return result(true, NAME, "Unsupported layout: " + remoteRepository);
428 }
429 return acceptPrefix(
430 remoteRepository,
431 repositoryLayout.getLocation(artifact, false).getPath());
432 }
433
434 @Override
435 public Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadata) {
436 RepositoryLayout repositoryLayout = cacheLayout(session, remoteRepository);
437 if (repositoryLayout == NOT_SUPPORTED) {
438 return result(true, NAME, "Unsupported layout: " + remoteRepository);
439 }
440 return acceptPrefix(
441 remoteRepository,
442 repositoryLayout.getLocation(metadata, false).getPath());
443 }
444
445 private Result acceptPrefix(RemoteRepository repository, String path) {
446 PrefixTree prefixTree = cachePrefixTree(session, basedir, repository);
447 if (prefixTree == DISABLED) {
448 return result(true, NAME, "Disabled");
449 } else if (prefixTree == ENABLED_NO_INPUT) {
450 return result(
451 ConfigUtils.getBoolean(
452 session,
453 DEFAULT_NO_INPUT_OUTCOME,
454 CONFIG_PROP_NO_INPUT_OUTCOME + "." + repository.getId(),
455 CONFIG_PROP_NO_INPUT_OUTCOME),
456 NAME,
457 "No input available");
458 }
459 boolean accepted = prefixTree.acceptedPath(path);
460 return result(
461 accepted,
462 NAME,
463 accepted
464 ? "Path " + path + " allowed from " + repository.getId()
465 : "Path " + path + " NOT allowed from " + repository.getId());
466 }
467 }
468
469 private static final RepositoryLayout NOT_SUPPORTED = new RepositoryLayout() {
470 @Override
471 public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories() {
472 throw new UnsupportedOperationException();
473 }
474
475 @Override
476 public boolean hasChecksums(Artifact artifact) {
477 throw new UnsupportedOperationException();
478 }
479
480 @Override
481 public URI getLocation(Artifact artifact, boolean upload) {
482 throw new UnsupportedOperationException();
483 }
484
485 @Override
486 public URI getLocation(Metadata metadata, boolean upload) {
487 throw new UnsupportedOperationException();
488 }
489
490 @Override
491 public List<ChecksumLocation> getChecksumLocations(Artifact artifact, boolean upload, URI location) {
492 throw new UnsupportedOperationException();
493 }
494
495 @Override
496 public List<ChecksumLocation> getChecksumLocations(Metadata metadata, boolean upload, URI location) {
497 throw new UnsupportedOperationException();
498 }
499 };
500 }