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