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