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> metadataRequests = new ArrayList<MetadataRequest>( request.getRepositories().size() );
237
238 metadataRequests.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 metadataRequests.add( metadataRequest );
248 }
249
250 List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataRequests );
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 versioning = readVersions( session, trace, metadataResult.getMetadata(), repository, result );
265 merge( artifact, infos, versioning, 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 subRequest.setRepositories( Collections.singletonList( (RemoteRepository) result.getRepository() ) );
286 }
287 else
288 {
289 subRequest.setRepositories( request.getRepositories() );
290 }
291 VersionResult subResult = resolveVersion( session, subRequest );
292 result.setVersion( subResult.getVersion() );
293 result.setRepository( subResult.getRepository() );
294 for ( Exception exception : subResult.getExceptions() )
295 {
296 result.addException( exception );
297 }
298 }
299 }
300 else
301 {
302 String key = SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() );
303 merge( infos, SNAPSHOT, key );
304 if ( !resolve( result, infos, key ) )
305 {
306 result.setVersion( version );
307 }
308 }
309
310 if ( StringUtils.isEmpty( result.getVersion() ) )
311 {
312 throw new VersionResolutionException( result );
313 }
314 }
315
316 if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) )
317 {
318 cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) );
319 }
320
321 return result;
322 }
323
324 private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key )
325 {
326 VersionInfo info = infos.get( key );
327 if ( info != null )
328 {
329 result.setVersion( info.version );
330 result.setRepository( info.repository );
331 }
332 return info != null;
333 }
334
335 private Versioning readVersions( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
336 ArtifactRepository repository, VersionResult result )
337 {
338 Versioning versioning = null;
339
340 FileInputStream fis = null;
341 try
342 {
343 if ( metadata != null )
344 {
345 SyncContext syncContext = syncContextFactory.newInstance( session, true );
346
347 try
348 {
349 syncContext.acquire( null, Collections.singleton( metadata ) );
350
351 if ( metadata.getFile() != null && metadata.getFile().exists() )
352 {
353 fis = new FileInputStream( metadata.getFile() );
354 org.apache.maven.artifact.repository.metadata.Metadata m =
355 new MetadataXpp3Reader().read( fis, false );
356 versioning = m.getVersioning();
357
358
359
360
361
362
363 if ( versioning != null && repository instanceof LocalRepository )
364 {
365 if ( versioning.getSnapshot() != null && versioning.getSnapshot().getBuildNumber() > 0 )
366 {
367 Versioning repaired = new Versioning();
368 repaired.setLastUpdated( versioning.getLastUpdated() );
369 Snapshot snapshot = new Snapshot();
370 snapshot.setLocalCopy( true );
371 repaired.setSnapshot( snapshot );
372 versioning = repaired;
373
374 throw new IOException( "Snapshot information corrupted with remote repository data"
375 + ", please verify that no remote repository uses the id '" + repository.getId()
376 + "'" );
377 }
378 }
379 }
380 }
381 finally
382 {
383 syncContext.close();
384 }
385 }
386 }
387 catch ( Exception e )
388 {
389 invalidMetadata( session, trace, metadata, repository, e );
390 result.addException( e );
391 }
392 finally
393 {
394 IOUtil.close( fis );
395 }
396
397 return ( versioning != null ) ? versioning : new Versioning();
398 }
399
400 private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
401 ArtifactRepository repository, Exception exception )
402 {
403 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID );
404 event.setTrace( trace );
405 event.setMetadata( metadata );
406 event.setException( exception );
407 event.setRepository( repository );
408
409 repositoryEventDispatcher.dispatch( event.build() );
410 }
411
412 private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning,
413 ArtifactRepository repository )
414 {
415 if ( StringUtils.isNotEmpty( versioning.getRelease() ) )
416 {
417 merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository );
418 }
419
420 if ( StringUtils.isNotEmpty( versioning.getLatest() ) )
421 {
422 merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository );
423 }
424
425 for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
426 {
427 if ( StringUtils.isNotEmpty( sv.getVersion() ) )
428 {
429 String key = getKey( sv.getClassifier(), sv.getExtension() );
430 merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository );
431 }
432 }
433
434 Snapshot snapshot = versioning.getSnapshot();
435 if ( snapshot != null && versioning.getSnapshotVersions().isEmpty() )
436 {
437 String version = artifact.getVersion();
438 if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 )
439 {
440 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
441 version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier;
442 }
443 merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository );
444 }
445 }
446
447 private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version,
448 ArtifactRepository repository )
449 {
450 VersionInfo info = infos.get( key );
451 if ( info == null )
452 {
453 info = new VersionInfo( timestamp, version, repository );
454 infos.put( key, info );
455 }
456 else if ( info.isOutdated( timestamp ) )
457 {
458 info.version = version;
459 info.repository = repository;
460 info.timestamp = timestamp;
461 }
462 }
463
464 private void merge( Map<String, VersionInfo> infos, String srcKey, String dstKey )
465 {
466 VersionInfo srcInfo = infos.get( srcKey );
467 VersionInfo dstInfo = infos.get( dstKey );
468
469 if ( dstInfo == null
470 || ( srcInfo != null && dstInfo.isOutdated( srcInfo.timestamp ) && srcInfo.repository != dstInfo.repository ) )
471 {
472 infos.put( dstKey, srcInfo );
473 }
474 }
475
476 private String getKey( String classifier, String extension )
477 {
478 return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension );
479 }
480
481 private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact )
482 {
483
484
485
486
487
488 WorkspaceReader workspace = session.getWorkspaceReader();
489 if ( workspace == null )
490 {
491 return true;
492 }
493
494 Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact( artifact );
495
496 return workspace.findArtifact( pomArtifact ) == null;
497 }
498
499 private static class VersionInfo
500 {
501
502 String timestamp;
503
504 String version;
505
506 ArtifactRepository repository;
507
508 public VersionInfo( String timestamp, String version, ArtifactRepository repository )
509 {
510 this.timestamp = ( timestamp != null ) ? timestamp : "";
511 this.version = version;
512 this.repository = repository;
513 }
514
515 public boolean isOutdated( String timestamp )
516 {
517 return timestamp != null && timestamp.compareTo( this.timestamp ) > 0;
518 }
519
520 }
521
522 private static class Key
523 {
524
525 private final String groupId;
526
527 private final String artifactId;
528
529 private final String classifier;
530
531 private final String extension;
532
533 private final String version;
534
535 private final String context;
536
537 private final File localRepo;
538
539 private final WorkspaceRepository workspace;
540
541 private final List<RemoteRepository> repositories;
542
543 private final int hashCode;
544
545 public Key( RepositorySystemSession session, VersionRequest request )
546 {
547 Artifact artifact = request.getArtifact();
548 groupId = artifact.getGroupId();
549 artifactId = artifact.getArtifactId();
550 classifier = artifact.getClassifier();
551 extension = artifact.getExtension();
552 version = artifact.getVersion();
553 localRepo = session.getLocalRepository().getBasedir();
554 workspace = CacheUtils.getWorkspace( session );
555 repositories = new ArrayList<RemoteRepository>( request.getRepositories().size() );
556 boolean repoMan = false;
557 for ( RemoteRepository repository : request.getRepositories() )
558 {
559 if ( repository.isRepositoryManager() )
560 {
561 repoMan = true;
562 repositories.addAll( repository.getMirroredRepositories() );
563 }
564 else
565 {
566 repositories.add( repository );
567 }
568 }
569 context = repoMan ? request.getRequestContext() : "";
570
571 int hash = 17;
572 hash = hash * 31 + groupId.hashCode();
573 hash = hash * 31 + artifactId.hashCode();
574 hash = hash * 31 + classifier.hashCode();
575 hash = hash * 31 + extension.hashCode();
576 hash = hash * 31 + version.hashCode();
577 hash = hash * 31 + localRepo.hashCode();
578 hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories );
579 hashCode = hash;
580 }
581
582 @Override
583 public boolean equals( Object obj )
584 {
585 if ( obj == this )
586 {
587 return true;
588 }
589 else if ( obj == null || !getClass().equals( obj.getClass() ) )
590 {
591 return false;
592 }
593
594 Key that = (Key) obj;
595 return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId )
596 && classifier.equals( that.classifier ) && extension.equals( that.extension )
597 && version.equals( that.version ) && context.equals( that.context )
598 && localRepo.equals( that.localRepo ) && CacheUtils.eq( workspace, that.workspace )
599 && CacheUtils.repositoriesEquals( repositories, that.repositories );
600 }
601
602 @Override
603 public int hashCode()
604 {
605 return hashCode;
606 }
607
608 }
609
610 private static class Record
611 {
612 final String version;
613
614 final String repoId;
615
616 final Class<?> repoClass;
617
618 public Record( String version, ArtifactRepository repository )
619 {
620 this.version = version;
621 if ( repository != null )
622 {
623 repoId = repository.getId();
624 repoClass = repository.getClass();
625 }
626 else
627 {
628 repoId = null;
629 repoClass = null;
630 }
631 }
632 }
633
634 }