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