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