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