View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.internal.impl.collect;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.eclipse.aether.DefaultRepositorySystemSession;
30  import org.eclipse.aether.RepositoryException;
31  import org.eclipse.aether.RepositorySystemSession;
32  import org.eclipse.aether.RequestTrace;
33  import org.eclipse.aether.artifact.Artifact;
34  import org.eclipse.aether.collection.CollectRequest;
35  import org.eclipse.aether.collection.CollectResult;
36  import org.eclipse.aether.collection.DependencyCollectionException;
37  import org.eclipse.aether.collection.DependencyGraphTransformer;
38  import org.eclipse.aether.collection.DependencyTraverser;
39  import org.eclipse.aether.collection.VersionFilter;
40  import org.eclipse.aether.graph.DefaultDependencyNode;
41  import org.eclipse.aether.graph.Dependency;
42  import org.eclipse.aether.graph.DependencyNode;
43  import org.eclipse.aether.impl.ArtifactDescriptorReader;
44  import org.eclipse.aether.impl.DependencyCollector;
45  import org.eclipse.aether.impl.RemoteRepositoryManager;
46  import org.eclipse.aether.impl.VersionRangeResolver;
47  import org.eclipse.aether.impl.scope.InternalScopeManager;
48  import org.eclipse.aether.internal.impl.Utils;
49  import org.eclipse.aether.repository.ArtifactRepository;
50  import org.eclipse.aether.repository.RemoteRepository;
51  import org.eclipse.aether.resolution.ArtifactDescriptorException;
52  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
53  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
54  import org.eclipse.aether.resolution.VersionRangeRequest;
55  import org.eclipse.aether.resolution.VersionRangeResolutionException;
56  import org.eclipse.aether.resolution.VersionRangeResult;
57  import org.eclipse.aether.scope.ResolutionScope;
58  import org.eclipse.aether.scope.SystemDependencyScope;
59  import org.eclipse.aether.spi.artifact.decorator.ArtifactDecorator;
60  import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory;
61  import org.eclipse.aether.util.ConfigUtils;
62  import org.eclipse.aether.util.graph.transformer.TransformationContextKeys;
63  import org.eclipse.aether.version.Version;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  import static java.util.Objects.requireNonNull;
68  
69  /**
70   * Helper class for delegate implementations, they MUST subclass this class.
71   *
72   * @since 1.8.0
73   */
74  public abstract class DependencyCollectorDelegate implements DependencyCollector {
75      /**
76       * Only exceptions up to the number given in this configuration property are emitted. Exceptions which exceed
77       * that number are swallowed.
78       *
79       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
80       * @configurationType {@link java.lang.Integer}
81       * @configurationDefaultValue {@link #DEFAULT_MAX_EXCEPTIONS}
82       */
83      public static final String CONFIG_PROP_MAX_EXCEPTIONS =
84              DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxExceptions";
85  
86      public static final int DEFAULT_MAX_EXCEPTIONS = 50;
87  
88      /**
89       * Only up to the given amount cyclic dependencies are emitted.
90       *
91       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
92       * @configurationType {@link java.lang.Integer}
93       * @configurationDefaultValue {@link #DEFAULT_MAX_CYCLES}
94       */
95      public static final String CONFIG_PROP_MAX_CYCLES = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxCycles";
96  
97      public static final int DEFAULT_MAX_CYCLES = 10;
98  
99      protected final Logger logger = LoggerFactory.getLogger(getClass());
100 
101     protected final RemoteRepositoryManager remoteRepositoryManager;
102 
103     protected final ArtifactDescriptorReader descriptorReader;
104 
105     protected final VersionRangeResolver versionRangeResolver;
106 
107     protected final Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories;
108 
109     protected DependencyCollectorDelegate(
110             RemoteRepositoryManager remoteRepositoryManager,
111             ArtifactDescriptorReader artifactDescriptorReader,
112             VersionRangeResolver versionRangeResolver,
113             Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories) {
114         this.remoteRepositoryManager =
115                 requireNonNull(remoteRepositoryManager, "remote repository manager cannot be null");
116         this.descriptorReader = requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null");
117         this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null");
118         this.artifactDecoratorFactories =
119                 requireNonNull(artifactDecoratorFactories, "artifact decorator factories cannot be null");
120     }
121 
122     @SuppressWarnings("checkstyle:methodlength")
123     @Override
124     public final CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request)
125             throws DependencyCollectionException {
126         requireNonNull(session, "session cannot be null");
127         requireNonNull(request, "request cannot be null");
128 
129         InternalScopeManager scopeManager = (InternalScopeManager) session.getScopeManager();
130         session = setUpSession(session, request, scopeManager);
131 
132         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
133 
134         CollectResult result = new CollectResult(request);
135 
136         DependencyTraverser depTraverser = session.getDependencyTraverser();
137         VersionFilter verFilter = session.getVersionFilter();
138 
139         Dependency root = request.getRoot();
140         List<RemoteRepository> repositories = request.getRepositories();
141         List<Dependency> dependencies = request.getDependencies();
142         List<Dependency> managedDependencies = request.getManagedDependencies();
143 
144         Map<String, Object> stats = new LinkedHashMap<>();
145         long time1 = System.nanoTime();
146 
147         DefaultDependencyNode node;
148         if (root != null) {
149             List<? extends Version> versions;
150             VersionRangeResult rangeResult;
151             try {
152                 VersionRangeRequest rangeRequest = new VersionRangeRequest(
153                         root.getArtifact(), request.getRepositories(), request.getRequestContext());
154                 rangeRequest.setTrace(trace);
155                 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
156                 versions = filterVersions(root, rangeResult, verFilter, new DefaultVersionFilterContext(session));
157             } catch (VersionRangeResolutionException e) {
158                 result.addException(e);
159                 throw new DependencyCollectionException(result, e.getMessage());
160             }
161 
162             Version version = versions.get(versions.size() - 1);
163             root = root.setArtifact(root.getArtifact().setVersion(version.toString()));
164 
165             ArtifactDescriptorResult descriptorResult;
166             try {
167                 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
168                 descriptorRequest.setArtifact(root.getArtifact());
169                 descriptorRequest.setRepositories(request.getRepositories());
170                 descriptorRequest.setRequestContext(request.getRequestContext());
171                 descriptorRequest.setTrace(trace);
172                 if (isLackingDescriptor(session, root.getArtifact())) {
173                     descriptorResult = new ArtifactDescriptorResult(descriptorRequest);
174                 } else {
175                     descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
176                     for (ArtifactDecorator decorator :
177                             Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
178                         descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
179                     }
180                 }
181             } catch (ArtifactDescriptorException e) {
182                 result.addException(e);
183                 throw new DependencyCollectionException(result, e.getMessage());
184             }
185 
186             root = root.setArtifact(descriptorResult.getArtifact());
187 
188             if (!session.isIgnoreArtifactDescriptorRepositories()) {
189                 repositories = remoteRepositoryManager.aggregateRepositories(
190                         session, repositories, descriptorResult.getRepositories(), true);
191             }
192             dependencies = mergeDeps(dependencies, descriptorResult.getDependencies());
193             managedDependencies = mergeDeps(managedDependencies, descriptorResult.getManagedDependencies());
194 
195             node = new DefaultDependencyNode(root);
196             node.setRequestContext(request.getRequestContext());
197             node.setRelocations(descriptorResult.getRelocations());
198             node.setVersionConstraint(rangeResult.getVersionConstraint());
199             node.setVersion(version);
200             node.setAliases(descriptorResult.getAliases());
201             node.setRepositories(request.getRepositories());
202         } else {
203             node = new DefaultDependencyNode(request.getRootArtifact());
204             node.setRequestContext(request.getRequestContext());
205             node.setRepositories(request.getRepositories());
206         }
207 
208         result.setRoot(node);
209 
210         boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency(root);
211         String errorPath = null;
212         if (traverse && !dependencies.isEmpty()) {
213             DataPool pool = new DataPool(session);
214 
215             DefaultDependencyCollectionContext context = new DefaultDependencyCollectionContext(
216                     session, request.getRootArtifact(), root, managedDependencies);
217 
218             DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext(session);
219 
220             Results results = new Results(result, session);
221 
222             doCollectDependencies(
223                     session,
224                     trace,
225                     pool,
226                     context,
227                     versionContext,
228                     request,
229                     node,
230                     repositories,
231                     dependencies,
232                     managedDependencies,
233                     results);
234 
235             errorPath = results.getErrorPath();
236         }
237 
238         long time2 = System.nanoTime();
239 
240         DependencyGraphTransformer transformer = session.getDependencyGraphTransformer();
241         if (transformer != null) {
242             try {
243                 DefaultDependencyGraphTransformationContext context =
244                         new DefaultDependencyGraphTransformationContext(session);
245                 context.put(TransformationContextKeys.STATS, stats);
246                 result.setRoot(transformer.transformGraph(node, context));
247             } catch (RepositoryException e) {
248                 result.addException(e);
249             }
250         }
251 
252         long time3 = System.nanoTime();
253         if (logger.isDebugEnabled()) {
254             stats.put(getClass().getSimpleName() + ".collectTime", time2 - time1);
255             stats.put(getClass().getSimpleName() + ".transformTime", time3 - time2);
256             logger.debug("Dependency collection stats {}", stats);
257         }
258 
259         if (errorPath != null) {
260             throw new DependencyCollectionException(result, "Failed to collect dependencies at " + errorPath);
261         }
262         if (!result.getExceptions().isEmpty()) {
263             throw new DependencyCollectionException(result);
264         }
265 
266         if (request.getResolutionScope() != null) {
267             return scopeManager.postProcess(request.getResolutionScope(), result);
268         } else {
269             return result;
270         }
271     }
272 
273     /**
274      * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating
275      * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are
276      * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values
277      * should be immutable.
278      *
279      * @param trace   The current trace instance.
280      * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}.
281      * @param path    List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this
282      *                list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may
283      *                change, simplest is to pass here a copy of used list.
284      * @param node    Currently collected node, that collector came by following the passed in path.
285      * @return A child request trance instance, never {@code null}.
286      */
287     protected RequestTrace collectStepTrace(
288             RequestTrace trace, String context, List<DependencyNode> path, Dependency node) {
289         return RequestTrace.newChild(trace, new CollectStepDataImpl(context, path, node));
290     }
291 
292     @SuppressWarnings("checkstyle:parameternumber")
293     protected abstract void doCollectDependencies(
294             RepositorySystemSession session,
295             RequestTrace trace,
296             DataPool pool,
297             DefaultDependencyCollectionContext context,
298             DefaultVersionFilterContext versionContext,
299             CollectRequest request,
300             DependencyNode node,
301             List<RemoteRepository> repositories,
302             List<Dependency> dependencies,
303             List<Dependency> managedDependencies,
304             Results results)
305             throws DependencyCollectionException;
306 
307     protected RepositorySystemSession setUpSession(
308             RepositorySystemSession session, CollectRequest collectRequest, InternalScopeManager scopeManager) {
309         DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession(session);
310         optimized.setArtifactTypeRegistry(CachingArtifactTypeRegistry.newInstance(session));
311 
312         ResolutionScope resolutionScope = collectRequest.getResolutionScope();
313         if (resolutionScope != null) {
314             requireNonNull(scopeManager, "ScopeManager is not set on session");
315             optimized.setDependencySelector(scopeManager.getDependencySelector(resolutionScope));
316             optimized.setDependencyGraphTransformer(scopeManager.getDependencyGraphTransformer(resolutionScope));
317         }
318         return optimized;
319     }
320 
321     protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) {
322         List<Dependency> result;
323         if (dominant == null || dominant.isEmpty()) {
324             result = recessive;
325         } else if (recessive == null || recessive.isEmpty()) {
326             result = dominant;
327         } else {
328             int initialCapacity = dominant.size() + recessive.size();
329             result = new ArrayList<>(initialCapacity);
330             Collection<String> ids = new HashSet<>(initialCapacity, 1.0f);
331             for (Dependency dependency : dominant) {
332                 ids.add(getId(dependency.getArtifact()));
333                 result.add(dependency);
334             }
335             for (Dependency dependency : recessive) {
336                 if (!ids.contains(getId(dependency.getArtifact()))) {
337                     result.add(dependency);
338                 }
339             }
340         }
341         return result;
342     }
343 
344     protected static String getId(Artifact a) {
345         return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension();
346     }
347 
348     @SuppressWarnings("checkstyle:parameternumber")
349     protected static DefaultDependencyNode createDependencyNode(
350             List<Artifact> relocations,
351             PremanagedDependency preManaged,
352             VersionRangeResult rangeResult,
353             Version version,
354             Dependency d,
355             Collection<Artifact> aliases,
356             List<RemoteRepository> repos,
357             String requestContext) {
358         DefaultDependencyNode child = new DefaultDependencyNode(d);
359         preManaged.applyTo(child);
360         child.setRelocations(relocations);
361         child.setVersionConstraint(rangeResult.getVersionConstraint());
362         child.setVersion(version);
363         child.setAliases(aliases);
364         child.setRepositories(repos);
365         child.setRequestContext(requestContext);
366         return child;
367     }
368 
369     protected static DefaultDependencyNode createDependencyNode(
370             List<Artifact> relocations,
371             PremanagedDependency preManaged,
372             VersionRangeResult rangeResult,
373             Version version,
374             Dependency d,
375             ArtifactDescriptorResult descriptorResult,
376             DependencyNode cycleNode) {
377         DefaultDependencyNode child = createDependencyNode(
378                 relocations,
379                 preManaged,
380                 rangeResult,
381                 version,
382                 d,
383                 descriptorResult.getAliases(),
384                 cycleNode.getRepositories(),
385                 cycleNode.getRequestContext());
386         child.setChildren(cycleNode.getChildren());
387         return child;
388     }
389 
390     protected static ArtifactDescriptorRequest createArtifactDescriptorRequest(
391             String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) {
392         ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
393         descriptorRequest.setArtifact(d.getArtifact());
394         descriptorRequest.setRepositories(repositories);
395         descriptorRequest.setRequestContext(requestContext);
396         descriptorRequest.setTrace(requestTrace);
397         return descriptorRequest;
398     }
399 
400     protected static VersionRangeRequest createVersionRangeRequest(
401             String requestContext,
402             RequestTrace requestTrace,
403             List<RemoteRepository> repositories,
404             Dependency dependency) {
405         VersionRangeRequest rangeRequest = new VersionRangeRequest();
406         rangeRequest.setArtifact(dependency.getArtifact());
407         rangeRequest.setRepositories(repositories);
408         rangeRequest.setRequestContext(requestContext);
409         rangeRequest.setTrace(requestTrace);
410         return rangeRequest;
411     }
412 
413     protected VersionRangeResult cachedResolveRangeResult(
414             VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session)
415             throws VersionRangeResolutionException {
416         Object key = pool.toKey(rangeRequest);
417         VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest);
418         if (rangeResult == null) {
419             rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
420             pool.putConstraint(key, rangeResult);
421         }
422         return rangeResult;
423     }
424 
425     protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) {
426         SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
427         return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null;
428     }
429 
430     protected static List<RemoteRepository> getRemoteRepositories(
431             ArtifactRepository repository, List<RemoteRepository> repositories) {
432         if (repository instanceof RemoteRepository) {
433             return Collections.singletonList((RemoteRepository) repository);
434         }
435         if (repository != null) {
436             return Collections.emptyList();
437         }
438         return repositories;
439     }
440 
441     protected static List<? extends Version> filterVersions(
442             Dependency dependency,
443             VersionRangeResult rangeResult,
444             VersionFilter verFilter,
445             DefaultVersionFilterContext verContext)
446             throws VersionRangeResolutionException {
447         if (rangeResult.getVersions().isEmpty()) {
448             throw new VersionRangeResolutionException(
449                     rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range");
450         }
451 
452         List<? extends Version> versions;
453         if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) {
454             verContext.set(dependency, rangeResult);
455             try {
456                 verFilter.filterVersions(verContext);
457             } catch (RepositoryException e) {
458                 throw new VersionRangeResolutionException(
459                         rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e);
460             }
461             versions = verContext.get();
462             if (versions.isEmpty()) {
463                 throw new VersionRangeResolutionException(
464                         rangeResult,
465                         "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions());
466             }
467         } else {
468             versions = rangeResult.getVersions();
469         }
470         return versions;
471     }
472 
473     protected ArtifactDescriptorResult resolveCachedArtifactDescriptor(
474             DataPool pool,
475             ArtifactDescriptorRequest descriptorRequest,
476             RepositorySystemSession session,
477             Dependency d,
478             Results results,
479             List<DependencyNode> nodes) {
480         Object key = pool.toKey(descriptorRequest);
481         ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest);
482         if (descriptorResult == null) {
483             try {
484                 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
485                 for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
486                     descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
487                 }
488                 pool.putDescriptor(key, descriptorResult);
489             } catch (ArtifactDescriptorException e) {
490                 results.addException(d, e, nodes);
491                 pool.putDescriptor(key, e);
492                 return null;
493             }
494         } else if (descriptorResult == DataPool.NO_DESCRIPTOR) {
495             return null;
496         }
497         return descriptorResult;
498     }
499 
500     /**
501      * Helper class used during collection.
502      */
503     protected static class Results {
504 
505         private final CollectResult result;
506 
507         final int maxExceptions;
508 
509         final int maxCycles;
510 
511         String errorPath;
512 
513         public Results(CollectResult result, RepositorySystemSession session) {
514             this.result = result;
515 
516             maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS);
517 
518             maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES);
519         }
520 
521         public CollectResult getResult() {
522             return result;
523         }
524 
525         public String getErrorPath() {
526             return errorPath;
527         }
528 
529         public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) {
530             if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) {
531                 result.addException(e);
532                 if (errorPath == null) {
533                     StringBuilder buffer = new StringBuilder(256);
534                     for (DependencyNode node : nodes) {
535                         if (buffer.length() > 0) {
536                             buffer.append(" -> ");
537                         }
538                         Dependency dep = node.getDependency();
539                         if (dep != null) {
540                             buffer.append(dep.getArtifact());
541                         }
542                     }
543                     if (buffer.length() > 0) {
544                         buffer.append(" -> ");
545                     }
546                     buffer.append(dependency.getArtifact());
547                     errorPath = buffer.toString();
548                 }
549             }
550         }
551 
552         public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) {
553             if (maxCycles < 0 || result.getCycles().size() < maxCycles) {
554                 result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency));
555             }
556         }
557     }
558 }