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