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> 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 * NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata 361 * of the local repository. This is especially troublesome during snapshot resolution so we try 362 * to handle that gracefully. 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 * The workspace/reactor is in flux so we better not assume definitive information for any of its 487 * artifacts/projects. 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}