package org.apache.archiva.dependency.tree.maven2;
/*
* 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.archiva.common.plexusbridge.PlexusSisuBridge;
import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
import org.apache.archiva.metadata.repository.MetadataResolutionException;
import org.apache.archiva.metadata.repository.MetadataResolver;
import org.apache.archiva.metadata.repository.RepositorySession;
import org.apache.archiva.metadata.repository.RepositorySessionFactory;
import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
import org.apache.archiva.metadata.repository.storage.maven2.RepositoryModelResolver;
import org.apache.archiva.proxy.common.WagonFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.archiva.common.utils.Slf4JPlexusLogger;
import org.apache.archiva.configuration.ArchivaConfiguration;
import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.archiva.configuration.NetworkProxyConfiguration;
import org.apache.archiva.configuration.ProxyConnectorConfiguration;
import org.apache.archiva.configuration.RemoteRepositoryConfiguration;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
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.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.ManagedVersionMap;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.DefaultModelBuilderFactory;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.model.building.ModelBuildingException;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.resolution.UnresolvableModelException;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
import org.apache.maven.shared.dependency.tree.DependencyTreeResolutionListener;
import org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter;
import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter;
import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor;
import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Default implementation of <code>DependencyTreeBuilder</code>. Customized wrapper for maven-dependency-tree to use
* maven-model-builder instead of maven-project. Note that the role must differ to avoid conflicting with the
* maven-shared implementation.
*/
@Service( "dependencyTreeBuilder#maven2" )
public class DefaultDependencyTreeBuilder
implements DependencyTreeBuilder
{
private Logger log = LoggerFactory.getLogger( getClass() );
/**
*
*/
private ArtifactFactory factory;
/**
*
*/
private ArtifactCollector collector;
/**
*
*/
private ModelBuilder builder;
/**
* TODO: can have other types, and this might eventually come through from the main request
*
*/
@Inject
private RepositorySessionFactory repositorySessionFactory;
/**
*
*/
@Inject
@Named( value = "repositoryPathTranslator#maven2" )
private RepositoryPathTranslator pathTranslator;
/**
*
*/
@Inject
@Named( value = "archivaConfiguration#default" )
private ArchivaConfiguration archivaConfiguration;
@Inject
private PlexusSisuBridge plexusSisuBridge;
@Inject
private WagonFactory wagonFactory;
@PostConstruct
public void initialize()
throws PlexusSisuBridgeException
{
factory = plexusSisuBridge.lookup( ArtifactFactory.class , "default" );
collector = plexusSisuBridge.lookup( ArtifactCollector.class , "default" );
DefaultModelBuilderFactory defaultModelBuilderFactory = new DefaultModelBuilderFactory();
builder = defaultModelBuilderFactory.newInstance();
}
public void buildDependencyTree( List<String> repositoryIds, String groupId, String artifactId, String version,
DependencyNodeVisitor nodeVisitor )
throws DependencyTreeBuilderException
{
DependencyTreeResolutionListener listener =
new DependencyTreeResolutionListener( new Slf4JPlexusLogger( getClass() ) );
Artifact projectArtifact = factory.createProjectArtifact( groupId, artifactId, version );
ManagedRepositoryConfiguration repository = findArtifactInRepositories( repositoryIds, projectArtifact );
if ( repository == null )
{
// metadata could not be resolved
return;
}
File basedir = new File( repository.getLocation() );
try
{
// MRM-1411
// TODO: this is a workaround for a lack of proxy capability in the resolvers - replace when it can all be
// handled there. It doesn't cache anything locally!
List< RemoteRepositoryConfiguration > remoteRepositories = new ArrayList<RemoteRepositoryConfiguration>();
Map<String, ProxyInfo > networkProxies = new HashMap<String, ProxyInfo>();
Map<String, List< ProxyConnectorConfiguration >> proxyConnectorsMap = archivaConfiguration.getConfiguration().getProxyConnectorAsMap();
List<ProxyConnectorConfiguration> proxyConnectors = proxyConnectorsMap.get( repository.getId() );
if( proxyConnectors != null )
{
for( ProxyConnectorConfiguration proxyConnector : proxyConnectors )
{
remoteRepositories.add( archivaConfiguration.getConfiguration().findRemoteRepositoryById( proxyConnector.getTargetRepoId() ) );
NetworkProxyConfiguration networkProxyConfig = archivaConfiguration.getConfiguration().getNetworkProxiesAsMap().get(
proxyConnector.getProxyId() );
if( networkProxyConfig != null )
{
ProxyInfo proxy = new ProxyInfo();
proxy.setType( networkProxyConfig.getProtocol() );
proxy.setHost( networkProxyConfig.getHost() );
proxy.setPort( networkProxyConfig.getPort() );
proxy.setUserName( networkProxyConfig.getUsername() );
proxy.setPassword( networkProxyConfig.getPassword() );
// key/value: remote repo ID/proxy info
networkProxies.put( proxyConnector.getTargetRepoId(), proxy );
}
}
}
Model model = buildProject( new RepositoryModelResolver( basedir, pathTranslator, wagonFactory, remoteRepositories,
networkProxies, repository ), groupId, artifactId, version );
Map managedVersions = createManagedVersionMap( model );
Set<Artifact> dependencyArtifacts = createArtifacts( model, null );
RepositorySession repositorySession = repositorySessionFactory.createSession();
try
{
ArtifactMetadataSource metadataSource =
new MetadataArtifactMetadataSource( repositoryIds, repositorySession );
// Note that we don't permit going to external repositories. We don't need to pass in a local and remote
// since our metadata source has control over them
collector.collect( dependencyArtifacts, projectArtifact, managedVersions, null, null, metadataSource,
null, Collections.singletonList( listener ) );
//collector.collect( dependencyArtifacts, projectArtifact, null, Collections.<ArtifactRepository>emptyList(),
// metadataSource, null, Collections.singletonList( (ResolutionListener) listener ) );
/*
Set<Artifact> artifacts, Artifact originatingArtifact,
ArtifactRepository localRepository, List<ArtifactRepository> remoteRepositories,
ArtifactMetadataSource source, ArtifactFilter filter,
List< ResolutionListener > listeners
*/
}
finally
{
repositorySession.close();
}
DependencyNode rootNode = listener.getRootNode();
// TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor( nodeVisitor );
CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
DependencyNodeVisitor firstPassVisitor =
new FilteringDependencyNodeVisitor( collectingVisitor, StateDependencyNodeFilter.INCLUDED );
rootNode.accept( firstPassVisitor );
DependencyNodeFilter secondPassFilter =
new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
rootNode.accept( visitor );
}
catch ( ArtifactResolutionException e )
{
throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
}
catch ( InvalidVersionSpecificationException e )
{
throw new DependencyTreeBuilderException( "Invalid dependency version for artifact " + projectArtifact );
}
catch ( ModelBuildingException e )
{
throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
}
catch ( UnresolvableModelException e )
{
throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
}
}
private ManagedRepositoryConfiguration findArtifactInRepositories( List<String> repositoryIds, Artifact projectArtifact )
{
for ( String repoId : repositoryIds )
{
ManagedRepositoryConfiguration repositoryConfiguration =
archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
File repoDir = new File( repositoryConfiguration.getLocation() );
File file = pathTranslator.toFile( repoDir, projectArtifact.getGroupId(), projectArtifact.getArtifactId(),
projectArtifact.getBaseVersion(),
projectArtifact.getArtifactId() + "-" + projectArtifact.getVersion()
+ ".pom" );
if ( file.exists() )
{
return repositoryConfiguration;
}
}
return null;
}
private Model buildProject( RepositoryModelResolver modelResolver, String groupId, String artifactId,
String version )
throws ModelBuildingException, UnresolvableModelException
{
ModelBuildingRequest req = new DefaultModelBuildingRequest();
req.setProcessPlugins( false );
req.setModelSource( modelResolver.resolveModel( groupId, artifactId, version ) );
req.setModelResolver( modelResolver );
req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
return builder.build( req ).getEffectiveModel();
}
// from maven-project to avoid the dependency on it
private Set<Artifact> createArtifacts( Model model, ArtifactFilter dependencyFilter )
throws InvalidVersionSpecificationException
{
Collection<Dependency> dependencies = model.getDependencies();
Set<Artifact> projectArtifacts = new LinkedHashSet<Artifact>( dependencies.size() );
for ( Dependency dependency : dependencies )
{
String scope = dependency.getScope();
if ( StringUtils.isEmpty( scope ) )
{
scope = Artifact.SCOPE_COMPILE;
dependency.setScope( scope );
}
VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
Artifact artifact =
factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(), versionRange,
dependency.getType(), dependency.getClassifier(), scope, null,
dependency.isOptional() );
if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
{
artifact.setFile( new File( dependency.getSystemPath() ) );
}
ArtifactFilter artifactFilter = dependencyFilter;
// MNG-3769: It would be nice to be able to process relocations here,
// so we could have this filtering step apply to post-relocated dependencies.
// HOWEVER, this would require a much more invasive POM resolution process
// in order to look for relocations, which would make the early steps in
// a Maven build way too heavy.
if ( artifact != null && ( artifactFilter == null || artifactFilter.include( artifact ) ) )
{
if ( dependency.getExclusions() != null && !dependency.getExclusions().isEmpty() )
{
List<String> exclusions = new ArrayList<String>();
for ( Object o : dependency.getExclusions() )
{
Exclusion e = (Exclusion) o;
exclusions.add( e.getGroupId() + ":" + e.getArtifactId() );
}
ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions );
if ( artifactFilter != null )
{
AndArtifactFilter filter = new AndArtifactFilter();
filter.add( artifactFilter );
filter.add( newFilter );
artifactFilter = filter;
}
else
{
artifactFilter = newFilter;
}
}
artifact.setDependencyFilter( artifactFilter );
projectArtifacts.add( artifact );
}
}
return projectArtifacts;
}
// from maven-project to avoid the dependency on it
private Map createManagedVersionMap( Model model )
throws InvalidVersionSpecificationException
{
DependencyManagement dependencyManagement = model.getDependencyManagement();
Map<String, Artifact> map = null;
List<Dependency> deps;
if ( ( dependencyManagement != null ) && ( ( deps = dependencyManagement.getDependencies() ) != null ) && (
deps.size() > 0 ) )
{
map = new ManagedVersionMap( map );
for ( Dependency dependency : dependencyManagement.getDependencies() )
{
VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
Artifact artifact =
factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(), versionRange,
dependency.getType(), dependency.getClassifier(),
dependency.getScope(), dependency.isOptional() );
log.debug( "artifact {}", artifact );
// If the dependencyManagement section listed exclusions,
// add them to the managed artifacts here so that transitive
// dependencies will be excluded if necessary.
if ( ( null != dependency.getExclusions() ) && !dependency.getExclusions().isEmpty() )
{
List<String> exclusions = new ArrayList<String>();
for ( Exclusion exclusion : dependency.getExclusions() )
{
exclusions.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() );
}
ExcludesArtifactFilter eaf = new ExcludesArtifactFilter( exclusions );
artifact.setDependencyFilter( eaf );
}
else
{
artifact.setDependencyFilter( null );
}
map.put( dependency.getManagementKey(), artifact );
}
}
else
{
map = Collections.emptyMap();
}
return map;
}
private class MetadataArtifactMetadataSource
implements ArtifactMetadataSource
{
private final List<String> repositoryIds;
private final RepositorySession session;
private final MetadataResolver resolver;
public MetadataArtifactMetadataSource( List<String> repositoryIds, RepositorySession session )
{
this.repositoryIds = repositoryIds;
this.session = session;
resolver = this.session.getResolver();
}
// modified version from MavenMetadataSource to work with the simpler environment
public ResolutionGroup retrieve( Artifact artifact, ArtifactRepository localRepository,
List remoteRepositories )
throws ArtifactMetadataRetrievalException
{
// TODO: we removed relocation support here. This is something that might need to be generically handled
// throughout this module
Artifact pomArtifact =
factory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
artifact.getScope() );
ManagedRepositoryConfiguration repository = findArtifactInRepositories( repositoryIds, pomArtifact );
Model project = null;
if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) && repository != null )
{
File basedir = new File( repository.getLocation() );
try
{
project =
buildProject( new RepositoryModelResolver( basedir, pathTranslator ), artifact.getGroupId(),
artifact.getArtifactId(), artifact.getVersion() );
}
catch ( ModelBuildingException e )
{
throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
}
catch ( UnresolvableModelException e )
{
throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
}
}
ResolutionGroup result;
if ( project == null )
{
// TODO: we could record this so that it is displayed in the dependency tree as (...) or similar
// if the project is null, we encountered an invalid model (read: m1 POM)
// we'll just return an empty resolution group.
// or used the inherited scope (should that be passed to the buildFromRepository method above?)
result = new ResolutionGroup( pomArtifact, Collections.<Artifact>emptySet(), Collections.<ArtifactRepository>emptyList() );
}
else
{
Set<Artifact> artifacts = Collections.emptySet();
if ( !artifact.getArtifactHandler().isIncludesDependencies() )
{
try
{
artifacts = createArtifacts( project, artifact.getDependencyFilter() );
}
catch ( InvalidVersionSpecificationException e )
{
throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
}
}
result = new ResolutionGroup( pomArtifact, artifacts, Collections.<ArtifactRepository>emptyList() );
}
return result;
}
public List retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
List remoteRepositories )
throws ArtifactMetadataRetrievalException
{
Set<ArtifactVersion> versions = new HashSet<ArtifactVersion>();
for ( String repoId : repositoryIds )
{
Collection<String> projectVersions;
try
{
projectVersions = resolver.resolveProjectVersions( session, repoId, artifact.getGroupId(),
artifact.getArtifactId() );
}
catch ( MetadataResolutionException e )
{
throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
}
for ( String version : projectVersions )
{
versions.add( new DefaultArtifactVersion( version ) );
}
}
return new ArrayList<ArtifactVersion>( versions );
}
public List<ArtifactVersion> retrieveAvailableVersionsFromDeploymentRepository( Artifact artifact,
ArtifactRepository artifactRepository,
ArtifactRepository artifactRepository1 )
throws ArtifactMetadataRetrievalException
{
// TODO
return null;
}
}
public ArtifactFactory getFactory()
{
return factory;
}
}