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;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.Map;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.stream.Collectors;
31
32 import org.eclipse.aether.ConfigurationProperties;
33 import org.eclipse.aether.Keys;
34 import org.eclipse.aether.RepositoryCache;
35 import org.eclipse.aether.RepositorySystemSession;
36 import org.eclipse.aether.impl.RemoteRepositoryManager;
37 import org.eclipse.aether.impl.UpdatePolicyAnalyzer;
38 import org.eclipse.aether.repository.Authentication;
39 import org.eclipse.aether.repository.AuthenticationSelector;
40 import org.eclipse.aether.repository.MirrorSelector;
41 import org.eclipse.aether.repository.Proxy;
42 import org.eclipse.aether.repository.ProxySelector;
43 import org.eclipse.aether.repository.RemoteRepository;
44 import org.eclipse.aether.repository.RepositoryKeyFunction;
45 import org.eclipse.aether.repository.RepositoryPolicy;
46 import org.eclipse.aether.spi.connector.checksum.ChecksumPolicyProvider;
47 import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
48 import org.eclipse.aether.util.ConfigUtils;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import static java.util.Objects.requireNonNull;
53
54
55
56 @Singleton
57 @Named
58 public class DefaultRemoteRepositoryManager implements RemoteRepositoryManager {
59
60 private static final String CONFIG_PROPS_PREFIX =
61 ConfigurationProperties.PREFIX_AETHER + "remoteRepositoryManager.";
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public static final String CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES =
80 CONFIG_PROPS_PREFIX + "authToDescriptorRepositories";
81
82 public static final boolean DEFAULT_AUTH_TO_DESCRIPTOR_REPOSITORIES = false;
83
84 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRemoteRepositoryManager.class);
85
86 private final UpdatePolicyAnalyzer updatePolicyAnalyzer;
87
88 private final ChecksumPolicyProvider checksumPolicyProvider;
89
90 private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory;
91
92 @Inject
93 public DefaultRemoteRepositoryManager(
94 UpdatePolicyAnalyzer updatePolicyAnalyzer,
95 ChecksumPolicyProvider checksumPolicyProvider,
96 RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) {
97 this.updatePolicyAnalyzer = requireNonNull(updatePolicyAnalyzer, "update policy analyzer cannot be null");
98 this.checksumPolicyProvider = requireNonNull(checksumPolicyProvider, "checksum policy provider cannot be null");
99 this.repositoryKeyFunctionFactory =
100 requireNonNull(repositoryKeyFunctionFactory, "repository key function factory cannot be null");
101 }
102
103 @Override
104 public List<RemoteRepository> aggregateRepositories(
105 RepositorySystemSession session,
106 List<RemoteRepository> dominantRepositories,
107 List<RemoteRepository> recessiveRepositories,
108 boolean recessiveIsRaw) {
109 return aggregateRepositories(session, dominantRepositories, recessiveRepositories, recessiveIsRaw, false);
110 }
111
112 @Override
113 public List<RemoteRepository> aggregateRepositories(
114 RepositorySystemSession session,
115 List<RemoteRepository> dominantRepositories,
116 List<RemoteRepository> recessiveRepositories,
117 boolean recessiveIsRaw,
118 boolean recessiveIsFromDescriptor) {
119 requireNonNull(session, "session cannot be null");
120 requireNonNull(dominantRepositories, "dominantRepositories cannot be null");
121 requireNonNull(recessiveRepositories, "recessiveRepositories cannot be null");
122 if (recessiveRepositories.isEmpty()) {
123 return dominantRepositories;
124 }
125
126 RepositoryKeyFunction repositoryKeyFunction = repositoryKeyFunctionFactory.systemRepositoryKeyFunction(session);
127 MirrorSelector mirrorSelector = session.getMirrorSelector();
128 AuthenticationSelector authSelector = session.getAuthenticationSelector();
129 ProxySelector proxySelector = session.getProxySelector();
130
131 boolean authToDescriptorRepositories = ConfigUtils.getBoolean(
132 session, DEFAULT_AUTH_TO_DESCRIPTOR_REPOSITORIES, CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES);
133
134 List<RemoteRepository> result = new ArrayList<>(dominantRepositories);
135
136 next:
137 for (RemoteRepository recessiveRepository : recessiveRepositories) {
138 RemoteRepository repository = recessiveRepository;
139 boolean mirrored = false;
140
141 if (recessiveIsRaw
142 && (recessiveIsFromDescriptor
143 || recessiveRepository.getMirroredRepositories().isEmpty())) {
144 RemoteRepository mirrorRepository = mirrorSelector.getMirror(recessiveRepository);
145
146 if (mirrorRepository != null) {
147 logMirror(session, recessiveRepository, mirrorRepository);
148 repository = mirrorRepository;
149 mirrored = true;
150 }
151 }
152
153 String key = repositoryKeyFunction.apply(repository, null);
154
155 for (ListIterator<RemoteRepository> it = result.listIterator(); it.hasNext(); ) {
156 RemoteRepository dominantRepository = it.next();
157
158 if (key.equals(repositoryKeyFunction.apply(dominantRepository, null))) {
159 if (!dominantRepository.getMirroredRepositories().isEmpty()
160 && !repository.getMirroredRepositories().isEmpty()) {
161 RemoteRepository mergedRepository = mergeMirrors(
162 session, repositoryKeyFunction, dominantRepository, repository, recessiveIsRaw);
163 if (mergedRepository != dominantRepository) {
164 it.set(mergedRepository);
165 }
166 }
167
168 continue next;
169 }
170 }
171
172 if (recessiveIsRaw) {
173 RemoteRepository.Builder builder = null;
174 Authentication auth = authSelector.getAuthentication(repository);
175 if (auth != null) {
176 if (!recessiveIsFromDescriptor || mirrored || authToDescriptorRepositories) {
177 builder = new RemoteRepository.Builder(repository);
178 builder.setAuthentication(auth);
179 } else if (auth != repository.getAuthentication()) {
180 logWarnOnce(
181 session,
182 "Not applying session authentication to repository {} ({}) declared by a remote"
183 + " artifact descriptor; set {}=true to restore the legacy behavior of"
184 + " matching credentials to such repositories by repository ID",
185 repository.getId(),
186 repository.getUrl(),
187 CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES);
188 }
189 }
190 Proxy proxy = proxySelector.getProxy(repository);
191 if (proxy != null) {
192 if (builder == null) {
193 builder = new RemoteRepository.Builder(repository);
194 }
195 builder.setProxy(proxy);
196 }
197 if (builder != null) {
198 repository = builder.build();
199 }
200 }
201
202 result.add(repository);
203 }
204
205 return result.stream()
206 .map(r -> new RemoteRepository.Builder(r)
207 .setIntent(RemoteRepository.Intent.RESOLUTION)
208 .build())
209 .collect(Collectors.toList());
210 }
211
212 private void logMirror(RepositorySystemSession session, RemoteRepository original, RemoteRepository mirror) {
213 if (!LOGGER.isDebugEnabled()) {
214 return;
215 }
216 RepositoryCache cache = session.getCache();
217 if (cache != null) {
218 Object key = Keys.of(mirror.getId(), mirror.getUrl(), original.getId(), original.getUrl());
219 if (cache.get(session, key) != null) {
220 return;
221 }
222 cache.put(session, key, Boolean.TRUE);
223 }
224 LOGGER.debug(
225 "Using mirror {} ({}) for {} ({}).",
226 mirror.getId(),
227 mirror.getUrl(),
228 original.getId(),
229 original.getUrl());
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246 public static final String CONFIG_PROP_RAW_CHECKSUM_POLICY_DOWNGRADE =
247 "aether.remoteRepositoryManager.rawRepositoryChecksumPolicyDowngrade";
248
249 public static final boolean DEFAULT_RAW_CHECKSUM_POLICY_DOWNGRADE = false;
250
251 private static RepositoryPolicy clampChecksumPolicy(
252 RepositorySystemSession session, RepositoryPolicy merged, RepositoryPolicy floor, RemoteRepository rec) {
253 if (merged == null || floor == null) {
254 return merged;
255 }
256 if (checksumPolicyRank(merged.getChecksumPolicy()) < checksumPolicyRank(floor.getChecksumPolicy())) {
257 logWarnOnce(
258 session,
259 "Ignoring checksum policy '{}' contributed by repository {} ({}) declared by a remote artifact"
260 + " descriptor: it would downgrade the checksum policy '{}' of the mirror serving it;"
261 + " set {}=true to restore the legacy weakest-wins merge",
262 merged.getChecksumPolicy(),
263 rec.getId(),
264 rec.getUrl(),
265 floor.getChecksumPolicy(),
266 CONFIG_PROP_RAW_CHECKSUM_POLICY_DOWNGRADE);
267 return new RepositoryPolicy(
268 merged.isEnabled(),
269 merged.getArtifactUpdatePolicy(),
270 merged.getMetadataUpdatePolicy(),
271 floor.getChecksumPolicy());
272 }
273 return merged;
274 }
275
276 private static int checksumPolicyRank(String policy) {
277 if (policy == null) {
278 return -1;
279 }
280 switch (policy) {
281 case RepositoryPolicy.CHECKSUM_POLICY_FAIL:
282 return 2;
283 case RepositoryPolicy.CHECKSUM_POLICY_WARN:
284 return 1;
285 case RepositoryPolicy.CHECKSUM_POLICY_IGNORE:
286 return 0;
287 default:
288
289
290 return -1;
291 }
292 }
293
294 private RemoteRepository mergeMirrors(
295 RepositorySystemSession session,
296 RepositoryKeyFunction repositoryKeyFunction,
297 RemoteRepository dominant,
298 RemoteRepository recessive,
299 boolean recessiveIsRaw) {
300 boolean rawChecksumPolicyDowngrade = org.eclipse.aether.util.ConfigUtils.getBoolean(
301 session, DEFAULT_RAW_CHECKSUM_POLICY_DOWNGRADE, CONFIG_PROP_RAW_CHECKSUM_POLICY_DOWNGRADE);
302 RemoteRepository.Builder merged = null;
303 RepositoryPolicy releases = null, snapshots = null;
304
305 next:
306 for (RemoteRepository rec : recessive.getMirroredRepositories()) {
307 String recKey = repositoryKeyFunction.apply(rec, null);
308
309 for (RemoteRepository dom : dominant.getMirroredRepositories()) {
310 if (recKey.equals(repositoryKeyFunction.apply(dom, null))) {
311 continue next;
312 }
313 }
314
315 if (merged == null) {
316 merged = new RemoteRepository.Builder(dominant);
317 releases = dominant.getPolicy(false);
318 snapshots = dominant.getPolicy(true);
319 }
320
321 releases = merge(session, releases, rec.getPolicy(false), false);
322 snapshots = merge(session, snapshots, rec.getPolicy(true), false);
323
324 if (recessiveIsRaw && !rawChecksumPolicyDowngrade) {
325
326
327 releases = clampChecksumPolicy(session, releases, dominant.getPolicy(false), rec);
328 snapshots = clampChecksumPolicy(session, snapshots, dominant.getPolicy(true), rec);
329 }
330
331 merged.addMirroredRepository(rec);
332 }
333
334 if (merged == null) {
335 return dominant;
336 }
337 return merged.setReleasePolicy(releases).setSnapshotPolicy(snapshots).build();
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354 public static final String CONFIG_PROP_NATURE_MERGE_WEAKEST_CHECKSUM_POLICY =
355 "aether.remoteRepositoryManager.natureMergeWeakestChecksumPolicy";
356
357 public static final boolean DEFAULT_NATURE_MERGE_WEAKEST_CHECKSUM_POLICY = false;
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 public static final String CONFIG_PROP_NATURE_MERGE_WEAKEST_UPDATE_POLICY =
374 "aether.remoteRepositoryManager.natureMergeWeakestUpdatePolicy";
375
376 public static final boolean DEFAULT_NATURE_MERGE_WEAKEST_UPDATE_POLICY = false;
377
378 @Override
379 public RepositoryPolicy getPolicy(
380 RepositorySystemSession session, RemoteRepository repository, boolean releases, boolean snapshots) {
381 requireNonNull(session, "session cannot be null");
382 requireNonNull(repository, "repository cannot be null");
383 RepositoryPolicy policy1 = releases ? repository.getPolicy(false) : null;
384 RepositoryPolicy policy2 = snapshots ? repository.getPolicy(true) : null;
385 return merge(session, policy1, policy2, true);
386 }
387
388 private RepositoryPolicy merge(
389 RepositorySystemSession session, RepositoryPolicy policy1, RepositoryPolicy policy2, boolean globalPolicy) {
390 RepositoryPolicy policy;
391
392 if (policy2 == null) {
393 if (globalPolicy) {
394 policy = merge(
395 policy1,
396 session.getArtifactUpdatePolicy(),
397 session.getMetadataUpdatePolicy(),
398 session.getChecksumPolicy());
399 } else {
400 policy = policy1;
401 }
402 } else if (policy1 == null) {
403 if (globalPolicy) {
404 policy = merge(
405 policy2,
406 session.getArtifactUpdatePolicy(),
407 session.getMetadataUpdatePolicy(),
408 session.getChecksumPolicy());
409 } else {
410 policy = policy2;
411 }
412 } else if (!policy2.isEnabled()) {
413 if (globalPolicy) {
414 policy = merge(
415 policy1,
416 session.getArtifactUpdatePolicy(),
417 session.getMetadataUpdatePolicy(),
418 session.getChecksumPolicy());
419 } else {
420 policy = policy1;
421 }
422 } else if (!policy1.isEnabled()) {
423 if (globalPolicy) {
424 policy = merge(
425 policy2,
426 session.getArtifactUpdatePolicy(),
427 session.getMetadataUpdatePolicy(),
428 session.getChecksumPolicy());
429 } else {
430 policy = policy2;
431 }
432 } else {
433 String checksums = session.getChecksumPolicy();
434
435 if (globalPolicy && checksums != null && !checksums.isEmpty()) {
436
437 } else if (globalPolicy
438 && !org.eclipse.aether.util.ConfigUtils.getBoolean(
439 session,
440 DEFAULT_NATURE_MERGE_WEAKEST_CHECKSUM_POLICY,
441 CONFIG_PROP_NATURE_MERGE_WEAKEST_CHECKSUM_POLICY)) {
442
443
444
445 checksums = strongerChecksumPolicy(policy1.getChecksumPolicy(), policy2.getChecksumPolicy());
446 } else {
447 checksums = checksumPolicyProvider.getEffectiveChecksumPolicy(
448 session, policy1.getChecksumPolicy(), policy2.getChecksumPolicy());
449 }
450
451 boolean mergeWeakestUpdatePolicy = ConfigUtils.getBoolean(
452 session,
453 DEFAULT_NATURE_MERGE_WEAKEST_UPDATE_POLICY,
454 CONFIG_PROP_NATURE_MERGE_WEAKEST_UPDATE_POLICY);
455
456 String artifactUpdates = session.getArtifactUpdatePolicy();
457
458 if (globalPolicy && artifactUpdates != null && !artifactUpdates.isEmpty()) {
459
460 } else if (globalPolicy && !mergeWeakestUpdatePolicy) {
461
462
463
464 artifactUpdates = updatePolicyAnalyzer.getStrongerUpdatePolicy(
465 session, policy1.getArtifactUpdatePolicy(), policy2.getArtifactUpdatePolicy());
466 } else {
467 artifactUpdates = updatePolicyAnalyzer.getEffectiveUpdatePolicy(
468 session, policy1.getArtifactUpdatePolicy(), policy2.getArtifactUpdatePolicy());
469 }
470 String metadataUpdates = session.getMetadataUpdatePolicy();
471 if (globalPolicy && metadataUpdates != null && !metadataUpdates.isEmpty()) {
472
473 } else if (globalPolicy && !mergeWeakestUpdatePolicy) {
474
475
476 metadataUpdates = updatePolicyAnalyzer.getStrongerUpdatePolicy(
477 session, policy1.getMetadataUpdatePolicy(), policy2.getMetadataUpdatePolicy());
478 } else {
479 metadataUpdates = updatePolicyAnalyzer.getEffectiveUpdatePolicy(
480 session, policy1.getMetadataUpdatePolicy(), policy2.getMetadataUpdatePolicy());
481 }
482
483 policy = new RepositoryPolicy(true, artifactUpdates, metadataUpdates, checksums);
484 }
485
486 return policy;
487 }
488
489 private RepositoryPolicy merge(
490 RepositoryPolicy policy, String artifactUpdates, String metadataUpdates, String checksums) {
491 if (policy != null) {
492 if (artifactUpdates == null || artifactUpdates.isEmpty()) {
493 artifactUpdates = policy.getArtifactUpdatePolicy();
494 }
495 if (metadataUpdates == null || metadataUpdates.isEmpty()) {
496 metadataUpdates = policy.getMetadataUpdatePolicy();
497 }
498 if (checksums == null || checksums.isEmpty()) {
499 checksums = policy.getChecksumPolicy();
500 }
501 if (!policy.getArtifactUpdatePolicy().equals(artifactUpdates)
502 || !policy.getMetadataUpdatePolicy().equals(metadataUpdates)
503 || !policy.getChecksumPolicy().equals(checksums)) {
504 policy = new RepositoryPolicy(policy.isEnabled(), artifactUpdates, metadataUpdates, checksums);
505 }
506 }
507 return policy;
508 }
509
510 private static String strongerChecksumPolicy(String policy1, String policy2) {
511 if (checksumPolicyStrength(policy2) > checksumPolicyStrength(policy1)) {
512 return policy2;
513 }
514 return policy1;
515 }
516
517 private static int checksumPolicyStrength(String policy) {
518 if (policy == null) {
519 return -1;
520 }
521 switch (policy) {
522 case RepositoryPolicy.CHECKSUM_POLICY_FAIL:
523 return 2;
524 case RepositoryPolicy.CHECKSUM_POLICY_WARN:
525 return 1;
526 case RepositoryPolicy.CHECKSUM_POLICY_IGNORE:
527 return 0;
528 default:
529
530
531 return -1;
532 }
533 }
534
535 @SuppressWarnings("unchecked")
536 private static void logWarnOnce(RepositorySystemSession session, String message, Object... args) {
537 if (!LOGGER.isWarnEnabled()) {
538 return;
539 }
540 Object[] keys = new Object[args.length + 1];
541 keys[0] = message;
542 System.arraycopy(args, 0, keys, 1, args.length);
543 Object key = Keys.of(keys);
544 ((Map<Object, Boolean>) session.getData()
545 .computeIfAbsent(
546 Keys.of(DefaultRemoteRepositoryManager.class, "logWarnOnce"), ConcurrentHashMap::new))
547 .computeIfAbsent(key, k -> {
548 LOGGER.warn(message, args);
549 return true;
550 });
551 }
552 }