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