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