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