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