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