View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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       * Flag indicating whether session authentication (i.e. credentials configured in {@code settings.xml}) may be
65       * applied, matched by plain repository ID, to repositories declared by remote artifact descriptors (POMs) that
66       * are merged into the effective repository list during dependency collection. When disabled (the default),
67       * session authentication is only applied to such a repository when an operator-defined mirror has been selected
68       * for it; if credentials would have matched a descriptor-declared repository, a warning naming the repository ID
69       * and URL is logged instead. Repositories supplied by the build itself (e.g. aggregated via
70       * {@code RepositorySystem#newResolutionRepositories}) are unaffected and keep receiving matching credentials.
71       * Enabling this restores the legacy behavior of applying matching session authentication to descriptor
72       * declared repositories regardless of their provenance.
73       *
74       * @since 2.0.23
75       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
76       * @configurationType {@link java.lang.Boolean}
77       * @configurationDefaultValue {@link #DEFAULT_AUTH_TO_DESCRIPTOR_REPOSITORIES}
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      * Flag indicating whether a repository declared by a remote artifact descriptor (POM) may weaken the checksum
234      * policy of the operator-defined mirror it is merged into. When disabled (the default), the effective checksum
235      * policy of a mirror never becomes weaker than what the mirror itself declares for the same nature: a recessive
236      * raw repository may still enable a nature or influence update policies, but a weaker checksum policy (e.g.
237      * {@code <checksumPolicy>ignore</checksumPolicy>} in a transitive POM) is not honored and a warning is logged
238      * instead. Enabling this restores the legacy weakest-wins merge, which let any POM in the dependency graph
239      * degrade or switch off checksum verification for downloads routed through the mirror.
240      *
241      * @since 2.0.23
242      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
243      * @configurationType {@link java.lang.Boolean}
244      * @configurationDefaultValue {@link #DEFAULT_RAW_CHECKSUM_POLICY_DOWNGRADE}
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                 // unknown (potentially attacker-supplied) values rank below any known policy, so they can never
289                 // displace an operator-configured one
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                 // "recessive" originates from a remote artifact descriptor (POM): remotely supplied input must
326                 // never weaken the checksum policy the operator configured on the mirror itself
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      * Flag indicating whether the merge of a repository's release and snapshot policies (used when a single
342      * effective policy has to serve both natures, most notably for metadata of nature RELEASE_OR_SNAPSHOT such as
343      * {@code maven-metadata.xml} version lists) may pick the <em>weaker</em> of the two checksum policies, which was
344      * the legacy behavior. When disabled (the default), the stronger of the two checksum policies wins, so enabling
345      * snapshots with a lenient checksum policy no longer silently downgrades checksum enforcement below what the
346      * operator configured for releases (or vice versa). An explicit checksum policy set on the session (e.g. via
347      * {@code --strict-checksums}) takes precedence over either behavior, as before.
348      *
349      * @since 2.0.23
350      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
351      * @configurationType {@link java.lang.Boolean}
352      * @configurationDefaultValue {@link #DEFAULT_NATURE_MERGE_WEAKEST_CHECKSUM_POLICY}
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      * Flag indicating whether the merge of a repository's release and snapshot update policies (used when a single
361      * effective policy has to serve both natures, most notably for metadata of nature RELEASE_OR_SNAPSHOT such as
362      * {@code maven-metadata.xml} version lists) may pick the policy with the <em>shorter</em> update interval of the
363      * two, which was the legacy behavior. When disabled (the default), the policy with the longer update interval
364      * wins, so enabling snapshots on a repository configured with {@code updatePolicy=never} for releases no longer
365      * silently forces metadata updates that the operator configured to never happen (or vice versa). An explicit
366      * update policy set on the session takes precedence over either behavior, as before.
367      *
368      * @since 2.0.25
369      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
370      * @configurationType {@link java.lang.Boolean}
371      * @configurationDefaultValue {@link #DEFAULT_NATURE_MERGE_WEAKEST_UPDATE_POLICY}
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             //noinspection StatementWithEmptyBody
435             if (globalPolicy && checksums != null && !checksums.isEmpty()) {
436                 // use global override
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                 // merging the release and snapshot policies of a single repository into one effective policy
443                 // (e.g. for metadata of nature RELEASE_OR_SNAPSHOT): the result must not be weaker than what the
444                 // operator configured for either nature, so the stronger checksum policy wins
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             //noinspection StatementWithEmptyBody
458             if (globalPolicy && artifactUpdates != null && !artifactUpdates.isEmpty()) {
459                 // use global override
460             } else if (globalPolicy && !mergeWeakestUpdatePolicy) {
461                 // merging the release and snapshot policies of a single repository into one effective policy
462                 // (e.g. for metadata of nature RELEASE_OR_SNAPSHOT): the result must not be more eager than what
463                 // the operator configured for either nature, so the policy with the longer update interval wins
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                 // use global override
473             } else if (globalPolicy && !mergeWeakestUpdatePolicy) {
474                 // same reasoning as above, for metadata update policy; this is the composite RELEASE_OR_SNAPSHOT
475                 // case that version-range and LATEST/RELEASE metadata requests go through
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                 // unknown values never win a strength comparison; they are rejected downstream when a
530                 // ChecksumPolicy instance is created for them
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 }