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.FileNotFoundException;
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.RepositoryListener;
42  import org.sonatype.aether.RepositorySystemSession;
43  import org.sonatype.aether.util.artifact.SubArtifact;
44  import org.sonatype.aether.util.listener.DefaultRepositoryEvent;
45  import org.sonatype.aether.util.metadata.DefaultMetadata;
46  import org.sonatype.aether.artifact.Artifact;
47  import org.sonatype.aether.impl.MetadataResolver;
48  import org.sonatype.aether.impl.VersionResolver;
49  import org.sonatype.aether.impl.internal.CacheUtils;
50  import org.sonatype.aether.metadata.Metadata;
51  import org.sonatype.aether.repository.ArtifactRepository;
52  import org.sonatype.aether.repository.RemoteRepository;
53  import org.sonatype.aether.repository.WorkspaceReader;
54  import org.sonatype.aether.repository.WorkspaceRepository;
55  import org.sonatype.aether.resolution.MetadataRequest;
56  import org.sonatype.aether.resolution.MetadataResult;
57  import org.sonatype.aether.resolution.VersionRequest;
58  import org.sonatype.aether.resolution.VersionResolutionException;
59  import org.sonatype.aether.resolution.VersionResult;
60  import org.sonatype.aether.spi.locator.Service;
61  import org.sonatype.aether.spi.locator.ServiceLocator;
62  import org.sonatype.aether.spi.log.Logger;
63  import org.sonatype.aether.spi.log.NullLogger;
64  
65  /**
66   * @author Benjamin Bentmann
67   */
68  @Component( role = VersionResolver.class )
69  public class DefaultVersionResolver
70      implements VersionResolver, Service
71  {
72  
73      private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
74  
75      private static final String RELEASE = "RELEASE";
76  
77      private static final String LATEST = "LATEST";
78  
79      private static final String SNAPSHOT = "SNAPSHOT";
80  
81      @Requirement
82      private Logger logger = NullLogger.INSTANCE;
83  
84      @Requirement
85      private MetadataResolver metadataResolver;
86  
87      public void initService( ServiceLocator locator )
88      {
89          setLogger( locator.getService( Logger.class ) );
90          setMetadataResolver( locator.getService( MetadataResolver.class ) );
91      }
92  
93      public DefaultVersionResolver setLogger( Logger logger )
94      {
95          this.logger = ( logger != null ) ? logger : NullLogger.INSTANCE;
96          return this;
97      }
98  
99      public DefaultVersionResolver setMetadataResolver( MetadataResolver metadataResolver )
100     {
101         if ( metadataResolver == null )
102         {
103             throw new IllegalArgumentException( "metadata resolver has not been specified" );
104         }
105         this.metadataResolver = metadataResolver;
106         return this;
107     }
108 
109     public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request )
110         throws VersionResolutionException
111     {
112         Artifact artifact = request.getArtifact();
113 
114         String version = artifact.getVersion();
115 
116         VersionResult result = new VersionResult( request );
117 
118         Key cacheKey = null;
119         RepositoryCache cache = session.getCache();
120         if ( cache != null && !ConfigurationProperties.get( session, "aether.versionResolver.noCache", false ) )
121         {
122             cacheKey = new Key( session, request );
123 
124             Object obj = cache.get( session, cacheKey );
125             if ( obj instanceof Record )
126             {
127                 Record record = (Record) obj;
128                 result.setVersion( record.version );
129                 result.setRepository( CacheUtils.getRepository( session, request.getRepositories(), record.repoClass,
130                                                                 record.repoId ) );
131                 return result;
132             }
133         }
134 
135         Metadata metadata;
136 
137         if ( RELEASE.equals( version ) )
138         {
139             metadata =
140                 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
141                                      Metadata.Nature.RELEASE );
142         }
143         else if ( LATEST.equals( version ) )
144         {
145             metadata =
146                 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML,
147                                      Metadata.Nature.RELEASE_OR_SNAPSHOT );
148         }
149         else if ( version.endsWith( SNAPSHOT ) )
150         {
151             WorkspaceReader workspace = session.getWorkspaceReader();
152             if ( workspace != null && workspace.findVersions( artifact ).contains( version ) )
153             {
154                 metadata = null;
155                 result.setRepository( workspace.getRepository() );
156             }
157             else
158             {
159                 metadata =
160                     new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), version, MAVEN_METADATA_XML,
161                                          Metadata.Nature.SNAPSHOT );
162             }
163         }
164         else
165         {
166             metadata = null;
167         }
168 
169         if ( metadata == null )
170         {
171             result.setVersion( version );
172         }
173         else
174         {
175             List<MetadataRequest> metadataRequests = new ArrayList<MetadataRequest>( request.getRepositories().size() );
176 
177             metadataRequests.add( new MetadataRequest( metadata, null, request.getRequestContext() ) );
178 
179             for ( RemoteRepository repository : request.getRepositories() )
180             {
181                 MetadataRequest metadataRequest =
182                     new MetadataRequest( metadata, repository, request.getRequestContext() );
183                 metadataRequest.setDeleteLocalCopyIfMissing( true );
184                 metadataRequest.setFavorLocalRepository( true );
185                 metadataRequests.add( metadataRequest );
186             }
187 
188             List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataRequests );
189 
190             Map<String, VersionInfo> infos = new HashMap<String, VersionInfo>();
191 
192             for ( MetadataResult metadataResult : metadataResults )
193             {
194                 result.addException( metadataResult.getException() );
195 
196                 ArtifactRepository repository = metadataResult.getRequest().getRepository();
197                 if ( repository == null )
198                 {
199                     repository = session.getLocalRepository();
200                 }
201 
202                 Versioning versioning = readVersions( session, metadataResult.getMetadata(), repository, result );
203                 merge( artifact, infos, versioning, repository );
204             }
205 
206             if ( RELEASE.equals( version ) )
207             {
208                 resolve( result, infos, RELEASE );
209             }
210             else if ( LATEST.equals( version ) )
211             {
212                 if ( !resolve( result, infos, LATEST ) )
213                 {
214                     resolve( result, infos, RELEASE );
215                 }
216 
217                 if ( result.getVersion() != null && result.getVersion().endsWith( SNAPSHOT ) )
218                 {
219                     VersionRequest subRequest = new VersionRequest();
220                     subRequest.setArtifact( artifact.setVersion( result.getVersion() ) );
221                     if ( result.getRepository() instanceof RemoteRepository )
222                     {
223                         subRequest.setRepositories( Collections.singletonList( (RemoteRepository) result.getRepository() ) );
224                     }
225                     else
226                     {
227                         subRequest.setRepositories( request.getRepositories() );
228                     }
229                     VersionResult subResult = resolveVersion( session, subRequest );
230                     result.setVersion( subResult.getVersion() );
231                     result.setRepository( subResult.getRepository() );
232                     for ( Exception exception : subResult.getExceptions() )
233                     {
234                         result.addException( exception );
235                     }
236                 }
237             }
238             else
239             {
240                 if ( !resolve( result, infos, SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() ) )
241                     && !resolve( result, infos, SNAPSHOT ) )
242                 {
243                     result.setVersion( version );
244                 }
245             }
246 
247             if ( StringUtils.isEmpty( result.getVersion() ) )
248             {
249                 throw new VersionResolutionException( result );
250             }
251         }
252 
253         if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) )
254         {
255             cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
256         }
257 
258         return result;
259     }
260 
261     private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key )
262     {
263         VersionInfo info = infos.get( key );
264         if ( info != null )
265         {
266             result.setVersion( info.version );
267             result.setRepository( info.repository );
268         }
269         return info != null;
270     }
271 
272     private Versioning readVersions( RepositorySystemSession session, Metadata metadata, ArtifactRepository repository,
273                                      VersionResult result )
274     {
275         Versioning versioning = null;
276 
277         FileInputStream fis = null;
278         try
279         {
280             if ( metadata != null && metadata.getFile() != null )
281             {
282                 fis = new FileInputStream( metadata.getFile() );
283                 org.apache.maven.artifact.repository.metadata.Metadata m = new MetadataXpp3Reader().read( fis, false );
284                 versioning = m.getVersioning();
285             }
286         }
287         catch ( FileNotFoundException e )
288         {
289             // tolerable
290         }
291         catch ( Exception e )
292         {
293             invalidMetadata( session, metadata, repository, e );
294             result.addException( e );
295         }
296         finally
297         {
298             IOUtil.close( fis );
299         }
300 
301         return ( versioning != null ) ? versioning : new Versioning();
302     }
303 
304     private void invalidMetadata( RepositorySystemSession session, Metadata metadata, ArtifactRepository repository,
305                                   Exception exception )
306     {
307         RepositoryListener listener = session.getRepositoryListener();
308         if ( listener != null )
309         {
310             DefaultRepositoryEvent event = new DefaultRepositoryEvent( session, metadata );
311             event.setException( exception );
312             event.setRepository( repository );
313             listener.metadataInvalid( event );
314         }
315     }
316 
317     private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning,
318                         ArtifactRepository repository )
319     {
320         if ( StringUtils.isNotEmpty( versioning.getRelease() ) )
321         {
322             merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository );
323         }
324 
325         if ( StringUtils.isNotEmpty( versioning.getLatest() ) )
326         {
327             merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository );
328         }
329 
330         for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
331         {
332             if ( StringUtils.isNotEmpty( sv.getVersion() ) )
333             {
334                 String key = getKey( sv.getClassifier(), sv.getExtension() );
335                 merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository );
336             }
337         }
338 
339         Snapshot snapshot = versioning.getSnapshot();
340         if ( snapshot != null )
341         {
342             String version = artifact.getVersion();
343             if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 )
344             {
345                 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
346                 version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier;
347             }
348             merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository );
349         }
350     }
351 
352     private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version,
353                         ArtifactRepository repository )
354     {
355         VersionInfo info = infos.get( key );
356         if ( info == null )
357         {
358             info = new VersionInfo( timestamp, version, repository );
359             infos.put( key, info );
360         }
361         else if ( info.isOutdated( timestamp ) )
362         {
363             info.version = version;
364             info.repository = repository;
365         }
366     }
367 
368     private String getKey( String classifier, String extension )
369     {
370         return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension );
371     }
372 
373     private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact )
374     {
375         /*
376          * The workspace/reactor is in flux so we better not assume definitive information for any of its
377          * artifacts/projects.
378          */
379 
380         WorkspaceReader workspace = session.getWorkspaceReader();
381         if ( workspace == null )
382         {
383             return true;
384         }
385 
386         Artifact pomArtifact = artifact;
387         if ( pomArtifact.getClassifier().length() > 0 || !"pom".equals( pomArtifact.getExtension() ) )
388         {
389             pomArtifact = new SubArtifact( artifact, "", "pom" );
390         }
391 
392         return workspace.findArtifact( pomArtifact ) == null;
393     }
394 
395     private static class VersionInfo
396     {
397 
398         String timestamp;
399 
400         String version;
401 
402         ArtifactRepository repository;
403 
404         public VersionInfo( String timestamp, String version, ArtifactRepository repository )
405         {
406             this.timestamp = ( timestamp != null ) ? timestamp : "";
407             this.version = version;
408             this.repository = repository;
409         }
410 
411         public boolean isOutdated( String timestamp )
412         {
413             return timestamp != null && timestamp.compareTo( this.timestamp ) > 0;
414         }
415 
416     }
417 
418     private static class Key
419     {
420 
421         private final String groupId;
422 
423         private final String artifactId;
424 
425         private final String classifier;
426 
427         private final String extension;
428 
429         private final String version;
430 
431         private final String context;
432 
433         private final File localRepo;
434 
435         private final WorkspaceRepository workspace;
436 
437         private final List<RemoteRepository> repositories;
438 
439         private final int hashCode;
440 
441         public Key( RepositorySystemSession session, VersionRequest request )
442         {
443             Artifact artifact = request.getArtifact();
444             groupId = artifact.getGroupId();
445             artifactId = artifact.getArtifactId();
446             classifier = artifact.getClassifier();
447             extension = artifact.getExtension();
448             version = artifact.getVersion();
449             context = request.getRequestContext();
450             localRepo = session.getLocalRepository().getBasedir();
451             workspace = CacheUtils.getWorkspace( session );
452             repositories = new ArrayList<RemoteRepository>( request.getRepositories().size() );
453             for ( RemoteRepository repository : request.getRepositories() )
454             {
455                 if ( repository.isRepositoryManager() )
456                 {
457                     repositories.addAll( repository.getMirroredRepositories() );
458                 }
459                 else
460                 {
461                     repositories.add( repository );
462                 }
463             }
464 
465             int hash = 17;
466             hash = hash * 31 + groupId.hashCode();
467             hash = hash * 31 + artifactId.hashCode();
468             hash = hash * 31 + classifier.hashCode();
469             hash = hash * 31 + extension.hashCode();
470             hash = hash * 31 + version.hashCode();
471             hash = hash * 31 + localRepo.hashCode();
472             hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories );
473             hashCode = hash;
474         }
475 
476         @Override
477         public boolean equals( Object obj )
478         {
479             if ( obj == this )
480             {
481                 return true;
482             }
483             else if ( obj == null || !getClass().equals( obj.getClass() ) )
484             {
485                 return false;
486             }
487 
488             Key that = (Key) obj;
489             return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
490                 && classifier.equals( that.classifier ) && extension.equals( that.extension )
491                 && version.equals( that.version ) && context.equals( that.context )
492                 && localRepo.equals( that.localRepo ) && CacheUtils.eq( workspace, that.workspace )
493                 && CacheUtils.repositoriesEquals( repositories, that.repositories );
494         }
495 
496         @Override
497         public int hashCode()
498         {
499             return hashCode;
500         }
501 
502     }
503 
504     private static class Record
505     {
506         final String version;
507 
508         final String repoId;
509 
510         final Class<?> repoClass;
511 
512         public Record( String version, ArtifactRepository repository )
513         {
514             this.version = version;
515             if ( repository != null )
516             {
517                 repoId = repository.getId();
518                 repoClass = repository.getClass();
519             }
520             else
521             {
522                 repoId = null;
523                 repoClass = null;
524             }
525         }
526     }
527 
528 }