View Javadoc

1   package org.apache.maven.project.artifact;
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.maven.artifact.Artifact;
23  import org.apache.maven.artifact.factory.ArtifactFactory;
24  import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
25  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
26  import org.apache.maven.artifact.metadata.ResolutionGroup;
27  import org.apache.maven.artifact.repository.ArtifactRepository;
28  import org.apache.maven.artifact.repository.metadata.ArtifactRepositoryMetadata;
29  import org.apache.maven.artifact.repository.metadata.Metadata;
30  import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
31  import org.apache.maven.artifact.repository.metadata.RepositoryMetadataManager;
32  import org.apache.maven.artifact.repository.metadata.RepositoryMetadataResolutionException;
33  import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
34  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
35  import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
36  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
37  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
38  import org.apache.maven.artifact.versioning.VersionRange;
39  import org.apache.maven.model.Dependency;
40  import org.apache.maven.model.DistributionManagement;
41  import org.apache.maven.model.Exclusion;
42  import org.apache.maven.model.Relocation;
43  import org.apache.maven.project.DefaultProjectBuilderConfiguration;
44  import org.apache.maven.project.InvalidProjectModelException;
45  import org.apache.maven.project.MavenProject;
46  import org.apache.maven.project.MavenProjectBuilder;
47  import org.apache.maven.project.ProjectBuildingException;
48  import org.apache.maven.project.validation.ModelValidationResult;
49  import org.codehaus.plexus.logging.AbstractLogEnabled;
50  import org.codehaus.plexus.util.StringUtils;
51  
52  import java.io.File;
53  import java.util.ArrayList;
54  import java.util.Collections;
55  import java.util.Iterator;
56  import java.util.LinkedHashSet;
57  import java.util.List;
58  import java.util.Set;
59  
60  /**
61   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
62   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
63   * @version $Id: MavenMetadataSource.java 736543 2009-01-22 03:33:16Z jdcasey $
64   */
65  public class MavenMetadataSource
66      extends AbstractLogEnabled
67      implements ArtifactMetadataSource
68  {
69      public static final String ROLE_HINT = "maven";
70  
71      private MavenProjectBuilder mavenProjectBuilder;
72  
73      private ArtifactFactory artifactFactory;
74  
75      private RepositoryMetadataManager repositoryMetadataManager;
76  
77      // lazily instantiated and cached.
78      private MavenProject superProject;
79  
80      /**
81       * Resolve all relocations in the POM for this artifact, and return the new artifact coordinate.
82       */
83      public Artifact retrieveRelocatedArtifact( Artifact artifact, ArtifactRepository localRepository, List remoteRepositories )
84          throws ArtifactMetadataRetrievalException
85      {
86          if ( artifact instanceof ActiveProjectArtifact )
87          {
88              return artifact;
89          }
90  
91          MavenProject project = retrieveRelocatedProject( artifact, localRepository, remoteRepositories );
92  
93          if ( project == null || getRelocationKey( artifact ).equals( getRelocationKey( project.getArtifact() ) ) )
94          {
95              return artifact;
96          }
97  
98          
99          // NOTE: Using artifact information here, since some POMs are deployed 
100         // to central with one version in the filename, but another in the <version> string!
101         // Case in point: org.apache.ws.commons:XmlSchema:1.1:pom.
102         //
103         // Since relocation triggers a reconfiguration of the artifact's information
104         // in retrieveRelocatedProject(..), this is safe to do.
105         Artifact result = null;
106         if ( artifact.getClassifier() != null )
107         {
108             result = artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), artifact.getType(), artifact.getClassifier() );
109         }
110         else
111         {
112             result = artifactFactory.createArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), artifact.getScope(), artifact.getType() );
113         }
114 
115         result.setResolved( artifact.isResolved() );
116         result.setFile( artifact.getFile() );
117 
118         result.setScope( artifact.getScope() );
119         result.setArtifactHandler( artifact.getArtifactHandler() );
120         result.setDependencyFilter( artifact.getDependencyFilter() );
121         result.setDependencyTrail( artifact.getDependencyTrail() );
122         result.setOptional( artifact.isOptional() );
123         result.setRelease( artifact.isRelease() );
124 
125         return result;
126     }
127 
128     private String getRelocationKey( Artifact artifact )
129     {
130         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion();
131     }
132 
133     private MavenProject retrieveRelocatedProject( Artifact artifact, ArtifactRepository localRepository, List remoteRepositories )
134         throws ArtifactMetadataRetrievalException
135     {
136         MavenProject project = null;
137 
138         Artifact pomArtifact;
139         boolean done = false;
140         do
141         {
142             // TODO: can we just modify the original?
143             pomArtifact = artifactFactory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(),
144                                                                  artifact.getVersion(), artifact.getScope() );
145 
146             if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
147             {
148                 done = true;
149             }
150             else
151             {
152                 try
153                 {
154                     project = mavenProjectBuilder.buildFromRepository( pomArtifact, remoteRepositories, localRepository,
155                                                                        true );
156                 }
157                 catch ( InvalidProjectModelException e )
158                 {
159                     getLogger().warn( "POM for \'" + pomArtifact +
160                         "\' is invalid. It will be ignored for artifact resolution. Reason: " + e.getMessage() );
161 
162                     if ( getLogger().isDebugEnabled() )
163                     {
164                         getLogger().debug( "Reason: " + e.getMessage() );
165 
166                         ModelValidationResult validationResult = e.getValidationResult();
167 
168                         if ( validationResult != null )
169                         {
170                             getLogger().debug( "\nValidation Errors:" );
171                             for ( Iterator i = validationResult.getMessages().iterator(); i.hasNext(); )
172                             {
173                                 getLogger().debug( i.next().toString() );
174                             }
175                             getLogger().debug( "\n" );
176                         }
177                     }
178 
179                     project = null;
180                 }
181                 catch ( ProjectBuildingException e )
182                 {
183                     throw new ArtifactMetadataRetrievalException( "Unable to read the metadata file for artifact '" +
184                         artifact.getDependencyConflictId() + "': " + e.getMessage(), e, artifact );
185                 }
186 
187                 if ( project != null )
188                 {
189                     Relocation relocation = null;
190 
191                     DistributionManagement distMgmt = project.getDistributionManagement();
192                     if ( distMgmt != null )
193                     {
194                         relocation = distMgmt.getRelocation();
195 
196                         artifact.setDownloadUrl( distMgmt.getDownloadUrl() );
197                         pomArtifact.setDownloadUrl( distMgmt.getDownloadUrl() );
198                     }
199 
200                     if ( relocation != null )
201                     {
202                         if ( relocation.getGroupId() != null )
203                         {
204                             artifact.setGroupId( relocation.getGroupId() );
205                             project.setGroupId( relocation.getGroupId() );
206                         }
207                         if ( relocation.getArtifactId() != null )
208                         {
209                             artifact.setArtifactId( relocation.getArtifactId() );
210                             project.setArtifactId( relocation.getArtifactId() );
211                         }
212                         if ( relocation.getVersion() != null )
213                         {
214                             //note: see MNG-3454. This causes a problem, but fixing it may break more.
215                             artifact.setVersionRange( VersionRange.createFromVersion( relocation.getVersion() ) );
216                             project.setVersion( relocation.getVersion() );
217                         }
218 
219                         if ( artifact.getDependencyFilter() != null &&
220                             !artifact.getDependencyFilter().include( artifact ) )
221                         {
222                             return null;
223                         }
224 
225                         //MNG-2861: the artifact data has changed. If the available versions where previously retrieved,
226                         //we need to update it. TODO: shouldn't the versions be merged across relocations?
227                         List available = artifact.getAvailableVersions();
228                         if ( available != null && !available.isEmpty() )
229                         {
230                             artifact.setAvailableVersions( retrieveAvailableVersions( artifact, localRepository,
231                                                                                            remoteRepositories ) );
232 
233                         }
234 
235                         String message = "\n  This artifact has been relocated to " + artifact.getGroupId() + ":" +
236                             artifact.getArtifactId() + ":" + artifact.getVersion() + ".\n";
237 
238                         if ( relocation.getMessage() != null )
239                         {
240                             message += "  " + relocation.getMessage() + "\n";
241                         }
242 
243                         if ( artifact.getDependencyTrail() != null && artifact.getDependencyTrail().size() == 1 )
244                         {
245                             getLogger().warn( "While downloading " + pomArtifact.getGroupId() + ":" +
246                                 pomArtifact.getArtifactId() + ":" + pomArtifact.getVersion() + message + "\n" );
247                         }
248                         else
249                         {
250                             getLogger().debug( "While downloading " + pomArtifact.getGroupId() + ":" +
251                                 pomArtifact.getArtifactId() + ":" + pomArtifact.getVersion() + message + "\n" );
252                         }
253                     }
254                     else
255                     {
256                         done = true;
257                     }
258                 }
259                 else
260                 {
261                     done = true;
262                 }
263             }
264         }
265         while ( !done );
266 
267         return project;
268     }
269 
270     /**
271      * Retrieve the metadata for the project from the repository.
272      * Uses the ProjectBuilder, to enable post-processing and inheritance calculation before retrieving the
273      * associated artifacts.
274      */
275     public ResolutionGroup retrieve( Artifact artifact, ArtifactRepository localRepository, List remoteRepositories )
276         throws ArtifactMetadataRetrievalException
277     {
278         MavenProject project = retrieveRelocatedProject( artifact, localRepository, remoteRepositories );
279         Artifact pomArtifact;
280         if ( project != null )
281         {
282             pomArtifact = project.getArtifact();
283         }
284         else
285         {
286             pomArtifact = artifactFactory.createProjectArtifact( artifact.getGroupId(),
287                                                    artifact.getArtifactId(),
288                                                    artifact.getVersion(),
289                                                    artifact.getScope() );
290         }
291 
292 
293         // last ditch effort to try to get this set...
294         if ( artifact.getDownloadUrl() == null && pomArtifact != null )
295         {
296             // TODO: this could come straight from the project, negating the need to set it in the project itself?
297             artifact.setDownloadUrl( pomArtifact.getDownloadUrl() );
298         }
299 
300         ResolutionGroup result;
301 
302         if ( project == null )
303         {
304             // if the project is null, we encountered an invalid model (read: m1 POM)
305             // we'll just return an empty resolution group.
306             // or used the inherited scope (should that be passed to the buildFromRepository method above?)
307             result = new ResolutionGroup( pomArtifact, Collections.EMPTY_SET, Collections.EMPTY_LIST );
308         }
309         else
310         {
311             Set artifacts = Collections.EMPTY_SET;
312             if ( !artifact.getArtifactHandler().isIncludesDependencies() )
313             {
314                 // TODO: we could possibly use p.getDependencyArtifacts instead of this call, but they haven't been filtered
315                 // or used the inherited scope (should that be passed to the buildFromRepository method above?)
316                 try
317                 {
318                     artifacts = project.createArtifacts( artifactFactory, artifact.getScope(),
319                                                          artifact.getDependencyFilter() );
320                 }
321                 catch ( InvalidDependencyVersionException e )
322                 {
323                     throw new ArtifactMetadataRetrievalException( "Error in metadata for artifact '" +
324                         artifact.getDependencyConflictId() + "': " + e.getMessage(), e );
325                 }
326             }
327 
328             List repositories = aggregateRepositoryLists( remoteRepositories, project.getRemoteArtifactRepositories() );
329 
330             result = new ResolutionGroup( pomArtifact, artifacts, repositories );
331         }
332 
333         return result;
334     }
335 
336     private List aggregateRepositoryLists( List remoteRepositories, List remoteArtifactRepositories )
337         throws ArtifactMetadataRetrievalException
338     {
339         if ( superProject == null )
340         {
341             try
342             {
343                 superProject = mavenProjectBuilder.buildStandaloneSuperProject( new DefaultProjectBuilderConfiguration() );
344             }
345             catch ( ProjectBuildingException e )
346             {
347                 throw new ArtifactMetadataRetrievalException(
348                     "Unable to parse the Maven built-in model: " + e.getMessage(), e );
349             }
350         }
351 
352         List repositories = new ArrayList();
353 
354         repositories.addAll( remoteRepositories );
355 
356         // ensure that these are defined
357         for ( Iterator it = superProject.getRemoteArtifactRepositories().iterator(); it.hasNext(); )
358         {
359             ArtifactRepository superRepo = (ArtifactRepository) it.next();
360 
361             for ( Iterator aggregatedIterator = repositories.iterator(); aggregatedIterator.hasNext(); )
362             {
363                 ArtifactRepository repo = (ArtifactRepository) aggregatedIterator.next();
364 
365                 // if the repository exists in the list and was introduced by another POM's super-pom,
366                 // remove it...the repository definitions from the super-POM should only be at the end of
367                 // the list.
368                 // if the repository has been redefined, leave it.
369                 if ( repo.getId().equals( superRepo.getId() ) && repo.getUrl().equals( superRepo.getUrl() ) )
370                 {
371                     aggregatedIterator.remove();
372                 }
373             }
374         }
375 
376         // this list should contain the super-POM repositories, so we don't have to explicitly add them back.
377         for ( Iterator it = remoteArtifactRepositories.iterator(); it.hasNext(); )
378         {
379             ArtifactRepository repository = (ArtifactRepository) it.next();
380 
381             if ( !repositories.contains( repository ) )
382             {
383                 repositories.add( repository );
384             }
385         }
386 
387         return repositories;
388     }
389 
390     /**
391      * @todo desperately needs refactoring. It's just here because it's implementation is maven-project specific
392      * @return {@link Set} &lt; {@link Artifact} >
393      */
394     public static Set createArtifacts( ArtifactFactory artifactFactory, List dependencies, String inheritedScope,
395                                        ArtifactFilter dependencyFilter, MavenProject project )
396         throws InvalidDependencyVersionException
397     {
398         Set projectArtifacts = new LinkedHashSet( dependencies.size() );
399 
400         for ( Iterator i = dependencies.iterator(); i.hasNext(); )
401         {
402             Dependency d = (Dependency) i.next();
403 
404             String scope = d.getScope();
405 
406             if ( StringUtils.isEmpty( scope ) )
407             {
408                 scope = Artifact.SCOPE_COMPILE;
409 
410                 d.setScope( scope );
411             }
412 
413             VersionRange versionRange;
414             try
415             {
416                 versionRange = VersionRange.createFromVersionSpec( d.getVersion() );
417             }
418             catch ( InvalidVersionSpecificationException e )
419             {
420                 throw new InvalidDependencyVersionException( "Unable to parse version '" + d.getVersion() +
421                     "' for dependency '" + d.getManagementKey() + "': " + e.getMessage(), e );
422             }
423             Artifact artifact = artifactFactory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(),
424                                                                           versionRange, d.getType(), d.getClassifier(),
425                                                                           scope, inheritedScope, d.isOptional() );
426 
427             if ( Artifact.SCOPE_SYSTEM.equals( scope ) )
428             {
429                 artifact.setFile( new File( d.getSystemPath() ) );
430             }
431 
432             ArtifactFilter artifactFilter = dependencyFilter;
433 
434             // MNG-3769: It would be nice to be able to process relocations here, 
435             // so we could have this filtering step apply to post-relocated dependencies.
436             // HOWEVER, this would require a much more invasive POM resolution process
437             // in order to look for relocations, which would make the early steps in 
438             // a Maven build way too heavy.
439             if ( artifact != null && ( artifactFilter == null || artifactFilter.include( artifact ) ) )
440             {
441                 if ( d.getExclusions() != null && !d.getExclusions().isEmpty() )
442                 {
443                     List exclusions = new ArrayList();
444                     for ( Iterator j = d.getExclusions().iterator(); j.hasNext(); )
445                     {
446                         Exclusion e = (Exclusion) j.next();
447                         exclusions.add( e.getGroupId() + ":" + e.getArtifactId() );
448                     }
449 
450                     ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions );
451 
452                     if ( artifactFilter != null )
453                     {
454                         AndArtifactFilter filter = new AndArtifactFilter();
455                         filter.add( artifactFilter );
456                         filter.add( newFilter );
457                         artifactFilter = filter;
458                     }
459                     else
460                     {
461                         artifactFilter = newFilter;
462                     }
463                 }
464 
465                 artifact.setDependencyFilter( artifactFilter );
466 
467                 if ( project != null )
468                 {
469                     artifact = project.replaceWithActiveArtifact( artifact );
470                 }
471 
472                 projectArtifacts.add( artifact );
473             }
474         }
475 
476         return projectArtifacts;
477     }
478 
479     public List retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
480                                            List remoteRepositories )
481         throws ArtifactMetadataRetrievalException
482     {
483         RepositoryMetadata metadata = new ArtifactRepositoryMetadata( artifact );
484         try
485         {
486             repositoryMetadataManager.resolve( metadata, remoteRepositories, localRepository );
487         }
488         catch ( RepositoryMetadataResolutionException e )
489         {
490             throw new ArtifactMetadataRetrievalException( e.getMessage(), e );
491         }
492 
493         List versions;
494         Metadata repoMetadata = metadata.getMetadata();
495         if ( repoMetadata != null && repoMetadata.getVersioning() != null )
496         {
497             List metadataVersions = repoMetadata.getVersioning().getVersions();
498             versions = new ArrayList( metadataVersions.size() );
499             for ( Iterator i = metadataVersions.iterator(); i.hasNext(); )
500             {
501                 String version = (String) i.next();
502                 versions.add( new DefaultArtifactVersion( version ) );
503             }
504         }
505         else
506         {
507             versions = Collections.EMPTY_LIST;
508         }
509 
510         return versions;
511     }
512 }