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