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