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