1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.collect.bf;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.Closeable;
26 import java.util.ArrayDeque;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.Queue;
34 import java.util.Set;
35 import java.util.concurrent.Callable;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ExecutionException;
38 import java.util.concurrent.ExecutorService;
39 import java.util.concurrent.Future;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.TimeoutException;
42 import java.util.concurrent.atomic.AtomicReference;
43 import java.util.stream.Collectors;
44 import java.util.stream.Stream;
45
46 import org.eclipse.aether.RepositorySystemSession;
47 import org.eclipse.aether.RequestTrace;
48 import org.eclipse.aether.artifact.Artifact;
49 import org.eclipse.aether.collection.CollectRequest;
50 import org.eclipse.aether.collection.DependencyCollectionException;
51 import org.eclipse.aether.collection.DependencyManager;
52 import org.eclipse.aether.collection.DependencySelector;
53 import org.eclipse.aether.collection.DependencyTraverser;
54 import org.eclipse.aether.collection.VersionFilter;
55 import org.eclipse.aether.graph.DefaultDependencyNode;
56 import org.eclipse.aether.graph.Dependency;
57 import org.eclipse.aether.graph.DependencyNode;
58 import org.eclipse.aether.impl.ArtifactDescriptorReader;
59 import org.eclipse.aether.impl.RemoteRepositoryManager;
60 import org.eclipse.aether.impl.VersionRangeResolver;
61 import org.eclipse.aether.internal.impl.collect.DataPool;
62 import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollectionContext;
63 import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollector;
64 import org.eclipse.aether.internal.impl.collect.DefaultVersionFilterContext;
65 import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate;
66 import org.eclipse.aether.internal.impl.collect.PremanagedDependency;
67 import org.eclipse.aether.repository.RemoteRepository;
68 import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
69 import org.eclipse.aether.resolution.ArtifactDescriptorResult;
70 import org.eclipse.aether.resolution.VersionRangeRequest;
71 import org.eclipse.aether.resolution.VersionRangeResult;
72 import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory;
73 import org.eclipse.aether.util.ConfigUtils;
74 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
75 import org.eclipse.aether.util.concurrency.ExecutorUtils;
76 import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
77 import org.eclipse.aether.version.Version;
78
79 import static org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle.find;
80
81
82
83
84
85
86 @Singleton
87 @Named(BfDependencyCollector.NAME)
88 public class BfDependencyCollector extends DependencyCollectorDelegate {
89 public static final String NAME = "bf";
90
91 private static final String CONFIG_PROPS_PREFIX = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + NAME + ".";
92
93
94
95
96
97
98
99
100
101
102 public static final String CONFIG_PROP_SKIPPER = CONFIG_PROPS_PREFIX + "skipper";
103
104
105
106
107
108
109 public static final boolean DEFAULT_SKIPPER = true;
110
111
112
113
114
115
116
117
118
119 public static final String CONFIG_PROP_THREADS = CONFIG_PROPS_PREFIX + "threads";
120
121
122
123
124
125
126 public static final int DEFAULT_THREADS = 5;
127
128 @Inject
129 public BfDependencyCollector(
130 RemoteRepositoryManager remoteRepositoryManager,
131 ArtifactDescriptorReader artifactDescriptorReader,
132 VersionRangeResolver versionRangeResolver,
133 Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories) {
134 super(remoteRepositoryManager, artifactDescriptorReader, versionRangeResolver, artifactDecoratorFactories);
135 }
136
137 @SuppressWarnings("checkstyle:parameternumber")
138 @Override
139 protected void doCollectDependencies(
140 RepositorySystemSession session,
141 RequestTrace trace,
142 DataPool pool,
143 DefaultDependencyCollectionContext context,
144 DefaultVersionFilterContext versionContext,
145 CollectRequest request,
146 DependencyNode node,
147 List<RemoteRepository> repositories,
148 List<Dependency> dependencies,
149 List<Dependency> managedDependencies,
150 Results results)
151 throws DependencyCollectionException {
152 boolean useSkip = ConfigUtils.getBoolean(session, DEFAULT_SKIPPER, CONFIG_PROP_SKIPPER);
153 int nThreads = ExecutorUtils.threadCount(session, DEFAULT_THREADS, CONFIG_PROP_THREADS);
154 logger.debug("Using thread pool with {} threads to resolve descriptors.", nThreads);
155
156 if (useSkip) {
157 logger.debug("Collector skip mode enabled");
158 }
159
160 try (DependencyResolutionSkipper skipper = useSkip
161 ? DependencyResolutionSkipper.defaultSkipper()
162 : DependencyResolutionSkipper.neverSkipper();
163 ParallelDescriptorResolver parallelDescriptorResolver = new ParallelDescriptorResolver(nThreads)) {
164 Args args = new Args(session, pool, context, versionContext, request, skipper, parallelDescriptorResolver);
165
166 DependencySelector rootDepSelector = session.getDependencySelector() != null
167 ? session.getDependencySelector().deriveChildSelector(context)
168 : null;
169 DependencyManager rootDepManager = session.getDependencyManager() != null
170 ? session.getDependencyManager().deriveChildManager(context)
171 : null;
172 DependencyTraverser rootDepTraverser = session.getDependencyTraverser() != null
173 ? session.getDependencyTraverser().deriveChildTraverser(context)
174 : null;
175 VersionFilter rootVerFilter = session.getVersionFilter() != null
176 ? session.getVersionFilter().deriveChildFilter(context)
177 : null;
178
179 List<DependencyNode> parents = Collections.singletonList(node);
180 for (Dependency dependency : dependencies) {
181 RequestTrace childTrace =
182 collectStepTrace(trace, args.request.getRequestContext(), parents, dependency);
183 DependencyProcessingContext processingContext = new DependencyProcessingContext(
184 rootDepSelector,
185 rootDepManager,
186 rootDepTraverser,
187 rootVerFilter,
188 childTrace,
189 repositories,
190 managedDependencies,
191 parents,
192 dependency,
193 PremanagedDependency.create(rootDepManager, dependency, false, args.premanagedState));
194 if (!filter(processingContext)) {
195 processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency());
196 resolveArtifactDescriptorAsync(args, processingContext, results);
197 args.dependencyProcessingQueue.add(processingContext);
198 }
199 }
200
201 while (!args.dependencyProcessingQueue.isEmpty()) {
202 processDependency(
203 args, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), false);
204 }
205
206 if (args.interruptedException.get() != null) {
207 throw new DependencyCollectionException(
208 results.getResult(), "Collection interrupted", args.interruptedException.get());
209 }
210 }
211 }
212
213 @SuppressWarnings("checkstyle:parameternumber")
214 private void processDependency(
215 Args args,
216 Results results,
217 DependencyProcessingContext context,
218 List<Artifact> relocations,
219 boolean disableVersionManagement) {
220 if (Thread.interrupted()) {
221 args.interruptedException.set(new InterruptedException());
222 }
223 if (args.interruptedException.get() != null) {
224 return;
225 }
226 Dependency dependency = context.dependency;
227 PremanagedDependency preManaged = context.premanagedDependency;
228
229 boolean noDescriptor = isLackingDescriptor(args.session, dependency.getArtifact());
230 boolean traverse =
231 !noDescriptor && (context.depTraverser == null || context.depTraverser.traverseDependency(dependency));
232
233 Future<DescriptorResolutionResult> resolutionResultFuture = args.resolver.find(dependency.getArtifact());
234 DescriptorResolutionResult resolutionResult;
235 VersionRangeResult rangeResult;
236 try {
237 resolutionResult = resolutionResultFuture.get();
238 rangeResult = resolutionResult.rangeResult;
239 } catch (Exception e) {
240 results.addException(dependency, e, context.parents);
241 return;
242 }
243
244 Set<Version> versions = resolutionResult.descriptors.keySet();
245 for (Version version : versions) {
246 Artifact originalArtifact = dependency.getArtifact().setVersion(version.toString());
247 Dependency d = dependency.setArtifact(originalArtifact);
248
249 final ArtifactDescriptorResult descriptorResult = resolutionResult.descriptors.get(version);
250 if (descriptorResult != null) {
251 d = d.setArtifact(descriptorResult.getArtifact());
252
253 int cycleEntry = find(context.parents, d.getArtifact());
254 if (cycleEntry >= 0) {
255 results.addCycle(context.parents, cycleEntry, d);
256 DependencyNode cycleNode = context.parents.get(cycleEntry);
257 if (cycleNode.getDependency() != null) {
258 DefaultDependencyNode child = createDependencyNode(
259 relocations, preManaged, rangeResult, version, d, descriptorResult, cycleNode);
260 context.getParent().getChildren().add(child);
261 continue;
262 }
263 }
264
265 if (!descriptorResult.getRelocations().isEmpty()) {
266 boolean disableVersionManagementSubsequently =
267 originalArtifact.getGroupId().equals(d.getArtifact().getGroupId())
268 && originalArtifact
269 .getArtifactId()
270 .equals(d.getArtifact().getArtifactId());
271
272 PremanagedDependency premanagedDependency = PremanagedDependency.create(
273 context.depManager, d, disableVersionManagementSubsequently, args.premanagedState);
274 DependencyProcessingContext relocatedContext = new DependencyProcessingContext(
275 context.depSelector,
276 context.depManager,
277 context.depTraverser,
278 context.verFilter,
279 context.trace,
280 context.repositories,
281 descriptorResult.getManagedDependencies(),
282 context.parents,
283 d,
284 premanagedDependency);
285
286 if (!filter(relocatedContext)) {
287 relocatedContext.withDependency(premanagedDependency.getManagedDependency());
288 resolveArtifactDescriptorAsync(args, relocatedContext, results);
289 processDependency(
290 args,
291 results,
292 relocatedContext,
293 descriptorResult.getRelocations(),
294 disableVersionManagementSubsequently);
295 }
296
297 return;
298 } else {
299 d = args.pool.intern(d.setArtifact(args.pool.intern(d.getArtifact())));
300
301 List<RemoteRepository> repos =
302 getRemoteRepositories(rangeResult.getRepository(version), context.repositories);
303
304 DefaultDependencyNode child = createDependencyNode(
305 relocations,
306 preManaged,
307 rangeResult,
308 version,
309 d,
310 descriptorResult.getAliases(),
311 repos,
312 args.request.getRequestContext());
313
314 context.getParent().getChildren().add(child);
315
316 boolean recurse =
317 traverse && !descriptorResult.getDependencies().isEmpty();
318 DependencyProcessingContext parentContext = context.withDependency(d);
319 if (recurse) {
320 doRecurse(args, parentContext, descriptorResult, child, results, disableVersionManagement);
321 } else if (!args.skipper.skipResolution(child, parentContext.parents)) {
322 List<DependencyNode> parents = new ArrayList<>(parentContext.parents.size() + 1);
323 parents.addAll(parentContext.parents);
324 parents.add(child);
325 args.skipper.cache(child, parents);
326 }
327 }
328 } else {
329 List<RemoteRepository> repos =
330 getRemoteRepositories(rangeResult.getRepository(version), context.repositories);
331 DefaultDependencyNode child = createDependencyNode(
332 relocations,
333 preManaged,
334 rangeResult,
335 version,
336 d,
337 null,
338 repos,
339 args.request.getRequestContext());
340 context.getParent().getChildren().add(child);
341 }
342 }
343 }
344
345 @SuppressWarnings("checkstyle:parameternumber")
346 private void doRecurse(
347 Args args,
348 DependencyProcessingContext parentContext,
349 ArtifactDescriptorResult descriptorResult,
350 DefaultDependencyNode child,
351 Results results,
352 boolean disableVersionManagement) {
353 DefaultDependencyCollectionContext context = args.collectionContext;
354 context.set(parentContext.dependency, descriptorResult.getManagedDependencies());
355
356 DependencySelector childSelector =
357 parentContext.depSelector != null ? parentContext.depSelector.deriveChildSelector(context) : null;
358 DependencyManager childManager =
359 parentContext.depManager != null ? parentContext.depManager.deriveChildManager(context) : null;
360 DependencyTraverser childTraverser =
361 parentContext.depTraverser != null ? parentContext.depTraverser.deriveChildTraverser(context) : null;
362 VersionFilter childFilter =
363 parentContext.verFilter != null ? parentContext.verFilter.deriveChildFilter(context) : null;
364
365 final List<RemoteRepository> childRepos = args.ignoreRepos
366 ? parentContext.repositories
367 : remoteRepositoryManager.aggregateRepositories(
368 args.session, parentContext.repositories, descriptorResult.getRepositories(), true);
369
370 Object key = args.pool.toKey(
371 parentContext.dependency.getArtifact(),
372 childRepos,
373 childSelector,
374 childManager,
375 childTraverser,
376 childFilter);
377
378 List<DependencyNode> children = args.pool.getChildren(key);
379 if (children == null) {
380 boolean skipResolution = args.skipper.skipResolution(child, parentContext.parents);
381 if (!skipResolution) {
382 List<DependencyNode> parents = new ArrayList<>(parentContext.parents.size() + 1);
383 parents.addAll(parentContext.parents);
384 parents.add(child);
385 for (Dependency dependency : descriptorResult.getDependencies()) {
386 RequestTrace childTrace = collectStepTrace(
387 parentContext.trace, args.request.getRequestContext(), parents, dependency);
388 PremanagedDependency premanagedDependency = PremanagedDependency.create(
389 childManager, dependency, disableVersionManagement, args.premanagedState);
390 DependencyProcessingContext processingContext = new DependencyProcessingContext(
391 childSelector,
392 childManager,
393 childTraverser,
394 childFilter,
395 childTrace,
396 childRepos,
397 descriptorResult.getManagedDependencies(),
398 parents,
399 dependency,
400 premanagedDependency);
401 if (!filter(processingContext)) {
402
403 processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency());
404 resolveArtifactDescriptorAsync(args, processingContext, results);
405 args.dependencyProcessingQueue.add(processingContext);
406 }
407 }
408 args.pool.putChildren(key, child.getChildren());
409 args.skipper.cache(child, parents);
410 }
411 } else {
412 child.setChildren(children);
413 }
414 }
415
416 private boolean filter(DependencyProcessingContext context) {
417 return context.depSelector != null && !context.depSelector.selectDependency(context.dependency);
418 }
419
420 private void resolveArtifactDescriptorAsync(Args args, DependencyProcessingContext context, Results results) {
421 Dependency dependency = context.dependency;
422 args.resolver.resolveDescriptors(dependency.getArtifact(), () -> {
423 VersionRangeRequest rangeRequest = createVersionRangeRequest(
424 args.request.getRequestContext(), context.trace, context.repositories, dependency);
425 VersionRangeResult rangeResult = cachedResolveRangeResult(rangeRequest, args.pool, args.session);
426 List<? extends Version> versions =
427 filterVersions(dependency, rangeResult, context.verFilter, args.versionContext);
428
429
430 Collections.reverse(versions);
431
432 Map<Version, ArtifactDescriptorResult> descriptors = new ConcurrentHashMap<>(versions.size());
433 Stream<? extends Version> stream = versions.size() > 1 ? versions.parallelStream() : versions.stream();
434 stream.forEach(version -> Optional.ofNullable(
435 resolveDescriptorForVersion(args, context, results, dependency, version))
436 .ifPresent(r -> descriptors.put(version, r)));
437
438 DescriptorResolutionResult resolutionResult =
439 new DescriptorResolutionResult(dependency.getArtifact(), rangeResult);
440
441 versions.forEach(version -> resolutionResult.descriptors.put(version, descriptors.get(version)));
442
443 resolutionResult.flatten().forEach(dr -> args.resolver.cacheVersionRangeDescriptor(dr.artifact, dr));
444
445 return resolutionResult;
446 });
447 }
448
449 private ArtifactDescriptorResult resolveDescriptorForVersion(
450 Args args, DependencyProcessingContext context, Results results, Dependency dependency, Version version) {
451 Artifact original = dependency.getArtifact();
452 Artifact newArtifact = original.setVersion(version.toString());
453 Dependency newDependency =
454 new Dependency(newArtifact, dependency.getScope(), dependency.isOptional(), dependency.getExclusions());
455 DependencyProcessingContext newContext = context.copy();
456
457 ArtifactDescriptorRequest descriptorRequest = createArtifactDescriptorRequest(
458 args.request.getRequestContext(), context.trace, newContext.repositories, newDependency);
459 return isLackingDescriptor(args.session, newArtifact)
460 ? new ArtifactDescriptorResult(descriptorRequest)
461 : resolveCachedArtifactDescriptor(
462 args.pool,
463 descriptorRequest,
464 args.session,
465 newContext.withDependency(newDependency).dependency,
466 results,
467 context.parents);
468 }
469
470 static class ParallelDescriptorResolver implements Closeable {
471 private final ExecutorService executorService;
472
473
474
475
476 private final Map<String, Future<DescriptorResolutionResult>> results = new ConcurrentHashMap<>(256);
477
478 ParallelDescriptorResolver(int threads) {
479 this.executorService = ExecutorUtils.threadPool(threads, getClass().getSimpleName() + "-");
480 }
481
482 void resolveDescriptors(Artifact artifact, Callable<DescriptorResolutionResult> callable) {
483 results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> this.executorService.submit(callable));
484 }
485
486 void cacheVersionRangeDescriptor(Artifact artifact, DescriptorResolutionResult resolutionResult) {
487 results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> new DoneFuture<>(resolutionResult));
488 }
489
490 Future<DescriptorResolutionResult> find(Artifact artifact) {
491 return results.get(ArtifactIdUtils.toId(artifact));
492 }
493
494 @Override
495 public void close() {
496 executorService.shutdown();
497 }
498 }
499
500 static class DoneFuture<V> implements Future<V> {
501 private final V v;
502
503 DoneFuture(V v) {
504 this.v = v;
505 }
506
507 @Override
508 public boolean cancel(boolean mayInterruptIfRunning) {
509 return false;
510 }
511
512 @Override
513 public boolean isCancelled() {
514 return false;
515 }
516
517 @Override
518 public boolean isDone() {
519 return true;
520 }
521
522 @Override
523 public V get() throws InterruptedException, ExecutionException {
524 return v;
525 }
526
527 @Override
528 public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
529 return v;
530 }
531 }
532
533 static class DescriptorResolutionResult {
534 Artifact artifact;
535
536 VersionRangeResult rangeResult;
537
538 Map<Version, ArtifactDescriptorResult> descriptors;
539
540 DescriptorResolutionResult(Artifact artifact, VersionRangeResult rangeResult) {
541 this.artifact = artifact;
542 this.rangeResult = rangeResult;
543 this.descriptors = new LinkedHashMap<>(rangeResult.getVersions().size());
544 }
545
546 DescriptorResolutionResult(
547 VersionRangeResult rangeResult, Version version, ArtifactDescriptorResult descriptor) {
548
549
550
551
552
553
554
555 this(descriptor.getRequest().getArtifact(), rangeResult);
556 this.descriptors.put(version, descriptor);
557 }
558
559 List<DescriptorResolutionResult> flatten() {
560 if (descriptors.size() > 1) {
561 return descriptors.entrySet().stream()
562 .map(e -> new DescriptorResolutionResult(rangeResult, e.getKey(), e.getValue()))
563 .collect(Collectors.toList());
564 } else {
565 return Collections.emptyList();
566 }
567 }
568 }
569
570 static class Args {
571
572 final RepositorySystemSession session;
573
574 final boolean ignoreRepos;
575
576 final boolean premanagedState;
577
578 final DataPool pool;
579
580 final Queue<DependencyProcessingContext> dependencyProcessingQueue = new ArrayDeque<>(128);
581
582 final DefaultDependencyCollectionContext collectionContext;
583
584 final DefaultVersionFilterContext versionContext;
585
586 final CollectRequest request;
587
588 final DependencyResolutionSkipper skipper;
589
590 final ParallelDescriptorResolver resolver;
591
592 final AtomicReference<InterruptedException> interruptedException;
593
594 Args(
595 RepositorySystemSession session,
596 DataPool pool,
597 DefaultDependencyCollectionContext collectionContext,
598 DefaultVersionFilterContext versionContext,
599 CollectRequest request,
600 DependencyResolutionSkipper skipper,
601 ParallelDescriptorResolver resolver) {
602 this.session = session;
603 this.request = request;
604 this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories();
605 this.premanagedState = ConfigUtils.getBoolean(session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE);
606 this.pool = pool;
607 this.collectionContext = collectionContext;
608 this.versionContext = versionContext;
609 this.skipper = skipper;
610 this.resolver = resolver;
611 this.interruptedException = new AtomicReference<>(null);
612 }
613 }
614 }