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