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