001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.internal.impl.collect;
020
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.eclipse.aether.DefaultRepositorySystemSession;
030import org.eclipse.aether.RepositoryException;
031import org.eclipse.aether.RepositorySystemSession;
032import org.eclipse.aether.RequestTrace;
033import org.eclipse.aether.artifact.Artifact;
034import org.eclipse.aether.collection.CollectRequest;
035import org.eclipse.aether.collection.CollectResult;
036import org.eclipse.aether.collection.DependencyCollectionException;
037import org.eclipse.aether.collection.DependencyGraphTransformer;
038import org.eclipse.aether.collection.DependencyTraverser;
039import org.eclipse.aether.collection.VersionFilter;
040import org.eclipse.aether.graph.DefaultDependencyNode;
041import org.eclipse.aether.graph.Dependency;
042import org.eclipse.aether.graph.DependencyNode;
043import org.eclipse.aether.impl.ArtifactDescriptorReader;
044import org.eclipse.aether.impl.DependencyCollector;
045import org.eclipse.aether.impl.RemoteRepositoryManager;
046import org.eclipse.aether.impl.VersionRangeResolver;
047import org.eclipse.aether.impl.scope.InternalScopeManager;
048import org.eclipse.aether.repository.ArtifactRepository;
049import org.eclipse.aether.repository.RemoteRepository;
050import org.eclipse.aether.resolution.ArtifactDescriptorException;
051import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
052import org.eclipse.aether.resolution.ArtifactDescriptorResult;
053import org.eclipse.aether.resolution.VersionRangeRequest;
054import org.eclipse.aether.resolution.VersionRangeResolutionException;
055import org.eclipse.aether.resolution.VersionRangeResult;
056import org.eclipse.aether.scope.ResolutionScope;
057import org.eclipse.aether.scope.SystemDependencyScope;
058import org.eclipse.aether.util.ConfigUtils;
059import org.eclipse.aether.util.graph.transformer.TransformationContextKeys;
060import org.eclipse.aether.version.Version;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import static java.util.Objects.requireNonNull;
065
066/**
067 * Helper class for delegate implementations, they MUST subclass this class.
068 *
069 * @since 1.8.0
070 */
071public abstract class DependencyCollectorDelegate implements DependencyCollector {
072    /**
073     * Only exceptions up to the number given in this configuration property are emitted. Exceptions which exceed
074     * that number are swallowed.
075     *
076     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
077     * @configurationType {@link java.lang.Integer}
078     * @configurationDefaultValue {@link #DEFAULT_MAX_EXCEPTIONS}
079     */
080    public static final String CONFIG_PROP_MAX_EXCEPTIONS =
081            DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxExceptions";
082
083    public static final int DEFAULT_MAX_EXCEPTIONS = 50;
084
085    /**
086     * Only up to the given amount cyclic dependencies are emitted.
087     *
088     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
089     * @configurationType {@link java.lang.Integer}
090     * @configurationDefaultValue {@link #DEFAULT_MAX_CYCLES}
091     */
092    public static final String CONFIG_PROP_MAX_CYCLES = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxCycles";
093
094    public static final int DEFAULT_MAX_CYCLES = 10;
095
096    protected final Logger logger = LoggerFactory.getLogger(getClass());
097
098    protected final RemoteRepositoryManager remoteRepositoryManager;
099
100    protected final ArtifactDescriptorReader descriptorReader;
101
102    protected final VersionRangeResolver versionRangeResolver;
103
104    protected DependencyCollectorDelegate(
105            RemoteRepositoryManager remoteRepositoryManager,
106            ArtifactDescriptorReader artifactDescriptorReader,
107            VersionRangeResolver versionRangeResolver) {
108        this.remoteRepositoryManager =
109                requireNonNull(remoteRepositoryManager, "remote repository manager cannot be null");
110        this.descriptorReader = requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null");
111        this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null");
112    }
113
114    @SuppressWarnings("checkstyle:methodlength")
115    @Override
116    public final CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request)
117            throws DependencyCollectionException {
118        requireNonNull(session, "session cannot be null");
119        requireNonNull(request, "request cannot be null");
120
121        InternalScopeManager scopeManager = (InternalScopeManager) session.getScopeManager();
122        session = setUpSession(session, request, scopeManager);
123
124        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
125
126        CollectResult result = new CollectResult(request);
127
128        DependencyTraverser depTraverser = session.getDependencyTraverser();
129        VersionFilter verFilter = session.getVersionFilter();
130
131        Dependency root = request.getRoot();
132        List<RemoteRepository> repositories = request.getRepositories();
133        List<Dependency> dependencies = request.getDependencies();
134        List<Dependency> managedDependencies = request.getManagedDependencies();
135
136        Map<String, Object> stats = new LinkedHashMap<>();
137        long time1 = System.nanoTime();
138
139        DefaultDependencyNode node;
140        if (root != null) {
141            List<? extends Version> versions;
142            VersionRangeResult rangeResult;
143            try {
144                VersionRangeRequest rangeRequest = new VersionRangeRequest(
145                        root.getArtifact(), request.getRepositories(), request.getRequestContext());
146                rangeRequest.setTrace(trace);
147                rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
148                versions = filterVersions(root, rangeResult, verFilter, new DefaultVersionFilterContext(session));
149            } catch (VersionRangeResolutionException e) {
150                result.addException(e);
151                throw new DependencyCollectionException(result, e.getMessage());
152            }
153
154            Version version = versions.get(versions.size() - 1);
155            root = root.setArtifact(root.getArtifact().setVersion(version.toString()));
156
157            ArtifactDescriptorResult descriptorResult;
158            try {
159                ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
160                descriptorRequest.setArtifact(root.getArtifact());
161                descriptorRequest.setRepositories(request.getRepositories());
162                descriptorRequest.setRequestContext(request.getRequestContext());
163                descriptorRequest.setTrace(trace);
164                if (isLackingDescriptor(session, root.getArtifact())) {
165                    descriptorResult = new ArtifactDescriptorResult(descriptorRequest);
166                } else {
167                    descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
168                }
169            } catch (ArtifactDescriptorException e) {
170                result.addException(e);
171                throw new DependencyCollectionException(result, e.getMessage());
172            }
173
174            root = root.setArtifact(descriptorResult.getArtifact());
175
176            if (!session.isIgnoreArtifactDescriptorRepositories()) {
177                repositories = remoteRepositoryManager.aggregateRepositories(
178                        session, repositories, descriptorResult.getRepositories(), true);
179            }
180            dependencies = mergeDeps(dependencies, descriptorResult.getDependencies());
181            managedDependencies = mergeDeps(managedDependencies, descriptorResult.getManagedDependencies());
182
183            node = new DefaultDependencyNode(root);
184            node.setRequestContext(request.getRequestContext());
185            node.setRelocations(descriptorResult.getRelocations());
186            node.setVersionConstraint(rangeResult.getVersionConstraint());
187            node.setVersion(version);
188            node.setAliases(descriptorResult.getAliases());
189            node.setRepositories(request.getRepositories());
190        } else {
191            node = new DefaultDependencyNode(request.getRootArtifact());
192            node.setRequestContext(request.getRequestContext());
193            node.setRepositories(request.getRepositories());
194        }
195
196        result.setRoot(node);
197
198        boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency(root);
199        String errorPath = null;
200        if (traverse && !dependencies.isEmpty()) {
201            DataPool pool = new DataPool(session);
202
203            DefaultDependencyCollectionContext context = new DefaultDependencyCollectionContext(
204                    session, request.getRootArtifact(), root, managedDependencies);
205
206            DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext(session);
207
208            Results results = new Results(result, session);
209
210            doCollectDependencies(
211                    session,
212                    trace,
213                    pool,
214                    context,
215                    versionContext,
216                    request,
217                    node,
218                    repositories,
219                    dependencies,
220                    managedDependencies,
221                    results);
222
223            errorPath = results.getErrorPath();
224        }
225
226        long time2 = System.nanoTime();
227
228        DependencyGraphTransformer transformer = session.getDependencyGraphTransformer();
229        if (transformer != null) {
230            try {
231                DefaultDependencyGraphTransformationContext context =
232                        new DefaultDependencyGraphTransformationContext(session);
233                context.put(TransformationContextKeys.STATS, stats);
234                result.setRoot(transformer.transformGraph(node, context));
235            } catch (RepositoryException e) {
236                result.addException(e);
237            }
238        }
239
240        long time3 = System.nanoTime();
241        if (logger.isDebugEnabled()) {
242            stats.put(getClass().getSimpleName() + ".collectTime", time2 - time1);
243            stats.put(getClass().getSimpleName() + ".transformTime", time3 - time2);
244            logger.debug("Dependency collection stats {}", stats);
245        }
246
247        if (errorPath != null) {
248            throw new DependencyCollectionException(result, "Failed to collect dependencies at " + errorPath);
249        }
250        if (!result.getExceptions().isEmpty()) {
251            throw new DependencyCollectionException(result);
252        }
253
254        if (request.getResolutionScope() != null) {
255            return scopeManager.postProcess(request.getResolutionScope(), result);
256        } else {
257            return result;
258        }
259    }
260
261    /**
262     * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating
263     * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are
264     * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values
265     * should be immutable.
266     *
267     * @param trace   The current trace instance.
268     * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}.
269     * @param path    List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this
270     *                list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may
271     *                change, simplest is to pass here a copy of used list.
272     * @param node    Currently collected node, that collector came by following the passed in path.
273     * @return A child request trance instance, never {@code null}.
274     */
275    protected RequestTrace collectStepTrace(
276            RequestTrace trace, String context, List<DependencyNode> path, Dependency node) {
277        return RequestTrace.newChild(trace, new CollectStepDataImpl(context, path, node));
278    }
279
280    @SuppressWarnings("checkstyle:parameternumber")
281    protected abstract void doCollectDependencies(
282            RepositorySystemSession session,
283            RequestTrace trace,
284            DataPool pool,
285            DefaultDependencyCollectionContext context,
286            DefaultVersionFilterContext versionContext,
287            CollectRequest request,
288            DependencyNode node,
289            List<RemoteRepository> repositories,
290            List<Dependency> dependencies,
291            List<Dependency> managedDependencies,
292            Results results)
293            throws DependencyCollectionException;
294
295    protected RepositorySystemSession setUpSession(
296            RepositorySystemSession session, CollectRequest collectRequest, InternalScopeManager scopeManager) {
297        DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession(session);
298        optimized.setArtifactTypeRegistry(CachingArtifactTypeRegistry.newInstance(session));
299
300        ResolutionScope resolutionScope = collectRequest.getResolutionScope();
301        if (resolutionScope != null) {
302            requireNonNull(scopeManager, "ScopeManager is not set on session");
303            optimized.setDependencySelector(scopeManager.getDependencySelector(resolutionScope));
304            optimized.setDependencyGraphTransformer(scopeManager.getDependencyGraphTransformer(resolutionScope));
305        }
306        return optimized;
307    }
308
309    protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) {
310        List<Dependency> result;
311        if (dominant == null || dominant.isEmpty()) {
312            result = recessive;
313        } else if (recessive == null || recessive.isEmpty()) {
314            result = dominant;
315        } else {
316            int initialCapacity = dominant.size() + recessive.size();
317            result = new ArrayList<>(initialCapacity);
318            Collection<String> ids = new HashSet<>(initialCapacity, 1.0f);
319            for (Dependency dependency : dominant) {
320                ids.add(getId(dependency.getArtifact()));
321                result.add(dependency);
322            }
323            for (Dependency dependency : recessive) {
324                if (!ids.contains(getId(dependency.getArtifact()))) {
325                    result.add(dependency);
326                }
327            }
328        }
329        return result;
330    }
331
332    protected static String getId(Artifact a) {
333        return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension();
334    }
335
336    @SuppressWarnings("checkstyle:parameternumber")
337    protected static DefaultDependencyNode createDependencyNode(
338            List<Artifact> relocations,
339            PremanagedDependency preManaged,
340            VersionRangeResult rangeResult,
341            Version version,
342            Dependency d,
343            Collection<Artifact> aliases,
344            List<RemoteRepository> repos,
345            String requestContext) {
346        DefaultDependencyNode child = new DefaultDependencyNode(d);
347        preManaged.applyTo(child);
348        child.setRelocations(relocations);
349        child.setVersionConstraint(rangeResult.getVersionConstraint());
350        child.setVersion(version);
351        child.setAliases(aliases);
352        child.setRepositories(repos);
353        child.setRequestContext(requestContext);
354        return child;
355    }
356
357    protected static DefaultDependencyNode createDependencyNode(
358            List<Artifact> relocations,
359            PremanagedDependency preManaged,
360            VersionRangeResult rangeResult,
361            Version version,
362            Dependency d,
363            ArtifactDescriptorResult descriptorResult,
364            DependencyNode cycleNode) {
365        DefaultDependencyNode child = createDependencyNode(
366                relocations,
367                preManaged,
368                rangeResult,
369                version,
370                d,
371                descriptorResult.getAliases(),
372                cycleNode.getRepositories(),
373                cycleNode.getRequestContext());
374        child.setChildren(cycleNode.getChildren());
375        return child;
376    }
377
378    protected static ArtifactDescriptorRequest createArtifactDescriptorRequest(
379            String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) {
380        ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
381        descriptorRequest.setArtifact(d.getArtifact());
382        descriptorRequest.setRepositories(repositories);
383        descriptorRequest.setRequestContext(requestContext);
384        descriptorRequest.setTrace(requestTrace);
385        return descriptorRequest;
386    }
387
388    protected static VersionRangeRequest createVersionRangeRequest(
389            String requestContext,
390            RequestTrace requestTrace,
391            List<RemoteRepository> repositories,
392            Dependency dependency) {
393        VersionRangeRequest rangeRequest = new VersionRangeRequest();
394        rangeRequest.setArtifact(dependency.getArtifact());
395        rangeRequest.setRepositories(repositories);
396        rangeRequest.setRequestContext(requestContext);
397        rangeRequest.setTrace(requestTrace);
398        return rangeRequest;
399    }
400
401    protected VersionRangeResult cachedResolveRangeResult(
402            VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session)
403            throws VersionRangeResolutionException {
404        Object key = pool.toKey(rangeRequest);
405        VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest);
406        if (rangeResult == null) {
407            rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
408            pool.putConstraint(key, rangeResult);
409        }
410        return rangeResult;
411    }
412
413    protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) {
414        SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
415        return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null;
416    }
417
418    protected static List<RemoteRepository> getRemoteRepositories(
419            ArtifactRepository repository, List<RemoteRepository> repositories) {
420        if (repository instanceof RemoteRepository) {
421            return Collections.singletonList((RemoteRepository) repository);
422        }
423        if (repository != null) {
424            return Collections.emptyList();
425        }
426        return repositories;
427    }
428
429    protected static List<? extends Version> filterVersions(
430            Dependency dependency,
431            VersionRangeResult rangeResult,
432            VersionFilter verFilter,
433            DefaultVersionFilterContext verContext)
434            throws VersionRangeResolutionException {
435        if (rangeResult.getVersions().isEmpty()) {
436            throw new VersionRangeResolutionException(
437                    rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range");
438        }
439
440        List<? extends Version> versions;
441        if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) {
442            verContext.set(dependency, rangeResult);
443            try {
444                verFilter.filterVersions(verContext);
445            } catch (RepositoryException e) {
446                throw new VersionRangeResolutionException(
447                        rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e);
448            }
449            versions = verContext.get();
450            if (versions.isEmpty()) {
451                throw new VersionRangeResolutionException(
452                        rangeResult,
453                        "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions());
454            }
455        } else {
456            versions = rangeResult.getVersions();
457        }
458        return versions;
459    }
460
461    /**
462     * Helper class used during collection.
463     */
464    protected static class Results {
465
466        private final CollectResult result;
467
468        final int maxExceptions;
469
470        final int maxCycles;
471
472        String errorPath;
473
474        public Results(CollectResult result, RepositorySystemSession session) {
475            this.result = result;
476
477            maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS);
478
479            maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES);
480        }
481
482        public CollectResult getResult() {
483            return result;
484        }
485
486        public String getErrorPath() {
487            return errorPath;
488        }
489
490        public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) {
491            if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) {
492                result.addException(e);
493                if (errorPath == null) {
494                    StringBuilder buffer = new StringBuilder(256);
495                    for (DependencyNode node : nodes) {
496                        if (buffer.length() > 0) {
497                            buffer.append(" -> ");
498                        }
499                        Dependency dep = node.getDependency();
500                        if (dep != null) {
501                            buffer.append(dep.getArtifact());
502                        }
503                    }
504                    if (buffer.length() > 0) {
505                        buffer.append(" -> ");
506                    }
507                    buffer.append(dependency.getArtifact());
508                    errorPath = buffer.toString();
509                }
510            }
511        }
512
513        public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) {
514            if (maxCycles < 0 || result.getCycles().size() < maxCycles) {
515                result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency));
516            }
517        }
518    }
519}