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