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