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