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