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.internal.impl.Utils;
049import org.eclipse.aether.repository.ArtifactRepository;
050import org.eclipse.aether.repository.RemoteRepository;
051import org.eclipse.aether.resolution.ArtifactDescriptorException;
052import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
053import org.eclipse.aether.resolution.ArtifactDescriptorResult;
054import org.eclipse.aether.resolution.VersionRangeRequest;
055import org.eclipse.aether.resolution.VersionRangeResolutionException;
056import org.eclipse.aether.resolution.VersionRangeResult;
057import org.eclipse.aether.scope.ResolutionScope;
058import org.eclipse.aether.scope.SystemDependencyScope;
059import org.eclipse.aether.spi.artifact.decorator.ArtifactDecorator;
060import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory;
061import org.eclipse.aether.util.ConfigUtils;
062import org.eclipse.aether.util.graph.transformer.TransformationContextKeys;
063import org.eclipse.aether.version.Version;
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067import static java.util.Objects.requireNonNull;
068
069/**
070 * Helper class for delegate implementations, they MUST subclass this class.
071 *
072 * @since 1.8.0
073 */
074public abstract class DependencyCollectorDelegate implements DependencyCollector {
075    /**
076     * Only exceptions up to the number given in this configuration property are emitted. Exceptions which exceed
077     * that number are swallowed.
078     *
079     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
080     * @configurationType {@link java.lang.Integer}
081     * @configurationDefaultValue {@link #DEFAULT_MAX_EXCEPTIONS}
082     */
083    public static final String CONFIG_PROP_MAX_EXCEPTIONS =
084            DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxExceptions";
085
086    public static final int DEFAULT_MAX_EXCEPTIONS = 50;
087
088    /**
089     * Only up to the given amount cyclic dependencies are emitted.
090     *
091     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
092     * @configurationType {@link java.lang.Integer}
093     * @configurationDefaultValue {@link #DEFAULT_MAX_CYCLES}
094     */
095    public static final String CONFIG_PROP_MAX_CYCLES = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxCycles";
096
097    public static final int DEFAULT_MAX_CYCLES = 10;
098
099    protected final Logger logger = LoggerFactory.getLogger(getClass());
100
101    protected final RemoteRepositoryManager remoteRepositoryManager;
102
103    protected final ArtifactDescriptorReader descriptorReader;
104
105    protected final VersionRangeResolver versionRangeResolver;
106
107    protected final Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories;
108
109    protected DependencyCollectorDelegate(
110            RemoteRepositoryManager remoteRepositoryManager,
111            ArtifactDescriptorReader artifactDescriptorReader,
112            VersionRangeResolver versionRangeResolver,
113            Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories) {
114        this.remoteRepositoryManager =
115                requireNonNull(remoteRepositoryManager, "remote repository manager cannot be null");
116        this.descriptorReader = requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null");
117        this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null");
118        this.artifactDecoratorFactories =
119                requireNonNull(artifactDecoratorFactories, "artifact decorator factories cannot be null");
120    }
121
122    @SuppressWarnings("checkstyle:methodlength")
123    @Override
124    public final CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request)
125            throws DependencyCollectionException {
126        requireNonNull(session, "session cannot be null");
127        requireNonNull(request, "request cannot be null");
128
129        InternalScopeManager scopeManager = (InternalScopeManager) session.getScopeManager();
130        session = setUpSession(session, request, scopeManager);
131
132        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
133
134        CollectResult result = new CollectResult(request);
135
136        DependencyTraverser depTraverser = session.getDependencyTraverser();
137        VersionFilter verFilter = session.getVersionFilter();
138
139        Dependency root = request.getRoot();
140        List<RemoteRepository> repositories = request.getRepositories();
141        List<Dependency> dependencies = request.getDependencies();
142        List<Dependency> managedDependencies = request.getManagedDependencies();
143
144        Map<String, Object> stats = new LinkedHashMap<>();
145        long time1 = System.nanoTime();
146
147        DefaultDependencyNode node;
148        if (root != null) {
149            List<? extends Version> versions;
150            VersionRangeResult rangeResult;
151            try {
152                VersionRangeRequest rangeRequest = new VersionRangeRequest(
153                        root.getArtifact(), request.getRepositories(), request.getRequestContext());
154                rangeRequest.setTrace(trace);
155                rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
156                versions = filterVersions(root, rangeResult, verFilter, new DefaultVersionFilterContext(session));
157            } catch (VersionRangeResolutionException e) {
158                result.addException(e);
159                throw new DependencyCollectionException(result, e.getMessage());
160            }
161
162            Version version = versions.get(versions.size() - 1);
163            root = root.setArtifact(root.getArtifact().setVersion(version.toString()));
164
165            ArtifactDescriptorResult descriptorResult;
166            try {
167                ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
168                descriptorRequest.setArtifact(root.getArtifact());
169                descriptorRequest.setRepositories(request.getRepositories());
170                descriptorRequest.setRequestContext(request.getRequestContext());
171                descriptorRequest.setTrace(trace);
172                if (isLackingDescriptor(session, root.getArtifact())) {
173                    descriptorResult = new ArtifactDescriptorResult(descriptorRequest);
174                } else {
175                    descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
176                    for (ArtifactDecorator decorator :
177                            Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
178                        descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
179                    }
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        if (request.getResolutionScope() != null) {
267            return scopeManager.postProcess(session, request.getResolutionScope(), result);
268        } else {
269            return result;
270        }
271    }
272
273    /**
274     * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating
275     * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are
276     * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values
277     * should be immutable.
278     *
279     * @param trace   The current trace instance.
280     * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}.
281     * @param path    List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this
282     *                list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may
283     *                change, simplest is to pass here a copy of used list.
284     * @param node    Currently collected node, that collector came by following the passed in path.
285     * @return A child request trance instance, never {@code null}.
286     */
287    protected RequestTrace collectStepTrace(
288            RequestTrace trace, String context, List<DependencyNode> path, Dependency node) {
289        return RequestTrace.newChild(trace, new CollectStepDataImpl(context, path, node));
290    }
291
292    @SuppressWarnings("checkstyle:parameternumber")
293    protected abstract void doCollectDependencies(
294            RepositorySystemSession session,
295            RequestTrace trace,
296            DataPool pool,
297            DefaultDependencyCollectionContext context,
298            DefaultVersionFilterContext versionContext,
299            CollectRequest request,
300            DependencyNode node,
301            List<RemoteRepository> repositories,
302            List<Dependency> dependencies,
303            List<Dependency> managedDependencies,
304            Results results)
305            throws DependencyCollectionException;
306
307    protected RepositorySystemSession setUpSession(
308            RepositorySystemSession session, CollectRequest collectRequest, InternalScopeManager scopeManager) {
309        DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession(session);
310        optimized.setArtifactTypeRegistry(CachingArtifactTypeRegistry.newInstance(session));
311
312        ResolutionScope resolutionScope = collectRequest.getResolutionScope();
313        if (resolutionScope != null) {
314            requireNonNull(scopeManager, "ScopeManager is not set on session");
315            optimized.setDependencySelector(scopeManager.getDependencySelector(session, resolutionScope));
316        }
317        return optimized;
318    }
319
320    protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) {
321        List<Dependency> result;
322        if (dominant == null || dominant.isEmpty()) {
323            result = recessive;
324        } else if (recessive == null || recessive.isEmpty()) {
325            result = dominant;
326        } else {
327            int initialCapacity = dominant.size() + recessive.size();
328            result = new ArrayList<>(initialCapacity);
329            Collection<String> ids = new HashSet<>(initialCapacity, 1.0f);
330            for (Dependency dependency : dominant) {
331                ids.add(getId(dependency.getArtifact()));
332                result.add(dependency);
333            }
334            for (Dependency dependency : recessive) {
335                if (!ids.contains(getId(dependency.getArtifact()))) {
336                    result.add(dependency);
337                }
338            }
339        }
340        return result;
341    }
342
343    protected static String getId(Artifact a) {
344        return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension();
345    }
346
347    @SuppressWarnings("checkstyle:parameternumber")
348    protected static DefaultDependencyNode createDependencyNode(
349            List<Artifact> relocations,
350            PremanagedDependency preManaged,
351            VersionRangeResult rangeResult,
352            Version version,
353            Dependency d,
354            Collection<Artifact> aliases,
355            List<RemoteRepository> repos,
356            String requestContext) {
357        DefaultDependencyNode child = new DefaultDependencyNode(d);
358        preManaged.applyTo(child);
359        child.setRelocations(relocations);
360        child.setVersionConstraint(rangeResult.getVersionConstraint());
361        child.setVersion(version);
362        child.setAliases(aliases);
363        child.setRepositories(repos);
364        child.setRequestContext(requestContext);
365        return child;
366    }
367
368    protected static DefaultDependencyNode createDependencyNode(
369            List<Artifact> relocations,
370            PremanagedDependency preManaged,
371            VersionRangeResult rangeResult,
372            Version version,
373            Dependency d,
374            ArtifactDescriptorResult descriptorResult,
375            DependencyNode cycleNode) {
376        DefaultDependencyNode child = createDependencyNode(
377                relocations,
378                preManaged,
379                rangeResult,
380                version,
381                d,
382                descriptorResult.getAliases(),
383                cycleNode.getRepositories(),
384                cycleNode.getRequestContext());
385        child.setChildren(cycleNode.getChildren());
386        return child;
387    }
388
389    protected static ArtifactDescriptorRequest createArtifactDescriptorRequest(
390            String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) {
391        ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
392        descriptorRequest.setArtifact(d.getArtifact());
393        descriptorRequest.setRepositories(repositories);
394        descriptorRequest.setRequestContext(requestContext);
395        descriptorRequest.setTrace(requestTrace);
396        return descriptorRequest;
397    }
398
399    protected static VersionRangeRequest createVersionRangeRequest(
400            String requestContext,
401            RequestTrace requestTrace,
402            List<RemoteRepository> repositories,
403            Dependency dependency) {
404        VersionRangeRequest rangeRequest = new VersionRangeRequest();
405        rangeRequest.setArtifact(dependency.getArtifact());
406        rangeRequest.setRepositories(repositories);
407        rangeRequest.setRequestContext(requestContext);
408        rangeRequest.setTrace(requestTrace);
409        return rangeRequest;
410    }
411
412    protected VersionRangeResult cachedResolveRangeResult(
413            VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session)
414            throws VersionRangeResolutionException {
415        Object key = pool.toKey(rangeRequest);
416        VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest);
417        if (rangeResult == null) {
418            rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
419            pool.putConstraint(key, rangeResult);
420        }
421        return rangeResult;
422    }
423
424    protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) {
425        SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
426        return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null;
427    }
428
429    protected static List<RemoteRepository> getRemoteRepositories(
430            ArtifactRepository repository, List<RemoteRepository> repositories) {
431        if (repository instanceof RemoteRepository) {
432            return Collections.singletonList((RemoteRepository) repository);
433        }
434        if (repository != null) {
435            return Collections.emptyList();
436        }
437        return repositories;
438    }
439
440    protected static List<? extends Version> filterVersions(
441            Dependency dependency,
442            VersionRangeResult rangeResult,
443            VersionFilter verFilter,
444            DefaultVersionFilterContext verContext)
445            throws VersionRangeResolutionException {
446        if (rangeResult.getVersions().isEmpty()) {
447            throw new VersionRangeResolutionException(
448                    rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range");
449        }
450
451        List<? extends Version> versions;
452        if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) {
453            verContext.set(dependency, rangeResult);
454            try {
455                verFilter.filterVersions(verContext);
456            } catch (RepositoryException e) {
457                throw new VersionRangeResolutionException(
458                        rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e);
459            }
460            versions = verContext.get();
461            if (versions.isEmpty()) {
462                throw new VersionRangeResolutionException(
463                        rangeResult,
464                        "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions());
465            }
466        } else {
467            versions = rangeResult.getVersions();
468        }
469        return versions;
470    }
471
472    protected ArtifactDescriptorResult resolveCachedArtifactDescriptor(
473            DataPool pool,
474            ArtifactDescriptorRequest descriptorRequest,
475            RepositorySystemSession session,
476            Dependency d,
477            Results results,
478            List<DependencyNode> nodes) {
479        DataPool.DescriptorKey key = pool.toKey(descriptorRequest);
480        ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest);
481        if (descriptorResult == null) {
482            try {
483                descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
484                for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
485                    descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
486                }
487                pool.putDescriptor(key, descriptorResult);
488            } catch (ArtifactDescriptorException e) {
489                results.addException(d, e, nodes);
490                pool.putDescriptor(key, e);
491                return null;
492            }
493        } else if (descriptorResult == DataPool.NO_DESCRIPTOR) {
494            return null;
495        }
496        return descriptorResult;
497    }
498
499    /**
500     * Helper class used during collection.
501     */
502    protected static class Results {
503
504        private final CollectResult result;
505
506        final int maxExceptions;
507
508        final int maxCycles;
509
510        String errorPath;
511
512        public Results(CollectResult result, RepositorySystemSession session) {
513            this.result = result;
514
515            maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS);
516
517            maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES);
518        }
519
520        public CollectResult getResult() {
521            return result;
522        }
523
524        public String getErrorPath() {
525            return errorPath;
526        }
527
528        public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) {
529            if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) {
530                result.addException(e);
531                if (errorPath == null) {
532                    StringBuilder buffer = new StringBuilder(256);
533                    for (DependencyNode node : nodes) {
534                        if (buffer.length() > 0) {
535                            buffer.append(" -> ");
536                        }
537                        Dependency dep = node.getDependency();
538                        if (dep != null) {
539                            buffer.append(dep.getArtifact());
540                        }
541                    }
542                    if (buffer.length() > 0) {
543                        buffer.append(" -> ");
544                    }
545                    buffer.append(dependency.getArtifact());
546                    errorPath = buffer.toString();
547                }
548            }
549        }
550
551        public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) {
552            if (maxCycles < 0 || result.getCycles().size() < maxCycles) {
553                result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency));
554            }
555        }
556    }
557}