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