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