1 package org.apache.archiva.dependency.tree.maven2;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
22 import org.apache.archiva.metadata.repository.MetadataResolutionException;
23 import org.apache.archiva.metadata.repository.MetadataResolver;
24 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
25 import org.apache.archiva.metadata.repository.storage.maven2.RepositoryModelResolver;
26 import org.apache.commons.lang.StringUtils;
27 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
28 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
29 import org.apache.maven.artifact.Artifact;
30 import org.apache.maven.artifact.factory.ArtifactFactory;
31 import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
32 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
33 import org.apache.maven.artifact.metadata.ResolutionGroup;
34 import org.apache.maven.artifact.repository.ArtifactRepository;
35 import org.apache.maven.artifact.resolver.ArtifactCollector;
36 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
37 import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
38 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
39 import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
40 import org.apache.maven.artifact.versioning.ArtifactVersion;
41 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
42 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
43 import org.apache.maven.artifact.versioning.ManagedVersionMap;
44 import org.apache.maven.artifact.versioning.VersionRange;
45 import org.apache.maven.model.Dependency;
46 import org.apache.maven.model.DependencyManagement;
47 import org.apache.maven.model.Exclusion;
48 import org.apache.maven.model.Model;
49 import org.apache.maven.model.building.DefaultModelBuildingRequest;
50 import org.apache.maven.model.building.ModelBuilder;
51 import org.apache.maven.model.building.ModelBuildingException;
52 import org.apache.maven.model.building.ModelBuildingRequest;
53 import org.apache.maven.model.resolution.UnresolvableModelException;
54 import org.apache.maven.shared.dependency.tree.DependencyNode;
55 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
56 import org.apache.maven.shared.dependency.tree.DependencyTreeResolutionListener;
57 import org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter;
58 import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
59 import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter;
60 import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor;
61 import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
62 import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
63 import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor;
64 import org.codehaus.plexus.logging.AbstractLogEnabled;
67 import java.util.ArrayList;
68 import java.util.Collection;
69 import java.util.Collections;
70 import java.util.HashSet;
71 import java.util.LinkedHashSet;
72 import java.util.List;
77 * Default implementation of <code>DependencyTreeBuilder</code>. Customized wrapper for maven-dependency-tree to use
78 * maven-model-builder instead of maven-project. Note that the role must differ to avoid conflicting with the
79 * maven-shared implementation.
81 * @plexus.component role="org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder" role-hint="maven2"
83 public class DefaultDependencyTreeBuilder
84 extends AbstractLogEnabled
85 implements DependencyTreeBuilder
90 private ArtifactFactory factory;
95 private ArtifactCollector collector;
100 private ModelBuilder builder;
103 * @plexus.requirement
105 private MetadataResolver metadataResolver;
108 * @plexus.requirement role-hint="maven2"
110 private RepositoryPathTranslator pathTranslator;
113 * @plexus.requirement
115 private ArchivaConfiguration archivaConfiguration;
117 public void buildDependencyTree( List<String> repositoryIds, String groupId, String artifactId, String version,
118 DependencyNodeVisitor nodeVisitor )
119 throws DependencyTreeBuilderException
121 DependencyTreeResolutionListener listener = new DependencyTreeResolutionListener( getLogger() );
123 Artifact projectArtifact = factory.createProjectArtifact( groupId, artifactId, version );
124 File basedir = findArtifactInRepositories( repositoryIds, projectArtifact );
126 if ( basedir == null )
128 // metadata could not be resolved
134 Model model = buildProject( new RepositoryModelResolver( basedir, pathTranslator ), groupId, artifactId,
137 Map managedVersions = createManagedVersionMap( model );
139 Set<Artifact> dependencyArtifacts = createArtifacts( model, null );
141 ArtifactMetadataSource metadataSource = new MetadataArtifactMetadataSource( repositoryIds );
143 // Note that we don't permit going to external repositories. We don't need to pass in a local and remote
144 // since our metadata source has control over them
145 collector.collect( dependencyArtifacts, projectArtifact, managedVersions, null, null, metadataSource, null,
146 Collections.singletonList( listener ) );
148 DependencyNode rootNode = listener.getRootNode();
150 // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
151 DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor( nodeVisitor );
153 CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
154 DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor( collectingVisitor,
155 StateDependencyNodeFilter.INCLUDED );
156 rootNode.accept( firstPassVisitor );
158 DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(
159 collectingVisitor.getNodes() );
160 visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
162 rootNode.accept( visitor );
164 catch ( ArtifactResolutionException e )
166 throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
168 catch ( InvalidVersionSpecificationException e )
170 throw new DependencyTreeBuilderException( "Invalid dependency version for artifact " + projectArtifact );
172 catch ( ModelBuildingException e )
174 throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
176 catch ( UnresolvableModelException e )
178 throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
182 private File findArtifactInRepositories( List<String> repositoryIds, Artifact projectArtifact )
184 for ( String repoId : repositoryIds )
186 ManagedRepositoryConfiguration repositoryConfiguration =
187 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
189 File repoDir = new File( repositoryConfiguration.getLocation() );
190 File file = pathTranslator.toFile( repoDir, projectArtifact.getGroupId(), projectArtifact.getArtifactId(),
191 projectArtifact.getBaseVersion(),
192 projectArtifact.getArtifactId() + "-" + projectArtifact.getVersion() +
203 private Model buildProject( RepositoryModelResolver modelResolver, String groupId, String artifactId,
205 throws ModelBuildingException, UnresolvableModelException
207 ModelBuildingRequest req = new DefaultModelBuildingRequest();
208 req.setProcessPlugins( false );
209 req.setModelSource( modelResolver.resolveModel( groupId, artifactId, version ) );
210 req.setModelResolver( modelResolver );
211 req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
213 return builder.build( req ).getEffectiveModel();
216 // from maven-project to avoid the dependency on it
217 private Set<Artifact> createArtifacts( Model model, ArtifactFilter dependencyFilter )
218 throws InvalidVersionSpecificationException
220 Collection<Dependency> dependencies = model.getDependencies();
221 Set<Artifact> projectArtifacts = new LinkedHashSet<Artifact>( dependencies.size() );
223 for ( Dependency dependency : dependencies )
225 String scope = dependency.getScope();
227 if ( StringUtils.isEmpty( scope ) )
229 scope = Artifact.SCOPE_COMPILE;
231 dependency.setScope( scope );
234 VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
235 Artifact artifact = factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(),
236 versionRange, dependency.getType(),
237 dependency.getClassifier(), scope, null,
238 dependency.isOptional() );
240 if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
242 artifact.setFile( new File( dependency.getSystemPath() ) );
245 ArtifactFilter artifactFilter = dependencyFilter;
247 // MNG-3769: It would be nice to be able to process relocations here,
248 // so we could have this filtering step apply to post-relocated dependencies.
249 // HOWEVER, this would require a much more invasive POM resolution process
250 // in order to look for relocations, which would make the early steps in
251 // a Maven build way too heavy.
252 if ( artifact != null && ( artifactFilter == null || artifactFilter.include( artifact ) ) )
254 if ( dependency.getExclusions() != null && !dependency.getExclusions().isEmpty() )
256 List<String> exclusions = new ArrayList<String>();
257 for ( Object o : dependency.getExclusions() )
259 Exclusion e = (Exclusion) o;
260 exclusions.add( e.getGroupId() + ":" + e.getArtifactId() );
263 ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions );
265 if ( artifactFilter != null )
267 AndArtifactFilter filter = new AndArtifactFilter();
268 filter.add( artifactFilter );
269 filter.add( newFilter );
270 artifactFilter = filter;
274 artifactFilter = newFilter;
278 artifact.setDependencyFilter( artifactFilter );
280 projectArtifacts.add( artifact );
284 return projectArtifacts;
288 // from maven-project to avoid the dependency on it
290 private Map createManagedVersionMap( Model model )
291 throws InvalidVersionSpecificationException
293 DependencyManagement dependencyManagement = model.getDependencyManagement();
295 Map<String, Artifact> map = null;
296 List<Dependency> deps;
297 if ( ( dependencyManagement != null ) && ( ( deps = dependencyManagement.getDependencies() ) != null ) &&
298 ( deps.size() > 0 ) )
300 map = new ManagedVersionMap( map );
302 for ( Dependency dependency : dependencyManagement.getDependencies() )
305 VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
307 Artifact artifact = factory.createDependencyArtifact( dependency.getGroupId(),
308 dependency.getArtifactId(), versionRange,
309 dependency.getType(), dependency.getClassifier(),
310 dependency.getScope(), dependency.isOptional() );
311 if ( getLogger().isDebugEnabled() )
313 getLogger().debug( " " + artifact );
316 // If the dependencyManagement section listed exclusions,
317 // add them to the managed artifacts here so that transitive
318 // dependencies will be excluded if necessary.
319 if ( ( null != dependency.getExclusions() ) && !dependency.getExclusions().isEmpty() )
321 List<String> exclusions = new ArrayList<String>();
323 for ( Exclusion exclusion : dependency.getExclusions() )
325 exclusions.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() );
327 ExcludesArtifactFilter eaf = new ExcludesArtifactFilter( exclusions );
328 artifact.setDependencyFilter( eaf );
332 artifact.setDependencyFilter( null );
334 map.put( dependency.getManagementKey(), artifact );
339 map = Collections.emptyMap();
345 private class MetadataArtifactMetadataSource
346 implements ArtifactMetadataSource
348 private final List<String> repositoryIds;
350 public MetadataArtifactMetadataSource( List<String> repositoryIds )
352 this.repositoryIds = repositoryIds;
355 // modified version from MavenMetadataSource to work with the simpler environment
356 public ResolutionGroup retrieve( Artifact artifact, ArtifactRepository localRepository,
357 List remoteRepositories )
358 throws ArtifactMetadataRetrievalException
360 // TODO: we removed relocation support here. This is something that might need to be generically handled
361 // throughout this module
363 Artifact pomArtifact = factory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(),
364 artifact.getVersion(), artifact.getScope() );
366 File basedir = findArtifactInRepositories( repositoryIds, pomArtifact );
368 Model project = null;
369 if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) && basedir != null )
373 project = buildProject( new RepositoryModelResolver( basedir, pathTranslator ),
374 artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion() );
376 catch ( ModelBuildingException e )
378 throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
380 catch ( UnresolvableModelException e )
382 throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
386 ResolutionGroup result;
388 if ( project == null )
390 // TODO: we could record this so that it is displayed in the dependency tree as (...) or similar
392 // if the project is null, we encountered an invalid model (read: m1 POM)
393 // we'll just return an empty resolution group.
394 // or used the inherited scope (should that be passed to the buildFromRepository method above?)
395 result = new ResolutionGroup( pomArtifact, Collections.emptySet(), Collections.emptyList() );
399 Set artifacts = Collections.emptySet();
400 if ( !artifact.getArtifactHandler().isIncludesDependencies() )
404 artifacts = createArtifacts( project, artifact.getDependencyFilter() );
406 catch ( InvalidVersionSpecificationException e )
408 throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
412 result = new ResolutionGroup( pomArtifact, artifacts, Collections.emptyList() );
418 public List retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
419 List remoteRepositories )
420 throws ArtifactMetadataRetrievalException
422 Set<ArtifactVersion> versions = new HashSet<ArtifactVersion>();
423 for ( String repoId : repositoryIds )
425 Collection<String> projectVersions;
428 projectVersions = metadataResolver.resolveProjectVersions( repoId, artifact.getGroupId(),
429 artifact.getArtifactId() );
431 catch ( MetadataResolutionException e )
433 throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
435 for ( String version : projectVersions )
437 versions.add( new DefaultArtifactVersion( version ) );
441 return new ArrayList<ArtifactVersion>( versions );