001package 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 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030 031import javax.inject.Inject; 032import javax.inject.Named; 033 034import org.apache.maven.artifact.repository.metadata.Snapshot; 035import org.apache.maven.artifact.repository.metadata.SnapshotVersion; 036import org.apache.maven.artifact.repository.metadata.Versioning; 037import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader; 038import org.codehaus.plexus.component.annotations.Component; 039import org.codehaus.plexus.component.annotations.Requirement; 040import org.codehaus.plexus.util.IOUtil; 041import org.codehaus.plexus.util.StringUtils; 042import org.eclipse.aether.RepositoryCache; 043import org.eclipse.aether.RepositoryEvent.EventType; 044import org.eclipse.aether.RepositoryEvent; 045import org.eclipse.aether.RepositorySystemSession; 046import org.eclipse.aether.RequestTrace; 047import org.eclipse.aether.SyncContext; 048import org.eclipse.aether.artifact.Artifact; 049import org.eclipse.aether.impl.MetadataResolver; 050import org.eclipse.aether.impl.RepositoryEventDispatcher; 051import org.eclipse.aether.impl.SyncContextFactory; 052import org.eclipse.aether.impl.VersionResolver; 053import org.eclipse.aether.internal.impl.CacheUtils; 054import org.eclipse.aether.metadata.DefaultMetadata; 055import org.eclipse.aether.metadata.Metadata; 056import org.eclipse.aether.repository.ArtifactRepository; 057import org.eclipse.aether.repository.LocalRepository; 058import org.eclipse.aether.repository.RemoteRepository; 059import org.eclipse.aether.repository.WorkspaceReader; 060import org.eclipse.aether.repository.WorkspaceRepository; 061import org.eclipse.aether.resolution.MetadataRequest; 062import org.eclipse.aether.resolution.MetadataResult; 063import org.eclipse.aether.resolution.VersionRequest; 064import org.eclipse.aether.resolution.VersionResolutionException; 065import org.eclipse.aether.resolution.VersionResult; 066import org.eclipse.aether.spi.locator.Service; 067import org.eclipse.aether.spi.locator.ServiceLocator; 068import org.eclipse.aether.spi.log.Logger; 069import org.eclipse.aether.spi.log.LoggerFactory; 070import org.eclipse.aether.spi.log.NullLoggerFactory; 071import org.eclipse.aether.util.ConfigUtils; 072 073/** 074 * @author Benjamin Bentmann 075 */ 076@Named 077@Component( role = VersionResolver.class ) 078public class DefaultVersionResolver 079 implements VersionResolver, Service 080{ 081 082 private static final String MAVEN_METADATA_XML = "maven-metadata.xml"; 083 084 private static final String RELEASE = "RELEASE"; 085 086 private static final String LATEST = "LATEST"; 087 088 private static final String SNAPSHOT = "SNAPSHOT"; 089 090 @SuppressWarnings( "unused" ) 091 @Requirement( role = LoggerFactory.class ) 092 private Logger logger = NullLoggerFactory.LOGGER; 093 094 @Requirement 095 private MetadataResolver metadataResolver; 096 097 @Requirement 098 private SyncContextFactory syncContextFactory; 099 100 @Requirement 101 private RepositoryEventDispatcher repositoryEventDispatcher; 102 103 public DefaultVersionResolver() 104 { 105 // enable no-arg constructor 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 // plexus support 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 * NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata 360 * of the local repository. This is especially troublesome during snapshot resolution so we try 361 * to handle that gracefully. 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 * The workspace/reactor is in flux so we better not assume definitive information for any of its 485 * artifacts/projects. 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}