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.spi.connector.ArtifactDownload;
063import org.eclipse.aether.spi.connector.RepositoryConnector;
064import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
065import org.eclipse.aether.spi.io.PathProcessor;
066import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
067import org.eclipse.aether.spi.synccontext.SyncContextFactory;
068import org.eclipse.aether.transfer.ArtifactFilteredOutException;
069import org.eclipse.aether.transfer.ArtifactNotFoundException;
070import org.eclipse.aether.transfer.ArtifactTransferException;
071import org.eclipse.aether.transfer.NoRepositoryConnectorException;
072import org.eclipse.aether.transfer.RepositoryOfflineException;
073import org.eclipse.aether.util.ConfigUtils;
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076
077import static java.util.Objects.requireNonNull;
078
079/**
080 *
081 */
082@Singleton
083@Named
084public class DefaultArtifactResolver implements ArtifactResolver {
085
086    public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "artifactResolver.";
087
088    /**
089     * Configuration to enable "snapshot normalization", downloaded snapshots from remote with timestamped file names
090     * will have file names converted back to baseVersion. It replaces the timestamped snapshot file name with a
091     * filename containing the SNAPSHOT qualifier only. This only affects resolving/retrieving artifacts but not
092     * uploading those.
093     *
094     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
095     * @configurationType {@link java.lang.Boolean}
096     * @configurationDefaultValue {@link #DEFAULT_SNAPSHOT_NORMALIZATION}
097     */
098    public static final String CONFIG_PROP_SNAPSHOT_NORMALIZATION = CONFIG_PROPS_PREFIX + "snapshotNormalization";
099
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}