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