]> source.dussan.org Git - archiva.git/blob
706349fe425d04a5ef7d62281cf8f899a93b4a1a
[archiva.git] /
1 package org.apache.archiva.dependency.tree.maven2;
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 java.io.File;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31
32 import org.apache.archiva.metadata.repository.MetadataResolver;
33 import org.apache.archiva.metadata.repository.MetadataResolverException;
34 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
35 import org.apache.archiva.metadata.repository.storage.maven2.RepositoryModelResolver;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
38 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
39 import org.apache.maven.artifact.Artifact;
40 import org.apache.maven.artifact.factory.ArtifactFactory;
41 import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
42 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
43 import org.apache.maven.artifact.metadata.ResolutionGroup;
44 import org.apache.maven.artifact.repository.ArtifactRepository;
45 import org.apache.maven.artifact.resolver.ArtifactCollector;
46 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
47 import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
48 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
49 import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
50 import org.apache.maven.artifact.versioning.ArtifactVersion;
51 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
52 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
53 import org.apache.maven.artifact.versioning.ManagedVersionMap;
54 import org.apache.maven.artifact.versioning.VersionRange;
55 import org.apache.maven.model.Dependency;
56 import org.apache.maven.model.DependencyManagement;
57 import org.apache.maven.model.Exclusion;
58 import org.apache.maven.model.Model;
59 import org.apache.maven.model.building.DefaultModelBuildingRequest;
60 import org.apache.maven.model.building.ModelBuilder;
61 import org.apache.maven.model.building.ModelBuildingException;
62 import org.apache.maven.model.building.ModelBuildingRequest;
63 import org.apache.maven.model.resolution.UnresolvableModelException;
64 import org.apache.maven.shared.dependency.tree.DependencyNode;
65 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
66 import org.apache.maven.shared.dependency.tree.DependencyTreeResolutionListener;
67 import org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter;
68 import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
69 import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter;
70 import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor;
71 import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
72 import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
73 import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor;
74 import org.codehaus.plexus.logging.AbstractLogEnabled;
75
76 /**
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.
80  *
81  * @plexus.component role="org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder" role-hint="maven2"
82  */
83 public class DefaultDependencyTreeBuilder
84     extends AbstractLogEnabled
85     implements DependencyTreeBuilder
86 {
87     /**
88      * @plexus.requirement
89      */
90     private ArtifactFactory factory;
91
92     /**
93      * @plexus.requirement
94      */
95     private ArtifactCollector collector;
96
97     /**
98      * @plexus.requirement
99      */
100     private ModelBuilder builder;
101
102     /**
103      * @plexus.requirement
104      */
105     private MetadataResolver metadataResolver;
106
107     /**
108      * @plexus.requirement role-hint="maven2"
109      */
110     private RepositoryPathTranslator pathTranslator;
111
112     /**
113      * @plexus.requirement
114      */
115     private ArchivaConfiguration archivaConfiguration;
116
117     public void buildDependencyTree( List<String> repositoryIds, String groupId, String artifactId, String version,
118                                      DependencyNodeVisitor nodeVisitor )
119         throws DependencyTreeBuilderException
120     {
121         DependencyTreeResolutionListener listener = new DependencyTreeResolutionListener( getLogger() );
122
123         Artifact projectArtifact = factory.createProjectArtifact( groupId, artifactId, version );
124         File basedir = findArtifactInRepositories( repositoryIds, projectArtifact );
125
126         if ( basedir == null )
127         {
128             // metadata could not be resolved
129             return;
130         }
131
132         try
133         {
134             Model model =
135                 buildProject( new RepositoryModelResolver( basedir, pathTranslator ), groupId, artifactId, version );
136
137             Map managedVersions = createManagedVersionMap( model );
138
139             Set<Artifact> dependencyArtifacts = createArtifacts( model, null );
140
141             ArtifactMetadataSource metadataSource = new MetadataArtifactMetadataSource( repositoryIds );
142
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 ) );
147
148             DependencyNode rootNode = listener.getRootNode();
149
150             // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
151             DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor( nodeVisitor );
152
153             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
154             DependencyNodeVisitor firstPassVisitor =
155                 new FilteringDependencyNodeVisitor( collectingVisitor, StateDependencyNodeFilter.INCLUDED );
156             rootNode.accept( firstPassVisitor );
157
158             DependencyNodeFilter secondPassFilter =
159                 new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
160             visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
161
162             rootNode.accept( visitor );
163         }
164         catch ( ArtifactResolutionException e )
165         {
166             throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
167         }
168         catch ( InvalidVersionSpecificationException e )
169         {
170             throw new DependencyTreeBuilderException( "Invalid dependency version for artifact " + projectArtifact );
171         }
172         catch ( ModelBuildingException e )
173         {
174             throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
175         }
176         catch ( UnresolvableModelException e )
177         {
178             throw new DependencyTreeBuilderException( "Cannot build project dependency tree " + e.getMessage(), e );
179         }
180     }
181
182     private File findArtifactInRepositories( List<String> repositoryIds, Artifact projectArtifact )
183     {
184         for ( String repoId : repositoryIds )
185         {
186             ManagedRepositoryConfiguration repositoryConfiguration =
187                 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
188
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() +
193                                                    ".pom" );
194
195             if ( file.exists() )
196             {
197                 return repoDir;
198             }
199         }
200         return null;
201     }
202
203     private Model buildProject( RepositoryModelResolver modelResolver, String groupId, String artifactId,
204                                 String version )
205         throws ModelBuildingException, UnresolvableModelException
206     {
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 );
212
213         return builder.build( req ).getEffectiveModel();
214     }
215
216     // from maven-project to avoid the dependency on it
217     private Set<Artifact> createArtifacts( Model model, ArtifactFilter dependencyFilter )
218         throws InvalidVersionSpecificationException
219     {
220         Collection<Dependency> dependencies = model.getDependencies();
221         Set<Artifact> projectArtifacts = new LinkedHashSet<Artifact>( dependencies.size() );
222
223         for ( Dependency dependency : dependencies )
224         {
225             String scope = dependency.getScope();
226
227             if ( StringUtils.isEmpty( scope ) )
228             {
229                 scope = Artifact.SCOPE_COMPILE;
230
231                 dependency.setScope( scope );
232             }
233
234             VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
235             Artifact artifact =
236                 factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(), versionRange,
237                                                   dependency.getType(), dependency.getClassifier(), scope, null,
238                                                   dependency.isOptional() );
239
240             if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
241             {
242                 artifact.setFile( new File( dependency.getSystemPath() ) );
243             }
244
245             ArtifactFilter artifactFilter = dependencyFilter;
246
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 ) ) )
253             {
254                 if ( dependency.getExclusions() != null && !dependency.getExclusions().isEmpty() )
255                 {
256                     List<String> exclusions = new ArrayList<String>();
257                     for ( Object o : dependency.getExclusions() )
258                     {
259                         Exclusion e = (Exclusion) o;
260                         exclusions.add( e.getGroupId() + ":" + e.getArtifactId() );
261                     }
262
263                     ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions );
264
265                     if ( artifactFilter != null )
266                     {
267                         AndArtifactFilter filter = new AndArtifactFilter();
268                         filter.add( artifactFilter );
269                         filter.add( newFilter );
270                         artifactFilter = filter;
271                     }
272                     else
273                     {
274                         artifactFilter = newFilter;
275                     }
276                 }
277
278                 artifact.setDependencyFilter( artifactFilter );
279
280                 projectArtifacts.add( artifact );
281             }
282         }
283
284         return projectArtifacts;
285
286     }
287
288     // from maven-project to avoid the dependency on it
289
290     private Map createManagedVersionMap( Model model )
291         throws InvalidVersionSpecificationException
292     {
293         DependencyManagement dependencyManagement = model.getDependencyManagement();
294
295         Map<String, Artifact> map = null;
296         List<Dependency> deps;
297         if ( ( dependencyManagement != null ) && ( ( deps = dependencyManagement.getDependencies() ) != null ) &&
298             ( deps.size() > 0 ) )
299         {
300             map = new ManagedVersionMap( map );
301
302             for ( Dependency dependency : dependencyManagement.getDependencies() )
303             {
304
305                 VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
306
307                 Artifact artifact =
308                     factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(), versionRange,
309                                                       dependency.getType(), dependency.getClassifier(),
310                                                       dependency.getScope(), dependency.isOptional() );
311                 if ( getLogger().isDebugEnabled() )
312                 {
313                     getLogger().debug( "  " + artifact );
314                 }
315
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() )
320                 {
321                     List<String> exclusions = new ArrayList<String>();
322
323                     for ( Exclusion exclusion : dependency.getExclusions() )
324                     {
325                         exclusions.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() );
326                     }
327                     ExcludesArtifactFilter eaf = new ExcludesArtifactFilter( exclusions );
328                     artifact.setDependencyFilter( eaf );
329                 }
330                 else
331                 {
332                     artifact.setDependencyFilter( null );
333                 }
334                 map.put( dependency.getManagementKey(), artifact );
335             }
336         }
337         else
338         {
339             map = Collections.emptyMap();
340         }
341
342         return map;
343     }
344
345     private class MetadataArtifactMetadataSource
346         implements ArtifactMetadataSource
347     {
348         private final List<String> repositoryIds;
349
350         public MetadataArtifactMetadataSource( List<String> repositoryIds )
351         {
352             this.repositoryIds = repositoryIds;
353         }
354
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
359         {
360             // TODO: we removed relocation support here. This is something that might need to be generically handled
361             //       throughout this module
362
363             Artifact pomArtifact =
364                 factory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
365                                                artifact.getScope() );
366
367             File basedir = findArtifactInRepositories( repositoryIds, pomArtifact );
368
369             Model project = null;
370             if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) && basedir != null )
371             {
372                 try
373                 {
374                     project =
375                         buildProject( new RepositoryModelResolver( basedir, pathTranslator ), artifact.getGroupId(),
376                                       artifact.getArtifactId(), artifact.getVersion() );
377                 }
378                 catch ( ModelBuildingException e )
379                 {
380                     throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
381                 }
382                 catch ( UnresolvableModelException e )
383                 {
384                     throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
385                 }
386             }
387
388             ResolutionGroup result;
389
390             if ( project == null )
391             {
392                 // TODO: we could record this so that it is displayed in the dependency tree as (...) or similar
393
394                 // if the project is null, we encountered an invalid model (read: m1 POM)
395                 // we'll just return an empty resolution group.
396                 // or used the inherited scope (should that be passed to the buildFromRepository method above?)
397                 result = new ResolutionGroup( pomArtifact, Collections.emptySet(), Collections.emptyList() );
398             }
399             else
400             {
401                 Set artifacts = Collections.emptySet();
402                 if ( !artifact.getArtifactHandler().isIncludesDependencies() )
403                 {
404                     try
405                     {
406                         artifacts = createArtifacts( project, artifact.getDependencyFilter() );
407                     }
408                     catch ( InvalidVersionSpecificationException e )
409                     {
410                         throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
411                     }
412                 }
413
414                 result = new ResolutionGroup( pomArtifact, artifacts, Collections.emptyList() );
415             }
416
417             return result;
418         }
419
420         public List retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
421                                                List remoteRepositories )
422             throws ArtifactMetadataRetrievalException
423         {
424             try
425             {
426                 Set<ArtifactVersion> versions = new HashSet<ArtifactVersion>();
427                 for ( String repoId : repositoryIds )
428                 {
429                     Collection<String> projectVersions =
430                         metadataResolver.getProjectVersions( repoId, artifact.getGroupId(), artifact.getArtifactId() );
431                     for ( String version : projectVersions )
432                     {
433                         versions.add( new DefaultArtifactVersion( version ) );
434                     }
435                 }
436
437                 return new ArrayList<ArtifactVersion>( versions );
438             }
439             catch ( MetadataResolverException e )
440             {
441                 throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
442             }
443         }
444     }
445 }