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