001package org.eclipse.aether.internal.impl.collect.bf;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import javax.inject.Inject;
023import javax.inject.Named;
024import javax.inject.Singleton;
025
026import java.util.ArrayDeque;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.List;
030import java.util.Queue;
031
032import org.eclipse.aether.RepositorySystemSession;
033import org.eclipse.aether.RequestTrace;
034import org.eclipse.aether.artifact.Artifact;
035import org.eclipse.aether.collection.CollectRequest;
036import org.eclipse.aether.collection.DependencyManager;
037import org.eclipse.aether.collection.DependencySelector;
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.RemoteRepositoryManager;
045import org.eclipse.aether.impl.VersionRangeResolver;
046import org.eclipse.aether.internal.impl.collect.DataPool;
047import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollectionContext;
048import org.eclipse.aether.internal.impl.collect.DefaultVersionFilterContext;
049import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate;
050import org.eclipse.aether.internal.impl.collect.PremanagedDependency;
051import org.eclipse.aether.repository.RemoteRepository;
052import org.eclipse.aether.resolution.ArtifactDescriptorException;
053import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
054import org.eclipse.aether.resolution.ArtifactDescriptorResult;
055import org.eclipse.aether.resolution.VersionRangeRequest;
056import org.eclipse.aether.resolution.VersionRangeResolutionException;
057import org.eclipse.aether.resolution.VersionRangeResult;
058import org.eclipse.aether.spi.locator.Service;
059import org.eclipse.aether.util.ConfigUtils;
060import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
061import org.eclipse.aether.version.Version;
062
063import static org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle.find;
064
065/**
066 * Breadth-first {@link org.eclipse.aether.impl.DependencyCollector}
067 *
068 * @since 1.8.0
069 */
070@Singleton
071@Named( BfDependencyCollector.NAME )
072public class BfDependencyCollector
073    extends DependencyCollectorDelegate implements Service
074{
075    public static final String NAME = "bf";
076
077    /**
078     * The key in the repository session's {@link RepositorySystemSession#getConfigProperties()
079     * configuration properties} used to store a {@link Boolean} flag controlling the resolver's skip mode.
080     *
081     * @since 1.8.0
082     */
083    public static final String CONFIG_PROP_SKIPPER = "aether.dependencyCollector.bf.skipper";
084
085    /**
086     * The default value for {@link #CONFIG_PROP_SKIPPER}, {@code true}.
087     *
088     * @since 1.8.0
089     */
090    public static final boolean CONFIG_PROP_SKIPPER_DEFAULT = true;
091
092    /**
093     * Default ctor for SL.
094     *
095     * @deprecated Will be dropped once SL gone.
096     */
097    @Deprecated
098    public BfDependencyCollector()
099    {
100        // enables default constructor
101    }
102
103    @Inject
104    BfDependencyCollector( RemoteRepositoryManager remoteRepositoryManager,
105                           ArtifactDescriptorReader artifactDescriptorReader,
106                           VersionRangeResolver versionRangeResolver )
107    {
108        super( remoteRepositoryManager, artifactDescriptorReader, versionRangeResolver );
109    }
110
111    @SuppressWarnings( "checkstyle:parameternumber" )
112    @Override
113    protected void doCollectDependencies( RepositorySystemSession session, RequestTrace trace, DataPool pool,
114                                          DefaultDependencyCollectionContext context,
115                                          DefaultVersionFilterContext versionContext,
116                                          CollectRequest request, DependencyNode node,
117                                          List<RemoteRepository> repositories, List<Dependency> dependencies,
118                                          List<Dependency> managedDependencies, Results results )
119    {
120        boolean useSkip = ConfigUtils.getBoolean(
121                session, CONFIG_PROP_SKIPPER_DEFAULT, CONFIG_PROP_SKIPPER
122        );
123        if ( useSkip )
124        {
125            logger.debug( "Collector skip mode enabled" );
126        }
127
128        Args args =
129                new Args( session, pool, context, versionContext, request,
130                        useSkip ? DependencyResolutionSkipper.defaultSkipper()
131                                : DependencyResolutionSkipper.neverSkipper() );
132
133        DependencySelector rootDepSelector = session.getDependencySelector() != null
134                ? session.getDependencySelector().deriveChildSelector( context ) : null;
135        DependencyManager rootDepManager = session.getDependencyManager() != null
136                ? session.getDependencyManager().deriveChildManager( context ) : null;
137        DependencyTraverser rootDepTraverser = session.getDependencyTraverser() != null
138                ? session.getDependencyTraverser().deriveChildTraverser( context ) : null;
139        VersionFilter rootVerFilter = session.getVersionFilter() != null
140                ? session.getVersionFilter().deriveChildFilter( context ) : null;
141
142        List<DependencyNode> parents = Collections.singletonList( node );
143        for ( Dependency dependency : dependencies )
144        {
145            args.dependencyProcessingQueue.add(
146                    new DependencyProcessingContext( rootDepSelector, rootDepManager, rootDepTraverser,
147                            rootVerFilter, repositories, managedDependencies, parents,
148                            dependency ) );
149        }
150
151        while ( !args.dependencyProcessingQueue.isEmpty() )
152        {
153            processDependency( args, trace, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(),
154                    false );
155        }
156
157        args.skipper.report();
158    }
159
160    @SuppressWarnings( "checkstyle:parameternumber" )
161    private void processDependency( Args args, RequestTrace parent, Results results,
162                                    DependencyProcessingContext context, List<Artifact> relocations,
163                                    boolean disableVersionManagement )
164    {
165        if ( context.depSelector != null && !context.depSelector.selectDependency( context.dependency ) )
166        {
167            return;
168        }
169
170        RequestTrace trace = collectStepTrace( parent, args.request.getRequestContext(), context.parents,
171                context.dependency );
172        PremanagedDependency preManaged =
173                PremanagedDependency.create( context.depManager, context.dependency, disableVersionManagement,
174                        args.premanagedState );
175        Dependency dependency = preManaged.getManagedDependency();
176
177        boolean noDescriptor = isLackingDescriptor( dependency.getArtifact() );
178
179        boolean traverse =
180                !noDescriptor && ( context.depTraverser == null || context.depTraverser.traverseDependency(
181                        dependency ) );
182
183        List<? extends Version> versions;
184        VersionRangeResult rangeResult;
185        try
186        {
187            VersionRangeRequest rangeRequest = createVersionRangeRequest( args.request.getRequestContext(), trace,
188                    context.repositories, dependency );
189
190            rangeResult = cachedResolveRangeResult( rangeRequest, args.pool, args.session );
191
192            versions = filterVersions( dependency, rangeResult, context.verFilter, args.versionContext );
193        }
194        catch ( VersionRangeResolutionException e )
195        {
196            results.addException( dependency, e, context.parents );
197            return;
198        }
199
200        //Resolve newer version first to maximize benefits of skipper
201        Collections.reverse( versions );
202        for ( Version version : versions )
203        {
204            Artifact originalArtifact = dependency.getArtifact().setVersion( version.toString() );
205            Dependency d = dependency.setArtifact( originalArtifact );
206
207            ArtifactDescriptorRequest descriptorRequest = createArtifactDescriptorRequest(
208                    args.request.getRequestContext(), trace, context.repositories, d );
209
210            final ArtifactDescriptorResult descriptorResult =
211                    noDescriptor
212                            ? new ArtifactDescriptorResult( descriptorRequest )
213                            : resolveCachedArtifactDescriptor( args.pool, descriptorRequest, args.session,
214                                    context.withDependency( d ), results );
215
216            if ( descriptorResult != null )
217            {
218                d = d.setArtifact( descriptorResult.getArtifact() );
219
220                int cycleEntry = find( context.parents, d.getArtifact() );
221                if ( cycleEntry >= 0 )
222                {
223                    results.addCycle( context.parents, cycleEntry, d );
224                    DependencyNode cycleNode = context.parents.get( cycleEntry );
225                    if ( cycleNode.getDependency() != null )
226                    {
227                        DefaultDependencyNode child =
228                                createDependencyNode( relocations, preManaged, rangeResult, version, d,
229                                        descriptorResult, cycleNode );
230                        context.getParent().getChildren().add( child );
231                        continue;
232                    }
233                }
234
235                if ( !descriptorResult.getRelocations().isEmpty() )
236                {
237                    boolean disableVersionManagementSubsequently =
238                        originalArtifact.getGroupId().equals( d.getArtifact().getGroupId() )
239                            && originalArtifact.getArtifactId().equals( d.getArtifact().getArtifactId() );
240
241                    processDependency( args, parent, results, context.withDependency( d ),
242                            descriptorResult.getRelocations(), disableVersionManagementSubsequently );
243                    return;
244                }
245                else
246                {
247                    d = args.pool.intern( d.setArtifact( args.pool.intern( d.getArtifact() ) ) );
248
249                    List<RemoteRepository> repos =
250                        getRemoteRepositories( rangeResult.getRepository( version ), context.repositories );
251
252                    DefaultDependencyNode child =
253                        createDependencyNode( relocations, preManaged, rangeResult, version, d,
254                                              descriptorResult.getAliases(), repos, args.request.getRequestContext() );
255
256                    context.getParent().getChildren().add( child );
257
258                    boolean recurse = traverse && !descriptorResult.getDependencies().isEmpty();
259                    DependencyProcessingContext parentContext = context.withDependency( d );
260                    if ( recurse )
261                    {
262                        doRecurse( args, parentContext, descriptorResult, child );
263                    }
264                    else if ( !args.skipper.skipResolution( child, parentContext.parents ) )
265                    {
266                        List<DependencyNode> parents = new ArrayList<>( parentContext.parents.size() + 1 );
267                        parents.addAll( parentContext.parents );
268                        parents.add( child );
269                        args.skipper.cache( child, parents );
270                    }
271                }
272            }
273            else
274            {
275                List<RemoteRepository> repos =
276                    getRemoteRepositories( rangeResult.getRepository( version ), context.repositories );
277                DefaultDependencyNode child =
278                    createDependencyNode( relocations, preManaged, rangeResult, version, d, null, repos,
279                                          args.request.getRequestContext() );
280                context.getParent().getChildren().add( child );
281            }
282        }
283    }
284
285    @SuppressWarnings( "checkstyle:parameternumber" )
286    private void doRecurse( Args args, DependencyProcessingContext parentContext,
287                            ArtifactDescriptorResult descriptorResult, DefaultDependencyNode child )
288    {
289        DefaultDependencyCollectionContext context = args.collectionContext;
290        context.set( parentContext.dependency, descriptorResult.getManagedDependencies() );
291
292        DependencySelector childSelector =
293                parentContext.depSelector != null ? parentContext.depSelector.deriveChildSelector( context ) : null;
294        DependencyManager childManager =
295                parentContext.depManager != null ? parentContext.depManager.deriveChildManager( context ) : null;
296        DependencyTraverser childTraverser =
297                parentContext.depTraverser != null ? parentContext.depTraverser.deriveChildTraverser( context ) : null;
298        VersionFilter childFilter =
299                parentContext.verFilter != null ? parentContext.verFilter.deriveChildFilter( context ) : null;
300
301        final List<RemoteRepository> childRepos =
302                args.ignoreRepos
303                        ? parentContext.repositories
304                        : remoteRepositoryManager.aggregateRepositories( args.session, parentContext.repositories,
305                        descriptorResult.getRepositories(), true );
306
307        Object key =
308                args.pool.toKey( parentContext.dependency.getArtifact(), childRepos, childSelector, childManager,
309                        childTraverser, childFilter );
310
311        List<DependencyNode> children = args.pool.getChildren( key );
312        if ( children == null )
313        {
314            boolean skipResolution = args.skipper.skipResolution( child, parentContext.parents );
315            if ( !skipResolution )
316            {
317                List<DependencyNode> parents = new ArrayList<>( parentContext.parents.size() + 1 );
318                parents.addAll( parentContext.parents );
319                parents.add( child );
320                for ( Dependency dependency : descriptorResult.getDependencies() )
321                {
322                    args.dependencyProcessingQueue.add(
323                            new DependencyProcessingContext( childSelector, childManager, childTraverser, childFilter,
324                                    childRepos, descriptorResult.getManagedDependencies(), parents, dependency ) );
325
326                }
327                args.pool.putChildren( key, child.getChildren() );
328                args.skipper.cache( child, parents );
329            }
330        }
331        else
332        {
333            child.setChildren( children );
334        }
335    }
336
337    private ArtifactDescriptorResult resolveCachedArtifactDescriptor( DataPool pool,
338                                                                      ArtifactDescriptorRequest descriptorRequest,
339                                                                      RepositorySystemSession session,
340                                                                      DependencyProcessingContext context,
341                                                                      Results results )
342    {
343        Object key = pool.toKey( descriptorRequest );
344        ArtifactDescriptorResult descriptorResult = pool.getDescriptor( key, descriptorRequest );
345        if ( descriptorResult == null )
346        {
347            try
348            {
349                descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest );
350                pool.putDescriptor( key, descriptorResult );
351            }
352            catch ( ArtifactDescriptorException e )
353            {
354                results.addException( context.dependency, e, context.parents );
355                pool.putDescriptor( key, e );
356                return null;
357            }
358
359        }
360        else if ( descriptorResult == DataPool.NO_DESCRIPTOR )
361        {
362            return null;
363        }
364
365        return descriptorResult;
366    }
367
368    static class Args
369    {
370
371        final RepositorySystemSession session;
372
373        final boolean ignoreRepos;
374
375        final boolean premanagedState;
376
377        final DataPool pool;
378
379        final Queue<DependencyProcessingContext> dependencyProcessingQueue = new ArrayDeque<>( 128 );
380
381        final DefaultDependencyCollectionContext collectionContext;
382
383        final DefaultVersionFilterContext versionContext;
384
385        final CollectRequest request;
386
387        final DependencyResolutionSkipper skipper;
388
389        Args( RepositorySystemSession session, DataPool pool,
390                     DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext,
391                     CollectRequest request, DependencyResolutionSkipper skipper )
392        {
393            this.session = session;
394            this.request = request;
395            this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories();
396            this.premanagedState = ConfigUtils.getBoolean( session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE );
397            this.pool = pool;
398            this.collectionContext = collectionContext;
399            this.versionContext = versionContext;
400            this.skipper = skipper;
401        }
402
403    }
404
405}