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 if ( !resolve( result, infos, SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() ) )
244 && !resolve( result, infos, SNAPSHOT ) )
245 {
246 result.setVersion( version );
247 }
248 }
249
250 if ( StringUtils.isEmpty( result.getVersion() ) )
251 {
252 throw new VersionResolutionException( result );
253 }
254 }
255
256 if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) )
257 {
258 cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
259 }
260
261 return result;
262 }
263
264 private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key )
265 {
266 VersionInfo info = infos.get( key );
267 if ( info != null )
268 {
269 result.setVersion( info.version );
270 result.setRepository( info.repository );
271 }
272 return info != null;
273 }
274
275 private Versioning readVersions( RepositorySystemSession session, Metadata metadata, ArtifactRepository repository,
276 VersionResult result )
277 {
278 Versioning versioning = null;
279
280 FileInputStream fis = null;
281 try
282 {
283 if ( metadata != null && metadata.getFile() != null )
284 {
285 fis = new FileInputStream( metadata.getFile() );
286 org.apache.maven.artifact.repository.metadata.Metadata m = new MetadataXpp3Reader().read( fis, false );
287 versioning = m.getVersioning();
288
289
290
291
292
293
294 if ( versioning != null && repository instanceof LocalRepository )
295 {
296 if ( !versioning.getSnapshotVersions().isEmpty()
297 || ( 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 )
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 String getKey( String classifier, String extension )
395 {
396 return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension );
397 }
398
399 private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact )
400 {
401
402
403
404
405
406 WorkspaceReader workspace = session.getWorkspaceReader();
407 if ( workspace == null )
408 {
409 return true;
410 }
411
412 Artifact pomArtifact = artifact;
413 if ( pomArtifact.getClassifier().length() > 0 || !"pom".equals( pomArtifact.getExtension() ) )
414 {
415 pomArtifact = new SubArtifact( artifact, "", "pom" );
416 }
417
418 return workspace.findArtifact( pomArtifact ) == null;
419 }
420
421 private static class VersionInfo
422 {
423
424 String timestamp;
425
426 String version;
427
428 ArtifactRepository repository;
429
430 public VersionInfo( String timestamp, String version, ArtifactRepository repository )
431 {
432 this.timestamp = ( timestamp != null ) ? timestamp : "";
433 this.version = version;
434 this.repository = repository;
435 }
436
437 public boolean isOutdated( String timestamp )
438 {
439 return timestamp != null && timestamp.compareTo( this.timestamp ) > 0;
440 }
441
442 }
443
444 private static class Key
445 {
446
447 private final String groupId;
448
449 private final String artifactId;
450
451 private final String classifier;
452
453 private final String extension;
454
455 private final String version;
456
457 private final String context;
458
459 private final File localRepo;
460
461 private final WorkspaceRepository workspace;
462
463 private final List<RemoteRepository> repositories;
464
465 private final int hashCode;
466
467 public Key( RepositorySystemSession session, VersionRequest request )
468 {
469 Artifact artifact = request.getArtifact();
470 groupId = artifact.getGroupId();
471 artifactId = artifact.getArtifactId();
472 classifier = artifact.getClassifier();
473 extension = artifact.getExtension();
474 version = artifact.getVersion();
475 localRepo = session.getLocalRepository().getBasedir();
476 workspace = CacheUtils.getWorkspace( session );
477 repositories = new ArrayList<RemoteRepository>( request.getRepositories().size() );
478 boolean repoMan = false;
479 for ( RemoteRepository repository : request.getRepositories() )
480 {
481 if ( repository.isRepositoryManager() )
482 {
483 repoMan = true;
484 repositories.addAll( repository.getMirroredRepositories() );
485 }
486 else
487 {
488 repositories.add( repository );
489 }
490 }
491 context = repoMan ? request.getRequestContext() : "";
492
493 int hash = 17;
494 hash = hash * 31 + groupId.hashCode();
495 hash = hash * 31 + artifactId.hashCode();
496 hash = hash * 31 + classifier.hashCode();
497 hash = hash * 31 + extension.hashCode();
498 hash = hash * 31 + version.hashCode();
499 hash = hash * 31 + localRepo.hashCode();
500 hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories );
501 hashCode = hash;
502 }
503
504 @Override
505 public boolean equals( Object obj )
506 {
507 if ( obj == this )
508 {
509 return true;
510 }
511 else if ( obj == null || !getClass().equals( obj.getClass() ) )
512 {
513 return false;
514 }
515
516 Key that = (Key) obj;
517 return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
518 && classifier.equals( that.classifier ) && extension.equals( that.extension )
519 && version.equals( that.version ) && context.equals( that.context )
520 && localRepo.equals( that.localRepo ) && CacheUtils.eq( workspace, that.workspace )
521 && CacheUtils.repositoriesEquals( repositories, that.repositories );
522 }
523
524 @Override
525 public int hashCode()
526 {
527 return hashCode;
528 }
529
530 }
531
532 private static class Record
533 {
534 final String version;
535
536 final String repoId;
537
538 final Class<?> repoClass;
539
540 public Record( String version, ArtifactRepository repository )
541 {
542 this.version = version;
543 if ( repository != null )
544 {
545 repoId = repository.getId();
546 repoClass = repository.getClass();
547 }
548 else
549 {
550 repoId = null;
551 repoClass = null;
552 }
553 }
554 }
555
556 }