blob: c9e507f27af5dec5f85d3c003638ed117d5cd512 [file] [log] [blame]
package org.apache.maven.artifact.resolver;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.metadata.ResolutionGroup;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.ManagedVersionMap;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.VersionRange;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Default implementation of the artifact collector.
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @version $Id$
*/
public class DefaultArtifactCollector
implements ArtifactCollector
{
public ArtifactResolutionResult collect( Set artifacts, Artifact originatingArtifact,
ArtifactRepository localRepository, List remoteRepositories,
ArtifactMetadataSource source, ArtifactFilter filter, List listeners )
throws ArtifactResolutionException
{
return collect( artifacts, originatingArtifact, Collections.EMPTY_MAP, localRepository, remoteRepositories,
source, filter, listeners );
}
public ArtifactResolutionResult collect( Set artifacts, Artifact originatingArtifact, Map managedVersions,
ArtifactRepository localRepository, List remoteRepositories,
ArtifactMetadataSource source, ArtifactFilter filter, List listeners )
throws ArtifactResolutionException
{
Map resolvedArtifacts = new LinkedHashMap();
ResolutionNode root = new ResolutionNode( originatingArtifact, remoteRepositories );
root.addDependencies( artifacts, remoteRepositories, filter );
ManagedVersionMap versionMap = getManagedVersionsMap( originatingArtifact, managedVersions );
recurse( originatingArtifact, root, resolvedArtifacts, versionMap, localRepository, remoteRepositories, source, filter,
listeners );
Set set = new LinkedHashSet();
for ( Iterator i = resolvedArtifacts.values().iterator(); i.hasNext(); )
{
List nodes = (List) i.next();
for ( Iterator j = nodes.iterator(); j.hasNext(); )
{
ResolutionNode node = (ResolutionNode) j.next();
if ( !node.equals( root ) && node.isActive() )
{
Artifact artifact = node.getArtifact();
if ( node.filterTrail( filter ) )
{
// If it was optional and not a direct dependency,
// we don't add it or its children, just allow the update of the version and scope
if ( node.isChildOfRootNode() || !artifact.isOptional() )
{
artifact.setDependencyTrail( node.getDependencyTrail() );
set.add( node );
}
}
}
}
}
ArtifactResolutionResult result = new ArtifactResolutionResult();
result.setArtifactResolutionNodes( set );
return result;
}
/**
* Get the map of managed versions, removing the originating artifact if it is also in managed versions
* @param originatingArtifact artifact we are processing
* @param managedVersions original managed versions
*/
private ManagedVersionMap getManagedVersionsMap( Artifact originatingArtifact, Map managedVersions )
{
ManagedVersionMap versionMap;
if ( managedVersions != null && managedVersions instanceof ManagedVersionMap )
{
versionMap = (ManagedVersionMap) managedVersions;
}
else
{
versionMap = new ManagedVersionMap( managedVersions );
}
/* remove the originating artifact if it is also in managed versions to avoid being modified during resolution */
Artifact managedOriginatingArtifact = (Artifact) versionMap.get( originatingArtifact.getDependencyConflictId() );
if ( managedOriginatingArtifact != null )
{
// TODO we probably want to warn the user that he is building an artifact with
// different values than in dependencyManagement
if ( managedVersions instanceof ManagedVersionMap )
{
/* avoid modifying the managedVersions parameter creating a new map */
versionMap = new ManagedVersionMap( managedVersions );
}
versionMap.remove( originatingArtifact.getDependencyConflictId() );
}
return versionMap;
}
private void recurse( Artifact originatingArtifact, ResolutionNode node, Map resolvedArtifacts, ManagedVersionMap managedVersions,
ArtifactRepository localRepository, List remoteRepositories, ArtifactMetadataSource source,
ArtifactFilter filter, List listeners )
throws CyclicDependencyException, ArtifactResolutionException, OverConstrainedVersionException
{
fireEvent( ResolutionListener.TEST_ARTIFACT, listeners, node );
Object key = node.getKey();
// TODO: Does this check need to happen here? Had to add the same call
// below when we iterate on child nodes -- will that suffice?
if ( managedVersions.containsKey( key ) )
{
manageArtifact( node, managedVersions, listeners );
}
List previousNodes = (List) resolvedArtifacts.get( key );
if ( previousNodes != null )
{
for ( Iterator i = previousNodes.iterator(); i.hasNext(); )
{
ResolutionNode previous = (ResolutionNode) i.next();
if ( previous.isActive() )
{
// Version mediation
VersionRange previousRange = previous.getArtifact().getVersionRange();
VersionRange currentRange = node.getArtifact().getVersionRange();
if ( previousRange != null && currentRange != null )
{
// TODO: shouldn't need to double up on this work, only done for simplicity of handling recommended
// version but the restriction is identical
VersionRange newRange = previousRange.restrict( currentRange );
// TODO: ick. this forces the OCE that should have come from the previous call. It is still correct
if ( newRange.isSelectedVersionKnown( previous.getArtifact() ) )
{
fireEvent( ResolutionListener.RESTRICT_RANGE, listeners, node, previous.getArtifact(),
newRange );
}
previous.getArtifact().setVersionRange( newRange );
node.getArtifact().setVersionRange( currentRange.restrict( previousRange ) );
//Select an appropriate available version from the (now restricted) range
//Note this version was selected before to get the appropriate POM
//But it was reset by the call to setVersionRange on restricting the version
ResolutionNode[] resetNodes = {previous, node};
for ( int j = 0; j < 2; j++ )
{
Artifact resetArtifact = resetNodes[j].getArtifact();
//MNG-2123: if the previous node was not a range, then it wouldn't have any available
//versions. We just clobbered the selected version above. (why? i have no idea.)
//So since we are here and this is ranges we must go figure out the version (for a third time...)
if ( resetArtifact.getVersion() == null && resetArtifact.getVersionRange() != null )
{
// go find the version. This is a total hack. See previous comment.
List versions = resetArtifact.getAvailableVersions();
if ( versions == null )
{
try
{
versions =
source.retrieveAvailableVersions( resetArtifact, localRepository,
remoteRepositories );
resetArtifact.setAvailableVersions( versions );
}
catch ( ArtifactMetadataRetrievalException e )
{
resetArtifact.setDependencyTrail( node.getDependencyTrail() );
throw new ArtifactResolutionException( "Unable to get dependency information: "
+ e.getMessage(), resetArtifact, remoteRepositories, e );
}
}
//end hack
//MNG-2861: match version can return null
ArtifactVersion selectedVersion =
resetArtifact.getVersionRange().matchVersion( resetArtifact.getAvailableVersions() );
if ( selectedVersion != null )
{
resetArtifact.selectVersion( selectedVersion.toString() );
}
else
{
throw new OverConstrainedVersionException( " Unable to find a version in "
+ resetArtifact.getAvailableVersions() + " to match the range "
+ resetArtifact.getVersionRange(), resetArtifact );
}
fireEvent( ResolutionListener.SELECT_VERSION_FROM_RANGE, listeners, resetNodes[j] );
}
}
}
// Conflict Resolution
// TODO: use as conflict resolver(s), chain
// TODO: should this be part of mediation?
// previous one is more dominant
ResolutionNode nearest;
ResolutionNode farthest;
if ( previous.getDepth() <= node.getDepth() )
{
nearest = previous;
farthest = node;
}
else
{
nearest = node;
farthest = previous;
}
if ( checkScopeUpdate( farthest, nearest, listeners ) )
{
// if we need to update scope of nearest to use farthest scope, use the nearest version, but farthest scope
nearest.disable();
farthest.getArtifact().setVersion( nearest.getArtifact().getVersion() );
fireEvent( ResolutionListener.OMIT_FOR_NEARER, listeners, nearest, farthest.getArtifact() );
}
else
{
farthest.disable();
fireEvent( ResolutionListener.OMIT_FOR_NEARER, listeners, farthest, nearest.getArtifact() );
}
}
}
}
else
{
previousNodes = new ArrayList();
resolvedArtifacts.put( key, previousNodes );
}
previousNodes.add( node );
if ( node.isActive() )
{
fireEvent( ResolutionListener.INCLUDE_ARTIFACT, listeners, node );
}
// don't pull in the transitive deps of a system-scoped dependency.
if ( node.isActive() && !Artifact.SCOPE_SYSTEM.equals( node.getArtifact().getScope() ) )
{
fireEvent( ResolutionListener.PROCESS_CHILDREN, listeners, node );
Artifact parentArtifact = node.getArtifact();
for ( Iterator i = node.getChildrenIterator(); i.hasNext(); )
{
ResolutionNode child = (ResolutionNode) i.next();
// We leave in optional ones, but don't pick up its dependencies
if ( !child.isResolved() && ( !child.getArtifact().isOptional() || child.isChildOfRootNode() ) )
{
Artifact artifact = child.getArtifact();
artifact.setDependencyTrail( node.getDependencyTrail() );
List childRemoteRepositories = child.getRemoteRepositories();
try
{
Object childKey;
do
{
childKey = child.getKey();
if ( managedVersions.containsKey( childKey ) )
{
// If this child node is a managed dependency, ensure
// we are using the dependency management version
// of this child if applicable b/c we want to use the
// managed version's POM, *not* any other version's POM.
// We retrieve the POM below in the retrieval step.
manageArtifact( child, managedVersions, listeners );
// Also, we need to ensure that any exclusions it presents are
// added to the artifact before we retrieve the metadata
// for the artifact; otherwise we may end up with unwanted
// dependencies.
Artifact ma = (Artifact) managedVersions.get( childKey );
ArtifactFilter managedExclusionFilter = ma.getDependencyFilter();
if ( null != managedExclusionFilter )
{
if ( null != artifact.getDependencyFilter() )
{
AndArtifactFilter aaf = new AndArtifactFilter();
aaf.add( artifact.getDependencyFilter() );
aaf.add( managedExclusionFilter );
artifact.setDependencyFilter( aaf );
}
else
{
artifact.setDependencyFilter( managedExclusionFilter );
}
}
}
if ( artifact.getVersion() == null )
{
// set the recommended version
// TODO: maybe its better to just pass the range through to retrieval and use a transformation?
ArtifactVersion version;
if ( artifact.isSelectedVersionKnown() )
{
version = artifact.getSelectedVersion();
}
else
{
//go find the version
List versions = artifact.getAvailableVersions();
if ( versions == null )
{
versions = source.retrieveAvailableVersions( artifact, localRepository,
childRemoteRepositories );
artifact.setAvailableVersions( versions );
}
Collections.sort( versions );
VersionRange versionRange = artifact.getVersionRange();
version = versionRange.matchVersion( versions );
if ( version == null )
{
if ( versions.isEmpty() )
{
throw new OverConstrainedVersionException(
"No versions are present in the repository for the artifact with a range "
+ versionRange, artifact, childRemoteRepositories );
}
throw new OverConstrainedVersionException( "Couldn't find a version in "
+ versions + " to match range " + versionRange, artifact,
childRemoteRepositories );
}
}
//this is dangerous because artifact.getSelectedVersion() can
//return null. However it is ok here because we first check if the
//selected version is known. As currently coded we can't get a null here.
artifact.selectVersion( version.toString() );
fireEvent( ResolutionListener.SELECT_VERSION_FROM_RANGE, listeners, child );
}
Artifact relocated = source.retrieveRelocatedArtifact( artifact, localRepository, childRemoteRepositories );
if ( relocated != null && !artifact.equals( relocated ) )
{
relocated.setDependencyFilter( artifact.getDependencyFilter() );
artifact = relocated;
child.setArtifact( artifact );
}
}
while ( !childKey.equals( child.getKey() ) );
if ( parentArtifact != null && parentArtifact.getDependencyFilter() != null
&& !parentArtifact.getDependencyFilter().include( artifact ) )
{
// MNG-3769: the [probably relocated] artifact is excluded.
// We could process exclusions on relocated artifact details in the
// MavenMetadataSource.createArtifacts(..) step, BUT that would
// require resolving the POM from the repository very early on in
// the build.
continue;
}
ResolutionGroup rGroup = source.retrieve( artifact, localRepository, childRemoteRepositories );
//TODO might be better to have source.retrieve() throw a specific exception for this situation
//and catch here rather than have it return null
if ( rGroup == null )
{
//relocated dependency artifact is declared excluded, no need to add and recurse further
continue;
}
child.addDependencies( rGroup.getArtifacts(), rGroup.getResolutionRepositories(), filter );
}
catch ( CyclicDependencyException e )
{
// would like to throw this, but we have crappy stuff in the repo
fireEvent( ResolutionListener.OMIT_FOR_CYCLE, listeners,
new ResolutionNode( e.getArtifact(), childRemoteRepositories, child ) );
}
catch ( ArtifactMetadataRetrievalException e )
{
artifact.setDependencyTrail( node.getDependencyTrail() );
throw new ArtifactResolutionException(
"Unable to get dependency information: " + e.getMessage(), artifact, childRemoteRepositories,
e );
}
recurse( originatingArtifact, child, resolvedArtifacts, managedVersions, localRepository, childRemoteRepositories, source,
filter, listeners );
}
}
fireEvent( ResolutionListener.FINISH_PROCESSING_CHILDREN, listeners, node );
}
}
private void manageArtifact( ResolutionNode node, ManagedVersionMap managedVersions, List listeners )
{
Artifact artifact = (Artifact) managedVersions.get( node.getKey() );
// Before we update the version of the artifact, we need to know
// whether we are working on a transitive dependency or not. This
// allows depMgmt to always override transitive dependencies, while
// explicit child override depMgmt (viz. depMgmt should only
// provide defaults to children, but should override transitives).
// We can do this by calling isChildOfRootNode on the current node.
if ( artifact.getVersion() != null
&& ( node.isChildOfRootNode() ? node.getArtifact().getVersion() == null : true ) )
{
fireEvent( ResolutionListener.MANAGE_ARTIFACT_VERSION, listeners, node, artifact );
node.getArtifact().setVersion( artifact.getVersion() );
}
if ( artifact.getScope() != null
&& ( node.isChildOfRootNode() ? node.getArtifact().getScope() == null : true ) )
{
fireEvent( ResolutionListener.MANAGE_ARTIFACT_SCOPE, listeners, node, artifact );
node.getArtifact().setScope( artifact.getScope() );
}
}
/**
* Check if the scope needs to be updated.
* <a href="http://docs.codehaus.org/x/IGU#DependencyMediationandConflictResolution-Scoperesolution">More info</a>.
*
* @param farthest farthest resolution node
* @param nearest nearest resolution node
* @param listeners
*/
boolean checkScopeUpdate( ResolutionNode farthest, ResolutionNode nearest, List listeners )
{
boolean updateScope = false;
Artifact farthestArtifact = farthest.getArtifact();
Artifact nearestArtifact = nearest.getArtifact();
/* farthest is runtime and nearest has lower priority, change to runtime */
if ( Artifact.SCOPE_RUNTIME.equals( farthestArtifact.getScope() )
&& ( Artifact.SCOPE_TEST.equals( nearestArtifact.getScope() ) || Artifact.SCOPE_PROVIDED.equals( nearestArtifact.getScope() ) ) )
{
updateScope = true;
}
/* farthest is compile and nearest is not (has lower priority), change to compile */
if ( Artifact.SCOPE_COMPILE.equals( farthestArtifact.getScope() )
&& !Artifact.SCOPE_COMPILE.equals( nearestArtifact.getScope() ) )
{
updateScope = true;
}
/* current POM rules all, if nearest is in current pom, do not update its scope */
if ( nearest.getDepth() < 2 && updateScope )
{
updateScope = false;
fireEvent( ResolutionListener.UPDATE_SCOPE_CURRENT_POM, listeners, nearest, farthestArtifact );
}
if ( updateScope )
{
fireEvent( ResolutionListener.UPDATE_SCOPE, listeners, nearest, farthestArtifact );
// previously we cloned the artifact, but it is more effecient to just update the scope
// if problems are later discovered that the original object needs its original scope value, cloning may
// again be appropriate
nearestArtifact.setScope( farthestArtifact.getScope() );
}
return updateScope;
}
private void fireEvent( int event, List listeners, ResolutionNode node )
{
fireEvent( event, listeners, node, null );
}
private void fireEvent( int event, List listeners, ResolutionNode node, Artifact replacement )
{
fireEvent( event, listeners, node, replacement, null );
}
private void fireEvent( int event, List listeners, ResolutionNode node, Artifact replacement,
VersionRange newRange )
{
for ( Iterator i = listeners.iterator(); i.hasNext(); )
{
ResolutionListener listener = (ResolutionListener) i.next();
switch ( event )
{
case ResolutionListener.TEST_ARTIFACT:
listener.testArtifact( node.getArtifact() );
break;
case ResolutionListener.PROCESS_CHILDREN:
listener.startProcessChildren( node.getArtifact() );
break;
case ResolutionListener.FINISH_PROCESSING_CHILDREN:
listener.endProcessChildren( node.getArtifact() );
break;
case ResolutionListener.INCLUDE_ARTIFACT:
listener.includeArtifact( node.getArtifact() );
break;
case ResolutionListener.OMIT_FOR_NEARER:
listener.omitForNearer( node.getArtifact(), replacement );
break;
case ResolutionListener.OMIT_FOR_CYCLE:
listener.omitForCycle( node.getArtifact() );
break;
case ResolutionListener.UPDATE_SCOPE:
listener.updateScope( node.getArtifact(), replacement.getScope() );
break;
case ResolutionListener.UPDATE_SCOPE_CURRENT_POM:
listener.updateScopeCurrentPom( node.getArtifact(), replacement.getScope() );
break;
case ResolutionListener.MANAGE_ARTIFACT_VERSION:
if ( listener instanceof ResolutionListenerForDepMgmt )
{
ResolutionListenerForDepMgmt asImpl = (ResolutionListenerForDepMgmt) listener;
asImpl.manageArtifactVersion( node.getArtifact(), replacement );
}
else
{
listener.manageArtifact( node.getArtifact(), replacement );
}
break;
case ResolutionListener.MANAGE_ARTIFACT_SCOPE:
if ( listener instanceof ResolutionListenerForDepMgmt )
{
ResolutionListenerForDepMgmt asImpl = (ResolutionListenerForDepMgmt) listener;
asImpl.manageArtifactScope( node.getArtifact(), replacement );
}
else
{
listener.manageArtifact( node.getArtifact(), replacement );
}
break;
case ResolutionListener.SELECT_VERSION_FROM_RANGE:
listener.selectVersionFromRange( node.getArtifact() );
break;
case ResolutionListener.RESTRICT_RANGE:
if ( node.getArtifact().getVersionRange().hasRestrictions()
|| replacement.getVersionRange().hasRestrictions() )
{
listener.restrictRange( node.getArtifact(), replacement, newRange );
}
break;
default:
throw new IllegalStateException( "Unknown event: " + event );
}
}
}
}