View Javadoc

1   package org.apache.maven.artifact.resolver;
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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
24  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
25  import org.apache.maven.artifact.metadata.ResolutionGroup;
26  import org.apache.maven.artifact.repository.ArtifactRepository;
27  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
28  import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
29  import org.apache.maven.artifact.versioning.ArtifactVersion;
30  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
31  import org.apache.maven.artifact.versioning.VersionRange;
32  import org.apache.maven.artifact.versioning.ManagedVersionMap;
33  
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.Iterator;
37  import java.util.LinkedHashMap;
38  import java.util.LinkedHashSet;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Set;
42  
43  /**
44   * Default implementation of the artifact collector.
45   *
46   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
47   * @version $Id: DefaultArtifactCollector.java 736543 2009-01-22 03:33:16Z jdcasey $
48   */
49  public class DefaultArtifactCollector
50      implements ArtifactCollector
51  {
52      public ArtifactResolutionResult collect( Set artifacts, Artifact originatingArtifact,
53                                               ArtifactRepository localRepository, List remoteRepositories,
54                                               ArtifactMetadataSource source, ArtifactFilter filter, List listeners )
55          throws ArtifactResolutionException
56      {
57          return collect( artifacts, originatingArtifact, Collections.EMPTY_MAP, localRepository, remoteRepositories,
58                          source, filter, listeners );
59      }
60  
61      public ArtifactResolutionResult collect( Set artifacts, Artifact originatingArtifact, Map managedVersions,
62                                               ArtifactRepository localRepository, List remoteRepositories,
63                                               ArtifactMetadataSource source, ArtifactFilter filter, List listeners )
64          throws ArtifactResolutionException
65      {
66          Map resolvedArtifacts = new LinkedHashMap();
67  
68          ResolutionNode root = new ResolutionNode( originatingArtifact, remoteRepositories );
69  
70          root.addDependencies( artifacts, remoteRepositories, filter );
71  
72          ManagedVersionMap versionMap = getManagedVersionsMap( originatingArtifact, managedVersions );
73  
74          recurse( originatingArtifact, root, resolvedArtifacts, versionMap, localRepository, remoteRepositories, source, filter,
75                   listeners );
76  
77          Set set = new LinkedHashSet();
78  
79          for ( Iterator i = resolvedArtifacts.values().iterator(); i.hasNext(); )
80          {
81              List nodes = (List) i.next();
82              for ( Iterator j = nodes.iterator(); j.hasNext(); )
83              {
84                  ResolutionNode node = (ResolutionNode) j.next();
85                  if ( !node.equals( root ) && node.isActive() )
86                  {
87                      Artifact artifact = node.getArtifact();
88  
89                      if ( node.filterTrail( filter ) )
90                      {
91                          // If it was optional and not a direct dependency,
92                          // we don't add it or its children, just allow the update of the version and scope
93                          if ( node.isChildOfRootNode() || !artifact.isOptional() )
94                          {
95                              artifact.setDependencyTrail( node.getDependencyTrail() );
96  
97                              set.add( node );
98                          }
99                      }
100                 }
101             }
102         }
103 
104         ArtifactResolutionResult result = new ArtifactResolutionResult();
105         result.setArtifactResolutionNodes( set );
106         return result;
107     }
108 
109     /**
110      * Get the map of managed versions, removing the originating artifact if it is also in managed versions
111      * @param originatingArtifact artifact we are processing
112      * @param managedVersions original managed versions
113      */
114     private ManagedVersionMap getManagedVersionsMap( Artifact originatingArtifact, Map managedVersions )
115     {
116         ManagedVersionMap versionMap;
117         if ( managedVersions != null && managedVersions instanceof ManagedVersionMap )
118         {
119             versionMap = (ManagedVersionMap) managedVersions;
120         }
121         else
122         {
123             versionMap = new ManagedVersionMap( managedVersions );
124         }
125 
126         /* remove the originating artifact if it is also in managed versions to avoid being modified during resolution */
127         Artifact managedOriginatingArtifact = (Artifact) versionMap.get( originatingArtifact.getDependencyConflictId() );
128         if ( managedOriginatingArtifact != null )
129         {
130             // TODO we probably want to warn the user that he is building an artifact with
131             // different values than in dependencyManagement
132             if ( managedVersions instanceof ManagedVersionMap )
133             {
134                 /* avoid modifying the managedVersions parameter creating a new map */
135                 versionMap = new ManagedVersionMap( managedVersions );
136             }
137             versionMap.remove( originatingArtifact.getDependencyConflictId() );
138         }
139 
140         return versionMap;
141     }
142 
143     private void recurse( Artifact originatingArtifact, ResolutionNode node, Map resolvedArtifacts, ManagedVersionMap managedVersions,
144                           ArtifactRepository localRepository, List remoteRepositories, ArtifactMetadataSource source,
145                           ArtifactFilter filter, List listeners )
146         throws CyclicDependencyException, ArtifactResolutionException, OverConstrainedVersionException
147     {
148         fireEvent( ResolutionListener.TEST_ARTIFACT, listeners, node );
149 
150         Object key = node.getKey();
151 
152         // TODO: Does this check need to happen here?  Had to add the same call
153         // below when we iterate on child nodes -- will that suffice?
154         if ( managedVersions.containsKey( key ))
155         {
156             manageArtifact( node, managedVersions, listeners );
157         }
158 
159         List previousNodes = (List) resolvedArtifacts.get( key );
160         if ( previousNodes != null )
161         {
162             for ( Iterator i = previousNodes.iterator(); i.hasNext(); )
163             {
164                 ResolutionNode previous = (ResolutionNode) i.next();
165 
166                 if ( previous.isActive() )
167                 {
168                     // Version mediation
169                     VersionRange previousRange = previous.getArtifact().getVersionRange();
170                     VersionRange currentRange = node.getArtifact().getVersionRange();
171 
172                     if ( previousRange != null && currentRange != null )
173                     {
174                         // TODO: shouldn't need to double up on this work, only done for simplicity of handling recommended
175                         // version but the restriction is identical
176                         VersionRange newRange = previousRange.restrict( currentRange );
177                         // TODO: ick. this forces the OCE that should have come from the previous call. It is still correct
178                         if ( newRange.isSelectedVersionKnown( previous.getArtifact() ) )
179                         {
180                             fireEvent( ResolutionListener.RESTRICT_RANGE, listeners, node, previous.getArtifact(),
181                                        newRange );
182                         }
183                         previous.getArtifact().setVersionRange( newRange );
184                         node.getArtifact().setVersionRange( currentRange.restrict( previousRange ) );
185 
186                         //Select an appropriate available version from the (now restricted) range
187                         //Note this version was selected before to get the appropriate POM
188                         //But it was reset by the call to setVersionRange on restricting the version
189                         ResolutionNode[] resetNodes = {previous, node};
190                         for ( int j = 0; j < 2; j++ )
191                         {
192                             Artifact resetArtifact = resetNodes[j].getArtifact();
193 
194                             //MNG-2123: if the previous node was not a range, then it wouldn't have any available
195                             //versions. We just clobbered the selected version above. (why? i have no idea.)
196                             //So since we are here and this is ranges we must go figure out the version (for a third time...)
197                             if ( resetArtifact.getVersion() == null && resetArtifact.getVersionRange() != null )
198                             {
199 
200                                 // go find the version. This is a total hack. See previous comment.
201                                 List versions = resetArtifact.getAvailableVersions();
202                                 if ( versions == null )
203                                 {
204                                     try
205                                     {
206                                         versions =
207                                             source.retrieveAvailableVersions( resetArtifact, localRepository,
208                                                                               remoteRepositories );
209                                         resetArtifact.setAvailableVersions( versions );
210                                     }
211                                     catch ( ArtifactMetadataRetrievalException e )
212                                     {
213                                         resetArtifact.setDependencyTrail( node.getDependencyTrail() );
214                                         throw new ArtifactResolutionException(
215                                                                                "Unable to get dependency information: " +
216                                                                                    e.getMessage(), resetArtifact,
217                                                                                remoteRepositories, e );
218                                     }
219                                 }
220                                 //end hack
221 
222                                 //MNG-2861: match version can return null
223                                 ArtifactVersion selectedVersion = resetArtifact.getVersionRange().matchVersion( resetArtifact.getAvailableVersions() );
224                                 if (selectedVersion != null)
225                                 {
226                                   resetArtifact.selectVersion( selectedVersion.toString() );
227                                 }
228                                 else
229                                 {
230                                   throw new OverConstrainedVersionException(" Unable to find a version in "+ resetArtifact.getAvailableVersions()+" to match the range "+ resetArtifact.getVersionRange(), resetArtifact);
231                                 }
232 
233                                 fireEvent( ResolutionListener.SELECT_VERSION_FROM_RANGE, listeners, resetNodes[j] );
234                             }
235                         }
236                     }
237 
238                     // Conflict Resolution
239                     // TODO: use as conflict resolver(s), chain
240 
241                     // TODO: should this be part of mediation?
242                     // previous one is more dominant
243                     ResolutionNode nearest;
244                     ResolutionNode farthest;
245                     if ( previous.getDepth() <= node.getDepth() )
246                     {
247                         nearest = previous;
248                         farthest = node;
249                     }
250                     else
251                     {
252                         nearest = node;
253                         farthest = previous;
254                     }
255 
256                     if ( checkScopeUpdate( farthest, nearest, listeners ) )
257                     {
258                         // if we need to update scope of nearest to use farthest scope, use the nearest version, but farthest scope
259                         nearest.disable();
260                         farthest.getArtifact().setVersion( nearest.getArtifact().getVersion() );
261                         fireEvent( ResolutionListener.OMIT_FOR_NEARER, listeners, nearest, farthest.getArtifact() );
262                     }
263                     else
264                     {
265                         farthest.disable();
266                         fireEvent( ResolutionListener.OMIT_FOR_NEARER, listeners, farthest, nearest.getArtifact() );
267                     }
268                 }
269             }
270         }
271         else
272         {
273             previousNodes = new ArrayList();
274             resolvedArtifacts.put( key, previousNodes );
275         }
276         previousNodes.add( node );
277 
278         if ( node.isActive() )
279         {
280             fireEvent( ResolutionListener.INCLUDE_ARTIFACT, listeners, node );
281         }
282 
283         // don't pull in the transitive deps of a system-scoped dependency.
284         if ( node.isActive() && !Artifact.SCOPE_SYSTEM.equals( node.getArtifact().getScope() ) )
285         {
286             fireEvent( ResolutionListener.PROCESS_CHILDREN, listeners, node );
287 
288             Artifact parentArtifact = node.getArtifact();
289             
290             for ( Iterator i = node.getChildrenIterator(); i.hasNext(); )
291             {
292                 ResolutionNode child = (ResolutionNode) i.next();
293                 // We leave in optional ones, but don't pick up its dependencies
294                 if ( !child.isResolved() && ( !child.getArtifact().isOptional() || child.isChildOfRootNode() ) )
295                 {
296                     List childRemoteRepositories = child.getRemoteRepositories();
297                     Artifact artifact = child.getArtifact();
298                     
299                     try
300                     {
301                         Object childKey;
302                         do
303                         {
304                             childKey = child.getKey();
305 
306                             if ( managedVersions.containsKey( childKey ) )
307                             {
308                                 // If this child node is a managed dependency, ensure
309                                 // we are using the dependency management version
310                                 // of this child if applicable b/c we want to use the
311                                 // managed version's POM, *not* any other version's POM.
312                                 // We retrieve the POM below in the retrieval step.
313                                 manageArtifact( child, managedVersions, listeners );
314 
315                                 // Also, we need to ensure that any exclusions it presents are
316                                 // added to the artifact before we retrieve the metadata
317                                 // for the artifact; otherwise we may end up with unwanted
318                                 // dependencies.
319                                 Artifact ma = (Artifact) managedVersions.get( childKey );
320                                 ArtifactFilter managedExclusionFilter = ma.getDependencyFilter();
321                                 if ( null != managedExclusionFilter )
322                                 {
323                                     if ( null != artifact.getDependencyFilter() )
324                                     {
325                                         AndArtifactFilter aaf = new AndArtifactFilter();
326                                         aaf.add( artifact.getDependencyFilter() );
327                                         aaf.add( managedExclusionFilter );
328                                         artifact.setDependencyFilter( aaf );
329                                     }
330                                     else
331                                     {
332                                         artifact.setDependencyFilter( managedExclusionFilter );
333                                     }
334                                 }
335                             }
336 
337                             if ( artifact.getVersion() == null )
338                             {
339                                 // set the recommended version
340                                 // TODO: maybe its better to just pass the range through to retrieval and use a transformation?
341                                 ArtifactVersion version;
342                                 if ( artifact.isSelectedVersionKnown() )
343                                 {
344                                     version = artifact.getSelectedVersion();
345                                 }
346                                 else
347                                 {
348                                     //go find the version
349                                     List versions = artifact.getAvailableVersions();
350                                     if ( versions == null )
351                                     {
352                                         versions = source.retrieveAvailableVersions( artifact, localRepository,
353                                                                                      childRemoteRepositories );
354                                         artifact.setAvailableVersions( versions );
355                                     }
356 
357                                     Collections.sort( versions );
358 
359                                     VersionRange versionRange = artifact.getVersionRange();
360 
361                                     version = versionRange.matchVersion( versions );
362 
363                                     if ( version == null )
364                                     {
365                                         // Getting the dependency trail so it can be logged in the exception
366                                         artifact.setDependencyTrail( node.getDependencyTrail() );
367 
368                                         if ( versions.isEmpty() )
369                                         {
370                                             throw new OverConstrainedVersionException(
371                                                 "No versions are present in the repository for the artifact with a range " +
372                                                     versionRange, artifact, childRemoteRepositories );
373                                         }
374 
375                                         throw new OverConstrainedVersionException( "Couldn't find a version in " +
376                                             versions + " to match range " + versionRange, artifact,
377                                             childRemoteRepositories );
378                                     }
379                                 }
380 
381                                 //this is dangerous because artifact.getSelectedVersion() can
382                                 //return null. However it is ok here because we first check if the
383                                 //selected version is known. As currently coded we can't get a null here.
384                                 artifact.selectVersion( version.toString() );
385                                 fireEvent( ResolutionListener.SELECT_VERSION_FROM_RANGE, listeners, child );
386                             }
387 
388                             Artifact relocated = source.retrieveRelocatedArtifact( artifact, localRepository, childRemoteRepositories );
389                             if ( !artifact.equals( relocated ) )
390                             {
391                                 relocated.setDependencyFilter( artifact.getDependencyFilter() );
392                                 artifact = relocated;
393                                 child.setArtifact( artifact );
394                             }
395                         }
396                         while( !childKey.equals( child.getKey() ) );
397                         
398                         if ( parentArtifact != null && parentArtifact.getDependencyFilter() != null && !parentArtifact.getDependencyFilter().include( artifact ) )
399                         {
400                             // MNG-3769: the [probably relocated] artifact is excluded. 
401                             // We could process exclusions on relocated artifact details in the
402                             // MavenMetadataSource.createArtifacts(..) step, BUT that would
403                             // require resolving the POM from the repository very early on in
404                             // the build.
405                             continue;
406                         }
407 
408                         artifact.setDependencyTrail( node.getDependencyTrail() );
409                         ResolutionGroup rGroup = source.retrieve( artifact, localRepository, remoteRepositories );
410 
411                         //TODO might be better to have source.retrieve() throw a specific exception for this situation
412                         //and catch here rather than have it return null
413                         if ( rGroup == null )
414                         {
415                             //relocated dependency artifact is declared excluded, no need to add and recurse further
416                             continue;
417                         }
418 
419                         child.addDependencies( rGroup.getArtifacts(), rGroup.getResolutionRepositories(), filter );
420 
421                     }
422                     catch ( CyclicDependencyException e )
423                     {
424                         // would like to throw this, but we have crappy stuff in the repo
425 
426                         fireEvent( ResolutionListener.OMIT_FOR_CYCLE, listeners,
427                                    new ResolutionNode( e.getArtifact(), remoteRepositories, child ) );
428                     }
429                     catch ( ArtifactMetadataRetrievalException e )
430                     {
431                         artifact.setDependencyTrail( node.getDependencyTrail() );
432                         throw new ArtifactResolutionException(
433                             "Unable to get dependency information: " + e.getMessage(), artifact, remoteRepositories,
434                             e );
435                     }
436 
437                     recurse( originatingArtifact, child, resolvedArtifacts, managedVersions, localRepository, remoteRepositories, source,
438                              filter, listeners );
439                 }
440             }
441 
442             fireEvent( ResolutionListener.FINISH_PROCESSING_CHILDREN, listeners, node );
443         }
444     }
445 
446     private void manageArtifact( ResolutionNode node, ManagedVersionMap managedVersions, List listeners )
447     {
448         Artifact artifact = (Artifact) managedVersions.get( node.getKey() );
449 
450         // Before we update the version of the artifact, we need to know
451         // whether we are working on a transitive dependency or not.  This
452         // allows depMgmt to always override transitive dependencies, while
453         // explicit child override depMgmt (viz. depMgmt should only
454         // provide defaults to children, but should override transitives).
455         // We can do this by calling isChildOfRootNode on the current node.
456 
457         if ( artifact.getVersion() != null
458                         && ( node.isChildOfRootNode() ? node.getArtifact().getVersion() == null : true ) )
459         {
460             fireEvent( ResolutionListener.MANAGE_ARTIFACT_VERSION, listeners, node, artifact );
461             node.getArtifact().setVersion( artifact.getVersion() );
462         }
463 
464         if ( artifact.getScope() != null
465                         && ( node.isChildOfRootNode() ? node.getArtifact().getScope() == null : true ) )
466         {
467             fireEvent( ResolutionListener.MANAGE_ARTIFACT_SCOPE, listeners, node, artifact );
468             node.getArtifact().setScope( artifact.getScope() );
469         }
470     }
471 
472     /**
473      * Check if the scope needs to be updated.
474      * <a href="http://docs.codehaus.org/x/IGU#DependencyMediationandConflictResolution-Scoperesolution">More info</a>.
475      *
476      * @param farthest  farthest resolution node
477      * @param nearest   nearest resolution node
478      * @param listeners
479      */
480     boolean checkScopeUpdate( ResolutionNode farthest, ResolutionNode nearest, List listeners )
481     {
482         boolean updateScope = false;
483         Artifact farthestArtifact = farthest.getArtifact();
484         Artifact nearestArtifact = nearest.getArtifact();
485 
486         /* farthest is runtime and nearest has lower priority, change to runtime */
487         if ( Artifact.SCOPE_RUNTIME.equals( farthestArtifact.getScope() ) && (
488             Artifact.SCOPE_TEST.equals( nearestArtifact.getScope() ) ||
489                 Artifact.SCOPE_PROVIDED.equals( nearestArtifact.getScope() ) ) )
490         {
491             updateScope = true;
492         }
493 
494         /* farthest is compile and nearest is not (has lower priority), change to compile */
495         if ( Artifact.SCOPE_COMPILE.equals( farthestArtifact.getScope() ) &&
496             !Artifact.SCOPE_COMPILE.equals( nearestArtifact.getScope() ) )
497         {
498             updateScope = true;
499         }
500 
501         /* current POM rules all, if nearest is in current pom, do not update its scope */
502         if ( nearest.getDepth() < 2 && updateScope )
503         {
504             updateScope = false;
505 
506             fireEvent( ResolutionListener.UPDATE_SCOPE_CURRENT_POM, listeners, nearest, farthestArtifact );
507         }
508 
509         if ( updateScope )
510         {
511             fireEvent( ResolutionListener.UPDATE_SCOPE, listeners, nearest, farthestArtifact );
512 
513             // previously we cloned the artifact, but it is more effecient to just update the scope
514             // if problems are later discovered that the original object needs its original scope value, cloning may
515             // again be appropriate
516             nearestArtifact.setScope( farthestArtifact.getScope() );
517         }
518 
519         return updateScope;
520     }
521 
522     private void fireEvent( int event, List listeners, ResolutionNode node )
523     {
524         fireEvent( event, listeners, node, null );
525     }
526 
527     private void fireEvent( int event, List listeners, ResolutionNode node, Artifact replacement )
528     {
529         fireEvent( event, listeners, node, replacement, null );
530     }
531 
532     private void fireEvent( int event, List listeners, ResolutionNode node, Artifact replacement,
533                             VersionRange newRange )
534     {
535         for ( Iterator i = listeners.iterator(); i.hasNext(); )
536         {
537             ResolutionListener listener = (ResolutionListener) i.next();
538 
539             switch ( event )
540             {
541                 case ResolutionListener.TEST_ARTIFACT:
542                     listener.testArtifact( node.getArtifact() );
543                     break;
544                 case ResolutionListener.PROCESS_CHILDREN:
545                     listener.startProcessChildren( node.getArtifact() );
546                     break;
547                 case ResolutionListener.FINISH_PROCESSING_CHILDREN:
548                     listener.endProcessChildren( node.getArtifact() );
549                     break;
550                 case ResolutionListener.INCLUDE_ARTIFACT:
551                     listener.includeArtifact( node.getArtifact() );
552                     break;
553                 case ResolutionListener.OMIT_FOR_NEARER:
554                     listener.omitForNearer( node.getArtifact(), replacement );
555                     break;
556                 case ResolutionListener.OMIT_FOR_CYCLE:
557                     listener.omitForCycle( node.getArtifact() );
558                     break;
559                 case ResolutionListener.UPDATE_SCOPE:
560                     listener.updateScope( node.getArtifact(), replacement.getScope() );
561                     break;
562                 case ResolutionListener.UPDATE_SCOPE_CURRENT_POM:
563                     listener.updateScopeCurrentPom( node.getArtifact(), replacement.getScope() );
564                     break;
565                 case ResolutionListener.MANAGE_ARTIFACT_VERSION:
566                     if (listener instanceof ResolutionListenerForDepMgmt) {
567                         ResolutionListenerForDepMgmt asImpl = (ResolutionListenerForDepMgmt) listener;
568                         asImpl.manageArtifactVersion( node.getArtifact(), replacement );
569                     } else {
570                         listener.manageArtifact( node.getArtifact(), replacement );
571                     }
572                     break;
573                 case ResolutionListener.MANAGE_ARTIFACT_SCOPE:
574                     if (listener instanceof ResolutionListenerForDepMgmt) {
575                         ResolutionListenerForDepMgmt asImpl = (ResolutionListenerForDepMgmt) listener;
576                         asImpl.manageArtifactScope( node.getArtifact(), replacement );
577                     } else {
578                         listener.manageArtifact( node.getArtifact(), replacement );
579                     }
580                     break;
581                 case ResolutionListener.SELECT_VERSION_FROM_RANGE:
582                     listener.selectVersionFromRange( node.getArtifact() );
583                     break;
584                 case ResolutionListener.RESTRICT_RANGE:
585                     if ( node.getArtifact().getVersionRange().hasRestrictions() ||
586                         replacement.getVersionRange().hasRestrictions() )
587                     {
588                         listener.restrictRange( node.getArtifact(), replacement, newRange );
589                     }
590                     break;
591                 default:
592                     throw new IllegalStateException( "Unknown event: " + event );
593             }
594         }
595     }
596 
597 }