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