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}