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