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                            if (!repository.getPolicy(artifact.isSnapshot()).isEnabled()) {
279                                filteredRemoteRepositories.remove(repository);
280                                continue;
281                            }
282                            RemoteRepositoryFilter.Result filterResult = filter.acceptArtifact(repository, artifact);
283                            if (!filterResult.isAccepted()) {
284                                result.addException(
285                                        repository,
286                                        new ArtifactFilteredOutException(
287                                                artifact, repository, filterResult.reasoning()));
288                                filteredRemoteRepositories.remove(repository);
289                            }
290                        }
291                    }
292
293                    VersionResult versionResult;
294                    try {
295                        VersionRequest versionRequest =
296                                new VersionRequest(artifact, filteredRemoteRepositories, request.getRequestContext());
297                        versionRequest.setTrace(trace);
298                        versionResult = versionResolver.resolveVersion(session, versionRequest);
299                    } catch (VersionResolutionException e) {
300                        if (filteredRemoteRepositories.isEmpty()) {
301                            result.addException(lrm.getRepository(), e);
302                        } else {
303                            filteredRemoteRepositories.forEach(r -> result.addException(r, e));
304                        }
305                        continue;
306                    }
307
308                    artifact = artifact.setVersion(versionResult.getVersion());
309
310                    if (artifact.getVersion().trim().isEmpty()) {
311                        // the version resolver reported success but produced no usable version (e.g. the
312                        // requested artifact had no version at all); fail here with a clear artifact-level
313                        // message instead of an opaque IllegalArgumentException from the local path composer
314                        result.addException(
315                                ArtifactResult.NO_REPOSITORY,
316                                new ArtifactNotFoundException(
317                                        artifact,
318                                        (RemoteRepository) null,
319                                        "The version of " + artifact + " is missing or blank"));
320                        continue;
321                    }
322
323                    if (versionResult.getRepository() != null) {
324                        if (versionResult.getRepository() instanceof RemoteRepository) {
325                            filteredRemoteRepositories =
326                                    Collections.singletonList((RemoteRepository) versionResult.getRepository());
327                        } else {
328                            filteredRemoteRepositories = Collections.emptyList();
329                        }
330                    }
331
332                    if (workspace != null) {
333                        Path path = workspace.findArtifactPath(artifact);
334                        if (path != null) {
335                            artifact = artifact.setPath(path);
336                            result.setArtifact(artifact);
337                            result.setRepository(workspace.getRepository());
338                            artifactResolved(session, trace, artifact, result.getRepository(), null);
339                            continue;
340                        }
341                    }
342
343                    LocalArtifactResult local = lrm.find(
344                            session,
345                            new LocalArtifactRequest(
346                                    artifact, filteredRemoteRepositories, request.getRequestContext()));
347                    result.setLocalArtifactResult(local);
348                    boolean found = (filter != null && local.isAvailable())
349                            || (filter == null && isLocallyInstalled(local, versionResult));
350                    // with filtering: availability drives the logic
351                    // without filtering: simply presence of file drives the logic
352                    // "interop" logic with simple LRM leads to RRF breakage: hence is ignored when filtering in effect
353                    if (found) {
354                        if (local.getRepository() != null) {
355                            result.setRepository(local.getRepository());
356                        } else {
357                            result.setRepository(lrm.getRepository());
358                        }
359
360                        try {
361                            Path localArtifactPath = local.getPath();
362                            artifact = artifact.setPath(getPath(session, artifact, localArtifactPath));
363                            result.setArtifact(artifact);
364                            if (local.getRepository() != null && !localArtifactPath.equals(artifact.getPath())) {
365                                /*
366                                 * NOTE: Snapshot normalization materialized a base-version copy next to the tracked
367                                 * timestamped file. The copy must carry the same provenance tracking as its source;
368                                 * otherwise the untracked copy is later treated as locally installed (accepted
369                                 * without any repository/offline checks) by the untracked-file interop logic.
370                                 */
371                                lrm.add(
372                                        session,
373                                        new LocalArtifactRegistration(
374                                                artifact.setVersion(artifact.getBaseVersion()),
375                                                local.getRepository(),
376                                                Collections.singleton(request.getRequestContext())));
377                            }
378                            artifactResolved(session, trace, artifact, result.getRepository(), null);
379                        } catch (ArtifactTransferException e) {
380                            result.addException(lrm.getRepository(), e);
381                        }
382                        if (filter == null && simpleLrmInterop && !local.isAvailable()) {
383                            /*
384                             * NOTE: Interop with simple local repository: An artifact installed by a simple local repo
385                             * manager will not show up in the repository tracking file of the enhanced local repository.
386                             * If however the maven-metadata-local.xml tells us the artifact was installed locally, we
387                             * sync the repository tracking file.
388                             */
389                            lrm.add(session, new LocalArtifactRegistration(artifact));
390                        }
391
392                        continue;
393                    }
394
395                    if (local.getPath() != null) {
396                        LOGGER.info(
397                                "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 {}",
398                                LogSanitizer.sanitize(String.valueOf(artifact)),
399                                LogSanitizer.sanitize(String.valueOf(remoteRepositories)));
400                    }
401
402                    LOGGER.debug(
403                            "Resolving artifact {} from {}",
404                            LogSanitizer.sanitize(String.valueOf(artifact)),
405                            LogSanitizer.sanitize(String.valueOf(remoteRepositories)));
406                    AtomicBoolean resolved = new AtomicBoolean(false);
407                    Iterator<ResolutionGroup> groupIt = groups.iterator();
408                    for (RemoteRepository repo : filteredRemoteRepositories) {
409                        if (!repo.getPolicy(artifact.isSnapshot()).isEnabled()) {
410                            continue;
411                        }
412
413                        try {
414                            Utils.checkOffline(session, offlineController, repo);
415                        } catch (RepositoryOfflineException e) {
416                            Exception exception = new ArtifactNotFoundException(
417                                    artifact,
418                                    repo,
419                                    "Cannot access " + repo.getId() + " ("
420                                            + repo.getUrl() + ") in offline mode and the artifact " + artifact
421                                            + " has not been downloaded from it before.",
422                                    e);
423                            result.addException(repo, exception);
424                            continue;
425                        }
426
427                        ResolutionGroup group = null;
428                        while (groupIt.hasNext()) {
429                            ResolutionGroup t = groupIt.next();
430                            if (t.matches(repo)) {
431                                group = t;
432                                break;
433                            }
434                        }
435                        if (group == null) {
436                            group = new ResolutionGroup(repo);
437                            groups.add(group);
438                            groupIt = Collections.emptyIterator();
439                        }
440                        group.items.add(new ResolutionItem(trace, artifact, resolved, result, local, repo));
441                    }
442                }
443
444                if (!groups.isEmpty() && current == shared) {
445                    SyncContext sharedContext = current;
446                    current = exclusive;
447                    sharedContext.close();
448                    continue;
449                }
450
451                for (ResolutionGroup group : groups) {
452                    performDownloads(session, group);
453                }
454
455                for (ArtifactResolverPostProcessor artifactResolverPostProcessor :
456                        artifactResolverPostProcessors.values()) {
457                    artifactResolverPostProcessor.postProcess(session, results);
458                }
459
460                for (ArtifactResult result : results) {
461                    ArtifactRequest request = result.getRequest();
462
463                    Artifact artifact = result.getArtifact();
464                    if (artifact == null || artifact.getPath() == null) {
465                        failures = true;
466                        if (result.getExceptions().isEmpty()) {
467                            Exception exception =
468                                    new ArtifactNotFoundException(request.getArtifact(), (RemoteRepository) null);
469                            // Note: result.getRepository() MAY BE null; in cases when
470                            // the artifact was not even tried by any remote repository (snapshot vs repo policy)
471                            // and local repository does not have it either
472                            result.addException(
473                                    result.getRepository() != null
474                                            ? result.getRepository()
475                                            : ArtifactResult.NO_REPOSITORY,
476                                    exception);
477                        }
478                        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
479                        artifactResolved(session, trace, request.getArtifact(), null, result.getExceptions());
480                    }
481                }
482
483                if (failures) {
484                    throw new ArtifactResolutionException(results);
485                }
486
487                return results;
488            }
489        } finally {
490            try {
491                current.close();
492            } finally {
493                if (current == shared) {
494                    exclusive.close();
495                }
496            }
497        }
498    }
499
500    /**
501     * This is the method that checks local artifact result if no RRF being used. Unlike with RRF, where only
502     * {@link LocalArtifactResult#isAvailable()} is checked, here we perform multiple checks:
503     * <ul>
504     *     <li>if {@link LocalArtifactResult#isAvailable()} is {@code true}, return {@code true}</li>
505     *     <li>if {@link LocalArtifactResult#getRepository()} is instance of {@link LocalRepository}, return {@code true}</li>
506     *     <li>if {@link LocalArtifactResult#getRepository()} is {@code null} and request had zero remote repositories set, return {@code true}</li>
507     * </ul>
508     * Note: the third check is interfering with RRF, as RRF may make list of remote repositories empty,  that was
509     * originally non-empty, by eliminating remote repositories to consider.
510     * Hence, we may use this method ONLY if RRF is inactive.
511     */
512    private boolean isLocallyInstalled(LocalArtifactResult lar, VersionResult vr) {
513        if (lar.isAvailable()) {
514            return true;
515        }
516        if (lar.getPath() != null) {
517            // resolution of version range found locally installed artifact
518            if (vr.getRepository() instanceof LocalRepository) {
519                // resolution of (snapshot) version found locally installed artifact
520                return true;
521            } else {
522                return vr.getRepository() == null
523                        && lar.getRequest().getRepositories().isEmpty();
524            }
525        }
526        return false;
527    }
528
529    private Path getPath(RepositorySystemSession session, Artifact artifact, Path path)
530            throws ArtifactTransferException {
531        if (artifact.isSnapshot()
532                && !artifact.getVersion().equals(artifact.getBaseVersion())
533                && ConfigUtils.getBoolean(
534                        session, DEFAULT_SNAPSHOT_NORMALIZATION, CONFIG_PROP_SNAPSHOT_NORMALIZATION)) {
535            String name = path.getFileName().toString().replace(artifact.getVersion(), artifact.getBaseVersion());
536            Path dst = path.getParent().resolve(name);
537
538            try {
539                long pathLastModified = pathProcessor.lastModified(path, 0L);
540                boolean copy = pathProcessor.size(dst, 0L) != pathProcessor.size(path, 0L)
541                        || pathProcessor.lastModified(dst, 0L) != pathLastModified;
542                if (copy) {
543                    pathProcessor.copyWithTimestamp(path, dst);
544                }
545            } catch (IOException e) {
546                throw new ArtifactTransferException(artifact, null, e);
547            }
548
549            path = dst;
550        }
551
552        return path;
553    }
554
555    private void performDownloads(RepositorySystemSession session, ResolutionGroup group) {
556        List<ArtifactDownload> downloads = gatherDownloads(session, group);
557        if (downloads.isEmpty()) {
558            return;
559        }
560
561        for (ArtifactDownload download : downloads) {
562            artifactDownloading(session, download.getTrace(), download.getArtifact(), group.repository);
563        }
564
565        try {
566            try (RepositoryConnector connector =
567                    repositoryConnectorProvider.newRepositoryConnector(session, group.repository)) {
568                connector.get(downloads, null);
569            }
570        } catch (NoRepositoryConnectorException e) {
571            for (ArtifactDownload download : downloads) {
572                download.setException(new ArtifactTransferException(download.getArtifact(), group.repository, e));
573            }
574        }
575
576        evaluateDownloads(session, group);
577    }
578
579    private List<ArtifactDownload> gatherDownloads(RepositorySystemSession session, ResolutionGroup group) {
580        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
581        final boolean existenceCheckRelabel =
582                ConfigUtils.getBoolean(session, DEFAULT_EXISTENCE_CHECK_RELABEL, CONFIG_PROP_EXISTENCE_CHECK_RELABEL);
583        List<ArtifactDownload> downloads = new ArrayList<>();
584
585        for (ResolutionItem item : group.items) {
586            Artifact artifact = item.artifact;
587
588            if (item.resolved.get()) {
589                // resolved in previous resolution group
590                continue;
591            }
592
593            ArtifactDownload download = new ArtifactDownload();
594            download.setArtifact(artifact);
595            download.setRequestContext(item.request.getRequestContext());
596            download.setListener(SafeTransferListener.wrap(session));
597            download.setTrace(item.trace);
598            if (item.local.getPath() != null && existenceCheckRelabel) {
599                /*
600                 * NOTE: Legacy behavior, disabled by default: a bare existence check transfers no content, so the
601                 * cached bytes are re-labeled to this repository without ever passing checksum validation.
602                 */
603                download.setPath(item.local.getPath());
604                download.setExistenceCheck(true);
605            } else {
606                /*
607                 * NOTE: Even if the artifact is present in the local repository (cached from a repository unavailable
608                 * in the current build context), download it again so the content passes the repository's checksum
609                 * policy before the artifact is registered with the local repository for this repository.
610                 */
611                download.setPath(lrm.getAbsolutePathForRemoteArtifact(
612                        artifact, group.repository, item.request.getRequestContext()));
613            }
614
615            boolean snapshot = artifact.isSnapshot();
616            RepositoryPolicy policy = remoteRepositoryManager.getPolicy(session, group.repository, !snapshot, snapshot);
617
618            int errorPolicy = Utils.getPolicy(session, artifact, group.repository);
619            if ((errorPolicy & ResolutionErrorPolicy.CACHE_ALL) != 0) {
620                UpdateCheck<Artifact, ArtifactTransferException> check = new UpdateCheck<>();
621                check.setItem(artifact);
622                check.setPath(download.getPath());
623                check.setFileValid(false);
624                check.setRepository(group.repository);
625                check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
626                check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
627                item.updateCheck = check;
628                updateCheckManager.checkArtifact(session, check);
629                if (!check.isRequired()) {
630                    item.result.addException(group.repository, check.getException());
631                    continue;
632                }
633            }
634
635            download.setChecksumPolicy(policy.getChecksumPolicy());
636            download.setRepositories(item.repository.getMirroredRepositories());
637            downloads.add(download);
638            item.download = download;
639        }
640
641        return downloads;
642    }
643
644    private void evaluateDownloads(RepositorySystemSession session, ResolutionGroup group) {
645        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
646
647        for (ResolutionItem item : group.items) {
648            ArtifactDownload download = item.download;
649            if (download == null) {
650                continue;
651            }
652
653            Artifact artifact = download.getArtifact();
654            if (download.getException() == null) {
655                item.resolved.set(true);
656                item.result.setRepository(group.repository);
657                try {
658                    artifact = artifact.setPath(getPath(session, artifact, download.getPath()));
659                    item.result.setArtifact(artifact);
660
661                    lrm.add(
662                            session,
663                            new LocalArtifactRegistration(artifact, group.repository, download.getSupportedContexts()));
664                    if (!download.getPath().equals(artifact.getPath())) {
665                        /*
666                         * NOTE: Snapshot normalization materialized a base-version copy of the downloaded file. Every
667                         * file the resolver derives from remote content must carry the same provenance tracking as
668                         * the download it stems from: register the copy under the same repository, otherwise the
669                         * untracked copy is later treated as locally installed (accepted without any
670                         * repository/offline checks) by the untracked-file interop logic.
671                         */
672                        lrm.add(
673                                session,
674                                new LocalArtifactRegistration(
675                                        artifact.setVersion(artifact.getBaseVersion()),
676                                        group.repository,
677                                        download.getSupportedContexts()));
678                    }
679                } catch (ArtifactTransferException e) {
680                    download.setException(e);
681                    item.result.addException(group.repository, e);
682                }
683            } else {
684                item.result.addException(group.repository, download.getException());
685            }
686
687            /*
688             * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with
689             * "already updated" via session data when actual update to local repo is still pending.
690             */
691            if (item.updateCheck != null) {
692                item.updateCheck.setException(download.getException());
693                updateCheckManager.touchArtifact(session, item.updateCheck);
694            }
695
696            artifactDownloaded(session, download.getTrace(), artifact, group.repository, download.getException());
697            if (download.getException() == null) {
698                artifactResolved(session, download.getTrace(), artifact, group.repository, null);
699            }
700        }
701    }
702
703    private void artifactResolving(RepositorySystemSession session, RequestTrace trace, Artifact artifact) {
704        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVING);
705        event.setTrace(trace);
706        event.setArtifact(artifact);
707
708        repositoryEventDispatcher.dispatch(event.build());
709    }
710
711    private void artifactResolved(
712            RepositorySystemSession session,
713            RequestTrace trace,
714            Artifact artifact,
715            ArtifactRepository repository,
716            Collection<Exception> exceptions) {
717        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_RESOLVED);
718        event.setTrace(trace);
719        event.setArtifact(artifact);
720        event.setRepository(repository);
721        event.setExceptions(exceptions != null ? new ArrayList<>(exceptions) : null);
722        if (artifact != null) {
723            event.setPath(artifact.getPath());
724        }
725
726        repositoryEventDispatcher.dispatch(event.build());
727    }
728
729    private void artifactDownloading(
730            RepositorySystemSession session, RequestTrace trace, Artifact artifact, RemoteRepository repository) {
731        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADING);
732        event.setTrace(trace);
733        event.setArtifact(artifact);
734        event.setRepository(repository);
735
736        repositoryEventDispatcher.dispatch(event.build());
737    }
738
739    private void artifactDownloaded(
740            RepositorySystemSession session,
741            RequestTrace trace,
742            Artifact artifact,
743            RemoteRepository repository,
744            Exception exception) {
745        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DOWNLOADED);
746        event.setTrace(trace);
747        event.setArtifact(artifact);
748        event.setRepository(repository);
749        event.setException(exception);
750        if (artifact != null) {
751            event.setPath(artifact.getPath());
752        }
753
754        repositoryEventDispatcher.dispatch(event.build());
755    }
756
757    static class ResolutionGroup {
758
759        final RemoteRepository repository;
760
761        final List<ResolutionItem> items = new ArrayList<>();
762
763        ResolutionGroup(RemoteRepository repository) {
764            this.repository = repository;
765        }
766
767        boolean matches(RemoteRepository repo) {
768            return repository.getUrl().equals(repo.getUrl())
769                    && repository.getContentType().equals(repo.getContentType())
770                    && repository.isRepositoryManager() == repo.isRepositoryManager();
771        }
772    }
773
774    static class ResolutionItem {
775
776        final RequestTrace trace;
777
778        final ArtifactRequest request;
779
780        final ArtifactResult result;
781
782        final LocalArtifactResult local;
783
784        final RemoteRepository repository;
785
786        final Artifact artifact;
787
788        final AtomicBoolean resolved;
789
790        ArtifactDownload download;
791
792        UpdateCheck<Artifact, ArtifactTransferException> updateCheck;
793
794        ResolutionItem(
795                RequestTrace trace,
796                Artifact artifact,
797                AtomicBoolean resolved,
798                ArtifactResult result,
799                LocalArtifactResult local,
800                RemoteRepository repository) {
801            this.trace = trace;
802            this.artifact = artifact;
803            this.resolved = resolved;
804            this.result = result;
805            this.request = result.getRequest();
806            this.local = local;
807            this.repository = repository;
808        }
809    }
810}