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