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