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