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