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