View Javadoc
1   package org.eclipse.aether.internal.impl.collect;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.eclipse.aether.DefaultRepositorySystemSession;
31  import org.eclipse.aether.RepositoryException;
32  import org.eclipse.aether.RepositorySystemSession;
33  import org.eclipse.aether.RequestTrace;
34  import org.eclipse.aether.artifact.Artifact;
35  import org.eclipse.aether.artifact.ArtifactProperties;
36  import org.eclipse.aether.collection.CollectRequest;
37  import org.eclipse.aether.collection.CollectResult;
38  import org.eclipse.aether.collection.DependencyCollectionException;
39  import org.eclipse.aether.collection.DependencyGraphTransformer;
40  import org.eclipse.aether.collection.DependencyTraverser;
41  import org.eclipse.aether.collection.VersionFilter;
42  import org.eclipse.aether.graph.DefaultDependencyNode;
43  import org.eclipse.aether.graph.Dependency;
44  import org.eclipse.aether.graph.DependencyNode;
45  import org.eclipse.aether.impl.ArtifactDescriptorReader;
46  import org.eclipse.aether.impl.DependencyCollector;
47  import org.eclipse.aether.impl.RemoteRepositoryManager;
48  import org.eclipse.aether.impl.VersionRangeResolver;
49  import org.eclipse.aether.repository.ArtifactRepository;
50  import org.eclipse.aether.repository.RemoteRepository;
51  import org.eclipse.aether.resolution.ArtifactDescriptorException;
52  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
53  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
54  import org.eclipse.aether.resolution.VersionRangeRequest;
55  import org.eclipse.aether.resolution.VersionRangeResolutionException;
56  import org.eclipse.aether.resolution.VersionRangeResult;
57  import org.eclipse.aether.spi.locator.ServiceLocator;
58  import org.eclipse.aether.util.ConfigUtils;
59  import org.eclipse.aether.util.graph.transformer.TransformationContextKeys;
60  import org.eclipse.aether.version.Version;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  import static java.util.Objects.requireNonNull;
65  
66  /**
67   * Helper class for delegate implementations, they MUST subclass this class.
68   *
69   * @since 1.8.0
70   */
71  public abstract class DependencyCollectorDelegate implements DependencyCollector
72  {
73      protected static final String CONFIG_PROP_MAX_EXCEPTIONS = "aether.dependencyCollector.maxExceptions";
74  
75      protected static final int CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT = 50;
76  
77      protected static final String CONFIG_PROP_MAX_CYCLES = "aether.dependencyCollector.maxCycles";
78  
79      protected static final int CONFIG_PROP_MAX_CYCLES_DEFAULT = 10;
80  
81      protected final Logger logger = LoggerFactory.getLogger( getClass() );
82  
83      protected RemoteRepositoryManager remoteRepositoryManager;
84  
85      protected ArtifactDescriptorReader descriptorReader;
86  
87      protected VersionRangeResolver versionRangeResolver;
88  
89      /**
90       * Default ctor for SL.
91       *
92       * @deprecated Will be dropped once SL gone.
93       */
94      @Deprecated
95      protected DependencyCollectorDelegate()
96      {
97          // enables default constructor
98      }
99  
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 }