]> source.dussan.org Git - archiva.git/blob
54007ab35e6830af386d47e3c3e2c06cf715d488
[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 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;
65
66 import java.io.File;
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;
73 import java.util.Map;
74 import java.util.Set;
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 = buildProject( new RepositoryModelResolver( basedir, pathTranslator ), groupId, artifactId,
135                                         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 = new FilteringDependencyNodeVisitor( collectingVisitor,
155                                                                                          StateDependencyNodeFilter.INCLUDED );
156             rootNode.accept( firstPassVisitor );
157
158             DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(
159                 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 = factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(),
236                                                                   versionRange, dependency.getType(),
237                                                                   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 = factory.createDependencyArtifact( dependency.getGroupId(),
308                                                                       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 = factory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(),
364                                                                   artifact.getVersion(), artifact.getScope() );
365
366             File basedir = findArtifactInRepositories( repositoryIds, pomArtifact );
367
368             Model project = null;
369             if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) && basedir != null )
370             {
371                 try
372                 {
373                     project = buildProject( new RepositoryModelResolver( basedir, pathTranslator ),
374                                             artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion() );
375                 }
376                 catch ( ModelBuildingException e )
377                 {
378                     throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
379                 }
380                 catch ( UnresolvableModelException e )
381                 {
382                     throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
383                 }
384             }
385
386             ResolutionGroup result;
387
388             if ( project == null )
389             {
390                 // TODO: we could record this so that it is displayed in the dependency tree as (...) or similar
391
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() );
396             }
397             else
398             {
399                 Set artifacts = Collections.emptySet();
400                 if ( !artifact.getArtifactHandler().isIncludesDependencies() )
401                 {
402                     try
403                     {
404                         artifacts = createArtifacts( project, artifact.getDependencyFilter() );
405                     }
406                     catch ( InvalidVersionSpecificationException e )
407                     {
408                         throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
409                     }
410                 }
411
412                 result = new ResolutionGroup( pomArtifact, artifacts, Collections.emptyList() );
413             }
414
415             return result;
416         }
417
418         public List retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
419                                                List remoteRepositories )
420             throws ArtifactMetadataRetrievalException
421         {
422             Set<ArtifactVersion> versions = new HashSet<ArtifactVersion>();
423             for ( String repoId : repositoryIds )
424             {
425                 Collection<String> projectVersions;
426                 try
427                 {
428                     projectVersions = metadataResolver.resolveProjectVersions( repoId, artifact.getGroupId(),
429                                                                                artifact.getArtifactId() );
430                 }
431                 catch ( MetadataResolutionException e )
432                 {
433                     throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
434                 }
435                 for ( String version : projectVersions )
436                 {
437                     versions.add( new DefaultArtifactVersion( version ) );
438                 }
439             }
440
441             return new ArrayList<ArtifactVersion>( versions );
442         }
443     }
444 }