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     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArtifactResolver.class);
123 
124     private final PathProcessor pathProcessor;
125 
126     private final RepositoryEventDispatcher repositoryEventDispatcher;
127 
128     private final VersionResolver versionResolver;
129 
130     private final UpdateCheckManager updateCheckManager;
131 
132     private final RepositoryConnectorProvider repositoryConnectorProvider;
133 
134     private final RemoteRepositoryManager remoteRepositoryManager;
135 
136     private final SyncContextFactory syncContextFactory;
137 
138     private final OfflineController offlineController;
139 
140     private final Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors;
141 
142     private final RemoteRepositoryFilterManager remoteRepositoryFilterManager;
143 
144     @SuppressWarnings("checkstyle:parameternumber")
145     @Inject
146     public DefaultArtifactResolver(
147             PathProcessor pathProcessor,
148             RepositoryEventDispatcher repositoryEventDispatcher,
149             VersionResolver versionResolver,
150             UpdateCheckManager updateCheckManager,
151             RepositoryConnectorProvider repositoryConnectorProvider,
152             RemoteRepositoryManager remoteRepositoryManager,
153             SyncContextFactory syncContextFactory,
154             OfflineController offlineController,
155             Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors,
156             RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
157         this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null");
158         this.repositoryEventDispatcher =
159                 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
160         this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null");
161         this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
162         this.repositoryConnectorProvider =
163                 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
164         this.remoteRepositoryManager =
165                 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
166         this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
167         this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
168         this.artifactResolverPostProcessors =
169                 requireNonNull(artifactResolverPostProcessors, "artifact resolver post-processors cannot be null");
170         this.remoteRepositoryFilterManager =
171                 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
172     }
173 
174     @Override
175     public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request)
176             throws ArtifactResolutionException {
177         requireNonNull(session, "session cannot be null");
178         requireNonNull(request, "request cannot be null");
179 
180         return resolveArtifacts(session, Collections.singleton(request)).get(0);
181     }
182 
183     @Override
184     public List<ArtifactResult> resolveArtifacts(
185             RepositorySystemSession session, Collection<? extends ArtifactRequest> requests)
186             throws ArtifactResolutionException {
187         requireNonNull(session, "session cannot be null");
188         requireNonNull(requests, "requests cannot be null");
189         try (SyncContext shared = syncContextFactory.newInstance(session, true);
190                 SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
191             Collection<Artifact> artifacts = new ArrayList<>(requests.size());
192             SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
193             for (ArtifactRequest request : requests) {
194                 if (systemDependencyScope != null
195                         && systemDependencyScope.getSystemPath(request.getArtifact()) != null) {
196                     continue;
197                 }
198                 artifacts.add(request.getArtifact());
199             }
200 
201             return resolve(shared, exclusive, artifacts, session, requests);
202         }
203     }
204 
205     @SuppressWarnings("checkstyle:methodlength")
206     private List<ArtifactResult> resolve(
207             SyncContext shared,
208             SyncContext exclusive,
209             Collection<Artifact> subjects,
210             RepositorySystemSession session,
211             Collection<? extends ArtifactRequest> requests)
212             throws ArtifactResolutionException {
213         SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
214         SyncContext current = shared;
215         try {
216             while (true) {
217                 current.acquire(subjects, null);
218 
219                 boolean failures = false;
220                 final List<ArtifactResult> results = new ArrayList<>(requests.size());
221                 final boolean simpleLrmInterop =
222                         ConfigUtils.getBoolean(session, DEFAULT_SIMPLE_LRM_INTEROP, CONFIG_PROP_SIMPLE_LRM_INTEROP);
223                 final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
224                 final WorkspaceReader workspace = session.getWorkspaceReader();
225                 final List<ResolutionGroup> groups = new ArrayList<>();
226                 // filter != null: means "filtering applied", if null no filtering applied (behave as before)
227                 final RemoteRepositoryFilter filter = remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
228 
229                 for (ArtifactRequest request : requests) {
230                     RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
231 
232                     ArtifactResult result = new ArtifactResult(request);
233                     results.add(result);
234 
235                     Artifact artifact = request.getArtifact();
236 
237                     if (current == shared) {
238                         artifactResolving(session, trace, artifact);
239                     }
240 
241                     String localPath =
242                             systemDependencyScope != null ? systemDependencyScope.getSystemPath(artifact) : null;
243                     if (localPath != null) {
244                         // unhosted artifact, just validate file
245                         Path path = Paths.get(localPath);
246                         if (!Files.isRegularFile(path)) {
247                             failures = true;
248                             result.addException(
249                                     ArtifactResult.NO_REPOSITORY, new ArtifactNotFoundException(artifact, localPath));
250                         } else {
251                             artifact = artifact.setPath(path);
252                             result.setArtifact(artifact);
253                             artifactResolved(session, trace, artifact, null, result.getExceptions());
254                         }
255                         continue;
256                     }
257 
258                     List<RemoteRepository> remoteRepositories = request.getRepositories();
259                     List<RemoteRepository> filteredRemoteRepositories = new ArrayList<>(remoteRepositories);
260                     if (filter != null) {
261                         for (RemoteRepository repository : remoteRepositories) {
262                             RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
263                             if (!filterResult.isAccepted()) {
264                                 result.addException(
265                                         repository,
266                                         new ArtifactFilteredOutException(
267                                                 artifact, repository, filterResult.reasoning()));
268                                 filteredRemoteRepositories.remove(repository);
269                             }
270                         }
271                     }
272 
273                     VersionResult versionResult;
274                     try {
275                         VersionRequest versionRequest =
276                                 new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
277                         versionRequest.setTrace(trace);
278                         versionResult = versionResolver.resolveVersion(session, versionRequest);
279                     } catch (VersionResolutionException e) {
280                         if (filteredRemoteRepositories.isEmpty()) {
281                             result.addException(lrm.getRepository(), e);
282                         } else {
283                             filteredRemoteRepositories.forEach(r -> result.addException(r, e));
284                         }
285                         continue;
286                     }
287 
288                     artifact = artifact.setVersion(versionResult.getVersion());
289 
290                     if (versionResult.getRepository() != null) {
291                         if (versionResult.getRepository() instanceof RemoteRepository) {
292                             filteredRemoteRepositories =
293                                     Collections.singletonList((RemoteRepository) versionResult.getRepository());
294                         } else {
295                             filteredRemoteRepositories = Collections.emptyList();
296                         }
297                     }
298 
299                     if (workspace != null) {
300                         Path path = workspace.findArtifactPath(artifact);
301                         if (path != null) {
302                             artifact = artifact.setPath(path);
303                             result.setArtifact(artifact);
304                             result.setRepository(workspace.getRepository());
305                             artifactResolved(session, trace, artifact, result.getRepository(), null);
306                             continue;
307                         }
308                     }
309 
310                     LocalArtifactResult local = lrm.find(
311                             session,
312                             new LocalArtifactRequest(
313                                     artifact, filteredRemoteRepositories, request.getRequestContext()));
314                     result.setLocalArtifactResult(local);
315                     boolean found = (filter != null && local.isAvailable())
316                             || (filter == null && isLocallyInstalled(local, versionResult));
317                     // with filtering: availability drives the logic
318                     // without filtering: simply presence of file drives the logic
319                     // "interop" logic with simple LRM leads to RRF breakage: hence is ignored when filtering in effect
320                     if (found) {
321                         if (local.getRepository() != null) {
322                             result.setRepository(local.getRepository());
323                         } else {
324                             result.setRepository(lrm.getRepository());
325                         }
326 
327                         try {
328                             artifact = artifact.setPath(getPath(session, artifact, local.getPath()));
329                             result.setArtifact(artifact);
330                             artifactResolved(session, trace, artifact, result.getRepository(), null);
331                         } catch (ArtifactTransferException e) {
332                             result.addException(lrm.getRepository(), e);
333                         }
334                         if (filter == null && simpleLrmInterop && !local.isAvailable()) {
335                             /*
336                              * NOTE: Interop with simple local repository: An artifact installed by a simple local repo
337                              * manager will not show up in the repository tracking file of the enhanced local repository.
338                              * If however the maven-metadata-local.xml tells us the artifact was installed locally, we
339                              * sync the repository tracking file.
340                              */
341                             lrm.add(session, new LocalArtifactRegistration(artifact));
342                         }
343 
344                         continue;
345                     }
346 
347                     if (local.getPath() != null) {
348                         LOGGER.info(
349                                 "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 {}",
350                                 artifact,
351                                 remoteRepositories);
352                     }
353 
354                     LOGGER.debug("Resolving artifact {} from {}", artifact, remoteRepositories);
355                     AtomicBoolean resolved = new AtomicBoolean(false);
356                     Iterator<ResolutionGroup> groupIt = groups.iterator();
357                     for (RemoteRepository repo : filteredRemoteRepositories) {
358                         if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
359                             continue;
360                         }
361 
362                         try {
363                             Utils.checkOffline(session, offlineController, repo);
364                         } catch (RepositoryOfflineException e) {
365                             Exception exception = new ArtifactNotFoundException(
366                                     artifact,
367                                     repo,
368                                     "Cannot access " + repo.getId() + " ("
369                                             + repo.getUrl() + ") in offline mode and the artifact " + artifact
370                                             + " has not been downloaded from it before.",
371                                     e);
372                             result.addException(repo, exception);
373                             continue;
374                         }
375 
376                         ResolutionGroup group = null;
377                         while (groupIt.hasNext()) {
378                             ResolutionGroup t = groupIt.next();
379                             if (t.matches(repo)) {
380                                 group = t;
381                                 break;
382                             }
383                         }
384                         if (group == null) {
385                             group = new ResolutionGroup(repo);
386                             groups.add(group);
387                             groupIt = Collections.emptyIterator();
388                         }
389                         group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
390                     }
391                 }
392 
393                 if (!groups.isEmpty() && current == shared) {
394                     current.close();
395                     current = exclusive;
396                     continue;
397                 }
398 
399                 for (ResolutionGroup group : groups) {
400                     performDownloads(session, group);
401                 }
402 
403                 for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
404                         artifactResolverPostProcessors.values()) {
405                     artifactResolverPostProcessor.postProcess(session, results);
406                 }
407 
408                 for (ArtifactResult result : results) {
409                     ArtifactRequest request = result.getRequest();
410 
411                     Artifact artifact = result.getArtifact();
412                     if (artifact == null || artifact.getPath() == null) {
413                         failures = true;
414                         if (result.getExceptions().isEmpty()) {
415                             Exception exception =
416                                     new ArtifactNotFoundException(request.getArtifact(), (RemoteRepository) null);
417                             // Note: result.getRepository() MAY BE null; in cases when
418                             // the artifact was not even tried by any remote repository (snapshot vs repo policy)
419                             // and local repository does not have it either
420                             result.addException(
421                                     result.getRepository() != null
422                                             ? result.getRepository()
423                                             : ArtifactResult.NO_REPOSITORY,
424                                     exception);
425                         }
426                         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
427                         artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
428                     }
429                 }
430 
431                 if (failures) {
432                     throw new ArtifactResolutionException(results);
433                 }
434 
435                 return results;
436             }
437         } finally {
438             current.close();
439         }
440     }
441 
442     /**
443      * This is the method that checks local artifact result if no RRF being used. Unlike with RRF, where only
444      * {@link LocalArtifactResult#isAvailable()} is checked, here we perform multiple checks:
445      * <ul>
446      *     <li>if {@link LocalArtifactResult#isAvailable()} is {@code true}, return {@code true}</li>
447      *     <li>if {@link LocalArtifactResult#getRepository()} is instance of {@link LocalRepository}, return {@code true}</li>
448      *     <li>if {@link LocalArtifactResult#getRepository()} is {@code null} and request had zero remote repositories set, return {@code true}</li>
449      * </ul>
450      * Note: the third check is interfering with RRF, as RRF may make list of remote repositories empty,  that was
451      * originally non-empty, by eliminating remote repositories to consider.
452      * Hence, we may use this method ONLY if RRF is inactive.
453      */
454     private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
455         if (lar.isAvailable()) {
456             return true;
457         }
458         if (lar.getPath() != null) {
459             // resolution of version range found locally installed artifact
460             if (vr.getRepository() instanceof LocalRepository) {
461                 // resolution of (snapshot) version found locally installed artifact
462                 return true;
463             } else {
464                 return vr.getRepository() == null
465                         && lar.getRequest().getRepositories().isEmpty();
466             }
467         }
468         return false;
469     }
470 
471     private Path getPath(RepositorySystemSession session, Artifact artifact, Path path)
472             throws ArtifactTransferException {
473         if (artifact.isSnapshot()
474                 && !artifact.getVersion().equals(artifact.getBaseVersion())
475                 && ConfigUtils.getBoolean(
476                         session, DEFAULT_SNAPSHOT_NORMALIZATION, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
477             String name = path.getFileName().toString().replace(artifact.getVersion(), artifact.getBaseVersion());
478             Path dst = path.getParent().resolve(name);
479 
480             try {
481                 long pathLastModified = pathProcessor.lastModified(path, 0L);
482                 boolean copy = pathProcessor.size(dst, 0L) != pathProcessor.size(path, 0L)
483                         || pathProcessor.lastModified(dst, 0L) != pathLastModified;
484                 if (copy) {
485                     pathProcessor.copyWithTimestamp(path, dst);
486                 }
487             } catch (IOException e) {
488                 throw new ArtifactTransferException(artifact, null, e);
489             }
490 
491             path = dst;
492         }
493 
494         return path;
495     }
496 
497     private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
498         List<ArtifactDownload> downloads = gatherDownloads(session, group);
499         if (downloads.isEmpty()) {
500             return;
501         }
502 
503         for (ArtifactDownload download : downloads) {
504             artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
505         }
506 
507         try {
508             try (RepositoryConnector connector =
509                     repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
510                 connector.get(downloads, null);
511             }
512         } catch (NoRepositoryConnectorException e) {
513             for (ArtifactDownload download : downloads) {
514                 download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
515             }
516         }
517 
518         evaluateDownloads(session, group);
519     }
520 
521     private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
522         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
523         List<ArtifactDownload> downloads = new ArrayList<>();
524 
525         for (ResolutionItem item : group.items) {
526             Artifact artifact = item.artifact;
527 
528             if (item.resolved.get()) {
529                 // resolved in previous resolution group
530                 continue;
531             }
532 
533             ArtifactDownload download = new ArtifactDownload();
534             download.setArtifact(artifact);
535             download.setRequestContext(item.request.getRequestContext());
536             download.setListener(SafeTransferListener.wrap(session));
537             download.setTrace(item.trace);
538             if (item.local.getPath() != null) {
539                 download.setPath(item.local.getPath());
540                 download.setExistenceCheck(true);
541             } else {
542                 download.setPath(lrm.getAbsolutePathForRemoteArtifact(
543                         artifact, group.repository, item.request.getRequestContext()));
544             }
545 
546             boolean snapshot = artifact.isSnapshot();
547             RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
548 
549             int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
550             if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
551                 UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
552                 check.setItem(artifact);
553                 check.setPath(download.getPath());
554                 check.setFileValid(false);
555                 check.setRepository(group.repository);
556                 check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
557                 check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
558                 item.updateCheck = check;
559                 updateCheckManager.checkArtifact(session, check);
560                 if (!check.isRequired()) {
561                     item.result.addException(group.repository, check.getException());
562                     continue;
563                 }
564             }
565 
566             download.setChecksumPolicy(policy.getChecksumPolicy());
567             download.setRepositories(item.repository.getMirroredRepositories());
568             downloads.add(download);
569             item.download = download;
570         }
571 
572         return downloads;
573     }
574 
575     private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
576         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
577 
578         for (ResolutionItem item : group.items) {
579             ArtifactDownload download = item.download;
580             if (download == null) {
581                 continue;
582             }
583 
584             Artifact artifact = download.getArtifact();
585             if (download.getException() == null) {
586                 item.resolved.set(true);
587                 item.result.setRepository(group.repository);
588                 try {
589                     artifact = artifact.setPath(getPath(session, artifact, download.getPath()));
590                     item.result.setArtifact(artifact);
591 
592                     lrm.add(
593                             session,
594                             new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
595                 } catch (ArtifactTransferException e) {
596                     download.setException(e);
597                     item.result.addException(group.repository, e);
598                 }
599             } else {
600                 item.result.addException(group.repository, download.getException());
601             }
602 
603             /*
604              * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
605              * "already updated" via session data when actual update to local repo is still pending.
606              */
607             if (item.updateCheck != null) {
608                 item.updateCheck.setException(download.getException());
609                 updateCheckManager.touchArtifact(session, item.updateCheck);
610             }
611 
612             artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
613             if (download.getException() == null) {
614                 artifactResolved(session, download.getTrace(), artifact, group.repository, null);
615             }
616         }
617     }
618 
619     private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
620         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
621         event.setTrace(trace);
622         event.setArtifact(artifact);
623 
624         repositoryEventDispatcher.dispatch(event.build());
625     }
626 
627     private void artifactResolved(
628             RepositorySystemSession session,
629             RequestTrace trace,
630             Artifact artifact,
631             ArtifactRepository repository,
632             Collection<Exception> exceptions) {
633         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
634         event.setTrace(trace);
635         event.setArtifact(artifact);
636         event.setRepository(repository);
637         event.setExceptions(exceptions != null ? new ArrayList<>(exceptions) : null);
638         if (artifact != null) {
639             event.setPath(artifact.getPath());
640         }
641 
642         repositoryEventDispatcher.dispatch(event.build());
643     }
644 
645     private void artifactDownloading(
646             RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
647         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
648         event.setTrace(trace);
649         event.setArtifact(artifact);
650         event.setRepository(repository);
651 
652         repositoryEventDispatcher.dispatch(event.build());
653     }
654 
655     private void artifactDownloaded(
656             RepositorySystemSession session,
657             RequestTrace trace,
658             Artifact artifact,
659             RemoteRepository repository,
660             Exception exception) {
661         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
662         event.setTrace(trace);
663         event.setArtifact(artifact);
664         event.setRepository(repository);
665         event.setException(exception);
666         if (artifact != null) {
667             event.setPath(artifact.getPath());
668         }
669 
670         repositoryEventDispatcher.dispatch(event.build());
671     }
672 
673     static class ResolutionGroup {
674 
675         final RemoteRepository repository;
676 
677         final List<ResolutionItem> items = new ArrayList<>();
678 
679         ResolutionGroup(RemoteRepository repository) {
680             this.repository = repository;
681         }
682 
683         boolean matches(RemoteRepository repo) {
684             return repository.getUrl().equals(repo.getUrl())
685                     && repository.getContentType().equals(repo.getContentType())
686                     && repository.isRepositoryManager() == repo.isRepositoryManager();
687         }
688     }
689 
690     static class ResolutionItem {
691 
692         final RequestTrace trace;
693 
694         final ArtifactRequest request;
695 
696         final ArtifactResult result;
697 
698         final LocalArtifactResult local;
699 
700         final RemoteRepository repository;
701 
702         final Artifact artifact;
703 
704         final AtomicBoolean resolved;
705 
706         ArtifactDownload download;
707 
708         UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
709 
710         ResolutionItem(
711                 RequestTrace trace,
712                 Artifact artifact,
713                 AtomicBoolean resolved,
714                 ArtifactResult result,
715                 LocalArtifactResult local,
716                 RemoteRepository repository) {
717             this.trace = trace;
718             this.artifact = artifact;
719             this.resolved = resolved;
720             this.result = result;
721             this.request = result.getRequest();
722             this.local = local;
723             this.repository = repository;
724         }
725     }
726 }