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(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(resolutionScope));
316            optimized.setDependencyGraphTransformer(scopeManager.getDependencyGraphTransformer(resolutionScope));
317        }
318        return optimized;
319    }
320
321    protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) {
322        List<Dependency> result;
323        if (dominant == null || dominant.isEmpty()) {
324            result = recessive;
325        } else if (recessive == null || recessive.isEmpty()) {
326            result = dominant;
327        } else {
328            int initialCapacity = dominant.size() + recessive.size();
329            result = new ArrayList<>(initialCapacity);
330            Collection<String> ids = new HashSet<>(initialCapacity, 1.0f);
331            for (Dependency dependency : dominant) {
332                ids.add(getId(dependency.getArtifact()));
333                result.add(dependency);
334            }
335            for (Dependency dependency : recessive) {
336                if (!ids.contains(getId(dependency.getArtifact()))) {
337                    result.add(dependency);
338                }
339            }
340        }
341        return result;
342    }
343
344    protected static String getId(Artifact a) {
345        return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension();
346    }
347
348    @SuppressWarnings("checkstyle:parameternumber")
349    protected static DefaultDependencyNode createDependencyNode(
350            List<Artifact> relocations,
351            PremanagedDependency preManaged,
352            VersionRangeResult rangeResult,
353            Version version,
354            Dependency d,
355            Collection<Artifact> aliases,
356            List<RemoteRepository> repos,
357            String requestContext) {
358        DefaultDependencyNode child = new DefaultDependencyNode(d);
359        preManaged.applyTo(child);
360        child.setRelocations(relocations);
361        child.setVersionConstraint(rangeResult.getVersionConstraint());
362        child.setVersion(version);
363        child.setAliases(aliases);
364        child.setRepositories(repos);
365        child.setRequestContext(requestContext);
366        return child;
367    }
368
369    protected static DefaultDependencyNode createDependencyNode(
370            List<Artifact> relocations,
371            PremanagedDependency preManaged,
372            VersionRangeResult rangeResult,
373            Version version,
374            Dependency d,
375            ArtifactDescriptorResult descriptorResult,
376            DependencyNode cycleNode) {
377        DefaultDependencyNode child = createDependencyNode(
378                relocations,
379                preManaged,
380                rangeResult,
381                version,
382                d,
383                descriptorResult.getAliases(),
384                cycleNode.getRepositories(),
385                cycleNode.getRequestContext());
386        child.setChildren(cycleNode.getChildren());
387        return child;
388    }
389
390    protected static ArtifactDescriptorRequest createArtifactDescriptorRequest(
391            String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) {
392        ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
393        descriptorRequest.setArtifact(d.getArtifact());
394        descriptorRequest.setRepositories(repositories);
395        descriptorRequest.setRequestContext(requestContext);
396        descriptorRequest.setTrace(requestTrace);
397        return descriptorRequest;
398    }
399
400    protected static VersionRangeRequest createVersionRangeRequest(
401            String requestContext,
402            RequestTrace requestTrace,
403            List<RemoteRepository> repositories,
404            Dependency dependency) {
405        VersionRangeRequest rangeRequest = new VersionRangeRequest();
406        rangeRequest.setArtifact(dependency.getArtifact());
407        rangeRequest.setRepositories(repositories);
408        rangeRequest.setRequestContext(requestContext);
409        rangeRequest.setTrace(requestTrace);
410        return rangeRequest;
411    }
412
413    protected VersionRangeResult cachedResolveRangeResult(
414            VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session)
415            throws VersionRangeResolutionException {
416        Object key = pool.toKey(rangeRequest);
417        VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest);
418        if (rangeResult == null) {
419            rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest);
420            pool.putConstraint(key, rangeResult);
421        }
422        return rangeResult;
423    }
424
425    protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) {
426        SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope();
427        return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null;
428    }
429
430    protected static List<RemoteRepository> getRemoteRepositories(
431            ArtifactRepository repository, List<RemoteRepository> repositories) {
432        if (repository instanceof RemoteRepository) {
433            return Collections.singletonList((RemoteRepository) repository);
434        }
435        if (repository != null) {
436            return Collections.emptyList();
437        }
438        return repositories;
439    }
440
441    protected static List<? extends Version> filterVersions(
442            Dependency dependency,
443            VersionRangeResult rangeResult,
444            VersionFilter verFilter,
445            DefaultVersionFilterContext verContext)
446            throws VersionRangeResolutionException {
447        if (rangeResult.getVersions().isEmpty()) {
448            throw new VersionRangeResolutionException(
449                    rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range");
450        }
451
452        List<? extends Version> versions;
453        if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) {
454            verContext.set(dependency, rangeResult);
455            try {
456                verFilter.filterVersions(verContext);
457            } catch (RepositoryException e) {
458                throw new VersionRangeResolutionException(
459                        rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e);
460            }
461            versions = verContext.get();
462            if (versions.isEmpty()) {
463                throw new VersionRangeResolutionException(
464                        rangeResult,
465                        "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions());
466            }
467        } else {
468            versions = rangeResult.getVersions();
469        }
470        return versions;
471    }
472
473    protected ArtifactDescriptorResult resolveCachedArtifactDescriptor(
474            DataPool pool,
475            ArtifactDescriptorRequest descriptorRequest,
476            RepositorySystemSession session,
477            Dependency d,
478            Results results,
479            List<DependencyNode> nodes) {
480        Object key = pool.toKey(descriptorRequest);
481        ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest);
482        if (descriptorResult == null) {
483            try {
484                descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
485                for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
486                    descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
487                }
488                pool.putDescriptor(key, descriptorResult);
489            } catch (ArtifactDescriptorException e) {
490                results.addException(d, e, nodes);
491                pool.putDescriptor(key, e);
492                return null;
493            }
494        } else if (descriptorResult == DataPool.NO_DESCRIPTOR) {
495            return null;
496        }
497        return descriptorResult;
498    }
499
500    /**
501     * Helper class used during collection.
502     */
503    protected static class Results {
504
505        private final CollectResult result;
506
507        final int maxExceptions;
508
509        final int maxCycles;
510
511        String errorPath;
512
513        public Results(CollectResult result, RepositorySystemSession session) {
514            this.result = result;
515
516            maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS);
517
518            maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES);
519        }
520
521        public CollectResult getResult() {
522            return result;
523        }
524
525        public String getErrorPath() {
526            return errorPath;
527        }
528
529        public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) {
530            if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) {
531                result.addException(e);
532                if (errorPath == null) {
533                    StringBuilder buffer = new StringBuilder(256);
534                    for (DependencyNode node : nodes) {
535                        if (buffer.length() > 0) {
536                            buffer.append(" -> ");
537                        }
538                        Dependency dep = node.getDependency();
539                        if (dep != null) {
540                            buffer.append(dep.getArtifact());
541                        }
542                    }
543                    if (buffer.length() > 0) {
544                        buffer.append(" -> ");
545                    }
546                    buffer.append(dependency.getArtifact());
547                    errorPath = buffer.toString();
548                }
549            }
550        }
551
552        public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) {
553            if (maxCycles < 0 || result.getCycles().size() < maxCycles) {
554                result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency));
555            }
556        }
557    }
558}