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