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.util.ArrayList;
030import java.util.Collection;
031import java.util.Collections;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Map;
035import java.util.concurrent.atomic.AtomicBoolean;
036
037import org.eclipse.aether.ConfigurationProperties;
038import org.eclipse.aether.RepositoryEvent;
039import org.eclipse.aether.RepositoryEvent.EventType;
040import org.eclipse.aether.RepositorySystemSession;
041import org.eclipse.aether.RequestTrace;
042import org.eclipse.aether.SyncContext;
043import org.eclipse.aether.artifact.Artifact;
044import org.eclipse.aether.impl.ArtifactResolver;
045import org.eclipse.aether.impl.OfflineController;
046import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
047import org.eclipse.aether.impl.RemoteRepositoryManager;
048import org.eclipse.aether.impl.RepositoryConnectorProvider;
049import org.eclipse.aether.impl.RepositoryEventDispatcher;
050import org.eclipse.aether.impl.UpdateCheck;
051import org.eclipse.aether.impl.UpdateCheckManager;
052import org.eclipse.aether.impl.VersionResolver;
053import org.eclipse.aether.repository.*;
054import org.eclipse.aether.resolution.ArtifactRequest;
055import org.eclipse.aether.resolution.ArtifactResolutionException;
056import org.eclipse.aether.resolution.ArtifactResult;
057import org.eclipse.aether.resolution.ResolutionErrorPolicy;
058import org.eclipse.aether.resolution.VersionRequest;
059import org.eclipse.aether.resolution.VersionResolutionException;
060import org.eclipse.aether.resolution.VersionResult;
061import org.eclipse.aether.scope.SystemDependencyScope;
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            SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
185            for (ArtifactRequest request : requests) {
186                if (systemDependencyScope != null
187                        && systemDependencyScope.getSystemPath(request.getArtifact()) != null) {
188                    continue;
189                }
190                artifacts.add(request.getArtifact());
191            }
192
193            return resolve(shared, exclusive, artifacts, session, requests);
194        }
195    }
196
197    @SuppressWarnings("checkstyle:methodlength")
198    private List<ArtifactResult> resolve(
199            SyncContext shared,
200            SyncContext exclusive,
201            Collection<Artifact> subjects,
202            RepositorySystemSession session,
203            Collection<? extends ArtifactRequest> requests)
204            throws ArtifactResolutionException {
205        SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
206        SyncContext current = shared;
207        try {
208            while (true) {
209                current.acquire(subjects, null);
210
211                boolean failures = false;
212                final List<ArtifactResult> results = new ArrayList<>(requests.size());
213                final boolean simpleLrmInterop =
214                        ConfigUtils.getBoolean(session, DEFAULT_SIMPLE_LRM_INTEROP, CONFIG_PROP_SIMPLE_LRM_INTEROP);
215                final LocalRepositoryManager lrm = session.getLocalRepositoryManager();
216                final WorkspaceReader workspace = session.getWorkspaceReader();
217                final List<ResolutionGroup> groups = new ArrayList<>();
218                // filter != null: means "filtering applied", if null no filtering applied (behave as before)
219                final RemoteRepositoryFilter filter = remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
220
221                for (ArtifactRequest request : requests) {
222                    RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
223
224                    ArtifactResult result = new ArtifactResult(request);
225                    results.add(result);
226
227                    Artifact artifact = request.getArtifact();
228
229                    if (current == shared) {
230                        artifactResolving(session, trace, artifact);
231                    }
232
233                    String localPath =
234                            systemDependencyScope != null ? systemDependencyScope.getSystemPath(artifact) : null;
235                    if (localPath != null) {
236                        // unhosted artifact, just validate file
237                        Path path = Paths.get(localPath);
238                        if (!Files.isRegularFile(path)) {
239                            failures = true;
240                            result.addException(
241                                    ArtifactResult.NO_REPOSITORY, new ArtifactNotFoundException(artifact, localPath));
242                        } else {
243                            artifact = artifact.setPath(path);
244                            result.setArtifact(artifact);
245                            artifactResolved(session, trace, artifact, null, result.getExceptions());
246                        }
247                        continue;
248                    }
249
250                    List<RemoteRepository> remoteRepositories = request.getRepositories();
251                    List<RemoteRepository> filteredRemoteRepositories = new ArrayList<>(remoteRepositories);
252                    if (filter != null) {
253                        for (RemoteRepository repository : remoteRepositories) {
254                            RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
255                            if (!filterResult.isAccepted()) {
256                                result.addException(
257                                        repository,
258                                        new ArtifactFilteredOutException(
259                                                artifact, repository, filterResult.reasoning()));
260                                filteredRemoteRepositories.remove(repository);
261                            }
262                        }
263                    }
264
265                    VersionResult versionResult;
266                    try {
267                        VersionRequest versionRequest =
268                                new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
269                        versionRequest.setTrace(trace);
270                        versionResult = versionResolver.resolveVersion(session, versionRequest);
271                    } catch (VersionResolutionException e) {
272                        if (filteredRemoteRepositories.isEmpty()) {
273                            result.addException(lrm.getRepository(), e);
274                        } else {
275                            filteredRemoteRepositories.forEach(r -> result.addException(r, e));
276                        }
277                        continue;
278                    }
279
280                    artifact = artifact.setVersion(versionResult.getVersion());
281
282                    if (versionResult.getRepository() != null) {
283                        if (versionResult.getRepository() instanceof RemoteRepository) {
284                            filteredRemoteRepositories =
285                                    Collections.singletonList((RemoteRepository) versionResult.getRepository());
286                        } else {
287                            filteredRemoteRepositories = Collections.emptyList();
288                        }
289                    }
290
291                    if (workspace != null) {
292                        Path path = workspace.findArtifactPath(artifact);
293                        if (path != null) {
294                            artifact = artifact.setPath(path);
295                            result.setArtifact(artifact);
296                            result.setRepository(workspace.getRepository());
297                            artifactResolved(session, trace, artifact, result.getRepository(), null);
298                            continue;
299                        }
300                    }
301
302                    LocalArtifactResult local = lrm.find(
303                            session,
304                            new LocalArtifactRequest(
305                                    artifact, filteredRemoteRepositories, request.getRequestContext()));
306                    result.setLocalArtifactResult(local);
307                    boolean found = (filter != null && local.isAvailable()) || isLocallyInstalled(local, versionResult);
308                    // with filtering it is availability that drives logic
309                    // without filtering it is simply presence of file that drives the logic
310                    // "interop" logic with simple LRM leads to RRF breakage: hence is ignored when filtering in effect
311                    if (found) {
312                        if (local.getRepository() != null) {
313                            result.setRepository(local.getRepository());
314                        } else {
315                            result.setRepository(lrm.getRepository());
316                        }
317
318                        try {
319                            artifact = artifact.setPath(getPath(session, artifact, local.getPath()));
320                            result.setArtifact(artifact);
321                            artifactResolved(session, trace, artifact, result.getRepository(), null);
322                        } catch (ArtifactTransferException e) {
323                            result.addException(lrm.getRepository(), e);
324                        }
325                        if (filter == null && simpleLrmInterop && !local.isAvailable()) {
326                            /*
327                             * NOTE: Interop with simple local repository: An artifact installed by a simple local repo
328                             * manager will not show up in the repository tracking file of the enhanced local repository.
329                             * If however the maven-metadata-local.xml tells us the artifact was installed locally, we
330                             * sync the repository tracking file.
331                             */
332                            lrm.add(session, new LocalArtifactRegistration(artifact));
333                        }
334
335                        continue;
336                    }
337
338                    if (local.getPath() != null) {
339                        LOGGER.info(
340                                "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 {}",
341                                artifact,
342                                remoteRepositories);
343                    }
344
345                    LOGGER.debug("Resolving artifact {} from {}", artifact, remoteRepositories);
346                    AtomicBoolean resolved = new AtomicBoolean(false);
347                    Iterator<ResolutionGroup> groupIt = groups.iterator();
348                    for (RemoteRepository repo : filteredRemoteRepositories) {
349                        if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
350                            continue;
351                        }
352
353                        try {
354                            Utils.checkOffline(session, offlineController, repo);
355                        } catch (RepositoryOfflineException e) {
356                            Exception exception = new ArtifactNotFoundException(
357                                    artifact,
358                                    repo,
359                                    "Cannot access " + repo.getId() + " ("
360                                            + repo.getUrl() + ") in offline mode and the artifact " + artifact
361                                            + " has not been downloaded from it before.",
362                                    e);
363                            result.addException(repo, exception);
364                            continue;
365                        }
366
367                        ResolutionGroup group = null;
368                        while (groupIt.hasNext()) {
369                            ResolutionGroup t = groupIt.next();
370                            if (t.matches(repo)) {
371                                group = t;
372                                break;
373                            }
374                        }
375                        if (group == null) {
376                            group = new ResolutionGroup(repo);
377                            groups.add(group);
378                            groupIt = Collections.emptyIterator();
379                        }
380                        group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
381                    }
382                }
383
384                if (!groups.isEmpty() && current == shared) {
385                    current.close();
386                    current = exclusive;
387                    continue;
388                }
389
390                for (ResolutionGroup group : groups) {
391                    performDownloads(session, group);
392                }
393
394                for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
395                        artifactResolverPostProcessors.values()) {
396                    artifactResolverPostProcessor.postProcess(session, results);
397                }
398
399                for (ArtifactResult result : results) {
400                    ArtifactRequest request = result.getRequest();
401
402                    Artifact artifact = result.getArtifact();
403                    if (artifact == null || artifact.getPath() == null) {
404                        failures = true;
405                        if (result.getExceptions().isEmpty()) {
406                            Exception exception =
407                                    new ArtifactNotFoundException(request.getArtifact(), (RemoteRepository) null);
408                            result.addException(result.getRepository(), exception);
409                        }
410                        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
411                        artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
412                    }
413                }
414
415                if (failures) {
416                    throw new ArtifactResolutionException(results);
417                }
418
419                return results;
420            }
421        } finally {
422            current.close();
423        }
424    }
425
426    private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
427        if (lar.isAvailable()) {
428            return true;
429        }
430        if (lar.getPath() != null) {
431            // resolution of version range found locally installed artifact
432            if (vr.getRepository() instanceof LocalRepository) {
433                // resolution of (snapshot) version found locally installed artifact
434                return true;
435            } else {
436                return vr.getRepository() == null
437                        && lar.getRequest().getRepositories().isEmpty();
438            }
439        }
440        return false;
441    }
442
443    private Path getPath(RepositorySystemSession session, Artifact artifact, Path path)
444            throws ArtifactTransferException {
445        if (artifact.isSnapshot()
446                && !artifact.getVersion().equals(artifact.getBaseVersion())
447                && ConfigUtils.getBoolean(
448                        session, DEFAULT_SNAPSHOT_NORMALIZATION, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
449            String name = path.getFileName().toString().replace(artifact.getVersion(), artifact.getBaseVersion());
450            Path dst = path.getParent().resolve(name);
451
452            try {
453                long pathLastModified = pathProcessor.lastModified(path, 0L);
454                boolean copy = pathProcessor.size(dst, 0L) != pathProcessor.size(path, 0L)
455                        || pathProcessor.lastModified(dst, 0L) != pathLastModified;
456                if (copy) {
457                    pathProcessor.copyWithTimestamp(path, dst);
458                }
459            } catch (IOException e) {
460                throw new ArtifactTransferException(artifact, null, e);
461            }
462
463            path = dst;
464        }
465
466        return path;
467    }
468
469    private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
470        List<ArtifactDownload> downloads = gatherDownloads(session, group);
471        if (downloads.isEmpty()) {
472            return;
473        }
474
475        for (ArtifactDownload download : downloads) {
476            artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
477        }
478
479        try {
480            try (RepositoryConnector connector =
481                    repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
482                connector.get(downloads, null);
483            }
484        } catch (NoRepositoryConnectorException e) {
485            for (ArtifactDownload download : downloads) {
486                download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
487            }
488        }
489
490        evaluateDownloads(session, group);
491    }
492
493    private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
494        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
495        List<ArtifactDownload> downloads = new ArrayList<>();
496
497        for (ResolutionItem item : group.items) {
498            Artifact artifact = item.artifact;
499
500            if (item.resolved.get()) {
501                // resolved in previous resolution group
502                continue;
503            }
504
505            ArtifactDownload download = new ArtifactDownload();
506            download.setArtifact(artifact);
507            download.setRequestContext(item.request.getRequestContext());
508            download.setListener(SafeTransferListener.wrap(session));
509            download.setTrace(item.trace);
510            if (item.local.getPath() != null) {
511                download.setPath(item.local.getPath());
512                download.setExistenceCheck(true);
513            } else {
514                String path =
515                        lrm.getPathForRemoteArtifact(artifact, group.repository, item.request.getRequestContext());
516                download.setPath(lrm.getRepository().getBasePath().resolve(path));
517            }
518
519            boolean snapshot = artifact.isSnapshot();
520            RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
521
522            int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
523            if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
524                UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
525                check.setItem(artifact);
526                check.setPath(download.getPath());
527                check.setFileValid(false);
528                check.setRepository(group.repository);
529                check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
530                check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
531                item.updateCheck = check;
532                updateCheckManager.checkArtifact(session, check);
533                if (!check.isRequired()) {
534                    item.result.addException(group.repository, check.getException());
535                    continue;
536                }
537            }
538
539            download.setChecksumPolicy(policy.getChecksumPolicy());
540            download.setRepositories(item.repository.getMirroredRepositories());
541            downloads.add(download);
542            item.download = download;
543        }
544
545        return downloads;
546    }
547
548    private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
549        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
550
551        for (ResolutionItem item : group.items) {
552            ArtifactDownload download = item.download;
553            if (download == null) {
554                continue;
555            }
556
557            Artifact artifact = download.getArtifact();
558            if (download.getException() == null) {
559                item.resolved.set(true);
560                item.result.setRepository(group.repository);
561                try {
562                    artifact = artifact.setPath(getPath(session, artifact, download.getPath()));
563                    item.result.setArtifact(artifact);
564
565                    lrm.add(
566                            session,
567                            new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
568                } catch (ArtifactTransferException e) {
569                    download.setException(e);
570                    item.result.addException(group.repository, e);
571                }
572            } else {
573                item.result.addException(group.repository, download.getException());
574            }
575
576            /*
577             * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
578             * "already updated" via session data when actual update to local repo is still pending.
579             */
580            if (item.updateCheck != null) {
581                item.updateCheck.setException(download.getException());
582                updateCheckManager.touchArtifact(session, item.updateCheck);
583            }
584
585            artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
586            if (download.getException() == null) {
587                artifactResolved(session, download.getTrace(), artifact, group.repository, null);
588            }
589        }
590    }
591
592    private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
593        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
594        event.setTrace(trace);
595        event.setArtifact(artifact);
596
597        repositoryEventDispatcher.dispatch(event.build());
598    }
599
600    private void artifactResolved(
601            RepositorySystemSession session,
602            RequestTrace trace,
603            Artifact artifact,
604            ArtifactRepository repository,
605            Collection<Exception> exceptions) {
606        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
607        event.setTrace(trace);
608        event.setArtifact(artifact);
609        event.setRepository(repository);
610        event.setExceptions(exceptions != null ? new ArrayList<>(exceptions) : null);
611        if (artifact != null) {
612            event.setPath(artifact.getPath());
613        }
614
615        repositoryEventDispatcher.dispatch(event.build());
616    }
617
618    private void artifactDownloading(
619            RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
620        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
621        event.setTrace(trace);
622        event.setArtifact(artifact);
623        event.setRepository(repository);
624
625        repositoryEventDispatcher.dispatch(event.build());
626    }
627
628    private void artifactDownloaded(
629            RepositorySystemSession session,
630            RequestTrace trace,
631            Artifact artifact,
632            RemoteRepository repository,
633            Exception exception) {
634        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
635        event.setTrace(trace);
636        event.setArtifact(artifact);
637        event.setRepository(repository);
638        event.setException(exception);
639        if (artifact != null) {
640            event.setPath(artifact.getPath());
641        }
642
643        repositoryEventDispatcher.dispatch(event.build());
644    }
645
646    static class ResolutionGroup {
647
648        final RemoteRepository repository;
649
650        final List<ResolutionItem> items = new ArrayList<>();
651
652        ResolutionGroup(RemoteRepository repository) {
653            this.repository = repository;
654        }
655
656        boolean matches(RemoteRepository repo) {
657            return repository.getUrl().equals(repo.getUrl())
658                    && repository.getContentType().equals(repo.getContentType())
659                    && repository.isRepositoryManager() == repo.isRepositoryManager();
660        }
661    }
662
663    static class ResolutionItem {
664
665        final RequestTrace trace;
666
667        final ArtifactRequest request;
668
669        final ArtifactResult result;
670
671        final LocalArtifactResult local;
672
673        final RemoteRepository repository;
674
675        final Artifact artifact;
676
677        final AtomicBoolean resolved;
678
679        ArtifactDownload download;
680
681        UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
682
683        ResolutionItem(
684                RequestTrace trace,
685                Artifact artifact,
686                AtomicBoolean resolved,
687                ArtifactResult result,
688                LocalArtifactResult local,
689                RemoteRepository repository) {
690            this.trace = trace;
691            this.artifact = artifact;
692            this.resolved = resolved;
693            this.result = result;
694            this.request = result.getRequest();
695            this.local = local;
696            this.repository = repository;
697        }
698    }
699}