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