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