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.File;
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  
35  import org.eclipse.aether.RepositoryEvent;
36  import org.eclipse.aether.RepositoryEvent.EventType;
37  import org.eclipse.aether.RepositorySystemSession;
38  import org.eclipse.aether.RequestTrace;
39  import org.eclipse.aether.SyncContext;
40  import org.eclipse.aether.artifact.Artifact;
41  import org.eclipse.aether.artifact.ArtifactProperties;
42  import org.eclipse.aether.impl.ArtifactResolver;
43  import org.eclipse.aether.impl.OfflineController;
44  import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
45  import org.eclipse.aether.impl.RemoteRepositoryManager;
46  import org.eclipse.aether.impl.RepositoryConnectorProvider;
47  import org.eclipse.aether.impl.RepositoryEventDispatcher;
48  import org.eclipse.aether.impl.UpdateCheck;
49  import org.eclipse.aether.impl.UpdateCheckManager;
50  import org.eclipse.aether.impl.VersionResolver;
51  import org.eclipse.aether.repository.ArtifactRepository;
52  import org.eclipse.aether.repository.LocalArtifactRegistration;
53  import org.eclipse.aether.repository.LocalArtifactRequest;
54  import org.eclipse.aether.repository.LocalArtifactResult;
55  import org.eclipse.aether.repository.LocalRepository;
56  import org.eclipse.aether.repository.LocalRepositoryManager;
57  import org.eclipse.aether.repository.RemoteRepository;
58  import org.eclipse.aether.repository.RepositoryPolicy;
59  import org.eclipse.aether.repository.WorkspaceReader;
60  import org.eclipse.aether.resolution.ArtifactRequest;
61  import org.eclipse.aether.resolution.ArtifactResolutionException;
62  import org.eclipse.aether.resolution.ArtifactResult;
63  import org.eclipse.aether.resolution.ResolutionErrorPolicy;
64  import org.eclipse.aether.resolution.VersionRequest;
65  import org.eclipse.aether.resolution.VersionResolutionException;
66  import org.eclipse.aether.resolution.VersionResult;
67  import org.eclipse.aether.spi.connector.ArtifactDownload;
68  import org.eclipse.aether.spi.connector.RepositoryConnector;
69  import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
70  import org.eclipse.aether.spi.io.FileProcessor;
71  import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
72  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
73  import org.eclipse.aether.transfer.ArtifactFilteredOutException;
74  import org.eclipse.aether.transfer.ArtifactNotFoundException;
75  import org.eclipse.aether.transfer.ArtifactTransferException;
76  import org.eclipse.aether.transfer.NoRepositoryConnectorException;
77  import org.eclipse.aether.transfer.RepositoryOfflineException;
78  import org.eclipse.aether.util.ConfigUtils;
79  import org.slf4j.Logger;
80  import org.slf4j.LoggerFactory;
81  
82  import static java.util.Objects.requireNonNull;
83  
84  /**
85   *
86   */
87  @Singleton
88  @Named
89  public class DefaultArtifactResolver implements ArtifactResolver {
90  
91      /**
92       * Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names
93       * will have file names converted back to baseVersion. Default: {@code true}.
94       */
95      private static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = "aether.artifactResolver.snapshotNormalization";
96  
97      /**
98       * Configuration to enable "interoperability" with Simple LRM, but this breaks RRF feature, hence this configuration
99       * is IGNORED when RRF is used, and is warmly recommended to leave it disabled even if no RRF is being used.
100      * Default: {@code false}.
101      */
102     private static final String CONFIG_PROP_SIMPLE_LRM_INTEROP = "aether.artifactResolver.simpleLrmInterop";
103 
104     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArtifactResolver.class);
105 
106     private final FileProcessor fileProcessor;
107 
108     private final RepositoryEventDispatcher repositoryEventDispatcher;
109 
110     private final VersionResolver versionResolver;
111 
112     private final UpdateCheckManager updateCheckManager;
113 
114     private final RepositoryConnectorProvider repositoryConnectorProvider;
115 
116     private final RemoteRepositoryManager remoteRepositoryManager;
117 
118     private final SyncContextFactory syncContextFactory;
119 
120     private final OfflineController offlineController;
121 
122     private final Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors;
123 
124     private final RemoteRepositoryFilterManager remoteRepositoryFilterManager;
125 
126     @SuppressWarnings("checkstyle:parameternumber")
127     @Inject
128     public DefaultArtifactResolver(
129             FileProcessor fileProcessor,
130             RepositoryEventDispatcher repositoryEventDispatcher,
131             VersionResolver versionResolver,
132             UpdateCheckManager updateCheckManager,
133             RepositoryConnectorProvider repositoryConnectorProvider,
134             RemoteRepositoryManager remoteRepositoryManager,
135             SyncContextFactory syncContextFactory,
136             OfflineController offlineController,
137             Map<String, ArtifactResolverPostProcessor> artifactResolverPostProcessors,
138             RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
139         this.fileProcessor = requireNonNull(fileProcessor, "file processor cannot be null");
140         this.repositoryEventDispatcher =
141                 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
142         this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null");
143         this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
144         this.repositoryConnectorProvider =
145                 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
146         this.remoteRepositoryManager =
147                 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
148         this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
149         this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
150         this.artifactResolverPostProcessors =
151                 requireNonNull(artifactResolverPostProcessors, "artifact resolver post-processors cannot be null");
152         this.remoteRepositoryFilterManager =
153                 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
154     }
155 
156     @Override
157     public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request)
158             throws ArtifactResolutionException {
159         requireNonNull(session, "session cannot be null");
160         requireNonNull(request, "request cannot be null");
161 
162         return resolveArtifacts(session, Collections.singleton(request)).get(0);
163     }
164 
165     @Override
166     public List<ArtifactResult> resolveArtifacts(
167             RepositorySystemSession session, Collection<? extends ArtifactRequest> requests)
168             throws ArtifactResolutionException {
169         requireNonNull(session, "session cannot be null");
170         requireNonNull(requests, "requests cannot be null");
171         try (SyncContext shared = syncContextFactory.newInstance(session, true);
172                 SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
173             Collection<Artifact> artifacts = new ArrayList<>(requests.size());
174             for (ArtifactRequest request : requests) {
175                 if (request.getArtifact().getProperty(ArtifactProperties.LOCAL_PATH, null) != null) {
176                     continue;
177                 }
178                 artifacts.add(request.getArtifact());
179             }
180 
181             return resolve(shared, exclusive, artifacts, session, requests);
182         }
183     }
184 
185     @SuppressWarnings("checkstyle:methodlength")
186     private List<ArtifactResult> resolve(
187             SyncContext shared,
188             SyncContext exclusive,
189             Collection<Artifact> subjects,
190             RepositorySystemSession session,
191             Collection<? extends ArtifactRequest> requests)
192             throws ArtifactResolutionException {
193         SyncContext current = shared;
194         try {
195             while (true) {
196                 current.acquire(subjects, null);
197 
198                 boolean failures = false;
199                 final List<ArtifactResult> results = new ArrayList<>(requests.size());
200                 final boolean simpleLrmInterop = ConfigUtils.getBoolean(session, false, CONFIG_PROP_SIMPLE_LRM_INTEROP);
201                 final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
202                 final WorkspaceReader workspace = session.getWorkspaceReader();
203                 final List<ResolutionGroup> groups = new ArrayList<>();
204                 // filter != null: means "filtering applied", if null no filtering applied (behave as before)
205                 final RemoteRepositoryFilter filter = remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
206 
207                 for (ArtifactRequest request : requests) {
208                     RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
209 
210                     ArtifactResult result = new ArtifactResult(request);
211                     results.add(result);
212 
213                     Artifact artifact = request.getArtifact();
214 
215                     if (current == shared) {
216                         artifactResolving(session, trace, artifact);
217                     }
218 
219                     String localPath = artifact.getProperty(ArtifactProperties.LOCAL_PATH, null);
220                     if (localPath != null) {
221                         // unhosted artifact, just validate file
222                         File file = new File(localPath);
223                         if (!file.isFile()) {
224                             failures = true;
225                             result.addException(new ArtifactNotFoundException(artifact, null));
226                         } else {
227                             artifact = artifact.setFile(file);
228                             result.setArtifact(artifact);
229                             artifactResolved(session, trace, artifact, null, result.getExceptions());
230                         }
231                         continue;
232                     }
233 
234                     List<RemoteRepository> remoteRepositories = request.getRepositories();
235                     List<RemoteRepository> filteredRemoteRepositories = new ArrayList<>(remoteRepositories);
236                     if (filter != null) {
237                         for (RemoteRepository repository : remoteRepositories) {
238                             RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
239                             if (!filterResult.isAccepted()) {
240                                 result.addException(new ArtifactFilteredOutException(
241                                         artifact, repository, filterResult.reasoning()));
242                                 filteredRemoteRepositories.remove(repository);
243                             }
244                         }
245                     }
246 
247                     VersionResult versionResult;
248                     try {
249                         VersionRequest versionRequest =
250                                 new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
251                         versionRequest.setTrace(trace);
252                         versionResult = versionResolver.resolveVersion(session, versionRequest);
253                     } catch (VersionResolutionException e) {
254                         result.addException(e);
255                         continue;
256                     }
257 
258                     artifact = artifact.setVersion(versionResult.getVersion());
259 
260                     if (versionResult.getRepository() != null) {
261                         if (versionResult.getRepository() instanceof RemoteRepository) {
262                             filteredRemoteRepositories =
263                                     Collections.singletonList((RemoteRepository) versionResult.getRepository());
264                         } else {
265                             filteredRemoteRepositories = Collections.emptyList();
266                         }
267                     }
268 
269                     if (workspace != null) {
270                         File file = workspace.findArtifact(artifact);
271                         if (file != null) {
272                             artifact = artifact.setFile(file);
273                             result.setArtifact(artifact);
274                             result.setRepository(workspace.getRepository());
275                             artifactResolved(session, trace, artifact, result.getRepository(), null);
276                             continue;
277                         }
278                     }
279 
280                     LocalArtifactResult local = lrm.find(
281                             session,
282                             new LocalArtifactRequest(
283                                     artifact, filteredRemoteRepositories, request.getRequestContext()));
284                     result.setLocalArtifactResult(local);
285                     boolean found = (filter != null && local.isAvailable()) || isLocallyInstalled(local, versionResult);
286                     // with filtering it is availability that drives logic
287                     // without filtering it is simply presence of file that drives the logic
288                     // "interop" logic with simple LRM leads to RRF breakage: hence is ignored when filtering in effect
289                     if (found) {
290                         if (local.getRepository() != null) {
291                             result.setRepository(local.getRepository());
292                         } else {
293                             result.setRepository(lrm.getRepository());
294                         }
295 
296                         try {
297                             artifact = artifact.setFile(getFile(session, artifact, local.getFile()));
298                             result.setArtifact(artifact);
299                             artifactResolved(session, trace, artifact, result.getRepository(), null);
300                         } catch (ArtifactTransferException e) {
301                             result.addException(e);
302                         }
303                         if (filter == null && simpleLrmInterop && !local.isAvailable()) {
304                             /*
305                              * NOTE: Interop with simple local repository: An artifact installed by a simple local repo
306                              * manager will not show up in the repository tracking file of the enhanced local repository.
307                              * If however the maven-metadata-local.xml tells us the artifact was installed locally, we
308                              * sync the repository tracking file.
309                              */
310                             lrm.add(session, new LocalArtifactRegistration(artifact));
311                         }
312 
313                         continue;
314                     }
315 
316                     if (local.getFile() != null) {
317                         LOGGER.info(
318                                 "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 {}",
319                                 artifact,
320                                 remoteRepositories);
321                     }
322 
323                     LOGGER.debug("Resolving artifact {} from {}", artifact, remoteRepositories);
324                     AtomicBoolean resolved = new AtomicBoolean(false);
325                     Iterator<ResolutionGroup> groupIt = groups.iterator();
326                     for (RemoteRepository repo : filteredRemoteRepositories) {
327                         if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
328                             continue;
329                         }
330 
331                         try {
332                             Utils.checkOffline(session, offlineController, repo);
333                         } catch (RepositoryOfflineException e) {
334                             Exception exception = new ArtifactNotFoundException(
335                                     artifact,
336                                     repo,
337                                     "Cannot access " + repo.getId() + " ("
338                                             + repo.getUrl() + ") in offline mode and the artifact " + artifact
339                                             + " has not been downloaded from it before.",
340                                     e);
341                             result.addException(exception);
342                             continue;
343                         }
344 
345                         ResolutionGroup group = null;
346                         while (groupIt.hasNext()) {
347                             ResolutionGroup t = groupIt.next();
348                             if (t.matches(repo)) {
349                                 group = t;
350                                 break;
351                             }
352                         }
353                         if (group == null) {
354                             group = new ResolutionGroup(repo);
355                             groups.add(group);
356                             groupIt = Collections.emptyIterator();
357                         }
358                         group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
359                     }
360                 }
361 
362                 if (!groups.isEmpty() && current == shared) {
363                     current.close();
364                     current = exclusive;
365                     continue;
366                 }
367 
368                 for (ResolutionGroup group : groups) {
369                     performDownloads(session, group);
370                 }
371 
372                 for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
373                         artifactResolverPostProcessors.values()) {
374                     artifactResolverPostProcessor.postProcess(session, results);
375                 }
376 
377                 for (ArtifactResult result : results) {
378                     ArtifactRequest request = result.getRequest();
379 
380                     Artifact artifact = result.getArtifact();
381                     if (artifact == null || artifact.getFile() == null) {
382                         failures = true;
383                         if (result.getExceptions().isEmpty()) {
384                             Exception exception = new ArtifactNotFoundException(request.getArtifact(), null);
385                             result.addException(exception);
386                         }
387                         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
388                         artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
389                     }
390                 }
391 
392                 if (failures) {
393                     throw new ArtifactResolutionException(results);
394                 }
395 
396                 return results;
397             }
398         } finally {
399             current.close();
400         }
401     }
402 
403     private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
404         if (lar.isAvailable()) {
405             return true;
406         }
407         if (lar.getFile() != null) {
408             // resolution of version range found locally installed artifact
409             if (vr.getRepository() instanceof LocalRepository) {
410                 // resolution of (snapshot) version found locally installed artifact
411                 return true;
412             } else {
413                 return vr.getRepository() == null
414                         && lar.getRequest().getRepositories().isEmpty();
415             }
416         }
417         return false;
418     }
419 
420     private File getFile(RepositorySystemSession session, Artifact artifact, File file)
421             throws ArtifactTransferException {
422         if (artifact.isSnapshot()
423                 && !artifact.getVersion().equals(artifact.getBaseVersion())
424                 && ConfigUtils.getBoolean(session, true, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
425             String name = file.getName().replace(artifact.getVersion(), artifact.getBaseVersion());
426             File dst = new File(file.getParent(), name);
427 
428             boolean copy = dst.length() != file.length() || dst.lastModified() != file.lastModified();
429             if (copy) {
430                 try {
431                     fileProcessor.copy(file, dst);
432                     dst.setLastModified(file.lastModified());
433                 } catch (IOException e) {
434                     throw new ArtifactTransferException(artifact, null, e);
435                 }
436             }
437 
438             file = dst;
439         }
440 
441         return file;
442     }
443 
444     private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
445         List<ArtifactDownload> downloads = gatherDownloads(session, group);
446         if (downloads.isEmpty()) {
447             return;
448         }
449 
450         for (ArtifactDownload download : downloads) {
451             artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
452         }
453 
454         try {
455             try (RepositoryConnector connector =
456                     repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
457                 connector.get(downloads, null);
458             }
459         } catch (NoRepositoryConnectorException e) {
460             for (ArtifactDownload download : downloads) {
461                 download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
462             }
463         }
464 
465         evaluateDownloads(session, group);
466     }
467 
468     private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
469         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
470         List<ArtifactDownload> downloads = new ArrayList<>();
471 
472         for (ResolutionItem item : group.items) {
473             Artifact artifact = item.artifact;
474 
475             if (item.resolved.get()) {
476                 // resolved in previous resolution group
477                 continue;
478             }
479 
480             ArtifactDownload download = new ArtifactDownload();
481             download.setArtifact(artifact);
482             download.setRequestContext(item.request.getRequestContext());
483             download.setListener(SafeTransferListener.wrap(session));
484             download.setTrace(item.trace);
485             if (item.local.getFile() != null) {
486                 download.setFile(item.local.getFile());
487                 download.setExistenceCheck(true);
488             } else {
489                 String path =
490                         lrm.getPathForRemoteArtifact(artifact, group.repository, item.request.getRequestContext());
491                 download.setFile(new File(lrm.getRepository().getBasedir(), path));
492             }
493 
494             boolean snapshot = artifact.isSnapshot();
495             RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
496 
497             int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
498             if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
499                 UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
500                 check.setItem(artifact);
501                 check.setFile(download.getFile());
502                 check.setFileValid(false);
503                 check.setRepository(group.repository);
504                 check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
505                 check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
506                 item.updateCheck = check;
507                 updateCheckManager.checkArtifact(session, check);
508                 if (!check.isRequired()) {
509                     item.result.addException(check.getException());
510                     continue;
511                 }
512             }
513 
514             download.setChecksumPolicy(policy.getChecksumPolicy());
515             download.setRepositories(item.repository.getMirroredRepositories());
516             downloads.add(download);
517             item.download = download;
518         }
519 
520         return downloads;
521     }
522 
523     private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
524         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
525 
526         for (ResolutionItem item : group.items) {
527             ArtifactDownload download = item.download;
528             if (download == null) {
529                 continue;
530             }
531 
532             Artifact artifact = download.getArtifact();
533             if (download.getException() == null) {
534                 item.resolved.set(true);
535                 item.result.setRepository(group.repository);
536                 try {
537                     artifact = artifact.setFile(getFile(session, artifact, download.getFile()));
538                     item.result.setArtifact(artifact);
539 
540                     lrm.add(
541                             session,
542                             new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
543                 } catch (ArtifactTransferException e) {
544                     download.setException(e);
545                     item.result.addException(e);
546                 }
547             } else {
548                 item.result.addException(download.getException());
549             }
550 
551             /*
552              * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
553              * "already updated" via session data when actual update to local repo is still pending.
554              */
555             if (item.updateCheck != null) {
556                 item.updateCheck.setException(download.getException());
557                 updateCheckManager.touchArtifact(session, item.updateCheck);
558             }
559 
560             artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
561             if (download.getException() == null) {
562                 artifactResolved(session, download.getTrace(), artifact, group.repository, null);
563             }
564         }
565     }
566 
567     private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
568         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
569         event.setTrace(trace);
570         event.setArtifact(artifact);
571 
572         repositoryEventDispatcher.dispatch(event.build());
573     }
574 
575     private void artifactResolved(
576             RepositorySystemSession session,
577             RequestTrace trace,
578             Artifact artifact,
579             ArtifactRepository repository,
580             List<Exception> exceptions) {
581         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
582         event.setTrace(trace);
583         event.setArtifact(artifact);
584         event.setRepository(repository);
585         event.setExceptions(exceptions);
586         if (artifact != null) {
587             event.setFile(artifact.getFile());
588         }
589 
590         repositoryEventDispatcher.dispatch(event.build());
591     }
592 
593     private void artifactDownloading(
594             RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
595         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
596         event.setTrace(trace);
597         event.setArtifact(artifact);
598         event.setRepository(repository);
599 
600         repositoryEventDispatcher.dispatch(event.build());
601     }
602 
603     private void artifactDownloaded(
604             RepositorySystemSession session,
605             RequestTrace trace,
606             Artifact artifact,
607             RemoteRepository repository,
608             Exception exception) {
609         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
610         event.setTrace(trace);
611         event.setArtifact(artifact);
612         event.setRepository(repository);
613         event.setException(exception);
614         if (artifact != null) {
615             event.setFile(artifact.getFile());
616         }
617 
618         repositoryEventDispatcher.dispatch(event.build());
619     }
620 
621     static class ResolutionGroup {
622 
623         final RemoteRepository repository;
624 
625         final List<ResolutionItem> items = new ArrayList<>();
626 
627         ResolutionGroup(RemoteRepository repository) {
628             this.repository = repository;
629         }
630 
631         boolean matches(RemoteRepository repo) {
632             return repository.getUrl().equals(repo.getUrl())
633                     && repository.getContentType().equals(repo.getContentType())
634                     && repository.isRepositoryManager() == repo.isRepositoryManager();
635         }
636     }
637 
638     static class ResolutionItem {
639 
640         final RequestTrace trace;
641 
642         final ArtifactRequest request;
643 
644         final ArtifactResult result;
645 
646         final LocalArtifactResult local;
647 
648         final RemoteRepository repository;
649 
650         final Artifact artifact;
651 
652         final AtomicBoolean resolved;
653 
654         ArtifactDownload download;
655 
656         UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
657 
658         ResolutionItem(
659                 RequestTrace trace,
660                 Artifact artifact,
661                 AtomicBoolean resolved,
662                 ArtifactResult result,
663                 LocalArtifactResult local,
664                 RemoteRepository repository) {
665             this.trace = trace;
666             this.artifact = artifact;
667             this.resolved = resolved;
668             this.result = result;
669             this.request = result.getRequest();
670             this.local = local;
671             this.repository = repository;
672         }
673     }
674 }