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