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