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