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.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.List; 030import java.util.Map; 031import java.util.Objects; 032import java.util.concurrent.atomic.AtomicBoolean; 033import java.util.concurrent.atomic.AtomicInteger; 034import java.util.function.Consumer; 035import java.util.stream.Collectors; 036 037import org.eclipse.aether.ConfigurationProperties; 038import org.eclipse.aether.RepositorySystem; 039import org.eclipse.aether.RepositorySystemSession; 040import org.eclipse.aether.RequestTrace; 041import org.eclipse.aether.SyncContext; 042import org.eclipse.aether.artifact.Artifact; 043import org.eclipse.aether.collection.CollectRequest; 044import org.eclipse.aether.collection.CollectResult; 045import org.eclipse.aether.collection.DependencyCollectionException; 046import org.eclipse.aether.deployment.DeployRequest; 047import org.eclipse.aether.deployment.DeployResult; 048import org.eclipse.aether.deployment.DeploymentException; 049import org.eclipse.aether.graph.DependencyFilter; 050import org.eclipse.aether.graph.DependencyNode; 051import org.eclipse.aether.graph.DependencyVisitor; 052import org.eclipse.aether.impl.ArtifactDescriptorReader; 053import org.eclipse.aether.impl.ArtifactResolver; 054import org.eclipse.aether.impl.DependencyCollector; 055import org.eclipse.aether.impl.Deployer; 056import org.eclipse.aether.impl.Installer; 057import org.eclipse.aether.impl.LocalRepositoryProvider; 058import org.eclipse.aether.impl.MetadataResolver; 059import org.eclipse.aether.impl.RemoteRepositoryManager; 060import org.eclipse.aether.impl.RepositorySystemLifecycle; 061import org.eclipse.aether.impl.RepositorySystemValidator; 062import org.eclipse.aether.impl.VersionRangeResolver; 063import org.eclipse.aether.impl.VersionResolver; 064import org.eclipse.aether.installation.InstallRequest; 065import org.eclipse.aether.installation.InstallResult; 066import org.eclipse.aether.installation.InstallationException; 067import org.eclipse.aether.internal.impl.session.DefaultSessionBuilder; 068import org.eclipse.aether.repository.Authentication; 069import org.eclipse.aether.repository.LocalRepository; 070import org.eclipse.aether.repository.LocalRepositoryManager; 071import org.eclipse.aether.repository.NoLocalRepositoryManagerException; 072import org.eclipse.aether.repository.Proxy; 073import org.eclipse.aether.repository.RemoteRepository; 074import org.eclipse.aether.resolution.ArtifactDescriptorException; 075import org.eclipse.aether.resolution.ArtifactDescriptorRequest; 076import org.eclipse.aether.resolution.ArtifactDescriptorResult; 077import org.eclipse.aether.resolution.ArtifactRequest; 078import org.eclipse.aether.resolution.ArtifactResolutionException; 079import org.eclipse.aether.resolution.ArtifactResult; 080import org.eclipse.aether.resolution.DependencyRequest; 081import org.eclipse.aether.resolution.DependencyResolutionException; 082import org.eclipse.aether.resolution.DependencyResult; 083import org.eclipse.aether.resolution.MetadataRequest; 084import org.eclipse.aether.resolution.MetadataResult; 085import org.eclipse.aether.resolution.VersionRangeRequest; 086import org.eclipse.aether.resolution.VersionRangeResolutionException; 087import org.eclipse.aether.resolution.VersionRangeResult; 088import org.eclipse.aether.resolution.VersionRequest; 089import org.eclipse.aether.resolution.VersionResolutionException; 090import org.eclipse.aether.resolution.VersionResult; 091import org.eclipse.aether.spi.artifact.decorator.ArtifactDecorator; 092import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory; 093import org.eclipse.aether.spi.synccontext.SyncContextFactory; 094import org.eclipse.aether.util.ConfigUtils; 095import org.eclipse.aether.util.graph.visitor.LevelOrderDependencyNodeConsumerVisitor; 096import org.eclipse.aether.util.graph.visitor.PostorderDependencyNodeConsumerVisitor; 097import org.eclipse.aether.util.graph.visitor.PreorderDependencyNodeConsumerVisitor; 098import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager; 099 100import static java.util.Objects.requireNonNull; 101import static java.util.stream.Collectors.toList; 102 103/** 104 * 105 */ 106@Singleton 107@Named 108public class DefaultRepositorySystem implements RepositorySystem { 109 /** 110 * Sentinel object placed into the {@link RequestTrace} chain by each public method 111 * to detect re-entrant calls into this {@code RepositorySystem}. 112 * <p> 113 * The resolver's architecture assumes that internal components ({@code ArtifactResolver}, 114 * {@code DependencyCollector}, {@code ArtifactDescriptorReader}, etc.) call each other 115 * directly via the internal API in {@code org.eclipse.aether.impl}, never going through 116 * the public {@code RepositorySystem} facade. This means session validation, request 117 * validation, and artifact decoration only need to run once — on the outermost call. 118 * <p> 119 * However, some {@code RepositorySystem} consumers (notably Maven 4's 120 * {@code ArtifactDescriptorReader} → {@code ModelBuilder} → {@code ModelResolver} chain) 121 * re-enter {@code RepositorySystem} during an ongoing operation. Without this guard, 122 * every re-entrant call redundantly validates the session, validates the request (which 123 * may reject intermediate state like uninterpolated expressions from transitive POMs), 124 * and applies artifact decorators. This causes both correctness issues (false validation 125 * failures) and unnecessary performance overhead. 126 * <p> 127 * Re-entrancy can happen on the <em>same</em> thread (e.g. {@code collectDependencies} 128 * → {@code readArtifactDescriptor} → model builder → model resolver → {@code resolveVersionRange}) 129 * or on a <em>different</em> thread when the resolver uses internal parallelism (e.g. 130 * {@code BfDependencyCollector}'s {@code SmartExecutor} dispatches descriptor resolution 131 * to pool threads, which then re-enter {@code RepositorySystem} via the model resolver). 132 * <p> 133 * To handle both cases, we leverage the {@link RequestTrace} chain that is already 134 * propagated across threads by the caller (e.g. Maven's model builder explicitly copies 135 * traces to pool threads). On the outermost call, we stamp this marker into the request's 136 * trace. On re-entry — whether on the same thread or a pool thread — the marker is found 137 * in the trace ancestry, so validation and decoration are skipped. 138 */ 139 private static final Object REPOSITORY_SYSTEM_CALL = new Object() { 140 @Override 141 public String toString() { 142 return "RepositorySystem"; 143 } 144 }; 145 146 private final AtomicBoolean shutdown; 147 148 private final AtomicInteger sessionIdCounter; 149 150 private final VersionResolver versionResolver; 151 152 private final VersionRangeResolver versionRangeResolver; 153 154 private final ArtifactResolver artifactResolver; 155 156 private final MetadataResolver metadataResolver; 157 158 private final ArtifactDescriptorReader artifactDescriptorReader; 159 160 private final DependencyCollector dependencyCollector; 161 162 private final Installer installer; 163 164 private final Deployer deployer; 165 166 private final LocalRepositoryProvider localRepositoryProvider; 167 168 private final SyncContextFactory syncContextFactory; 169 170 private final RemoteRepositoryManager remoteRepositoryManager; 171 172 private final RepositorySystemLifecycle repositorySystemLifecycle; 173 174 private final Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories; 175 176 private final RepositorySystemValidator repositorySystemValidator; 177 178 @SuppressWarnings("checkstyle:parameternumber") 179 @Inject 180 public DefaultRepositorySystem( 181 VersionResolver versionResolver, 182 VersionRangeResolver versionRangeResolver, 183 ArtifactResolver artifactResolver, 184 MetadataResolver metadataResolver, 185 ArtifactDescriptorReader artifactDescriptorReader, 186 DependencyCollector dependencyCollector, 187 Installer installer, 188 Deployer deployer, 189 LocalRepositoryProvider localRepositoryProvider, 190 SyncContextFactory syncContextFactory, 191 RemoteRepositoryManager remoteRepositoryManager, 192 RepositorySystemLifecycle repositorySystemLifecycle, 193 Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories, 194 RepositorySystemValidator repositorySystemValidator) { 195 this.shutdown = new AtomicBoolean(false); 196 this.sessionIdCounter = new AtomicInteger(0); 197 this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null"); 198 this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null"); 199 this.artifactResolver = requireNonNull(artifactResolver, "artifact resolver cannot be null"); 200 this.metadataResolver = requireNonNull(metadataResolver, "metadata resolver cannot be null"); 201 this.artifactDescriptorReader = 202 requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null"); 203 this.dependencyCollector = requireNonNull(dependencyCollector, "dependency collector cannot be null"); 204 this.installer = requireNonNull(installer, "installer cannot be null"); 205 this.deployer = requireNonNull(deployer, "deployer cannot be null"); 206 this.localRepositoryProvider = 207 requireNonNull(localRepositoryProvider, "local repository provider cannot be null"); 208 this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null"); 209 this.remoteRepositoryManager = 210 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null"); 211 this.repositorySystemLifecycle = 212 requireNonNull(repositorySystemLifecycle, "repository system lifecycle cannot be null"); 213 this.artifactDecoratorFactories = 214 requireNonNull(artifactDecoratorFactories, "artifact decorator factories cannot be null"); 215 this.repositorySystemValidator = 216 requireNonNull(repositorySystemValidator, "repository system validator cannot be null"); 217 } 218 219 @Override 220 public VersionResult resolveVersion(RepositorySystemSession session, VersionRequest request) 221 throws VersionResolutionException { 222 requireNonNull(request, "request cannot be null"); 223 Runnable exitGuard = null; 224 if (!isReentrant(request.getTrace(), session)) { 225 validateSession(session); 226 repositorySystemValidator.validateVersionRequest(session, request); 227 request.setTrace(stampReentrancyMarker(request.getTrace())); 228 exitGuard = enterSessionScope(session); 229 } 230 try { 231 return versionResolver.resolveVersion(session, request); 232 } finally { 233 if (exitGuard != null) { 234 exitGuard.run(); 235 } 236 } 237 } 238 239 @Override 240 public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request) 241 throws VersionRangeResolutionException { 242 requireNonNull(request, "request cannot be null"); 243 Runnable exitGuard = null; 244 if (!isReentrant(request.getTrace(), session)) { 245 validateSession(session); 246 repositorySystemValidator.validateVersionRangeRequest(session, request); 247 request.setTrace(stampReentrancyMarker(request.getTrace())); 248 exitGuard = enterSessionScope(session); 249 } 250 try { 251 return versionRangeResolver.resolveVersionRange(session, request); 252 } finally { 253 if (exitGuard != null) { 254 exitGuard.run(); 255 } 256 } 257 } 258 259 @Override 260 public ArtifactDescriptorResult readArtifactDescriptor( 261 RepositorySystemSession session, ArtifactDescriptorRequest request) throws ArtifactDescriptorException { 262 requireNonNull(request, "request cannot be null"); 263 boolean outermost = !isReentrant(request.getTrace(), session); 264 Runnable exitGuard = null; 265 if (outermost) { 266 validateSession(session); 267 repositorySystemValidator.validateArtifactDescriptorRequest(session, request); 268 request.setTrace(stampReentrancyMarker(request.getTrace())); 269 exitGuard = enterSessionScope(session); 270 } 271 try { 272 ArtifactDescriptorResult descriptorResult = 273 artifactDescriptorReader.readArtifactDescriptor(session, request); 274 if (outermost) { 275 for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) { 276 descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult)); 277 } 278 } 279 return descriptorResult; 280 } finally { 281 if (exitGuard != null) { 282 exitGuard.run(); 283 } 284 } 285 } 286 287 @Override 288 public ArtifactResult resolveArtifact(RepositorySystemSession session, ArtifactRequest request) 289 throws ArtifactResolutionException { 290 requireNonNull(request, "request cannot be null"); 291 Runnable exitGuard = null; 292 if (!isReentrant(request.getTrace(), session)) { 293 validateSession(session); 294 repositorySystemValidator.validateArtifactRequests(session, Collections.singleton(request)); 295 request.setTrace(stampReentrancyMarker(request.getTrace())); 296 exitGuard = enterSessionScope(session); 297 } 298 try { 299 return artifactResolver.resolveArtifact(session, request); 300 } finally { 301 if (exitGuard != null) { 302 exitGuard.run(); 303 } 304 } 305 } 306 307 @Override 308 public List<ArtifactResult> resolveArtifacts( 309 RepositorySystemSession session, Collection<? extends ArtifactRequest> requests) 310 throws ArtifactResolutionException { 311 requireNonNull(requests, "requests cannot be null"); 312 // All requests in a batch share the same trace context, so checking any one is sufficient. 313 RequestTrace firstTrace = requests.stream() 314 .map(ArtifactRequest::getTrace) 315 .filter(Objects::nonNull) 316 .findFirst() 317 .orElse(null); 318 Runnable exitGuard = null; 319 if (!isReentrant(firstTrace, session)) { 320 validateSession(session); 321 repositorySystemValidator.validateArtifactRequests(session, requests); 322 for (ArtifactRequest request : requests) { 323 request.setTrace(stampReentrancyMarker(request.getTrace())); 324 } 325 exitGuard = enterSessionScope(session); 326 } 327 try { 328 return artifactResolver.resolveArtifacts(session, requests); 329 } finally { 330 if (exitGuard != null) { 331 exitGuard.run(); 332 } 333 } 334 } 335 336 @Override 337 public List<MetadataResult> resolveMetadata( 338 RepositorySystemSession session, Collection<? extends MetadataRequest> requests) { 339 requireNonNull(requests, "requests cannot be null"); 340 // All requests in a batch share the same trace context, so checking any one is sufficient. 341 RequestTrace firstTrace = requests.stream() 342 .map(MetadataRequest::getTrace) 343 .filter(Objects::nonNull) 344 .findFirst() 345 .orElse(null); 346 Runnable exitGuard = null; 347 if (!isReentrant(firstTrace, session)) { 348 validateSession(session); 349 repositorySystemValidator.validateMetadataRequests(session, requests); 350 for (MetadataRequest request : requests) { 351 request.setTrace(stampReentrancyMarker(request.getTrace())); 352 } 353 exitGuard = enterSessionScope(session); 354 } 355 try { 356 return metadataResolver.resolveMetadata(session, requests); 357 } finally { 358 if (exitGuard != null) { 359 exitGuard.run(); 360 } 361 } 362 } 363 364 @Override 365 public CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request) 366 throws DependencyCollectionException { 367 requireNonNull(request, "request cannot be null"); 368 Runnable exitGuard = null; 369 if (!isReentrant(request.getTrace(), session)) { 370 validateSession(session); 371 repositorySystemValidator.validateCollectRequest(session, request); 372 request.setTrace(stampReentrancyMarker(request.getTrace())); 373 exitGuard = enterSessionScope(session); 374 } 375 try { 376 return dependencyCollector.collectDependencies(session, request); 377 } finally { 378 if (exitGuard != null) { 379 exitGuard.run(); 380 } 381 } 382 } 383 384 @Override 385 public DependencyResult resolveDependencies(RepositorySystemSession session, DependencyRequest request) 386 throws DependencyResolutionException { 387 requireNonNull(request, "request cannot be null"); 388 Runnable exitGuard = null; 389 if (!isReentrant(request.getTrace(), session)) { 390 validateSession(session); 391 repositorySystemValidator.validateDependencyRequest(session, request); 392 request.setTrace(stampReentrancyMarker(request.getTrace())); 393 exitGuard = enterSessionScope(session); 394 } 395 try { 396 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 397 398 DependencyResult result = new DependencyResult(request); 399 400 DependencyCollectionException dce = null; 401 ArtifactResolutionException are = null; 402 403 if (request.getRoot() != null) { 404 result.setRoot(request.getRoot()); 405 } else if (request.getCollectRequest() != null) { 406 CollectResult collectResult; 407 try { 408 request.getCollectRequest().setTrace(trace); 409 collectResult = dependencyCollector.collectDependencies(session, request.getCollectRequest()); 410 } catch (DependencyCollectionException e) { 411 dce = e; 412 collectResult = e.getResult(); 413 } 414 result.setRoot(collectResult.getRoot()); 415 result.setCycles(collectResult.getCycles()); 416 result.setCollectExceptions(collectResult.getExceptions()); 417 } else { 418 throw new NullPointerException("dependency node and collect request cannot be null"); 419 } 420 421 final List<DependencyNode> dependencyNodes = 422 doFlattenDependencyNodes(session, result.getRoot(), request.getFilter()); 423 424 final List<ArtifactRequest> requests = dependencyNodes.stream() 425 .map(n -> { 426 if (n.getDependency() != null) { 427 ArtifactRequest artifactRequest = new ArtifactRequest(n); 428 artifactRequest.setTrace(trace); 429 return artifactRequest; 430 } else { 431 return null; 432 } 433 }) 434 .filter(Objects::nonNull) 435 .collect(Collectors.toList()); 436 List<ArtifactResult> results; 437 try { 438 results = artifactResolver.resolveArtifacts(session, requests); 439 } catch (ArtifactResolutionException e) { 440 are = e; 441 results = e.getResults(); 442 } 443 result.setDependencyNodeResults(dependencyNodes); 444 result.setArtifactResults(results); 445 446 updateNodesWithResolvedArtifacts(results); 447 448 if (dce != null) { 449 throw new DependencyResolutionException(result, dce); 450 } else if (are != null) { 451 throw new DependencyResolutionException(result, are); 452 } 453 454 return result; 455 } finally { 456 if (exitGuard != null) { 457 exitGuard.run(); 458 } 459 } 460 } 461 462 @Override 463 public List<DependencyNode> flattenDependencyNodes( 464 RepositorySystemSession session, DependencyNode root, DependencyFilter dependencyFilter) { 465 validateSession(session); 466 requireNonNull(root, "root cannot be null"); 467 return doFlattenDependencyNodes(session, root, dependencyFilter); 468 } 469 470 private List<DependencyNode> doFlattenDependencyNodes( 471 RepositorySystemSession session, DependencyNode root, DependencyFilter dependencyFilter) { 472 final ArrayList<DependencyNode> dependencyNodes = new ArrayList<>(); 473 if (root != null) { 474 root.accept(getDependencyVisitor(session, dependencyNodes::add, dependencyFilter)); 475 } 476 return dependencyNodes; 477 } 478 479 private DependencyVisitor getDependencyVisitor( 480 RepositorySystemSession session, Consumer<DependencyNode> nodeConsumer, DependencyFilter dependencyFilter) { 481 String strategy = ConfigUtils.getString( 482 session, 483 ConfigurationProperties.DEFAULT_REPOSITORY_SYSTEM_DEPENDENCY_VISITOR, 484 ConfigurationProperties.REPOSITORY_SYSTEM_DEPENDENCY_VISITOR); 485 switch (strategy) { 486 case PreorderDependencyNodeConsumerVisitor.NAME: 487 return new PreorderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter); 488 case PostorderDependencyNodeConsumerVisitor.NAME: 489 return new PostorderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter); 490 case LevelOrderDependencyNodeConsumerVisitor.NAME: 491 return new LevelOrderDependencyNodeConsumerVisitor(nodeConsumer, dependencyFilter); 492 default: 493 throw new IllegalArgumentException("Invalid dependency visitor strategy: " + strategy); 494 } 495 } 496 497 private void updateNodesWithResolvedArtifacts(List<ArtifactResult> results) { 498 for (ArtifactResult result : results) { 499 Artifact artifact = result.getArtifact(); 500 if (artifact != null) { 501 result.getRequest().getDependencyNode().setArtifact(artifact); 502 } 503 } 504 } 505 506 @Override 507 public InstallResult install(RepositorySystemSession session, InstallRequest request) throws InstallationException { 508 validateSession(session); 509 requireNonNull(request, "request cannot be null"); 510 repositorySystemValidator.validateInstallRequest(session, request); 511 return installer.install(session, request); 512 } 513 514 @Override 515 public DeployResult deploy(RepositorySystemSession session, DeployRequest request) throws DeploymentException { 516 validateSession(session); 517 requireNonNull(request, "request cannot be null"); 518 repositorySystemValidator.validateDeployRequest(session, request); 519 return deployer.deploy(session, request); 520 } 521 522 @Override 523 public LocalRepositoryManager newLocalRepositoryManager( 524 RepositorySystemSession session, LocalRepository localRepository) { 525 requireNonNull(session, "session cannot be null"); 526 requireNonNull(localRepository, "localRepository cannot be null"); 527 validateSystem(); 528 repositorySystemValidator.validateLocalRepositories(session, Collections.singleton(localRepository)); 529 return createLocalRepositoryManager(session, localRepository); 530 } 531 532 @Override 533 public LocalRepositoryManager newLocalRepositoryManager( 534 RepositorySystemSession session, LocalRepository... localRepositories) { 535 requireNonNull(session, "session cannot be null"); 536 requireNonNull(localRepositories, "localRepositories cannot be null"); 537 validateSystem(); 538 repositorySystemValidator.validateLocalRepositories(session, Arrays.asList(localRepositories)); 539 return createLocalRepositoryManager(session, Arrays.asList(localRepositories)); 540 } 541 542 @Override 543 public LocalRepositoryManager newLocalRepositoryManager( 544 RepositorySystemSession session, List<LocalRepository> localRepositories) { 545 requireNonNull(session, "session cannot be null"); 546 requireNonNull(localRepositories, "localRepositories cannot be null"); 547 validateSystem(); 548 repositorySystemValidator.validateLocalRepositories(session, localRepositories); 549 return createLocalRepositoryManager(session, localRepositories); 550 } 551 552 private LocalRepositoryManager createLocalRepositoryManager( 553 RepositorySystemSession session, List<LocalRepository> localRepositories) { 554 if (localRepositories.isEmpty()) { 555 throw new IllegalArgumentException("empty localRepositories"); 556 } else if (localRepositories.size() == 1) { 557 return createLocalRepositoryManager(session, localRepositories.get(0)); 558 } else { 559 LocalRepositoryManager head = createLocalRepositoryManager(session, localRepositories.get(0)); 560 List<LocalRepositoryManager> tail = localRepositories.subList(1, localRepositories.size()).stream() 561 .map(l -> createLocalRepositoryManager(session, l)) 562 .collect(toList()); 563 return new ChainedLocalRepositoryManager(head, tail, session); 564 } 565 } 566 567 private LocalRepositoryManager createLocalRepositoryManager( 568 RepositorySystemSession session, LocalRepository localRepository) { 569 try { 570 return localRepositoryProvider.newLocalRepositoryManager(session, localRepository); 571 } catch (NoLocalRepositoryManagerException e) { 572 throw new IllegalArgumentException(e.getMessage(), e); 573 } 574 } 575 576 @Override 577 public SyncContext newSyncContext(RepositorySystemSession session, boolean shared) { 578 validateSession(session); 579 return syncContextFactory.newInstance(session, shared); 580 } 581 582 @Override 583 public List<RemoteRepository> newResolutionRepositories( 584 RepositorySystemSession session, List<RemoteRepository> repositories) { 585 validateSession(session); 586 validateRepositories(repositories); 587 repositorySystemValidator.validateRemoteRepositories(session, repositories); 588 return remoteRepositoryManager.aggregateRepositories(session, new ArrayList<>(), repositories, true); 589 } 590 591 @Override 592 public RemoteRepository newDeploymentRepository(RepositorySystemSession session, RemoteRepository repository) { 593 validateSession(session); 594 requireNonNull(repository, "repository cannot be null"); 595 repositorySystemValidator.validateRemoteRepositories(session, Collections.singletonList(repository)); 596 Authentication auth = session.getAuthenticationSelector().getAuthentication(repository); 597 Proxy proxy = session.getProxySelector().getProxy(repository); 598 return new RemoteRepository.Builder(repository) 599 .setAuthentication(auth) 600 .setProxy(proxy) 601 .setIntent(RemoteRepository.Intent.DEPLOYMENT) 602 .build(); 603 } 604 605 @Override 606 public void addOnSystemEndedHandler(Runnable handler) { 607 validateSystem(); 608 repositorySystemLifecycle.addOnSystemEndedHandler(handler); 609 } 610 611 @Override 612 public RepositorySystemSession.SessionBuilder createSessionBuilder() { 613 validateSystem(); 614 return new DefaultSessionBuilder( 615 this, repositorySystemLifecycle, () -> "id-" + sessionIdCounter.incrementAndGet()); 616 } 617 618 @Override 619 public void shutdown() { 620 if (shutdown.compareAndSet(false, true)) { 621 repositorySystemLifecycle.systemEnded(); 622 } 623 } 624 625 /** 626 * Thread-scoped re-entrancy depth counter. This supplements the {@link RequestTrace}-based 627 * detection for consumers that rebuild the trace chain from a different tracing system 628 * (e.g. Maven 4's {@code RequestTraceHelper} converts between Maven API traces and 629 * resolver traces, losing the {@link #REPOSITORY_SYSTEM_CALL} marker). 630 * <p> 631 * Re-entrancy is inherently per-call-stack (per-thread), so a {@code ThreadLocal} is the 632 * correct semantic. A value > 0 on entry means the current thread is already inside 633 * a {@code RepositorySystem} public method. Unlike a session-scoped counter, this avoids 634 * false positives in parallel builds where multiple threads share a single session. 635 */ 636 private static final ThreadLocal<int[]> REENTRY_DEPTH = ThreadLocal.withInitial(() -> new int[] {0}); 637 638 /** 639 * Stamps the {@link #REPOSITORY_SYSTEM_CALL} re-entrancy marker into the trace chain 640 * while preserving the original trace tip data. The marker is inserted <em>below</em> 641 * the tip so that code walking the trace and casting {@code getData()} to its expected 642 * type (e.g. {@code org.apache.maven.artifact.Artifact}) still finds the original data 643 * at the tip rather than the anonymous marker object. 644 * 645 * @param currentTrace the current request trace (may be {@code null}) 646 * @return a new trace with the marker inserted and original tip data preserved 647 */ 648 private static RequestTrace stampReentrancyMarker(RequestTrace currentTrace) { 649 RequestTrace markerTrace = RequestTrace.newChild(currentTrace, REPOSITORY_SYSTEM_CALL); 650 return currentTrace != null ? RequestTrace.newChild(markerTrace, currentTrace.getData()) : markerTrace; 651 } 652 653 /** 654 * Checks whether the given {@link RequestTrace} indicates a re-entrant call by looking 655 * for the {@link #REPOSITORY_SYSTEM_CALL} marker in the trace ancestry. 656 * 657 * @return {@code true} if the marker is found (re-entrant call), {@code false} otherwise 658 */ 659 private static boolean isReentrant(RequestTrace trace) { 660 for (RequestTrace t = trace; t != null; t = t.getParent()) { 661 if (t.getData() == REPOSITORY_SYSTEM_CALL) { 662 return true; 663 } 664 } 665 return false; 666 } 667 668 /** 669 * Combined re-entrancy check using both {@link RequestTrace} ancestry and thread-scoped 670 * depth tracking. Either mechanism detecting re-entrancy is sufficient to skip validation. 671 * <p> 672 * The trace-based check is the primary mechanism and works when callers properly propagate 673 * traces. The thread-scoped check is a fallback for callers that rebuild the trace chain 674 * from a different tracing system (e.g. Maven 4's trace conversion loses the resolver's 675 * re-entrancy marker). 676 * 677 * @param trace the current request trace (may be {@code null}) 678 * @param session the current repository system session (unused, kept for signature consistency) 679 * @return {@code true} if this is a re-entrant call, {@code false} if it is the outermost call 680 */ 681 private static boolean isReentrant(RequestTrace trace, RepositorySystemSession session) { 682 return isReentrant(trace) || REENTRY_DEPTH.get()[0] > 0; 683 } 684 685 /** 686 * Increments the thread-scoped re-entrancy depth counter. Must be called on every outermost 687 * entry into a public {@code RepositorySystem} method, and the returned {@link Runnable} must 688 * be invoked in a {@code finally} block to decrement the counter on exit. 689 * 690 * @param session the current repository system session (unused, kept for signature consistency) 691 * @return a {@link Runnable} that decrements the depth counter when invoked 692 */ 693 private static Runnable enterSessionScope(RepositorySystemSession session) { 694 int[] depth = REENTRY_DEPTH.get(); 695 depth[0]++; 696 return () -> depth[0]--; 697 } 698 699 private void validateSession(RepositorySystemSession session) { 700 requireNonNull(session, "repository system session cannot be null"); 701 invalidSession(session.getLocalRepositoryManager(), "local repository manager"); 702 invalidSession(session.getSystemProperties(), "system properties"); 703 invalidSession(session.getUserProperties(), "user properties"); 704 invalidSession(session.getConfigProperties(), "config properties"); 705 invalidSession(session.getMirrorSelector(), "mirror selector"); 706 invalidSession(session.getProxySelector(), "proxy selector"); 707 invalidSession(session.getAuthenticationSelector(), "authentication selector"); 708 invalidSession(session.getArtifactTypeRegistry(), "artifact type registry"); 709 invalidSession(session.getData(), "data"); 710 validateSystem(); 711 } 712 713 private void validateSystem() { 714 if (shutdown.get()) { 715 throw new IllegalStateException("repository system is already shut down"); 716 } 717 } 718 719 private void validateRepositories(List<RemoteRepository> repositories) { 720 requireNonNull(repositories, "repositories cannot be null"); 721 for (RemoteRepository repository : repositories) { 722 requireNonNull(repository, "repository cannot be null"); 723 } 724 } 725 726 private void invalidSession(Object obj, String name) { 727 requireNonNull(obj, "repository system session's " + name + " cannot be null"); 728 } 729}