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     public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request )
154         throws VersionResolutionException
155     {
156         RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
157 
158         Artifact artifact = request.getArtifact();
159 
160         String version = artifact.getVersion();
161 
162         VersionResult result = new VersionResult( request );
163 
164         Key cacheKey = null;
165         RepositoryCache cache = session.getCache();
166         if ( cache != null && !ConfigUtils.getBoolean( session, false, "aether.versionResolver.noCache" ) )
167         {
168             cacheKey = new Key( session, request );
169 
170             Object obj = cache.get( session, cacheKey );
171             if ( obj instanceof Record )
172             {
173                 Record record = (Record) obj;
174                 result.setVersion( record.version );
175                 result.setRepository(
176                     CacheUtils.getRepository( session, request.getRepositories(), record.repoClass, record.repoId ) );
177                 return result;
178             }
179         }
180 
181         Metadata metadata;
182 
183         if ( RELEASE.equals( version ) )
184         {
185             metadata = new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
186                                             Metadata.Nature.RELEASE );
187         }
188         else if ( LATEST.equals( version ) )
189         {
190             metadata = new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
191                                             Metadata.Nature.RELEASE_OR_SNAPSHOT );
192         }
193         else if ( version.endsWith( SNAPSHOT ) )
194         {
195             WorkspaceReader workspace = session.getWorkspaceReader();
196             if ( workspace != null && workspace.findVersions( artifact ).contains( version ) )
197             {
198                 metadata = null;
199                 result.setRepository( workspace.getRepository() );
200             }
201             else
202             {
203                 metadata =
204                     new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), version, MAVEN_METADATA_XML,
205                                          Metadata.Nature.SNAPSHOT );
206             }
207         }
208         else
209         {
210             metadata = null;
211         }
212 
213         if ( metadata == null )
214         {
215             result.setVersion( version );
216         }
217         else
218         {
219             List<MetadataRequest> metadataReqs = new ArrayList<>( request.getRepositories().size() );
220 
221             metadataReqs.add( new MetadataRequest( metadata, null, request.getRequestContext() ) );
222 
223             for ( RemoteRepository repository : request.getRepositories() )
224             {
225                 MetadataRequest metadataRequest =
226                     new MetadataRequest( metadata, repository, request.getRequestContext() );
227                 metadataRequest.setDeleteLocalCopyIfMissing( true );
228                 metadataRequest.setFavorLocalRepository( true );
229                 metadataRequest.setTrace( trace );
230                 metadataReqs.add( metadataRequest );
231             }
232 
233             List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataReqs );
234 
235             Map<String, VersionInfo> infos = new HashMap<>();
236 
237             for ( MetadataResult metadataResult : metadataResults )
238             {
239                 result.addException( metadataResult.getException() );
240 
241                 ArtifactRepository repository = metadataResult.getRequest().getRepository();
242                 if ( repository == null )
243                 {
244                     repository = session.getLocalRepository();
245                 }
246 
247                 Versioning v = readVersions( session, trace, metadataResult.getMetadata(), repository, result );
248                 merge( artifact, infos, v, repository );
249             }
250 
251             if ( RELEASE.equals( version ) )
252             {
253                 resolve( result, infos, RELEASE );
254             }
255             else if ( LATEST.equals( version ) )
256             {
257                 if ( !resolve( result, infos, LATEST ) )
258                 {
259                     resolve( result, infos, RELEASE );
260                 }
261 
262                 if ( result.getVersion() != null && result.getVersion().endsWith( SNAPSHOT ) )
263                 {
264                     VersionRequest subRequest = new VersionRequest();
265                     subRequest.setArtifact( artifact.setVersion( result.getVersion() ) );
266                     if ( result.getRepository() instanceof RemoteRepository )
267                     {
268                         RemoteRepository r = (RemoteRepository) result.getRepository();
269                         subRequest.setRepositories( Collections.singletonList( r ) );
270                     }
271                     else
272                     {
273                         subRequest.setRepositories( request.getRepositories() );
274                     }
275                     VersionResult subResult = resolveVersion( session, subRequest );
276                     result.setVersion( subResult.getVersion() );
277                     result.setRepository( subResult.getRepository() );
278                     for ( Exception exception : subResult.getExceptions() )
279                     {
280                         result.addException( exception );
281                     }
282                 }
283             }
284             else
285             {
286                 String key = SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() );
287                 merge( infos, SNAPSHOT, key );
288                 if ( !resolve( result, infos, key ) )
289                 {
290                     result.setVersion( version );
291                 }
292             }
293 
294             if ( StringUtils.isEmpty( result.getVersion() ) )
295             {
296                 throw new VersionResolutionException( result );
297             }
298         }
299 
300         if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) )
301         {
302             cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
303         }
304 
305         return result;
306     }
307 
308     private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key )
309     {
310         VersionInfo info = infos.get( key );
311         if ( info != null )
312         {
313             result.setVersion( info.version );
314             result.setRepository( info.repository );
315         }
316         return info != null;
317     }
318 
319     private Versioning readVersions( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
320                                      ArtifactRepository repository, VersionResult result )
321     {
322         Versioning versioning = null;
323         try
324         {
325             if ( metadata != null )
326             {
327                 try ( SyncContext syncContext = syncContextFactory.newInstance( session, true ) )
328                 {
329                     syncContext.acquire( null, Collections.singleton( metadata ) );
330 
331                     if ( metadata.getFile() != null && metadata.getFile().exists() )
332                     {
333                         try ( final InputStream in = new FileInputStream( metadata.getFile() ) )
334                         {
335                             versioning = new MetadataXpp3Reader().read( in, false ).getVersioning();
336 
337                             /*
338                             NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata
339                             of the local repository. This is especially troublesome during snapshot resolution so we try
340                             to handle that gracefully.
341                              */
342                             if ( versioning != null && repository instanceof LocalRepository
343                                      && versioning.getSnapshot() != null
344                                      && versioning.getSnapshot().getBuildNumber() > 0 )
345                             {
346                                 final Versioning repaired = new Versioning();
347                                 repaired.setLastUpdated( versioning.getLastUpdated() );
348                                 repaired.setSnapshot( new Snapshot() );
349                                 repaired.getSnapshot().setLocalCopy( true );
350                                 versioning = repaired;
351                                 throw new IOException( "Snapshot information corrupted with remote repository data"
352                                                            + ", please verify that no remote repository uses the id '"
353                                                            + repository.getId() + "'" );
354 
355                             }
356                         }
357                     }
358                 }
359             }
360         }
361         catch ( Exception e )
362         {
363             invalidMetadata( session, trace, metadata, repository, e );
364             result.addException( e );
365         }
366 
367         return ( versioning != null ) ? versioning : new Versioning();
368     }
369 
370     private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
371                                   ArtifactRepository repository, Exception exception )
372     {
373         RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID );
374         event.setTrace( trace );
375         event.setMetadata( metadata );
376         event.setException( exception );
377         event.setRepository( repository );
378 
379         repositoryEventDispatcher.dispatch( event.build() );
380     }
381 
382     private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning,
383                         ArtifactRepository repository )
384     {
385         if ( StringUtils.isNotEmpty( versioning.getRelease() ) )
386         {
387             merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository );
388         }
389 
390         if ( StringUtils.isNotEmpty( versioning.getLatest() ) )
391         {
392             merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository );
393         }
394 
395         for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
396         {
397             if ( StringUtils.isNotEmpty( sv.getVersion() ) )
398             {
399                 String key = getKey( sv.getClassifier(), sv.getExtension() );
400                 merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository );
401             }
402         }
403 
404         Snapshot snapshot = versioning.getSnapshot();
405         if ( snapshot != null && versioning.getSnapshotVersions().isEmpty() )
406         {
407             String version = artifact.getVersion();
408             if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 )
409             {
410                 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
411                 version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier;
412             }
413             merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository );
414         }
415     }
416 
417     private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version,
418                         ArtifactRepository repository )
419     {
420         VersionInfo info = infos.get( key );
421         if ( info == null )
422         {
423             info = new VersionInfo( timestamp, version, repository );
424             infos.put( key, info );
425         }
426         else if ( info.isOutdated( timestamp ) )
427         {
428             info.version = version;
429             info.repository = repository;
430             info.timestamp = timestamp;
431         }
432     }
433 
434     private void merge( Map<String, VersionInfo> infos, String srcKey, String dstKey )
435     {
436         VersionInfo srcInfo = infos.get( srcKey );
437         VersionInfo dstInfo = infos.get( dstKey );
438 
439         if ( dstInfo == null || ( srcInfo != null && dstInfo.isOutdated( srcInfo.timestamp )
440             && srcInfo.repository != dstInfo.repository ) )
441         {
442             infos.put( dstKey, srcInfo );
443         }
444     }
445 
446     private String getKey( String classifier, String extension )
447     {
448         return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension );
449     }
450 
451     private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact )
452     {
453         /*
454          * The workspace/reactor is in flux so we better not assume definitive information for any of its
455          * artifacts/projects.
456          */
457 
458         WorkspaceReader workspace = session.getWorkspaceReader();
459         if ( workspace == null )
460         {
461             return true;
462         }
463 
464         Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact( artifact );
465 
466         return workspace.findArtifact( pomArtifact ) == null;
467     }
468 
469     private static class VersionInfo
470     {
471 
472         String timestamp;
473 
474         String version;
475 
476         ArtifactRepository repository;
477 
478         public VersionInfo( String timestamp, String version, ArtifactRepository repository )
479         {
480             this.timestamp = ( timestamp != null ) ? timestamp : "";
481             this.version = version;
482             this.repository = repository;
483         }
484 
485         public boolean isOutdated( String timestamp )
486         {
487             return timestamp != null && timestamp.compareTo( this.timestamp ) > 0;
488         }
489 
490     }
491 
492     private static class Key
493     {
494 
495         private final String groupId;
496 
497         private final String artifactId;
498 
499         private final String classifier;
500 
501         private final String extension;
502 
503         private final String version;
504 
505         private final String context;
506 
507         private final File localRepo;
508 
509         private final WorkspaceRepository workspace;
510 
511         private final List<RemoteRepository> repositories;
512 
513         private final int hashCode;
514 
515         public Key( RepositorySystemSession session, VersionRequest request )
516         {
517             Artifact artifact = request.getArtifact();
518             groupId = artifact.getGroupId();
519             artifactId = artifact.getArtifactId();
520             classifier = artifact.getClassifier();
521             extension = artifact.getExtension();
522             version = artifact.getVersion();
523             localRepo = session.getLocalRepository().getBasedir();
524             workspace = CacheUtils.getWorkspace( session );
525             repositories = new ArrayList<>( request.getRepositories().size() );
526             boolean repoMan = false;
527             for ( RemoteRepository repository : request.getRepositories() )
528             {
529                 if ( repository.isRepositoryManager() )
530                 {
531                     repoMan = true;
532                     repositories.addAll( repository.getMirroredRepositories() );
533                 }
534                 else
535                 {
536                     repositories.add( repository );
537                 }
538             }
539             context = repoMan ? request.getRequestContext() : "";
540 
541             int hash = 17;
542             hash = hash * 31 + groupId.hashCode();
543             hash = hash * 31 + artifactId.hashCode();
544             hash = hash * 31 + classifier.hashCode();
545             hash = hash * 31 + extension.hashCode();
546             hash = hash * 31 + version.hashCode();
547             hash = hash * 31 + localRepo.hashCode();
548             hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories );
549             hashCode = hash;
550         }
551 
552         @Override
553         public boolean equals( Object obj )
554         {
555             if ( obj == this )
556             {
557                 return true;
558             }
559             else if ( obj == null || !getClass().equals( obj.getClass() ) )
560             {
561                 return false;
562             }
563 
564             Key that = (Key) obj;
565             return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId ) && classifier.equals(
566                 that.classifier ) && extension.equals( that.extension ) && version.equals( that.version )
567                 && context.equals( that.context ) && localRepo.equals( that.localRepo )
568                 && CacheUtils.eq( workspace, that.workspace )
569                 && CacheUtils.repositoriesEquals( repositories, that.repositories );
570         }
571 
572         @Override
573         public int hashCode()
574         {
575             return hashCode;
576         }
577 
578     }
579 
580     private static class Record
581     {
582         final String version;
583 
584         final String repoId;
585 
586         final Class<?> repoClass;
587 
588         public Record( String version, ArtifactRepository repository )
589         {
590             this.version = version;
591             if ( repository != null )
592             {
593                 repoId = repository.getId();
594                 repoClass = repository.getClass();
595             }
596             else
597             {
598                 repoId = null;
599                 repoClass = null;
600             }
601         }
602     }
603 
604 }