View Javadoc

1   package org.apache.maven.repository.internal;
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.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.maven.artifact.repository.metadata.Snapshot;
33  import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
34  import org.apache.maven.artifact.repository.metadata.Versioning;
35  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
36  import org.codehaus.plexus.component.annotations.Component;
37  import org.codehaus.plexus.component.annotations.Requirement;
38  import org.codehaus.plexus.util.IOUtil;
39  import org.codehaus.plexus.util.StringUtils;
40  import org.sonatype.aether.ConfigurationProperties;
41  import org.sonatype.aether.RepositoryCache;
42  import org.sonatype.aether.RepositoryEvent.EventType;
43  import org.sonatype.aether.RepositoryListener;
44  import org.sonatype.aether.RepositorySystemSession;
45  import org.sonatype.aether.util.artifact.SubArtifact;
46  import org.sonatype.aether.util.listener.DefaultRepositoryEvent;
47  import org.sonatype.aether.util.metadata.DefaultMetadata;
48  import org.sonatype.aether.artifact.Artifact;
49  import org.sonatype.aether.impl.MetadataResolver;
50  import org.sonatype.aether.impl.VersionResolver;
51  import org.sonatype.aether.impl.internal.CacheUtils;
52  import org.sonatype.aether.metadata.Metadata;
53  import org.sonatype.aether.repository.ArtifactRepository;
54  import org.sonatype.aether.repository.LocalRepository;
55  import org.sonatype.aether.repository.RemoteRepository;
56  import org.sonatype.aether.repository.WorkspaceReader;
57  import org.sonatype.aether.repository.WorkspaceRepository;
58  import org.sonatype.aether.resolution.MetadataRequest;
59  import org.sonatype.aether.resolution.MetadataResult;
60  import org.sonatype.aether.resolution.VersionRequest;
61  import org.sonatype.aether.resolution.VersionResolutionException;
62  import org.sonatype.aether.resolution.VersionResult;
63  import org.sonatype.aether.spi.locator.Service;
64  import org.sonatype.aether.spi.locator.ServiceLocator;
65  import org.sonatype.aether.spi.log.Logger;
66  import org.sonatype.aether.spi.log.NullLogger;
67  
68  /**
69   * @author Benjamin Bentmann
70   */
71  @Component( role = VersionResolver.class )
72  public class DefaultVersionResolver
73      implements VersionResolver, Service
74  {
75  
76      private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
77  
78      private static final String RELEASE = "RELEASE";
79  
80      private static final String LATEST = "LATEST";
81  
82      private static final String SNAPSHOT = "SNAPSHOT";
83  
84      @Requirement
85      private Logger logger = NullLogger.INSTANCE;
86  
87      @Requirement
88      private MetadataResolver metadataResolver;
89  
90      public void initService( ServiceLocator locator )
91      {
92          setLogger( locator.getService( Logger.class ) );
93          setMetadataResolver( locator.getService( MetadataResolver.class ) );
94      }
95  
96      public DefaultVersionResolver setLogger( Logger logger )
97      {
98          this.logger = ( logger != null ) ? logger : NullLogger.INSTANCE;
99          return this;
100     }
101 
102     public DefaultVersionResolver setMetadataResolver( MetadataResolver metadataResolver )
103     {
104         if ( metadataResolver == null )
105         {
106             throw new IllegalArgumentException( "metadata resolver has not been specified" );
107         }
108         this.metadataResolver = metadataResolver;
109         return this;
110     }
111 
112     public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request )
113         throws VersionResolutionException
114     {
115         Artifact artifact = request.getArtifact();
116 
117         String version = artifact.getVersion();
118 
119         VersionResult result = new VersionResult( request );
120 
121         Key cacheKey = null;
122         RepositoryCache cache = session.getCache();
123         if ( cache != null && !ConfigurationProperties.get( session, "aether.versionResolver.noCache", false ) )
124         {
125             cacheKey = new Key( session, request );
126 
127             Object obj = cache.get( session, cacheKey );
128             if ( obj instanceof Record )
129             {
130                 Record record = (Record) obj;
131                 result.setVersion( record.version );
132                 result.setRepository( CacheUtils.getRepository( session, request.getRepositories(), record.repoClass,
133                                                                 record.repoId ) );
134                 return result;
135             }
136         }
137 
138         Metadata metadata;
139 
140         if ( RELEASE.equals( version ) )
141         {
142             metadata =
143                 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
144                                      Metadata.Nature.RELEASE );
145         }
146         else if ( LATEST.equals( version ) )
147         {
148             metadata =
149                 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
150                                      Metadata.Nature.RELEASE_OR_SNAPSHOT );
151         }
152         else if ( version.endsWith( SNAPSHOT ) )
153         {
154             WorkspaceReader workspace = session.getWorkspaceReader();
155             if ( workspace != null && workspace.findVersions( artifact ).contains( version ) )
156             {
157                 metadata = null;
158                 result.setRepository( workspace.getRepository() );
159             }
160             else
161             {
162                 metadata =
163                     new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), version, MAVEN_METADATA_XML,
164                                          Metadata.Nature.SNAPSHOT );
165             }
166         }
167         else
168         {
169             metadata = null;
170         }
171 
172         if ( metadata == null )
173         {
174             result.setVersion( version );
175         }
176         else
177         {
178             List<MetadataRequest> metadataRequests = new ArrayList<MetadataRequest>( request.getRepositories().size() );
179 
180             metadataRequests.add( new MetadataRequest( metadata, null, request.getRequestContext() ) );
181 
182             for ( RemoteRepository repository : request.getRepositories() )
183             {
184                 MetadataRequest metadataRequest =
185                     new MetadataRequest( metadata, repository, request.getRequestContext() );
186                 metadataRequest.setDeleteLocalCopyIfMissing( true );
187                 metadataRequest.setFavorLocalRepository( true );
188                 metadataRequests.add( metadataRequest );
189             }
190 
191             List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataRequests );
192 
193             Map<String, VersionInfo> infos = new HashMap<String, VersionInfo>();
194 
195             for ( MetadataResult metadataResult : metadataResults )
196             {
197                 result.addException( metadataResult.getException() );
198 
199                 ArtifactRepository repository = metadataResult.getRequest().getRepository();
200                 if ( repository == null )
201                 {
202                     repository = session.getLocalRepository();
203                 }
204 
205                 Versioning versioning = readVersions( session, metadataResult.getMetadata(), repository, result );
206                 merge( artifact, infos, versioning, repository );
207             }
208 
209             if ( RELEASE.equals( version ) )
210             {
211                 resolve( result, infos, RELEASE );
212             }
213             else if ( LATEST.equals( version ) )
214             {
215                 if ( !resolve( result, infos, LATEST ) )
216                 {
217                     resolve( result, infos, RELEASE );
218                 }
219 
220                 if ( result.getVersion() != null && result.getVersion().endsWith( SNAPSHOT ) )
221                 {
222                     VersionRequest subRequest = new VersionRequest();
223                     subRequest.setArtifact( artifact.setVersion( result.getVersion() ) );
224                     if ( result.getRepository() instanceof RemoteRepository )
225                     {
226                         subRequest.setRepositories( Collections.singletonList( (RemoteRepository) result.getRepository() ) );
227                     }
228                     else
229                     {
230                         subRequest.setRepositories( request.getRepositories() );
231                     }
232                     VersionResult subResult = resolveVersion( session, subRequest );
233                     result.setVersion( subResult.getVersion() );
234                     result.setRepository( subResult.getRepository() );
235                     for ( Exception exception : subResult.getExceptions() )
236                     {
237                         result.addException( exception );
238                     }
239                 }
240             }
241             else
242             {
243                 if ( !resolve( result, infos, SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() ) )
244                     && !resolve( result, infos, SNAPSHOT ) )
245                 {
246                     result.setVersion( version );
247                 }
248             }
249 
250             if ( StringUtils.isEmpty( result.getVersion() ) )
251             {
252                 throw new VersionResolutionException( result );
253             }
254         }
255 
256         if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) )
257         {
258             cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
259         }
260 
261         return result;
262     }
263 
264     private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key )
265     {
266         VersionInfo info = infos.get( key );
267         if ( info != null )
268         {
269             result.setVersion( info.version );
270             result.setRepository( info.repository );
271         }
272         return info != null;
273     }
274 
275     private Versioning readVersions( RepositorySystemSession session, Metadata metadata, ArtifactRepository repository,
276                                      VersionResult result )
277     {
278         Versioning versioning = null;
279 
280         FileInputStream fis = null;
281         try
282         {
283             if ( metadata != null && metadata.getFile() != null )
284             {
285                 fis = new FileInputStream( metadata.getFile() );
286                 org.apache.maven.artifact.repository.metadata.Metadata m = new MetadataXpp3Reader().read( fis, false );
287                 versioning = m.getVersioning();
288 
289                 /*
290                  * NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata of the
291                  * local repository. This is especially troublesome during snapshot resolution so we try to handle that
292                  * gracefully.
293                  */
294                 if ( versioning != null && repository instanceof LocalRepository )
295                 {
296                     if ( !versioning.getSnapshotVersions().isEmpty()
297                         || ( versioning.getSnapshot() != null && versioning.getSnapshot().getBuildNumber() > 0 ) )
298                     {
299                         Versioning repaired = new Versioning();
300                         repaired.setLastUpdated( versioning.getLastUpdated() );
301                         Snapshot snapshot = new Snapshot();
302                         snapshot.setLocalCopy( true );
303                         repaired.setSnapshot( snapshot );
304                         versioning = repaired;
305 
306                         throw new IOException( "Snapshot information corrupted with remote repository data"
307                             + ", please verify that no remote repository uses the id '" + repository.getId() + "'" );
308                     }
309                 }
310             }
311         }
312         catch ( FileNotFoundException e )
313         {
314             // tolerable
315         }
316         catch ( Exception e )
317         {
318             invalidMetadata( session, metadata, repository, e );
319             result.addException( e );
320         }
321         finally
322         {
323             IOUtil.close( fis );
324         }
325 
326         return ( versioning != null ) ? versioning : new Versioning();
327     }
328 
329     private void invalidMetadata( RepositorySystemSession session, Metadata metadata, ArtifactRepository repository,
330                                   Exception exception )
331     {
332         RepositoryListener listener = session.getRepositoryListener();
333         if ( listener != null )
334         {
335             DefaultRepositoryEvent event = new DefaultRepositoryEvent( EventType.METADATA_INVALID, session );
336             event.setMetadata( metadata );
337             event.setException( exception );
338             event.setRepository( repository );
339             listener.metadataInvalid( event );
340         }
341     }
342 
343     private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning,
344                         ArtifactRepository repository )
345     {
346         if ( StringUtils.isNotEmpty( versioning.getRelease() ) )
347         {
348             merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository );
349         }
350 
351         if ( StringUtils.isNotEmpty( versioning.getLatest() ) )
352         {
353             merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository );
354         }
355 
356         for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
357         {
358             if ( StringUtils.isNotEmpty( sv.getVersion() ) )
359             {
360                 String key = getKey( sv.getClassifier(), sv.getExtension() );
361                 merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository );
362             }
363         }
364 
365         Snapshot snapshot = versioning.getSnapshot();
366         if ( snapshot != null )
367         {
368             String version = artifact.getVersion();
369             if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 )
370             {
371                 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
372                 version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier;
373             }
374             merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository );
375         }
376     }
377 
378     private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version,
379                         ArtifactRepository repository )
380     {
381         VersionInfo info = infos.get( key );
382         if ( info == null )
383         {
384             info = new VersionInfo( timestamp, version, repository );
385             infos.put( key, info );
386         }
387         else if ( info.isOutdated( timestamp ) )
388         {
389             info.version = version;
390             info.repository = repository;
391         }
392     }
393 
394     private String getKey( String classifier, String extension )
395     {
396         return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension );
397     }
398 
399     private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact )
400     {
401         /*
402          * The workspace/reactor is in flux so we better not assume definitive information for any of its
403          * artifacts/projects.
404          */
405 
406         WorkspaceReader workspace = session.getWorkspaceReader();
407         if ( workspace == null )
408         {
409             return true;
410         }
411 
412         Artifact pomArtifact = artifact;
413         if ( pomArtifact.getClassifier().length() > 0 || !"pom".equals( pomArtifact.getExtension() ) )
414         {
415             pomArtifact = new SubArtifact( artifact, "", "pom" );
416         }
417 
418         return workspace.findArtifact( pomArtifact ) == null;
419     }
420 
421     private static class VersionInfo
422     {
423 
424         String timestamp;
425 
426         String version;
427 
428         ArtifactRepository repository;
429 
430         public VersionInfo( String timestamp, String version, ArtifactRepository repository )
431         {
432             this.timestamp = ( timestamp != null ) ? timestamp : "";
433             this.version = version;
434             this.repository = repository;
435         }
436 
437         public boolean isOutdated( String timestamp )
438         {
439             return timestamp != null && timestamp.compareTo( this.timestamp ) > 0;
440         }
441 
442     }
443 
444     private static class Key
445     {
446 
447         private final String groupId;
448 
449         private final String artifactId;
450 
451         private final String classifier;
452 
453         private final String extension;
454 
455         private final String version;
456 
457         private final String context;
458 
459         private final File localRepo;
460 
461         private final WorkspaceRepository workspace;
462 
463         private final List<RemoteRepository> repositories;
464 
465         private final int hashCode;
466 
467         public Key( RepositorySystemSession session, VersionRequest request )
468         {
469             Artifact artifact = request.getArtifact();
470             groupId = artifact.getGroupId();
471             artifactId = artifact.getArtifactId();
472             classifier = artifact.getClassifier();
473             extension = artifact.getExtension();
474             version = artifact.getVersion();
475             localRepo = session.getLocalRepository().getBasedir();
476             workspace = CacheUtils.getWorkspace( session );
477             repositories = new ArrayList<RemoteRepository>( request.getRepositories().size() );
478             boolean repoMan = false;
479             for ( RemoteRepository repository : request.getRepositories() )
480             {
481                 if ( repository.isRepositoryManager() )
482                 {
483                     repoMan = true;
484                     repositories.addAll( repository.getMirroredRepositories() );
485                 }
486                 else
487                 {
488                     repositories.add( repository );
489                 }
490             }
491             context = repoMan ? request.getRequestContext() : "";
492 
493             int hash = 17;
494             hash = hash * 31 + groupId.hashCode();
495             hash = hash * 31 + artifactId.hashCode();
496             hash = hash * 31 + classifier.hashCode();
497             hash = hash * 31 + extension.hashCode();
498             hash = hash * 31 + version.hashCode();
499             hash = hash * 31 + localRepo.hashCode();
500             hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories );
501             hashCode = hash;
502         }
503 
504         @Override
505         public boolean equals( Object obj )
506         {
507             if ( obj == this )
508             {
509                 return true;
510             }
511             else if ( obj == null || !getClass().equals( obj.getClass() ) )
512             {
513                 return false;
514             }
515 
516             Key that = (Key) obj;
517             return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
518                 && classifier.equals( that.classifier ) && extension.equals( that.extension )
519                 && version.equals( that.version ) && context.equals( that.context )
520                 && localRepo.equals( that.localRepo ) && CacheUtils.eq( workspace, that.workspace )
521                 && CacheUtils.repositoriesEquals( repositories, that.repositories );
522         }
523 
524         @Override
525         public int hashCode()
526         {
527             return hashCode;
528         }
529 
530     }
531 
532     private static class Record
533     {
534         final String version;
535 
536         final String repoId;
537 
538         final Class<?> repoClass;
539 
540         public Record( String version, ArtifactRepository repository )
541         {
542             this.version = version;
543             if ( repository != null )
544             {
545                 repoId = repository.getId();
546                 repoClass = repository.getClass();
547             }
548             else
549             {
550                 repoId = null;
551                 repoClass = null;
552             }
553         }
554     }
555 
556 }