001 package org.apache.maven.repository.internal; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import java.io.File; 023 import java.io.FileInputStream; 024 import java.io.IOException; 025 import java.util.ArrayList; 026 import java.util.Collections; 027 import java.util.HashMap; 028 import java.util.List; 029 import java.util.Map; 030 031 import org.apache.maven.artifact.repository.metadata.Snapshot; 032 import org.apache.maven.artifact.repository.metadata.SnapshotVersion; 033 import org.apache.maven.artifact.repository.metadata.Versioning; 034 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader; 035 import org.codehaus.plexus.component.annotations.Component; 036 import org.codehaus.plexus.component.annotations.Requirement; 037 import org.codehaus.plexus.util.IOUtil; 038 import org.codehaus.plexus.util.StringUtils; 039 import org.sonatype.aether.ConfigurationProperties; 040 import org.sonatype.aether.RepositoryCache; 041 import org.sonatype.aether.RequestTrace; 042 import org.sonatype.aether.RepositoryEvent.EventType; 043 import org.sonatype.aether.RepositorySystemSession; 044 import org.sonatype.aether.SyncContext; 045 import org.sonatype.aether.util.DefaultRequestTrace; 046 import org.sonatype.aether.util.listener.DefaultRepositoryEvent; 047 import org.sonatype.aether.util.metadata.DefaultMetadata; 048 import org.sonatype.aether.artifact.Artifact; 049 import org.sonatype.aether.impl.MetadataResolver; 050 import org.sonatype.aether.impl.RepositoryEventDispatcher; 051 import org.sonatype.aether.impl.SyncContextFactory; 052 import org.sonatype.aether.impl.VersionResolver; 053 import org.sonatype.aether.impl.internal.CacheUtils; 054 import org.sonatype.aether.metadata.Metadata; 055 import org.sonatype.aether.repository.ArtifactRepository; 056 import org.sonatype.aether.repository.LocalRepository; 057 import org.sonatype.aether.repository.RemoteRepository; 058 import org.sonatype.aether.repository.WorkspaceReader; 059 import org.sonatype.aether.repository.WorkspaceRepository; 060 import org.sonatype.aether.resolution.MetadataRequest; 061 import org.sonatype.aether.resolution.MetadataResult; 062 import org.sonatype.aether.resolution.VersionRequest; 063 import org.sonatype.aether.resolution.VersionResolutionException; 064 import org.sonatype.aether.resolution.VersionResult; 065 import org.sonatype.aether.spi.locator.Service; 066 import org.sonatype.aether.spi.locator.ServiceLocator; 067 import org.sonatype.aether.spi.log.Logger; 068 import org.sonatype.aether.spi.log.NullLogger; 069 070 /** 071 * @author Benjamin Bentmann 072 */ 073 @Component( role = VersionResolver.class ) 074 public class DefaultVersionResolver 075 implements VersionResolver, Service 076 { 077 078 private static final String MAVEN_METADATA_XML = "maven-metadata.xml"; 079 080 private static final String RELEASE = "RELEASE"; 081 082 private static final String LATEST = "LATEST"; 083 084 private static final String SNAPSHOT = "SNAPSHOT"; 085 086 @SuppressWarnings( "unused" ) 087 @Requirement 088 private Logger logger = NullLogger.INSTANCE; 089 090 @Requirement 091 private MetadataResolver metadataResolver; 092 093 @Requirement 094 private SyncContextFactory syncContextFactory; 095 096 @Requirement 097 private RepositoryEventDispatcher repositoryEventDispatcher; 098 099 public void initService( ServiceLocator locator ) 100 { 101 setLogger( locator.getService( Logger.class ) ); 102 setMetadataResolver( locator.getService( MetadataResolver.class ) ); 103 setSyncContextFactory( locator.getService( SyncContextFactory.class ) ); 104 setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) ); 105 } 106 107 public DefaultVersionResolver setLogger( Logger logger ) 108 { 109 this.logger = ( logger != null ) ? logger : NullLogger.INSTANCE; 110 return this; 111 } 112 113 public DefaultVersionResolver setMetadataResolver( MetadataResolver metadataResolver ) 114 { 115 if ( metadataResolver == null ) 116 { 117 throw new IllegalArgumentException( "metadata resolver has not been specified" ); 118 } 119 this.metadataResolver = metadataResolver; 120 return this; 121 } 122 123 public DefaultVersionResolver setSyncContextFactory( SyncContextFactory syncContextFactory ) 124 { 125 if ( syncContextFactory == null ) 126 { 127 throw new IllegalArgumentException( "sync context factory has not been specified" ); 128 } 129 this.syncContextFactory = syncContextFactory; 130 return this; 131 } 132 133 public DefaultVersionResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher ) 134 { 135 if ( repositoryEventDispatcher == null ) 136 { 137 throw new IllegalArgumentException( "repository event dispatcher has not been specified" ); 138 } 139 this.repositoryEventDispatcher = repositoryEventDispatcher; 140 return this; 141 } 142 143 public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request ) 144 throws VersionResolutionException 145 { 146 RequestTrace trace = DefaultRequestTrace.newChild( request.getTrace(), request ); 147 148 Artifact artifact = request.getArtifact(); 149 150 String version = artifact.getVersion(); 151 152 VersionResult result = new VersionResult( request ); 153 154 Key cacheKey = null; 155 RepositoryCache cache = session.getCache(); 156 if ( cache != null && !ConfigurationProperties.get( session, "aether.versionResolver.noCache", false ) ) 157 { 158 cacheKey = new Key( session, request ); 159 160 Object obj = cache.get( session, cacheKey ); 161 if ( obj instanceof Record ) 162 { 163 Record record = (Record) obj; 164 result.setVersion( record.version ); 165 result.setRepository( CacheUtils.getRepository( session, request.getRepositories(), record.repoClass, 166 record.repoId ) ); 167 return result; 168 } 169 } 170 171 Metadata metadata; 172 173 if ( RELEASE.equals( version ) ) 174 { 175 metadata = 176 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML, 177 Metadata.Nature.RELEASE ); 178 } 179 else if ( LATEST.equals( version ) ) 180 { 181 metadata = 182 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML, 183 Metadata.Nature.RELEASE_OR_SNAPSHOT ); 184 } 185 else if ( version.endsWith( SNAPSHOT ) ) 186 { 187 WorkspaceReader workspace = session.getWorkspaceReader(); 188 if ( workspace != null && workspace.findVersions( artifact ).contains( version ) ) 189 { 190 metadata = null; 191 result.setRepository( workspace.getRepository() ); 192 } 193 else 194 { 195 metadata = 196 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), version, MAVEN_METADATA_XML, 197 Metadata.Nature.SNAPSHOT ); 198 } 199 } 200 else 201 { 202 metadata = null; 203 } 204 205 if ( metadata == null ) 206 { 207 result.setVersion( version ); 208 } 209 else 210 { 211 List<MetadataRequest> metadataRequests = new ArrayList<MetadataRequest>( request.getRepositories().size() ); 212 213 metadataRequests.add( new MetadataRequest( metadata, null, request.getRequestContext() ) ); 214 215 for ( RemoteRepository repository : request.getRepositories() ) 216 { 217 MetadataRequest metadataRequest = 218 new MetadataRequest( metadata, repository, request.getRequestContext() ); 219 metadataRequest.setDeleteLocalCopyIfMissing( true ); 220 metadataRequest.setFavorLocalRepository( true ); 221 metadataRequest.setTrace( trace ); 222 metadataRequests.add( metadataRequest ); 223 } 224 225 List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataRequests ); 226 227 Map<String, VersionInfo> infos = new HashMap<String, VersionInfo>(); 228 229 for ( MetadataResult metadataResult : metadataResults ) 230 { 231 result.addException( metadataResult.getException() ); 232 233 ArtifactRepository repository = metadataResult.getRequest().getRepository(); 234 if ( repository == null ) 235 { 236 repository = session.getLocalRepository(); 237 } 238 239 Versioning versioning = readVersions( session, trace, metadataResult.getMetadata(), repository, result ); 240 merge( artifact, infos, versioning, repository ); 241 } 242 243 if ( RELEASE.equals( version ) ) 244 { 245 resolve( result, infos, RELEASE ); 246 } 247 else if ( LATEST.equals( version ) ) 248 { 249 if ( !resolve( result, infos, LATEST ) ) 250 { 251 resolve( result, infos, RELEASE ); 252 } 253 254 if ( result.getVersion() != null && result.getVersion().endsWith( SNAPSHOT ) ) 255 { 256 VersionRequest subRequest = new VersionRequest(); 257 subRequest.setArtifact( artifact.setVersion( result.getVersion() ) ); 258 if ( result.getRepository() instanceof RemoteRepository ) 259 { 260 subRequest.setRepositories( Collections.singletonList( (RemoteRepository) result.getRepository() ) ); 261 } 262 else 263 { 264 subRequest.setRepositories( request.getRepositories() ); 265 } 266 VersionResult subResult = resolveVersion( session, subRequest ); 267 result.setVersion( subResult.getVersion() ); 268 result.setRepository( subResult.getRepository() ); 269 for ( Exception exception : subResult.getExceptions() ) 270 { 271 result.addException( exception ); 272 } 273 } 274 } 275 else 276 { 277 String key = SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() ); 278 merge( infos, SNAPSHOT, key ); 279 if ( !resolve( result, infos, key ) ) 280 { 281 result.setVersion( version ); 282 } 283 } 284 285 if ( StringUtils.isEmpty( result.getVersion() ) ) 286 { 287 throw new VersionResolutionException( result ); 288 } 289 } 290 291 if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) ) 292 { 293 cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) ); 294 } 295 296 return result; 297 } 298 299 private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key ) 300 { 301 VersionInfo info = infos.get( key ); 302 if ( info != null ) 303 { 304 result.setVersion( info.version ); 305 result.setRepository( info.repository ); 306 } 307 return info != null; 308 } 309 310 private Versioning readVersions( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 311 ArtifactRepository repository, VersionResult result ) 312 { 313 Versioning versioning = null; 314 315 FileInputStream fis = null; 316 try 317 { 318 if ( metadata != null ) 319 { 320 SyncContext syncContext = syncContextFactory.newInstance( session, true ); 321 322 try 323 { 324 syncContext.acquire( null, Collections.singleton( metadata ) ); 325 326 if ( metadata.getFile() != null && metadata.getFile().exists() ) 327 { 328 fis = new FileInputStream( metadata.getFile() ); 329 org.apache.maven.artifact.repository.metadata.Metadata m = 330 new MetadataXpp3Reader().read( fis, false ); 331 versioning = m.getVersioning(); 332 333 /* 334 * NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata 335 * of the local repository. This is especially troublesome during snapshot resolution so we try 336 * to handle that gracefully. 337 */ 338 if ( versioning != null && repository instanceof LocalRepository ) 339 { 340 if ( versioning.getSnapshot() != null && versioning.getSnapshot().getBuildNumber() > 0 ) 341 { 342 Versioning repaired = new Versioning(); 343 repaired.setLastUpdated( versioning.getLastUpdated() ); 344 Snapshot snapshot = new Snapshot(); 345 snapshot.setLocalCopy( true ); 346 repaired.setSnapshot( snapshot ); 347 versioning = repaired; 348 349 throw new IOException( "Snapshot information corrupted with remote repository data" 350 + ", please verify that no remote repository uses the id '" + repository.getId() 351 + "'" ); 352 } 353 } 354 } 355 } 356 finally 357 { 358 syncContext.release(); 359 } 360 } 361 } 362 catch ( Exception e ) 363 { 364 invalidMetadata( session, trace, metadata, repository, e ); 365 result.addException( e ); 366 } 367 finally 368 { 369 IOUtil.close( fis ); 370 } 371 372 return ( versioning != null ) ? versioning : new Versioning(); 373 } 374 375 private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 376 ArtifactRepository repository, Exception exception ) 377 { 378 DefaultRepositoryEvent event = new DefaultRepositoryEvent( EventType.METADATA_INVALID, session, trace ); 379 event.setMetadata( metadata ); 380 event.setException( exception ); 381 event.setRepository( repository ); 382 383 repositoryEventDispatcher.dispatch( event ); 384 } 385 386 private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning, 387 ArtifactRepository repository ) 388 { 389 if ( StringUtils.isNotEmpty( versioning.getRelease() ) ) 390 { 391 merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository ); 392 } 393 394 if ( StringUtils.isNotEmpty( versioning.getLatest() ) ) 395 { 396 merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository ); 397 } 398 399 for ( SnapshotVersion sv : versioning.getSnapshotVersions() ) 400 { 401 if ( StringUtils.isNotEmpty( sv.getVersion() ) ) 402 { 403 String key = getKey( sv.getClassifier(), sv.getExtension() ); 404 merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository ); 405 } 406 } 407 408 Snapshot snapshot = versioning.getSnapshot(); 409 if ( snapshot != null && versioning.getSnapshotVersions().isEmpty() ) 410 { 411 String version = artifact.getVersion(); 412 if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 ) 413 { 414 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber(); 415 version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier; 416 } 417 merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository ); 418 } 419 } 420 421 private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version, 422 ArtifactRepository repository ) 423 { 424 VersionInfo info = infos.get( key ); 425 if ( info == null ) 426 { 427 info = new VersionInfo( timestamp, version, repository ); 428 infos.put( key, info ); 429 } 430 else if ( info.isOutdated( timestamp ) ) 431 { 432 info.version = version; 433 info.repository = repository; 434 info.timestamp = timestamp; 435 } 436 } 437 438 private void merge( Map<String, VersionInfo> infos, String srcKey, String dstKey ) 439 { 440 VersionInfo srcInfo = infos.get( srcKey ); 441 VersionInfo dstInfo = infos.get( dstKey ); 442 443 if ( dstInfo == null 444 || ( srcInfo != null && dstInfo.isOutdated( srcInfo.timestamp ) && srcInfo.repository != dstInfo.repository ) ) 445 { 446 infos.put( dstKey, srcInfo ); 447 } 448 } 449 450 private String getKey( String classifier, String extension ) 451 { 452 return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension ); 453 } 454 455 private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact ) 456 { 457 /* 458 * The workspace/reactor is in flux so we better not assume definitive information for any of its 459 * artifacts/projects. 460 */ 461 462 WorkspaceReader workspace = session.getWorkspaceReader(); 463 if ( workspace == null ) 464 { 465 return true; 466 } 467 468 Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact( artifact ); 469 470 return workspace.findArtifact( pomArtifact ) == null; 471 } 472 473 private static class VersionInfo 474 { 475 476 String timestamp; 477 478 String version; 479 480 ArtifactRepository repository; 481 482 public VersionInfo( String timestamp, String version, ArtifactRepository repository ) 483 { 484 this.timestamp = ( timestamp != null ) ? timestamp : ""; 485 this.version = version; 486 this.repository = repository; 487 } 488 489 public boolean isOutdated( String timestamp ) 490 { 491 return timestamp != null && timestamp.compareTo( this.timestamp ) > 0; 492 } 493 494 } 495 496 private static class Key 497 { 498 499 private final String groupId; 500 501 private final String artifactId; 502 503 private final String classifier; 504 505 private final String extension; 506 507 private final String version; 508 509 private final String context; 510 511 private final File localRepo; 512 513 private final WorkspaceRepository workspace; 514 515 private final List<RemoteRepository> repositories; 516 517 private final int hashCode; 518 519 public Key( RepositorySystemSession session, VersionRequest request ) 520 { 521 Artifact artifact = request.getArtifact(); 522 groupId = artifact.getGroupId(); 523 artifactId = artifact.getArtifactId(); 524 classifier = artifact.getClassifier(); 525 extension = artifact.getExtension(); 526 version = artifact.getVersion(); 527 localRepo = session.getLocalRepository().getBasedir(); 528 workspace = CacheUtils.getWorkspace( session ); 529 repositories = new ArrayList<RemoteRepository>( request.getRepositories().size() ); 530 boolean repoMan = false; 531 for ( RemoteRepository repository : request.getRepositories() ) 532 { 533 if ( repository.isRepositoryManager() ) 534 { 535 repoMan = true; 536 repositories.addAll( repository.getMirroredRepositories() ); 537 } 538 else 539 { 540 repositories.add( repository ); 541 } 542 } 543 context = repoMan ? request.getRequestContext() : ""; 544 545 int hash = 17; 546 hash = hash * 31 + groupId.hashCode(); 547 hash = hash * 31 + artifactId.hashCode(); 548 hash = hash * 31 + classifier.hashCode(); 549 hash = hash * 31 + extension.hashCode(); 550 hash = hash * 31 + version.hashCode(); 551 hash = hash * 31 + localRepo.hashCode(); 552 hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories ); 553 hashCode = hash; 554 } 555 556 @Override 557 public boolean equals( Object obj ) 558 { 559 if ( obj == this ) 560 { 561 return true; 562 } 563 else if ( obj == null || !getClass().equals( obj.getClass() ) ) 564 { 565 return false; 566 } 567 568 Key that = (Key) obj; 569 return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId ) 570 && classifier.equals( that.classifier ) && extension.equals( that.extension ) 571 && version.equals( that.version ) && context.equals( that.context ) 572 && localRepo.equals( that.localRepo ) && CacheUtils.eq( workspace, that.workspace ) 573 && CacheUtils.repositoriesEquals( repositories, that.repositories ); 574 } 575 576 @Override 577 public int hashCode() 578 { 579 return hashCode; 580 } 581 582 } 583 584 private static class Record 585 { 586 final String version; 587 588 final String repoId; 589 590 final Class<?> repoClass; 591 592 public Record( String version, ArtifactRepository repository ) 593 { 594 this.version = version; 595 if ( repository != null ) 596 { 597 repoId = repository.getId(); 598 repoClass = repository.getClass(); 599 } 600 else 601 { 602 repoId = null; 603 repoClass = null; 604 } 605 } 606 } 607 608 }