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.collect.bf; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.Closeable; 026import java.util.ArrayDeque; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.LinkedHashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.Optional; 033import java.util.Queue; 034import java.util.Set; 035import java.util.concurrent.Callable; 036import java.util.concurrent.ConcurrentHashMap; 037import java.util.concurrent.ExecutionException; 038import java.util.concurrent.ExecutorService; 039import java.util.concurrent.Future; 040import java.util.concurrent.TimeUnit; 041import java.util.concurrent.TimeoutException; 042import java.util.stream.Collectors; 043import java.util.stream.Stream; 044 045import org.eclipse.aether.RepositorySystemSession; 046import org.eclipse.aether.RequestTrace; 047import org.eclipse.aether.artifact.Artifact; 048import org.eclipse.aether.artifact.ArtifactType; 049import org.eclipse.aether.artifact.DefaultArtifact; 050import org.eclipse.aether.collection.CollectRequest; 051import org.eclipse.aether.collection.DependencyManager; 052import org.eclipse.aether.collection.DependencySelector; 053import org.eclipse.aether.collection.DependencyTraverser; 054import org.eclipse.aether.collection.VersionFilter; 055import org.eclipse.aether.graph.DefaultDependencyNode; 056import org.eclipse.aether.graph.Dependency; 057import org.eclipse.aether.graph.DependencyNode; 058import org.eclipse.aether.impl.ArtifactDescriptorReader; 059import org.eclipse.aether.impl.RemoteRepositoryManager; 060import org.eclipse.aether.impl.VersionRangeResolver; 061import org.eclipse.aether.internal.impl.collect.DataPool; 062import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollectionContext; 063import org.eclipse.aether.internal.impl.collect.DefaultVersionFilterContext; 064import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate; 065import org.eclipse.aether.internal.impl.collect.PremanagedDependency; 066import org.eclipse.aether.repository.RemoteRepository; 067import org.eclipse.aether.resolution.ArtifactDescriptorException; 068import org.eclipse.aether.resolution.ArtifactDescriptorRequest; 069import org.eclipse.aether.resolution.ArtifactDescriptorResult; 070import org.eclipse.aether.resolution.VersionRangeRequest; 071import org.eclipse.aether.resolution.VersionRangeResult; 072import org.eclipse.aether.util.ConfigUtils; 073import org.eclipse.aether.util.artifact.ArtifactIdUtils; 074import org.eclipse.aether.util.concurrency.ExecutorUtils; 075import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; 076import org.eclipse.aether.version.Version; 077 078import static org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle.find; 079 080/** 081 * Breadth-first {@link org.eclipse.aether.impl.DependencyCollector} 082 * 083 * @since 1.8.0 084 */ 085@Singleton 086@Named(BfDependencyCollector.NAME) 087public class BfDependencyCollector extends DependencyCollectorDelegate { 088 public static final String NAME = "bf"; 089 090 /** 091 * The key in the repository session's {@link RepositorySystemSession#getConfigProperties() 092 * configuration properties} used to store a {@link Boolean} flag controlling the resolver's skip mode. 093 * 094 * @since 1.8.0 095 */ 096 static final String CONFIG_PROP_SKIPPER = "aether.dependencyCollector.bf.skipper"; 097 098 /** 099 * The default value for {@link #CONFIG_PROP_SKIPPER}, {@code true}. 100 * 101 * @since 1.8.0 102 */ 103 static final boolean CONFIG_PROP_SKIPPER_DEFAULT = true; 104 105 /** 106 * The count of threads to be used when collecting POMs in parallel, default value 5. 107 * 108 * @since 1.9.0 109 */ 110 static final String CONFIG_PROP_THREADS = "aether.dependencyCollector.bf.threads"; 111 112 @Inject 113 public BfDependencyCollector( 114 RemoteRepositoryManager remoteRepositoryManager, 115 ArtifactDescriptorReader artifactDescriptorReader, 116 VersionRangeResolver versionRangeResolver) { 117 super(remoteRepositoryManager, artifactDescriptorReader, versionRangeResolver); 118 } 119 120 @SuppressWarnings("checkstyle:parameternumber") 121 @Override 122 protected void doCollectDependencies( 123 RepositorySystemSession session, 124 RequestTrace trace, 125 DataPool pool, 126 DefaultDependencyCollectionContext context, 127 DefaultVersionFilterContext versionContext, 128 CollectRequest request, 129 DependencyNode node, 130 List<RemoteRepository> repositories, 131 List<Dependency> dependencies, 132 List<Dependency> managedDependencies, 133 Results results) { 134 boolean useSkip = ConfigUtils.getBoolean(session, CONFIG_PROP_SKIPPER_DEFAULT, CONFIG_PROP_SKIPPER); 135 int nThreads = ExecutorUtils.threadCount(session, 5, CONFIG_PROP_THREADS); 136 logger.debug("Using thread pool with {} threads to resolve descriptors.", nThreads); 137 138 if (useSkip) { 139 logger.debug("Collector skip mode enabled"); 140 } 141 142 try (DependencyResolutionSkipper skipper = useSkip 143 ? DependencyResolutionSkipper.defaultSkipper() 144 : DependencyResolutionSkipper.neverSkipper(); 145 ParallelDescriptorResolver parallelDescriptorResolver = new ParallelDescriptorResolver(nThreads)) { 146 Args args = new Args(session, pool, context, versionContext, request, skipper, parallelDescriptorResolver); 147 148 DependencySelector rootDepSelector = session.getDependencySelector() != null 149 ? session.getDependencySelector().deriveChildSelector(context) 150 : null; 151 DependencyManager rootDepManager = session.getDependencyManager() != null 152 ? session.getDependencyManager().deriveChildManager(context) 153 : null; 154 DependencyTraverser rootDepTraverser = session.getDependencyTraverser() != null 155 ? session.getDependencyTraverser().deriveChildTraverser(context) 156 : null; 157 VersionFilter rootVerFilter = session.getVersionFilter() != null 158 ? session.getVersionFilter().deriveChildFilter(context) 159 : null; 160 161 List<DependencyNode> parents = Collections.singletonList(node); 162 for (Dependency dependency : dependencies) { 163 RequestTrace childTrace = 164 collectStepTrace(trace, args.request.getRequestContext(), parents, dependency); 165 DependencyProcessingContext processingContext = new DependencyProcessingContext( 166 rootDepSelector, 167 rootDepManager, 168 rootDepTraverser, 169 rootVerFilter, 170 childTrace, 171 repositories, 172 managedDependencies, 173 parents, 174 dependency, 175 PremanagedDependency.create(rootDepManager, dependency, false, args.premanagedState)); 176 if (!filter(processingContext)) { 177 processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency()); 178 resolveArtifactDescriptorAsync(args, processingContext, results); 179 args.dependencyProcessingQueue.add(processingContext); 180 } 181 } 182 183 while (!args.dependencyProcessingQueue.isEmpty()) { 184 processDependency( 185 args, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), false); 186 } 187 } 188 } 189 190 @SuppressWarnings("checkstyle:parameternumber") 191 private void processDependency( 192 Args args, 193 Results results, 194 DependencyProcessingContext context, 195 List<Artifact> relocations, 196 boolean disableVersionManagement) { 197 Dependency dependency = context.dependency; 198 PremanagedDependency preManaged = context.premanagedDependency; 199 200 boolean noDescriptor = isLackingDescriptor(dependency.getArtifact()); 201 boolean traverse = 202 !noDescriptor && (context.depTraverser == null || context.depTraverser.traverseDependency(dependency)); 203 204 Future<DescriptorResolutionResult> resolutionResultFuture = args.resolver.find(dependency.getArtifact()); 205 DescriptorResolutionResult resolutionResult; 206 VersionRangeResult rangeResult; 207 try { 208 resolutionResult = resolutionResultFuture.get(); 209 rangeResult = resolutionResult.rangeResult; 210 } catch (Exception e) { 211 results.addException(dependency, e, context.parents); 212 return; 213 } 214 215 Set<Version> versions = resolutionResult.descriptors.keySet(); 216 for (Version version : versions) { 217 Artifact originalArtifact = dependency.getArtifact().setVersion(version.toString()); 218 Dependency d = dependency.setArtifact(originalArtifact); 219 220 final ArtifactDescriptorResult descriptorResult = resolutionResult.descriptors.get(version); 221 if (descriptorResult != null) { 222 d = d.setArtifact(descriptorResult.getArtifact()); 223 224 int cycleEntry = find(context.parents, d.getArtifact()); 225 if (cycleEntry >= 0) { 226 results.addCycle(context.parents, cycleEntry, d); 227 DependencyNode cycleNode = context.parents.get(cycleEntry); 228 if (cycleNode.getDependency() != null) { 229 DefaultDependencyNode child = createDependencyNode( 230 relocations, preManaged, rangeResult, version, d, descriptorResult, cycleNode); 231 context.getParent().getChildren().add(child); 232 continue; 233 } 234 } 235 236 if (!descriptorResult.getRelocations().isEmpty()) { 237 boolean disableVersionManagementSubsequently = 238 originalArtifact.getGroupId().equals(d.getArtifact().getGroupId()) 239 && originalArtifact 240 .getArtifactId() 241 .equals(d.getArtifact().getArtifactId()); 242 243 PremanagedDependency premanagedDependency = PremanagedDependency.create( 244 context.depManager, d, disableVersionManagementSubsequently, args.premanagedState); 245 DependencyProcessingContext relocatedContext = new DependencyProcessingContext( 246 context.depSelector, 247 context.depManager, 248 context.depTraverser, 249 context.verFilter, 250 context.trace, 251 context.repositories, 252 descriptorResult.getManagedDependencies(), 253 context.parents, 254 d, 255 premanagedDependency); 256 257 if (!filter(relocatedContext)) { 258 relocatedContext.withDependency(premanagedDependency.getManagedDependency()); 259 resolveArtifactDescriptorAsync(args, relocatedContext, results); 260 processDependency( 261 args, 262 results, 263 relocatedContext, 264 descriptorResult.getRelocations(), 265 disableVersionManagementSubsequently); 266 } 267 268 return; 269 } else { 270 d = args.pool.intern(d.setArtifact(args.pool.intern(d.getArtifact()))); 271 272 List<RemoteRepository> repos = 273 getRemoteRepositories(rangeResult.getRepository(version), context.repositories); 274 275 DefaultDependencyNode child = createDependencyNode( 276 relocations, 277 preManaged, 278 rangeResult, 279 version, 280 d, 281 descriptorResult.getAliases(), 282 repos, 283 args.request.getRequestContext()); 284 285 context.getParent().getChildren().add(child); 286 287 boolean recurse = 288 traverse && !descriptorResult.getDependencies().isEmpty(); 289 DependencyProcessingContext parentContext = context.withDependency(d); 290 if (recurse) { 291 doRecurse(args, parentContext, descriptorResult, child, results, disableVersionManagement); 292 } else if (!args.skipper.skipResolution(child, parentContext.parents)) { 293 List<DependencyNode> parents = new ArrayList<>(parentContext.parents.size() + 1); 294 parents.addAll(parentContext.parents); 295 parents.add(child); 296 args.skipper.cache(child, parents); 297 } 298 } 299 } else { 300 List<RemoteRepository> repos = 301 getRemoteRepositories(rangeResult.getRepository(version), context.repositories); 302 DefaultDependencyNode child = createDependencyNode( 303 relocations, 304 preManaged, 305 rangeResult, 306 version, 307 d, 308 null, 309 repos, 310 args.request.getRequestContext()); 311 context.getParent().getChildren().add(child); 312 } 313 } 314 } 315 316 @SuppressWarnings("checkstyle:parameternumber") 317 private void doRecurse( 318 Args args, 319 DependencyProcessingContext parentContext, 320 ArtifactDescriptorResult descriptorResult, 321 DefaultDependencyNode child, 322 Results results, 323 boolean disableVersionManagement) { 324 DefaultDependencyCollectionContext context = args.collectionContext; 325 context.set(parentContext.dependency, descriptorResult.getManagedDependencies()); 326 327 DependencySelector childSelector = 328 parentContext.depSelector != null ? parentContext.depSelector.deriveChildSelector(context) : null; 329 DependencyManager childManager = 330 parentContext.depManager != null ? parentContext.depManager.deriveChildManager(context) : null; 331 DependencyTraverser childTraverser = 332 parentContext.depTraverser != null ? parentContext.depTraverser.deriveChildTraverser(context) : null; 333 VersionFilter childFilter = 334 parentContext.verFilter != null ? parentContext.verFilter.deriveChildFilter(context) : null; 335 336 final List<RemoteRepository> childRepos = args.ignoreRepos 337 ? parentContext.repositories 338 : remoteRepositoryManager.aggregateRepositories( 339 args.session, parentContext.repositories, descriptorResult.getRepositories(), true); 340 341 Object key = args.pool.toKey( 342 parentContext.dependency.getArtifact(), 343 childRepos, 344 childSelector, 345 childManager, 346 childTraverser, 347 childFilter); 348 349 List<DependencyNode> children = args.pool.getChildren(key); 350 if (children == null) { 351 boolean skipResolution = args.skipper.skipResolution(child, parentContext.parents); 352 if (!skipResolution) { 353 List<DependencyNode> parents = new ArrayList<>(parentContext.parents.size() + 1); 354 parents.addAll(parentContext.parents); 355 parents.add(child); 356 for (Dependency dependency : descriptorResult.getDependencies()) { 357 RequestTrace childTrace = collectStepTrace( 358 parentContext.trace, args.request.getRequestContext(), parents, dependency); 359 PremanagedDependency premanagedDependency = PremanagedDependency.create( 360 childManager, dependency, disableVersionManagement, args.premanagedState); 361 DependencyProcessingContext processingContext = new DependencyProcessingContext( 362 childSelector, 363 childManager, 364 childTraverser, 365 childFilter, 366 childTrace, 367 childRepos, 368 descriptorResult.getManagedDependencies(), 369 parents, 370 dependency, 371 premanagedDependency); 372 if (!filter(processingContext)) { 373 // resolve descriptors ahead for managed dependency 374 processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency()); 375 resolveArtifactDescriptorAsync(args, processingContext, results); 376 args.dependencyProcessingQueue.add(processingContext); 377 } 378 } 379 args.pool.putChildren(key, child.getChildren()); 380 args.skipper.cache(child, parents); 381 } 382 } else { 383 child.setChildren(children); 384 } 385 } 386 387 private boolean filter(DependencyProcessingContext context) { 388 return context.depSelector != null && !context.depSelector.selectDependency(context.dependency); 389 } 390 391 private void resolveArtifactDescriptorAsync(Args args, DependencyProcessingContext context, Results results) { 392 Dependency dependency = context.dependency; 393 args.resolver.resolveDescriptors(dependency.getArtifact(), () -> { 394 VersionRangeRequest rangeRequest = createVersionRangeRequest( 395 args.request.getRequestContext(), context.trace, context.repositories, dependency); 396 VersionRangeResult rangeResult = cachedResolveRangeResult(rangeRequest, args.pool, args.session); 397 List<? extends Version> versions = 398 filterVersions(dependency, rangeResult, context.verFilter, args.versionContext); 399 400 // resolve newer version first to maximize benefits of skipper 401 Collections.reverse(versions); 402 403 Map<Version, ArtifactDescriptorResult> descriptors = new ConcurrentHashMap<>(versions.size()); 404 Stream<? extends Version> stream = versions.size() > 1 ? versions.parallelStream() : versions.stream(); 405 stream.forEach(version -> Optional.ofNullable( 406 resolveDescriptorForVersion(args, context, results, dependency, version)) 407 .ifPresent(r -> descriptors.put(version, r))); 408 409 DescriptorResolutionResult resolutionResult = 410 new DescriptorResolutionResult(dependency.getArtifact(), rangeResult); 411 // keep original sequence 412 versions.forEach(version -> resolutionResult.descriptors.put(version, descriptors.get(version))); 413 // populate for versions in version range 414 resolutionResult.flatten().forEach(dr -> args.resolver.cacheVersionRangeDescriptor(dr.artifact, dr)); 415 416 return resolutionResult; 417 }); 418 } 419 420 private ArtifactDescriptorResult resolveDescriptorForVersion( 421 Args args, DependencyProcessingContext context, Results results, Dependency dependency, Version version) { 422 Artifact original = dependency.getArtifact(); 423 Artifact newArtifact = new DefaultArtifact( 424 original.getGroupId(), 425 original.getArtifactId(), 426 original.getClassifier(), 427 original.getExtension(), 428 version.toString(), 429 original.getProperties(), 430 (ArtifactType) null); 431 Dependency newDependency = 432 new Dependency(newArtifact, dependency.getScope(), dependency.isOptional(), dependency.getExclusions()); 433 DependencyProcessingContext newContext = context.copy(); 434 435 ArtifactDescriptorRequest descriptorRequest = createArtifactDescriptorRequest( 436 args.request.getRequestContext(), context.trace, newContext.repositories, newDependency); 437 return isLackingDescriptor(newArtifact) 438 ? new ArtifactDescriptorResult(descriptorRequest) 439 : resolveCachedArtifactDescriptor( 440 args.pool, descriptorRequest, args.session, newContext.withDependency(newDependency), results); 441 } 442 443 private ArtifactDescriptorResult resolveCachedArtifactDescriptor( 444 DataPool pool, 445 ArtifactDescriptorRequest descriptorRequest, 446 RepositorySystemSession session, 447 DependencyProcessingContext context, 448 Results results) { 449 Object key = pool.toKey(descriptorRequest); 450 ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest); 451 if (descriptorResult == null) { 452 try { 453 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 454 pool.putDescriptor(key, descriptorResult); 455 } catch (ArtifactDescriptorException e) { 456 results.addException(context.dependency, e, context.parents); 457 pool.putDescriptor(key, e); 458 return null; 459 } 460 461 } else if (descriptorResult == DataPool.NO_DESCRIPTOR) { 462 return null; 463 } 464 465 return descriptorResult; 466 } 467 468 static class ParallelDescriptorResolver implements Closeable { 469 private final ExecutorService executorService; 470 471 /** 472 * Artifact ID -> Future of DescriptorResolutionResult 473 */ 474 private final Map<String, Future<DescriptorResolutionResult>> results = new ConcurrentHashMap<>(256); 475 476 ParallelDescriptorResolver(int threads) { 477 this.executorService = ExecutorUtils.threadPool(threads, getClass().getSimpleName() + "-"); 478 } 479 480 void resolveDescriptors(Artifact artifact, Callable<DescriptorResolutionResult> callable) { 481 results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> this.executorService.submit(callable)); 482 } 483 484 void cacheVersionRangeDescriptor(Artifact artifact, DescriptorResolutionResult resolutionResult) { 485 results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> new DoneFuture<>(resolutionResult)); 486 } 487 488 Future<DescriptorResolutionResult> find(Artifact artifact) { 489 return results.get(ArtifactIdUtils.toId(artifact)); 490 } 491 492 @Override 493 public void close() { 494 executorService.shutdown(); 495 } 496 } 497 498 static class DoneFuture<V> implements Future<V> { 499 private final V v; 500 501 DoneFuture(V v) { 502 this.v = v; 503 } 504 505 @Override 506 public boolean cancel(boolean mayInterruptIfRunning) { 507 return false; 508 } 509 510 @Override 511 public boolean isCancelled() { 512 return false; 513 } 514 515 @Override 516 public boolean isDone() { 517 return true; 518 } 519 520 @Override 521 public V get() throws InterruptedException, ExecutionException { 522 return v; 523 } 524 525 @Override 526 public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 527 return v; 528 } 529 } 530 531 static class DescriptorResolutionResult { 532 Artifact artifact; 533 534 VersionRangeResult rangeResult; 535 536 Map<Version, ArtifactDescriptorResult> descriptors; 537 538 DescriptorResolutionResult(Artifact artifact, VersionRangeResult rangeResult) { 539 this.artifact = artifact; 540 this.rangeResult = rangeResult; 541 this.descriptors = new LinkedHashMap<>(rangeResult.getVersions().size()); 542 } 543 544 DescriptorResolutionResult( 545 VersionRangeResult rangeResult, Version version, ArtifactDescriptorResult descriptor) { 546 // NOTE: In case of A1 -> A2 relocation this happens: 547 // ArtifactDescriptorResult read by ArtifactDescriptorResultReader for A1 548 // will return instance that will have artifact = A2 (as RelocatedArtifact). 549 // So to properly "key" this instance, we need to use "originally requested" A1 instead! 550 // In short: 551 // ArtifactDescriptorRequest.artifact != ArtifactDescriptorResult.artifact WHEN relocation in play 552 // otherwise (no relocation), they are EQUAL. 553 this(descriptor.getRequest().getArtifact(), rangeResult); 554 this.descriptors.put(version, descriptor); 555 } 556 557 List<DescriptorResolutionResult> flatten() { 558 if (descriptors.size() > 1) { 559 return descriptors.entrySet().stream() 560 .map(e -> new DescriptorResolutionResult(rangeResult, e.getKey(), e.getValue())) 561 .collect(Collectors.toList()); 562 } else { 563 return Collections.emptyList(); 564 } 565 } 566 } 567 568 static class Args { 569 570 final RepositorySystemSession session; 571 572 final boolean ignoreRepos; 573 574 final boolean premanagedState; 575 576 final DataPool pool; 577 578 final Queue<DependencyProcessingContext> dependencyProcessingQueue = new ArrayDeque<>(128); 579 580 final DefaultDependencyCollectionContext collectionContext; 581 582 final DefaultVersionFilterContext versionContext; 583 584 final CollectRequest request; 585 586 final DependencyResolutionSkipper skipper; 587 588 final ParallelDescriptorResolver resolver; 589 590 Args( 591 RepositorySystemSession session, 592 DataPool pool, 593 DefaultDependencyCollectionContext collectionContext, 594 DefaultVersionFilterContext versionContext, 595 CollectRequest request, 596 DependencyResolutionSkipper skipper, 597 ParallelDescriptorResolver resolver) { 598 this.session = session; 599 this.request = request; 600 this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories(); 601 this.premanagedState = ConfigUtils.getBoolean(session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE); 602 this.pool = pool; 603 this.collectionContext = collectionContext; 604 this.versionContext = versionContext; 605 this.skipper = skipper; 606 this.resolver = resolver; 607 } 608 } 609}