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.io.IOException;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.concurrent.atomic.AtomicBoolean;
36  
37  import org.eclipse.aether.ConfigurationProperties;
38  import org.eclipse.aether.RepositoryEvent;
39  import org.eclipse.aether.RepositoryEvent.EventType;
40  import org.eclipse.aether.RepositorySystemSession;
41  import org.eclipse.aether.RequestTrace;
42  import org.eclipse.aether.SyncContext;
43  import org.eclipse.aether.artifact.Artifact;
44  import org.eclipse.aether.impl.ArtifactResolver;
45  import org.eclipse.aether.impl.OfflineController;
46  import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
47  import org.eclipse.aether.impl.RemoteRepositoryManager;
48  import org.eclipse.aether.impl.RepositoryConnectorProvider;
49  import org.eclipse.aether.impl.RepositoryEventDispatcher;
50  import org.eclipse.aether.impl.UpdateCheck;
51  import org.eclipse.aether.impl.UpdateCheckManager;
52  import org.eclipse.aether.impl.VersionResolver;
53  import org.eclipse.aether.repository.ArtifactRepository;
54  import org.eclipse.aether.repository.LocalArtifactRegistration;
55  import org.eclipse.aether.repository.LocalArtifactRequest;
56  import org.eclipse.aether.repository.LocalArtifactResult;
57  import org.eclipse.aether.repository.LocalRepository;
58  import org.eclipse.aether.repository.LocalRepositoryManager;
59  import org.eclipse.aether.repository.RemoteRepository;
60  import org.eclipse.aether.repository.RepositoryPolicy;
61  import org.eclipse.aether.repository.WorkspaceReader;
62  import org.eclipse.aether.resolution.ArtifactRequest;
63  import org.eclipse.aether.resolution.ArtifactResolutionException;
64  import org.eclipse.aether.resolution.ArtifactResult;
65  import org.eclipse.aether.resolution.ResolutionErrorPolicy;
66  import org.eclipse.aether.resolution.VersionRequest;
67  import org.eclipse.aether.resolution.VersionResolutionException;
68  import org.eclipse.aether.resolution.VersionResult;
69  import org.eclipse.aether.scope.SystemDependencyScope;
70  import org.eclipse.aether.spi.connector.ArtifactDownload;
71  import org.eclipse.aether.spi.connector.RepositoryConnector;
72  import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
73  import org.eclipse.aether.spi.io.PathProcessor;
74  import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
75  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
76  import org.eclipse.aether.transfer.ArtifactFilteredOutException;
77  import org.eclipse.aether.transfer.ArtifactNotFoundException;
78  import org.eclipse.aether.transfer.ArtifactTransferException;
79  import org.eclipse.aether.transfer.NoRepositoryConnectorException;
80  import org.eclipse.aether.transfer.RepositoryOfflineException;
81  import org.eclipse.aether.util.ConfigUtils;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  
85  import static java.util.Objects.requireNonNull;
86  
87  /**
88   *
89   */
90  @Singleton
91  @Named
92  public class DefaultArtifactResolver implements ArtifactResolver {
93  
94      public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "artifactResolver.";
95  
96      /**
97       * Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names
98       * will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a
99       * filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not
100      * uploading those.
101      *
102      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
103      * @configurationType {@link java.lang.Boolean}
104      * @configurationDefaultValue {@link #DEFAULT_SNAPSHOT_NORMALIZATION}
105      */
106     public static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = CONFIG_PROPS_PREFIX + "snapshotNormalization";
107 
108     public static final boolean DEFAULT_SNAPSHOT_NORMALIZATION = true;
109 
110     /**
111      * Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration
112      * is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used.
113      *
114      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
115      * @configurationType {@link java.lang.Boolean}
116      * @configurationDefaultValue {@link #DEFAULT_SIMPLE_LRM_INTEROP}
117      */
118     public static final String CONFIG_PROP_SIMPLE_LRM_INTEROP = CONFIG_PROPS_PREFIX + "simpleLrmInterop";
119 
120     public static final boolean DEFAULT_SIMPLE_LRM_INTEROP = false;
121 
122     /**
123      * Configuration to restore the legacy "existence check" behavior for artifacts that are present in the local
124      * repository but were cached from a remote repository unavailable in the current build context: when enabled, a
125      * bare remote existence check (no content transfer, hence no checksum validation) suffices to re-label the cached
126      * bytes as originating from the queried repository. When disabled (the default), the artifact is downloaded again
127      * through the regular transfer path, so the content is validated against the repository's checksum policy before
128      * it is registered with the local repository for that repository. Enabling this trades integrity for bandwidth
129      * and is warmly recommended to leave it disabled.
130      *
131      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
132      * @configurationType {@link java.lang.Boolean}
133      * @configurationDefaultValue {@link #DEFAULT_EXISTENCE_CHECK_RELABEL}
134      */
135     public static final String CONFIG_PROP_EXISTENCE_CHECK_RELABEL = CONFIG_PROPS_PREFIX + "existenceCheckRelabel";
136 
137     public static final boolean DEFAULT_EXISTENCE_CHECK_RELABEL = false;
138 
139     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArtifactResolver.class);
140 
141     private final PathProcessor pathProcessor;
142 
143     private final RepositoryEventDispatcher repositoryEventDispatcher;
144 
145     private final VersionResolver versionResolver;
146 
147     private final UpdateCheckManager updateCheckManager;
148 
149     private final RepositoryConnectorProvider repositoryConnectorProvider;
150 
151     private final RemoteRepositoryManager remoteRepositoryManager;
152 
153     private final SyncContextFactory syncContextFactory;
154 
155     private final OfflineController offlineController;
156 
157     private final Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors;
158 
159     private final RemoteRepositoryFilterManager remoteRepositoryFilterManager;
160 
161     @SuppressWarnings("checkstyle:parameternumber")
162     @Inject
163     public DefaultArtifactResolver(
164             PathProcessor pathProcessor,
165             RepositoryEventDispatcher repositoryEventDispatcher,
166             VersionResolver versionResolver,
167             UpdateCheckManager updateCheckManager,
168             RepositoryConnectorProvider repositoryConnectorProvider,
169             RemoteRepositoryManager remoteRepositoryManager,
170             SyncContextFactory syncContextFactory,
171             OfflineController offlineController,
172             Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors,
173             RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
174         this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null");
175         this.repositoryEventDispatcher =
176                 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
177         this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null");
178         this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
179         this.repositoryConnectorProvider =
180                 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
181         this.remoteRepositoryManager =
182                 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
183         this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
184         this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
185         this.artifactResolverPostProcessors =
186                 requireNonNull(artifactResolverPostProcessors, "artifact resolver post-processors cannot be null");
187         this.remoteRepositoryFilterManager =
188                 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
189     }
190 
191     @Override
192     public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request)
193             throws ArtifactResolutionException {
194         requireNonNull(session, "session cannot be null");
195         requireNonNull(request, "request cannot be null");
196 
197         return resolveArtifacts(session, Collections.singleton(request)).get(0);
198     }
199 
200     @Override
201     public List<ArtifactResult> resolveArtifacts(
202             RepositorySystemSession session, Collection<? extends ArtifactRequest> requests)
203             throws ArtifactResolutionException {
204         requireNonNull(session, "session cannot be null");
205         requireNonNull(requests, "requests cannot be null");
206         Collection<Artifact> artifacts = new ArrayList<>(requests.size());
207         SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
208         for (ArtifactRequest request : requests) {
209             if (systemDependencyScope != null && systemDependencyScope.getSystemPath(request.getArtifact()) != null) {
210                 continue;
211             }
212             artifacts.add(request.getArtifact());
213         }
214 
215         try (SyncContext shared = new CloseOnceSyncContext(syncContextFactory.newInstance(session, true));
216                 SyncContext exclusive = new CloseOnceSyncContext(syncContextFactory.newInstance(session, false))) {
217             return resolve(shared, exclusive, artifacts, session, requests);
218         }
219     }
220 
221     @SuppressWarnings("checkstyle:methodlength")
222     private List<ArtifactResult> resolve(
223             SyncContext shared,
224             SyncContext exclusive,
225             Collection<Artifact> subjects,
226             RepositorySystemSession session,
227             Collection<? extends ArtifactRequest> requests)
228             throws ArtifactResolutionException {
229         SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
230         SyncContext current = shared;
231         try {
232             while (true) {
233                 current.acquire(subjects, null);
234 
235                 boolean failures = false;
236                 final List<ArtifactResult> results = new ArrayList<>(requests.size());
237                 final boolean simpleLrmInterop =
238                         ConfigUtils.getBoolean(session, DEFAULT_SIMPLE_LRM_INTEROP, CONFIG_PROP_SIMPLE_LRM_INTEROP);
239                 final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
240                 final WorkspaceReader workspace = session.getWorkspaceReader();
241                 final List<ResolutionGroup> groups = new ArrayList<>();
242                 // filter != null: means "filtering applied", if null no filtering applied (behave as before)
243                 final RemoteRepositoryFilter filter = remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
244 
245                 for (ArtifactRequest request : requests) {
246                     RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
247 
248                     ArtifactResult result = new ArtifactResult(request);
249                     results.add(result);
250 
251                     Artifact artifact = request.getArtifact();
252 
253                     if (current == shared) {
254                         artifactResolving(session, trace, artifact);
255                     }
256 
257                     String localPath =
258                             systemDependencyScope != null ? systemDependencyScope.getSystemPath(artifact) : null;
259                     if (localPath != null) {
260                         // unhosted artifact, just validate file
261                         Path path = Paths.get(localPath);
262                         if (!Files.isRegularFile(path)) {
263                             failures = true;
264                             result.addException(
265                                     ArtifactResult.NO_REPOSITORY, new ArtifactNotFoundException(artifact, localPath));
266                         } else {
267                             artifact = artifact.setPath(path);
268                             result.setArtifact(artifact);
269                             artifactResolved(session, trace, artifact, null, result.getExceptions());
270                         }
271                         continue;
272                     }
273 
274                     List<RemoteRepository> remoteRepositories = request.getRepositories();
275                     List<RemoteRepository> filteredRemoteRepositories = new ArrayList<>(remoteRepositories);
276                     if (filter != null) {
277                         for (RemoteRepository repository : remoteRepositories) {
278                             if (!repository.getPolicy(artifact.isSnapshot()).isEnabled()) {
279                                 filteredRemoteRepositories.remove(repository);
280                                 continue;
281                             }
282                             RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
283                             if (!filterResult.isAccepted()) {
284                                 result.addException(
285                                         repository,
286                                         new ArtifactFilteredOutException(
287                                                 artifact, repository, filterResult.reasoning()));
288                                 filteredRemoteRepositories.remove(repository);
289                             }
290                         }
291                     }
292 
293                     VersionResult versionResult;
294                     try {
295                         VersionRequest versionRequest =
296                                 new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
297                         versionRequest.setTrace(trace);
298                         versionResult = versionResolver.resolveVersion(session, versionRequest);
299                     } catch (VersionResolutionException e) {
300                         if (filteredRemoteRepositories.isEmpty()) {
301                             result.addException(lrm.getRepository(), e);
302                         } else {
303                             filteredRemoteRepositories.forEach(r -> result.addException(r, e));
304                         }
305                         continue;
306                     }
307 
308                     artifact = artifact.setVersion(versionResult.getVersion());
309 
310                     if (artifact.getVersion().trim().isEmpty()) {
311                         // the version resolver reported success but produced no usable version (e.g. the
312                         // requested artifact had no version at all); fail here with a clear artifact-level
313                         // message instead of an opaque IllegalArgumentException from the local path composer
314                         result.addException(
315                                 ArtifactResult.NO_REPOSITORY,
316                                 new ArtifactNotFoundException(
317                                         artifact,
318                                         (RemoteRepository) null,
319                                         "The version of " + artifact + " is missing or blank"));
320                         continue;
321                     }
322 
323                     if (versionResult.getRepository() != null) {
324                         if (versionResult.getRepository() instanceof RemoteRepository) {
325                             filteredRemoteRepositories =
326                                     Collections.singletonList((RemoteRepository) versionResult.getRepository());
327                         } else {
328                             filteredRemoteRepositories = Collections.emptyList();
329                         }
330                     }
331 
332                     if (workspace != null) {
333                         Path path = workspace.findArtifactPath(artifact);
334                         if (path != null) {
335                             artifact = artifact.setPath(path);
336                             result.setArtifact(artifact);
337                             result.setRepository(workspace.getRepository());
338                             artifactResolved(session, trace, artifact, result.getRepository(), null);
339                             continue;
340                         }
341                     }
342 
343                     LocalArtifactResult local = lrm.find(
344                             session,
345                             new LocalArtifactRequest(
346                                     artifact, filteredRemoteRepositories, request.getRequestContext()));
347                     result.setLocalArtifactResult(local);
348                     boolean found = (filter != null && local.isAvailable())
349                             || (filter == null && isLocallyInstalled(local, versionResult));
350                     // with filtering: availability drives the logic
351                     // without filtering: simply presence of file drives the logic
352                     // "interop" logic with simple LRM leads to RRF breakage: hence is ignored when filtering in effect
353                     if (found) {
354                         if (local.getRepository() != null) {
355                             result.setRepository(local.getRepository());
356                         } else {
357                             result.setRepository(lrm.getRepository());
358                         }
359 
360                         try {
361                             Path localArtifactPath = local.getPath();
362                             artifact = artifact.setPath(getPath(session, artifact, localArtifactPath));
363                             result.setArtifact(artifact);
364                             if (local.getRepository() != null && !localArtifactPath.equals(artifact.getPath())) {
365                                 /*
366                                  * NOTE: Snapshot normalization materialized a base-version copy next to the tracked
367                                  * timestamped file. The copy must carry the same provenance tracking as its source;
368                                  * otherwise the untracked copy is later treated as locally installed (accepted
369                                  * without any repository/offline checks) by the untracked-file interop logic.
370                                  */
371                                 lrm.add(
372                                         session,
373                                         new LocalArtifactRegistration(
374                                                 artifact.setVersion(artifact.getBaseVersion()),
375                                                 local.getRepository(),
376                                                 Collections.singleton(request.getRequestContext())));
377                             }
378                             artifactResolved(session, trace, artifact, result.getRepository(), null);
379                         } catch (ArtifactTransferException e) {
380                             result.addException(lrm.getRepository(), e);
381                         }
382                         if (filter == null && simpleLrmInterop && !local.isAvailable()) {
383                             /*
384                              * NOTE: Interop with simple local repository: An artifact installed by a simple local repo
385                              * manager will not show up in the repository tracking file of the enhanced local repository.
386                              * If however the maven-metadata-local.xml tells us the artifact was installed locally, we
387                              * sync the repository tracking file.
388                              */
389                             lrm.add(session, new LocalArtifactRegistration(artifact));
390                         }
391 
392                         continue;
393                     }
394 
395                     if (local.getPath() != null) {
396                         LOGGER.info(
397                                 "Artifact {} is present in the local repository, but cached from a remote repository ID that is unavailable in current build context, verifying that is downloadable from {}",
398                                 LogSanitizer.sanitize(String.valueOf(artifact)),
399                                 LogSanitizer.sanitize(String.valueOf(remoteRepositories)));
400                     }
401 
402                     LOGGER.debug(
403                             "Resolving artifact {} from {}",
404                             LogSanitizer.sanitize(String.valueOf(artifact)),
405                             LogSanitizer.sanitize(String.valueOf(remoteRepositories)));
406                     AtomicBoolean resolved = new AtomicBoolean(false);
407                     Iterator<ResolutionGroup> groupIt = groups.iterator();
408                     for (RemoteRepository repo : filteredRemoteRepositories) {
409                         if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
410                             continue;
411                         }
412 
413                         try {
414                             Utils.checkOffline(session, offlineController, repo);
415                         } catch (RepositoryOfflineException e) {
416                             Exception exception = new ArtifactNotFoundException(
417                                     artifact,
418                                     repo,
419                                     "Cannot access " + repo.getId() + " ("
420                                             + repo.getUrl() + ") in offline mode and the artifact " + artifact
421                                             + " has not been downloaded from it before.",
422                                     e);
423                             result.addException(repo, exception);
424                             continue;
425                         }
426 
427                         ResolutionGroup group = null;
428                         while (groupIt.hasNext()) {
429                             ResolutionGroup t = groupIt.next();
430                             if (t.matches(repo)) {
431                                 group = t;
432                                 break;
433                             }
434                         }
435                         if (group == null) {
436                             group = new ResolutionGroup(repo);
437                             groups.add(group);
438                             groupIt = Collections.emptyIterator();
439                         }
440                         group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
441                     }
442                 }
443 
444                 if (!groups.isEmpty() && current == shared) {
445                     SyncContext sharedContext = current;
446                     current = exclusive;
447                     sharedContext.close();
448                     continue;
449                 }
450 
451                 for (ResolutionGroup group : groups) {
452                     performDownloads(session, group);
453                 }
454 
455                 for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
456                         artifactResolverPostProcessors.values()) {
457                     artifactResolverPostProcessor.postProcess(session, results);
458                 }
459 
460                 for (ArtifactResult result : results) {
461                     ArtifactRequest request = result.getRequest();
462 
463                     Artifact artifact = result.getArtifact();
464                     if (artifact == null || artifact.getPath() == null) {
465                         failures = true;
466                         if (result.getExceptions().isEmpty()) {
467                             Exception exception =
468                                     new ArtifactNotFoundException(request.getArtifact(), (RemoteRepository) null);
469                             // Note: result.getRepository() MAY BE null; in cases when
470                             // the artifact was not even tried by any remote repository (snapshot vs repo policy)
471                             // and local repository does not have it either
472                             result.addException(
473                                     result.getRepository() != null
474                                             ? result.getRepository()
475                                             : ArtifactResult.NO_REPOSITORY,
476                                     exception);
477                         }
478                         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
479                         artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
480                     }
481                 }
482 
483                 if (failures) {
484                     throw new ArtifactResolutionException(results);
485                 }
486 
487                 return results;
488             }
489         } finally {
490             try {
491                 current.close();
492             } finally {
493                 if (current == shared) {
494                     exclusive.close();
495                 }
496             }
497         }
498     }
499 
500     /**
501      * This is the method that checks local artifact result if no RRF being used. Unlike with RRF, where only
502      * {@link LocalArtifactResult#isAvailable()} is checked, here we perform multiple checks:
503      * <ul>
504      *     <li>if {@link LocalArtifactResult#isAvailable()} is {@code true}, return {@code true}</li>
505      *     <li>if {@link LocalArtifactResult#getRepository()} is instance of {@link LocalRepository}, return {@code true}</li>
506      *     <li>if {@link LocalArtifactResult#getRepository()} is {@code null} and request had zero remote repositories set, return {@code true}</li>
507      * </ul>
508      * Note: the third check is interfering with RRF, as RRF may make list of remote repositories empty,  that was
509      * originally non-empty, by eliminating remote repositories to consider.
510      * Hence, we may use this method ONLY if RRF is inactive.
511      */
512     private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
513         if (lar.isAvailable()) {
514             return true;
515         }
516         if (lar.getPath() != null) {
517             // resolution of version range found locally installed artifact
518             if (vr.getRepository() instanceof LocalRepository) {
519                 // resolution of (snapshot) version found locally installed artifact
520                 return true;
521             } else {
522                 return vr.getRepository() == null
523                         && lar.getRequest().getRepositories().isEmpty();
524             }
525         }
526         return false;
527     }
528 
529     private Path getPath(RepositorySystemSession session, Artifact artifact, Path path)
530             throws ArtifactTransferException {
531         if (artifact.isSnapshot()
532                 && !artifact.getVersion().equals(artifact.getBaseVersion())
533                 && ConfigUtils.getBoolean(
534                         session, DEFAULT_SNAPSHOT_NORMALIZATION, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
535             String name = path.getFileName().toString().replace(artifact.getVersion(), artifact.getBaseVersion());
536             Path dst = path.getParent().resolve(name);
537 
538             try {
539                 long pathLastModified = pathProcessor.lastModified(path, 0L);
540                 boolean copy = pathProcessor.size(dst, 0L) != pathProcessor.size(path, 0L)
541                         || pathProcessor.lastModified(dst, 0L) != pathLastModified;
542                 if (copy) {
543                     pathProcessor.copyWithTimestamp(path, dst);
544                 }
545             } catch (IOException e) {
546                 throw new ArtifactTransferException(artifact, null, e);
547             }
548 
549             path = dst;
550         }
551 
552         return path;
553     }
554 
555     private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
556         List<ArtifactDownload> downloads = gatherDownloads(session, group);
557         if (downloads.isEmpty()) {
558             return;
559         }
560 
561         for (ArtifactDownload download : downloads) {
562             artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
563         }
564 
565         try {
566             try (RepositoryConnector connector =
567                     repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
568                 connector.get(downloads, null);
569             }
570         } catch (NoRepositoryConnectorException e) {
571             for (ArtifactDownload download : downloads) {
572                 download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
573             }
574         }
575 
576         evaluateDownloads(session, group);
577     }
578 
579     private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
580         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
581         final boolean existenceCheckRelabel =
582                 ConfigUtils.getBoolean(session, DEFAULT_EXISTENCE_CHECK_RELABEL, CONFIG_PROP_EXISTENCE_CHECK_RELABEL);
583         List<ArtifactDownload> downloads = new ArrayList<>();
584 
585         for (ResolutionItem item : group.items) {
586             Artifact artifact = item.artifact;
587 
588             if (item.resolved.get()) {
589                 // resolved in previous resolution group
590                 continue;
591             }
592 
593             ArtifactDownload download = new ArtifactDownload();
594             download.setArtifact(artifact);
595             download.setRequestContext(item.request.getRequestContext());
596             download.setListener(SafeTransferListener.wrap(session));
597             download.setTrace(item.trace);
598             if (item.local.getPath() != null && existenceCheckRelabel) {
599                 /*
600                  * NOTE: Legacy behavior, disabled by default: a bare existence check transfers no content, so the
601                  * cached bytes are re-labeled to this repository without ever passing checksum validation.
602                  */
603                 download.setPath(item.local.getPath());
604                 download.setExistenceCheck(true);
605             } else {
606                 /*
607                  * NOTE: Even if the artifact is present in the local repository (cached from a repository unavailable
608                  * in the current build context), download it again so the content passes the repository's checksum
609                  * policy before the artifact is registered with the local repository for this repository.
610                  */
611                 download.setPath(lrm.getAbsolutePathForRemoteArtifact(
612                         artifact, group.repository, item.request.getRequestContext()));
613             }
614 
615             boolean snapshot = artifact.isSnapshot();
616             RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
617 
618             int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
619             if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
620                 UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
621                 check.setItem(artifact);
622                 check.setPath(download.getPath());
623                 check.setFileValid(false);
624                 check.setRepository(group.repository);
625                 check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
626                 check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
627                 item.updateCheck = check;
628                 updateCheckManager.checkArtifact(session, check);
629                 if (!check.isRequired()) {
630                     item.result.addException(group.repository, check.getException());
631                     continue;
632                 }
633             }
634 
635             download.setChecksumPolicy(policy.getChecksumPolicy());
636             download.setRepositories(item.repository.getMirroredRepositories());
637             downloads.add(download);
638             item.download = download;
639         }
640 
641         return downloads;
642     }
643 
644     private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
645         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
646 
647         for (ResolutionItem item : group.items) {
648             ArtifactDownload download = item.download;
649             if (download == null) {
650                 continue;
651             }
652 
653             Artifact artifact = download.getArtifact();
654             if (download.getException() == null) {
655                 item.resolved.set(true);
656                 item.result.setRepository(group.repository);
657                 try {
658                     artifact = artifact.setPath(getPath(session, artifact, download.getPath()));
659                     item.result.setArtifact(artifact);
660 
661                     lrm.add(
662                             session,
663                             new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
664                     if (!download.getPath().equals(artifact.getPath())) {
665                         /*
666                          * NOTE: Snapshot normalization materialized a base-version copy of the downloaded file. Every
667                          * file the resolver derives from remote content must carry the same provenance tracking as
668                          * the download it stems from: register the copy under the same repository, otherwise the
669                          * untracked copy is later treated as locally installed (accepted without any
670                          * repository/offline checks) by the untracked-file interop logic.
671                          */
672                         lrm.add(
673                                 session,
674                                 new LocalArtifactRegistration(
675                                         artifact.setVersion(artifact.getBaseVersion()),
676                                         group.repository,
677                                         download.getSupportedContexts()));
678                     }
679                 } catch (ArtifactTransferException e) {
680                     download.setException(e);
681                     item.result.addException(group.repository, e);
682                 }
683             } else {
684                 item.result.addException(group.repository, download.getException());
685             }
686 
687             /*
688              * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
689              * "already updated" via session data when actual update to local repo is still pending.
690              */
691             if (item.updateCheck != null) {
692                 item.updateCheck.setException(download.getException());
693                 updateCheckManager.touchArtifact(session, item.updateCheck);
694             }
695 
696             artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
697             if (download.getException() == null) {
698                 artifactResolved(session, download.getTrace(), artifact, group.repository, null);
699             }
700         }
701     }
702 
703     private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
704         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
705         event.setTrace(trace);
706         event.setArtifact(artifact);
707 
708         repositoryEventDispatcher.dispatch(event.build());
709     }
710 
711     private void artifactResolved(
712             RepositorySystemSession session,
713             RequestTrace trace,
714             Artifact artifact,
715             ArtifactRepository repository,
716             Collection<Exception> exceptions) {
717         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
718         event.setTrace(trace);
719         event.setArtifact(artifact);
720         event.setRepository(repository);
721         event.setExceptions(exceptions != null ? new ArrayList<>(exceptions) : null);
722         if (artifact != null) {
723             event.setPath(artifact.getPath());
724         }
725 
726         repositoryEventDispatcher.dispatch(event.build());
727     }
728 
729     private void artifactDownloading(
730             RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
731         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
732         event.setTrace(trace);
733         event.setArtifact(artifact);
734         event.setRepository(repository);
735 
736         repositoryEventDispatcher.dispatch(event.build());
737     }
738 
739     private void artifactDownloaded(
740             RepositorySystemSession session,
741             RequestTrace trace,
742             Artifact artifact,
743             RemoteRepository repository,
744             Exception exception) {
745         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
746         event.setTrace(trace);
747         event.setArtifact(artifact);
748         event.setRepository(repository);
749         event.setException(exception);
750         if (artifact != null) {
751             event.setPath(artifact.getPath());
752         }
753 
754         repositoryEventDispatcher.dispatch(event.build());
755     }
756 
757     static class ResolutionGroup {
758 
759         final RemoteRepository repository;
760 
761         final List<ResolutionItem> items = new ArrayList<>();
762 
763         ResolutionGroup(RemoteRepository repository) {
764             this.repository = repository;
765         }
766 
767         boolean matches(RemoteRepository repo) {
768             return repository.getUrl().equals(repo.getUrl())
769                     && repository.getContentType().equals(repo.getContentType())
770                     && repository.isRepositoryManager() == repo.isRepositoryManager();
771         }
772     }
773 
774     static class ResolutionItem {
775 
776         final RequestTrace trace;
777 
778         final ArtifactRequest request;
779 
780         final ArtifactResult result;
781 
782         final LocalArtifactResult local;
783 
784         final RemoteRepository repository;
785 
786         final Artifact artifact;
787 
788         final AtomicBoolean resolved;
789 
790         ArtifactDownload download;
791 
792         UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
793 
794         ResolutionItem(
795                 RequestTrace trace,
796                 Artifact artifact,
797                 AtomicBoolean resolved,
798                 ArtifactResult result,
799                 LocalArtifactResult local,
800                 RemoteRepository repository) {
801             this.trace = trace;
802             this.artifact = artifact;
803             this.resolved = resolved;
804             this.result = result;
805             this.request = result.getRequest();
806             this.local = local;
807             this.repository = repository;
808         }
809     }
810 }