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.AndArtifactFilter;
28  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
29  import org.apache.maven.artifact.versioning.ArtifactVersion;
30  import org.apache.maven.artifact.versioning.ManagedVersionMap;
31  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
32  import org.apache.maven.artifact.versioning.VersionRange;
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 801437 2009-08-05 22:06:54Z 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 
294                 // We leave in optional ones, but don't pick up its dependencies
295                 if ( !child.isResolved() && ( !child.getArtifact().isOptional() || child.isChildOfRootNode() ) )
296                 {
297                     Artifact artifact = child.getArtifact();
298                     artifact.setDependencyTrail( node.getDependencyTrail() );
299                     
300                     List childRemoteRepositories = child.getRemoteRepositories();
301                     try
302                     {
303                         Object childKey;
304                         do
305                         {
306                             childKey = child.getKey();
307 
308                             if ( managedVersions.containsKey( childKey ) )
309                             {
310                                 // If this child node is a managed dependency, ensure
311                                 // we are using the dependency management version
312                                 // of this child if applicable b/c we want to use the
313                                 // managed version's POM, *not* any other version's POM.
314                                 // We retrieve the POM below in the retrieval step.
315                                 manageArtifact( child, managedVersions, listeners );
316 
317                                 // Also, we need to ensure that any exclusions it presents are
318                                 // added to the artifact before we retrieve the metadata
319                                 // for the artifact; otherwise we may end up with unwanted
320                                 // dependencies.
321                                 Artifact ma = (Artifact) managedVersions.get( childKey );
322                                 ArtifactFilter managedExclusionFilter = ma.getDependencyFilter();
323                                 if ( null != managedExclusionFilter )
324                                 {
325                                     if ( null != artifact.getDependencyFilter() )
326                                     {
327                                         AndArtifactFilter aaf = new AndArtifactFilter();
328                                         aaf.add( artifact.getDependencyFilter() );
329                                         aaf.add( managedExclusionFilter );
330                                         artifact.setDependencyFilter( aaf );
331                                     }
332                                     else
333                                     {
334                                         artifact.setDependencyFilter( managedExclusionFilter );
335                                     }
336                                 }
337                             }
338 
339                             if ( artifact.getVersion() == null )
340                             {
341                                 // set the recommended version
342                                 // TODO: maybe its better to just pass the range through to retrieval and use a transformation?
343                                 ArtifactVersion version;
344                                 if ( artifact.isSelectedVersionKnown() )
345                                 {
346                                     version = artifact.getSelectedVersion();
347                                 }
348                                 else
349                                 {
350                                     //go find the version
351                                     List versions = artifact.getAvailableVersions();
352                                     if ( versions == null )
353                                     {
354                                         versions = source.retrieveAvailableVersions( artifact, localRepository,
355                                                                                      childRemoteRepositories );
356                                         artifact.setAvailableVersions( versions );
357                                     }
358 
359                                     Collections.sort( versions );
360 
361                                     VersionRange versionRange = artifact.getVersionRange();
362 
363                                     version = versionRange.matchVersion( versions );
364 
365                                     if ( version == null )
366                                     {
367                                         if ( versions.isEmpty() )
368                                         {
369                                             throw new OverConstrainedVersionException(
370                                                 "No versions are present in the repository for the artifact with a range " +
371                                                     versionRange, artifact, childRemoteRepositories );
372                                         }
373 
374                                         throw new OverConstrainedVersionException( "Couldn't find a version in " +
375                                             versions + " to match range " + versionRange, artifact,
376                                             childRemoteRepositories );
377                                     }
378                                 }
379 
380                                 //this is dangerous because artifact.getSelectedVersion() can
381                                 //return null. However it is ok here because we first check if the
382                                 //selected version is known. As currently coded we can't get a null here.
383                                 artifact.selectVersion( version.toString() );
384                                 fireEvent( ResolutionListener.SELECT_VERSION_FROM_RANGE, listeners, child );
385                             }
386 
387                             Artifact relocated = source.retrieveRelocatedArtifact( artifact, localRepository, childRemoteRepositories );
388                             if ( relocated != null && !artifact.equals( relocated ) )
389                             {
390                                 relocated.setDependencyFilter( artifact.getDependencyFilter() );
391                                 artifact = relocated;
392                                 child.setArtifact( artifact );
393                             }
394                         }
395                         while( !childKey.equals( child.getKey() ) );
396                         
397                         if ( parentArtifact != null && parentArtifact.getDependencyFilter() != null && !parentArtifact.getDependencyFilter().include( artifact ) )
398                         {
399                             // MNG-3769: the [probably relocated] artifact is excluded. 
400                             // We could process exclusions on relocated artifact details in the
401                             // MavenMetadataSource.createArtifacts(..) step, BUT that would
402                             // require resolving the POM from the repository very early on in
403                             // the build.
404                             continue;
405                         }
406 
407                         ResolutionGroup rGroup = source.retrieve( artifact, localRepository, childRemoteRepositories );
408 
409                         //TODO might be better to have source.retrieve() throw a specific exception for this situation
410                         //and catch here rather than have it return null
411                         if ( rGroup == null )
412                         {
413                             //relocated dependency artifact is declared excluded, no need to add and recurse further
414                             continue;
415                         }
416 
417                         child.addDependencies( rGroup.getArtifacts(), rGroup.getResolutionRepositories(), filter );
418 
419                     }
420                     catch ( CyclicDependencyException e )
421                     {
422                         // would like to throw this, but we have crappy stuff in the repo
423 
424                         fireEvent( ResolutionListener.OMIT_FOR_CYCLE, listeners,
425                                    new ResolutionNode( e.getArtifact(), childRemoteRepositories, child ) );
426                     }
427                     catch ( ArtifactMetadataRetrievalException e )
428                     {
429                         artifact.setDependencyTrail( node.getDependencyTrail() );
430                         throw new ArtifactResolutionException(
431                             "Unable to get dependency information: " + e.getMessage(), artifact, childRemoteRepositories,
432                             e );
433                     }
434 
435                     recurse( originatingArtifact, child, resolvedArtifacts, managedVersions, localRepository, childRemoteRepositories, source,
436                              filter, listeners );
437                 }
438             }
439 
440             fireEvent( ResolutionListener.FINISH_PROCESSING_CHILDREN, listeners, node );
441         }
442     }
443 
444     private void manageArtifact( ResolutionNode node, ManagedVersionMap managedVersions, List listeners )
445     {
446         Artifact artifact = (Artifact) managedVersions.get( node.getKey() );
447 
448         // Before we update the version of the artifact, we need to know
449         // whether we are working on a transitive dependency or not.  This
450         // allows depMgmt to always override transitive dependencies, while
451         // explicit child override depMgmt (viz. depMgmt should only
452         // provide defaults to children, but should override transitives).
453         // We can do this by calling isChildOfRootNode on the current node.
454 
455         if ( artifact.getVersion() != null
456                         && ( node.isChildOfRootNode() ? node.getArtifact().getVersion() == null : true ) )
457         {
458             fireEvent( ResolutionListener.MANAGE_ARTIFACT_VERSION, listeners, node, artifact );
459             node.getArtifact().setVersion( artifact.getVersion() );
460         }
461 
462         if ( artifact.getScope() != null
463                         && ( node.isChildOfRootNode() ? node.getArtifact().getScope() == null : true ) )
464         {
465             fireEvent( ResolutionListener.MANAGE_ARTIFACT_SCOPE, listeners, node, artifact );
466             node.getArtifact().setScope( artifact.getScope() );
467         }
468     }
469 
470     /**
471      * Check if the scope needs to be updated.
472      * <a href="http://docs.codehaus.org/x/IGU#DependencyMediationandConflictResolution-Scoperesolution">More info</a>.
473      *
474      * @param farthest  farthest resolution node
475      * @param nearest   nearest resolution node
476      * @param listeners
477      */
478     boolean checkScopeUpdate( ResolutionNode farthest, ResolutionNode nearest, List listeners )
479     {
480         boolean updateScope = false;
481         Artifact farthestArtifact = farthest.getArtifact();
482         Artifact nearestArtifact = nearest.getArtifact();
483 
484         /* farthest is runtime and nearest has lower priority, change to runtime */
485         if ( Artifact.SCOPE_RUNTIME.equals( farthestArtifact.getScope() ) && (
486             Artifact.SCOPE_TEST.equals( nearestArtifact.getScope() ) ||
487                 Artifact.SCOPE_PROVIDED.equals( nearestArtifact.getScope() ) ) )
488         {
489             updateScope = true;
490         }
491 
492         /* farthest is compile and nearest is not (has lower priority), change to compile */
493         if ( Artifact.SCOPE_COMPILE.equals( farthestArtifact.getScope() ) &&
494             !Artifact.SCOPE_COMPILE.equals( nearestArtifact.getScope() ) )
495         {
496             updateScope = true;
497         }
498 
499         /* current POM rules all, if nearest is in current pom, do not update its scope */
500         if ( nearest.getDepth() < 2 && updateScope )
501         {
502             updateScope = false;
503 
504             fireEvent( ResolutionListener.UPDATE_SCOPE_CURRENT_POM, listeners, nearest, farthestArtifact );
505         }
506 
507         if ( updateScope )
508         {
509             fireEvent( ResolutionListener.UPDATE_SCOPE, listeners, nearest, farthestArtifact );
510 
511             // previously we cloned the artifact, but it is more effecient to just update the scope
512             // if problems are later discovered that the original object needs its original scope value, cloning may
513             // again be appropriate
514             nearestArtifact.setScope( farthestArtifact.getScope() );
515         }
516 
517         return updateScope;
518     }
519 
520     private void fireEvent( int event, List listeners, ResolutionNode node )
521     {
522         fireEvent( event, listeners, node, null );
523     }
524 
525     private void fireEvent( int event, List listeners, ResolutionNode node, Artifact replacement )
526     {
527         fireEvent( event, listeners, node, replacement, null );
528     }
529 
530     private void fireEvent( int event, List listeners, ResolutionNode node, Artifact replacement,
531                             VersionRange newRange )
532     {
533         for ( Iterator i = listeners.iterator(); i.hasNext(); )
534         {
535             ResolutionListener listener = (ResolutionListener) i.next();
536 
537             switch ( event )
538             {
539                 case ResolutionListener.TEST_ARTIFACT:
540                     listener.testArtifact( node.getArtifact() );
541                     break;
542                 case ResolutionListener.PROCESS_CHILDREN:
543                     listener.startProcessChildren( node.getArtifact() );
544                     break;
545                 case ResolutionListener.FINISH_PROCESSING_CHILDREN:
546                     listener.endProcessChildren( node.getArtifact() );
547                     break;
548                 case ResolutionListener.INCLUDE_ARTIFACT:
549                     listener.includeArtifact( node.getArtifact() );
550                     break;
551                 case ResolutionListener.OMIT_FOR_NEARER:
552                     listener.omitForNearer( node.getArtifact(), replacement );
553                     break;
554                 case ResolutionListener.OMIT_FOR_CYCLE:
555                     listener.omitForCycle( node.getArtifact() );
556                     break;
557                 case ResolutionListener.UPDATE_SCOPE:
558                     listener.updateScope( node.getArtifact(), replacement.getScope() );
559                     break;
560                 case ResolutionListener.UPDATE_SCOPE_CURRENT_POM:
561                     listener.updateScopeCurrentPom( node.getArtifact(), replacement.getScope() );
562                     break;
563                 case ResolutionListener.MANAGE_ARTIFACT_VERSION:
564                     if (listener instanceof ResolutionListenerForDepMgmt) {
565                         ResolutionListenerForDepMgmt asImpl = (ResolutionListenerForDepMgmt) listener;
566                         asImpl.manageArtifactVersion( node.getArtifact(), replacement );
567                     } else {
568                         listener.manageArtifact( node.getArtifact(), replacement );
569                     }
570                     break;
571                 case ResolutionListener.MANAGE_ARTIFACT_SCOPE:
572                     if (listener instanceof ResolutionListenerForDepMgmt) {
573                         ResolutionListenerForDepMgmt asImpl = (ResolutionListenerForDepMgmt) listener;
574                         asImpl.manageArtifactScope( node.getArtifact(), replacement );
575                     } else {
576                         listener.manageArtifact( node.getArtifact(), replacement );
577                     }
578                     break;
579                 case ResolutionListener.SELECT_VERSION_FROM_RANGE:
580                     listener.selectVersionFromRange( node.getArtifact() );
581                     break;
582                 case ResolutionListener.RESTRICT_RANGE:
583                     if ( node.getArtifact().getVersionRange().hasRestrictions() ||
584                         replacement.getVersionRange().hasRestrictions() )
585                     {
586                         listener.restrictRange( node.getArtifact(), replacement, newRange );
587                     }
588                     break;
589                 default:
590                     throw new IllegalStateException( "Unknown event: " + event );
591             }
592         }
593     }
594 
595 }