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 org.apache.commons.lang3.Validate; 023import org.apache.maven.artifact.repository.metadata.Snapshot; 024import org.apache.maven.artifact.repository.metadata.SnapshotVersion; 025import org.apache.maven.artifact.repository.metadata.Versioning; 026import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader; 027import org.codehaus.plexus.component.annotations.Component; 028import org.codehaus.plexus.component.annotations.Requirement; 029import org.codehaus.plexus.util.IOUtil; 030import org.codehaus.plexus.util.StringUtils; 031import org.eclipse.aether.RepositoryCache; 032import org.eclipse.aether.RepositoryEvent; 033import org.eclipse.aether.RepositoryEvent.EventType; 034import org.eclipse.aether.RepositorySystemSession; 035import org.eclipse.aether.RequestTrace; 036import org.eclipse.aether.SyncContext; 037import org.eclipse.aether.artifact.Artifact; 038import org.eclipse.aether.impl.MetadataResolver; 039import org.eclipse.aether.impl.RepositoryEventDispatcher; 040import org.eclipse.aether.impl.SyncContextFactory; 041import org.eclipse.aether.impl.VersionResolver; 042import org.eclipse.aether.internal.impl.CacheUtils; 043import org.eclipse.aether.metadata.DefaultMetadata; 044import org.eclipse.aether.metadata.Metadata; 045import org.eclipse.aether.repository.ArtifactRepository; 046import org.eclipse.aether.repository.LocalRepository; 047import org.eclipse.aether.repository.RemoteRepository; 048import org.eclipse.aether.repository.WorkspaceReader; 049import org.eclipse.aether.repository.WorkspaceRepository; 050import org.eclipse.aether.resolution.MetadataRequest; 051import org.eclipse.aether.resolution.MetadataResult; 052import org.eclipse.aether.resolution.VersionRequest; 053import org.eclipse.aether.resolution.VersionResolutionException; 054import org.eclipse.aether.resolution.VersionResult; 055import org.eclipse.aether.spi.locator.Service; 056import org.eclipse.aether.spi.locator.ServiceLocator; 057import org.eclipse.aether.spi.log.Logger; 058import org.eclipse.aether.spi.log.LoggerFactory; 059import org.eclipse.aether.spi.log.NullLoggerFactory; 060import org.eclipse.aether.util.ConfigUtils; 061 062import javax.inject.Inject; 063import javax.inject.Named; 064import java.io.File; 065import java.io.FileInputStream; 066import java.io.IOException; 067import java.util.ArrayList; 068import java.util.Collections; 069import java.util.HashMap; 070import java.util.List; 071import java.util.Map; 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 this.metadataResolver = Validate.notNull( metadataResolver, "metadataResolver cannot be null" ); 141 return this; 142 } 143 144 public DefaultVersionResolver setSyncContextFactory( SyncContextFactory syncContextFactory ) 145 { 146 this.syncContextFactory = Validate.notNull( syncContextFactory, "syncContextFactory cannot be null" ); 147 return this; 148 } 149 150 public DefaultVersionResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher ) 151 { 152 this.repositoryEventDispatcher = Validate.notNull( repositoryEventDispatcher, 153 "repositoryEventDispatcher cannot be null" ); 154 return this; 155 } 156 157 public VersionResult resolveVersion( RepositorySystemSession session, VersionRequest request ) 158 throws VersionResolutionException 159 { 160 RequestTrace trace = RequestTrace.newChild( request.getTrace(), request ); 161 162 Artifact artifact = request.getArtifact(); 163 164 String version = artifact.getVersion(); 165 166 VersionResult result = new VersionResult( request ); 167 168 Key cacheKey = null; 169 RepositoryCache cache = session.getCache(); 170 if ( cache != null && !ConfigUtils.getBoolean( session, false, "aether.versionResolver.noCache" ) ) 171 { 172 cacheKey = new Key( session, request ); 173 174 Object obj = cache.get( session, cacheKey ); 175 if ( obj instanceof Record ) 176 { 177 Record record = (Record) obj; 178 result.setVersion( record.version ); 179 result.setRepository( 180 CacheUtils.getRepository( session, request.getRepositories(), record.repoClass, record.repoId ) ); 181 return result; 182 } 183 } 184 185 Metadata metadata; 186 187 if ( RELEASE.equals( version ) ) 188 { 189 metadata = new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML, 190 Metadata.Nature.RELEASE ); 191 } 192 else if ( LATEST.equals( version ) ) 193 { 194 metadata = new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML, 195 Metadata.Nature.RELEASE_OR_SNAPSHOT ); 196 } 197 else if ( version.endsWith( SNAPSHOT ) ) 198 { 199 WorkspaceReader workspace = session.getWorkspaceReader(); 200 if ( workspace != null && workspace.findVersions( artifact ).contains( version ) ) 201 { 202 metadata = null; 203 result.setRepository( workspace.getRepository() ); 204 } 205 else 206 { 207 metadata = 208 new DefaultMetadata( artifact.getGroupId(), artifact.getArtifactId(), version, MAVEN_METADATA_XML, 209 Metadata.Nature.SNAPSHOT ); 210 } 211 } 212 else 213 { 214 metadata = null; 215 } 216 217 if ( metadata == null ) 218 { 219 result.setVersion( version ); 220 } 221 else 222 { 223 List<MetadataRequest> metadataReqs = new ArrayList<>( request.getRepositories().size() ); 224 225 metadataReqs.add( new MetadataRequest( metadata, null, request.getRequestContext() ) ); 226 227 for ( RemoteRepository repository : request.getRepositories() ) 228 { 229 MetadataRequest metadataRequest = 230 new MetadataRequest( metadata, repository, request.getRequestContext() ); 231 metadataRequest.setDeleteLocalCopyIfMissing( true ); 232 metadataRequest.setFavorLocalRepository( true ); 233 metadataRequest.setTrace( trace ); 234 metadataReqs.add( metadataRequest ); 235 } 236 237 List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataReqs ); 238 239 Map<String, VersionInfo> infos = new HashMap<>(); 240 241 for ( MetadataResult metadataResult : metadataResults ) 242 { 243 result.addException( metadataResult.getException() ); 244 245 ArtifactRepository repository = metadataResult.getRequest().getRepository(); 246 if ( repository == null ) 247 { 248 repository = session.getLocalRepository(); 249 } 250 251 Versioning v = readVersions( session, trace, metadataResult.getMetadata(), repository, result ); 252 merge( artifact, infos, v, repository ); 253 } 254 255 if ( RELEASE.equals( version ) ) 256 { 257 resolve( result, infos, RELEASE ); 258 } 259 else if ( LATEST.equals( version ) ) 260 { 261 if ( !resolve( result, infos, LATEST ) ) 262 { 263 resolve( result, infos, RELEASE ); 264 } 265 266 if ( result.getVersion() != null && result.getVersion().endsWith( SNAPSHOT ) ) 267 { 268 VersionRequest subRequest = new VersionRequest(); 269 subRequest.setArtifact( artifact.setVersion( result.getVersion() ) ); 270 if ( result.getRepository() instanceof RemoteRepository ) 271 { 272 RemoteRepository r = (RemoteRepository) result.getRepository(); 273 subRequest.setRepositories( Collections.singletonList( r ) ); 274 } 275 else 276 { 277 subRequest.setRepositories( request.getRepositories() ); 278 } 279 VersionResult subResult = resolveVersion( session, subRequest ); 280 result.setVersion( subResult.getVersion() ); 281 result.setRepository( subResult.getRepository() ); 282 for ( Exception exception : subResult.getExceptions() ) 283 { 284 result.addException( exception ); 285 } 286 } 287 } 288 else 289 { 290 String key = SNAPSHOT + getKey( artifact.getClassifier(), artifact.getExtension() ); 291 merge( infos, SNAPSHOT, key ); 292 if ( !resolve( result, infos, key ) ) 293 { 294 result.setVersion( version ); 295 } 296 } 297 298 if ( StringUtils.isEmpty( result.getVersion() ) ) 299 { 300 throw new VersionResolutionException( result ); 301 } 302 } 303 304 if ( cacheKey != null && metadata != null && isSafelyCacheable( session, artifact ) ) 305 { 306 cache.put( session, cacheKey, new Record( result.getVersion(), result.getRepository() ) ); 307 } 308 309 return result; 310 } 311 312 private boolean resolve( VersionResult result, Map<String, VersionInfo> infos, String key ) 313 { 314 VersionInfo info = infos.get( key ); 315 if ( info != null ) 316 { 317 result.setVersion( info.version ); 318 result.setRepository( info.repository ); 319 } 320 return info != null; 321 } 322 323 private Versioning readVersions( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 324 ArtifactRepository repository, VersionResult result ) 325 { 326 Versioning versioning = null; 327 328 FileInputStream fis = null; 329 try 330 { 331 if ( metadata != null ) 332 { 333 334 try ( SyncContext syncContext = syncContextFactory.newInstance( session, true ) ) 335 { 336 syncContext.acquire( null, Collections.singleton( metadata ) ); 337 338 if ( metadata.getFile() != null && metadata.getFile().exists() ) 339 { 340 fis = new FileInputStream( metadata.getFile() ); 341 org.apache.maven.artifact.repository.metadata.Metadata m = 342 new MetadataXpp3Reader().read( fis, false ); 343 versioning = m.getVersioning(); 344 345 /* 346 * NOTE: Users occasionally misuse the id "local" for remote repos which screws up the metadata 347 * of the local repository. This is especially troublesome during snapshot resolution so we try 348 * to handle that gracefully. 349 */ 350 if ( versioning != null && repository instanceof LocalRepository ) 351 { 352 if ( versioning.getSnapshot() != null && versioning.getSnapshot().getBuildNumber() > 0 ) 353 { 354 Versioning repaired = new Versioning(); 355 repaired.setLastUpdated( versioning.getLastUpdated() ); 356 Snapshot snapshot = new Snapshot(); 357 snapshot.setLocalCopy( true ); 358 repaired.setSnapshot( snapshot ); 359 versioning = repaired; 360 361 throw new IOException( "Snapshot information corrupted with remote repository data" 362 + ", please verify that no remote repository uses the id '" 363 + repository.getId() + "'" ); 364 } 365 } 366 } 367 } 368 } 369 } 370 catch ( Exception e ) 371 { 372 invalidMetadata( session, trace, metadata, repository, e ); 373 result.addException( e ); 374 } 375 finally 376 { 377 IOUtil.close( fis ); 378 } 379 380 return ( versioning != null ) ? versioning : new Versioning(); 381 } 382 383 private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 384 ArtifactRepository repository, Exception exception ) 385 { 386 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID ); 387 event.setTrace( trace ); 388 event.setMetadata( metadata ); 389 event.setException( exception ); 390 event.setRepository( repository ); 391 392 repositoryEventDispatcher.dispatch( event.build() ); 393 } 394 395 private void merge( Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning, 396 ArtifactRepository repository ) 397 { 398 if ( StringUtils.isNotEmpty( versioning.getRelease() ) ) 399 { 400 merge( RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository ); 401 } 402 403 if ( StringUtils.isNotEmpty( versioning.getLatest() ) ) 404 { 405 merge( LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository ); 406 } 407 408 for ( SnapshotVersion sv : versioning.getSnapshotVersions() ) 409 { 410 if ( StringUtils.isNotEmpty( sv.getVersion() ) ) 411 { 412 String key = getKey( sv.getClassifier(), sv.getExtension() ); 413 merge( SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository ); 414 } 415 } 416 417 Snapshot snapshot = versioning.getSnapshot(); 418 if ( snapshot != null && versioning.getSnapshotVersions().isEmpty() ) 419 { 420 String version = artifact.getVersion(); 421 if ( snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0 ) 422 { 423 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber(); 424 version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier; 425 } 426 merge( SNAPSHOT, infos, versioning.getLastUpdated(), version, repository ); 427 } 428 } 429 430 private void merge( String key, Map<String, VersionInfo> infos, String timestamp, String version, 431 ArtifactRepository repository ) 432 { 433 VersionInfo info = infos.get( key ); 434 if ( info == null ) 435 { 436 info = new VersionInfo( timestamp, version, repository ); 437 infos.put( key, info ); 438 } 439 else if ( info.isOutdated( timestamp ) ) 440 { 441 info.version = version; 442 info.repository = repository; 443 info.timestamp = timestamp; 444 } 445 } 446 447 private void merge( Map<String, VersionInfo> infos, String srcKey, String dstKey ) 448 { 449 VersionInfo srcInfo = infos.get( srcKey ); 450 VersionInfo dstInfo = infos.get( dstKey ); 451 452 if ( dstInfo == null || ( srcInfo != null && dstInfo.isOutdated( srcInfo.timestamp ) 453 && srcInfo.repository != dstInfo.repository ) ) 454 { 455 infos.put( dstKey, srcInfo ); 456 } 457 } 458 459 private String getKey( String classifier, String extension ) 460 { 461 return StringUtils.clean( classifier ) + ':' + StringUtils.clean( extension ); 462 } 463 464 private boolean isSafelyCacheable( RepositorySystemSession session, Artifact artifact ) 465 { 466 /* 467 * The workspace/reactor is in flux so we better not assume definitive information for any of its 468 * artifacts/projects. 469 */ 470 471 WorkspaceReader workspace = session.getWorkspaceReader(); 472 if ( workspace == null ) 473 { 474 return true; 475 } 476 477 Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact( artifact ); 478 479 return workspace.findArtifact( pomArtifact ) == null; 480 } 481 482 private static class VersionInfo 483 { 484 485 String timestamp; 486 487 String version; 488 489 ArtifactRepository repository; 490 491 public VersionInfo( String timestamp, String version, ArtifactRepository repository ) 492 { 493 this.timestamp = ( timestamp != null ) ? timestamp : ""; 494 this.version = version; 495 this.repository = repository; 496 } 497 498 public boolean isOutdated( String timestamp ) 499 { 500 return timestamp != null && timestamp.compareTo( this.timestamp ) > 0; 501 } 502 503 } 504 505 private static class Key 506 { 507 508 private final String groupId; 509 510 private final String artifactId; 511 512 private final String classifier; 513 514 private final String extension; 515 516 private final String version; 517 518 private final String context; 519 520 private final File localRepo; 521 522 private final WorkspaceRepository workspace; 523 524 private final List<RemoteRepository> repositories; 525 526 private final int hashCode; 527 528 public Key( RepositorySystemSession session, VersionRequest request ) 529 { 530 Artifact artifact = request.getArtifact(); 531 groupId = artifact.getGroupId(); 532 artifactId = artifact.getArtifactId(); 533 classifier = artifact.getClassifier(); 534 extension = artifact.getExtension(); 535 version = artifact.getVersion(); 536 localRepo = session.getLocalRepository().getBasedir(); 537 workspace = CacheUtils.getWorkspace( session ); 538 repositories = new ArrayList<>( request.getRepositories().size() ); 539 boolean repoMan = false; 540 for ( RemoteRepository repository : request.getRepositories() ) 541 { 542 if ( repository.isRepositoryManager() ) 543 { 544 repoMan = true; 545 repositories.addAll( repository.getMirroredRepositories() ); 546 } 547 else 548 { 549 repositories.add( repository ); 550 } 551 } 552 context = repoMan ? request.getRequestContext() : ""; 553 554 int hash = 17; 555 hash = hash * 31 + groupId.hashCode(); 556 hash = hash * 31 + artifactId.hashCode(); 557 hash = hash * 31 + classifier.hashCode(); 558 hash = hash * 31 + extension.hashCode(); 559 hash = hash * 31 + version.hashCode(); 560 hash = hash * 31 + localRepo.hashCode(); 561 hash = hash * 31 + CacheUtils.repositoriesHashCode( repositories ); 562 hashCode = hash; 563 } 564 565 @Override 566 public boolean equals( Object obj ) 567 { 568 if ( obj == this ) 569 { 570 return true; 571 } 572 else if ( obj == null || !getClass().equals( obj.getClass() ) ) 573 { 574 return false; 575 } 576 577 Key that = (Key) obj; 578 return artifactId.equals( that.artifactId ) && groupId.equals( that.groupId ) && classifier.equals( 579 that.classifier ) && extension.equals( that.extension ) && version.equals( that.version ) 580 && context.equals( that.context ) && localRepo.equals( that.localRepo ) 581 && CacheUtils.eq( workspace, that.workspace ) 582 && CacheUtils.repositoriesEquals( repositories, that.repositories ); 583 } 584 585 @Override 586 public int hashCode() 587 { 588 return hashCode; 589 } 590 591 } 592 593 private static class Record 594 { 595 final String version; 596 597 final String repoId; 598 599 final Class<?> repoClass; 600 601 public Record( String version, ArtifactRepository repository ) 602 { 603 this.version = version; 604 if ( repository != null ) 605 { 606 repoId = repository.getId(); 607 repoClass = repository.getClass(); 608 } 609 else 610 { 611 repoId = null; 612 repoClass = null; 613 } 614 } 615 } 616 617}