View Javadoc

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