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