001package org.eclipse.aether.internal.impl; 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.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.concurrent.Executor; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.LinkedBlockingQueue; 033import java.util.concurrent.ThreadPoolExecutor; 034import java.util.concurrent.TimeUnit; 035 036import javax.inject.Inject; 037import javax.inject.Named; 038 039import org.eclipse.aether.RepositoryEvent; 040import org.eclipse.aether.RepositoryEvent.EventType; 041import org.eclipse.aether.RepositorySystemSession; 042import org.eclipse.aether.RequestTrace; 043import org.eclipse.aether.SyncContext; 044import org.eclipse.aether.impl.MetadataResolver; 045import org.eclipse.aether.impl.OfflineController; 046import org.eclipse.aether.impl.RemoteRepositoryManager; 047import org.eclipse.aether.impl.RepositoryConnectorProvider; 048import org.eclipse.aether.impl.RepositoryEventDispatcher; 049import org.eclipse.aether.impl.SyncContextFactory; 050import org.eclipse.aether.impl.UpdateCheck; 051import org.eclipse.aether.impl.UpdateCheckManager; 052import org.eclipse.aether.metadata.Metadata; 053import org.eclipse.aether.repository.ArtifactRepository; 054import org.eclipse.aether.repository.LocalMetadataRegistration; 055import org.eclipse.aether.repository.LocalMetadataRequest; 056import org.eclipse.aether.repository.LocalMetadataResult; 057import org.eclipse.aether.repository.LocalRepository; 058import org.eclipse.aether.repository.LocalRepositoryManager; 059import org.eclipse.aether.repository.RemoteRepository; 060import org.eclipse.aether.repository.RepositoryPolicy; 061import org.eclipse.aether.resolution.MetadataRequest; 062import org.eclipse.aether.resolution.MetadataResult; 063import org.eclipse.aether.spi.connector.MetadataDownload; 064import org.eclipse.aether.spi.connector.RepositoryConnector; 065import org.eclipse.aether.spi.locator.Service; 066import org.eclipse.aether.spi.locator.ServiceLocator; 067import org.eclipse.aether.spi.log.Logger; 068import org.eclipse.aether.spi.log.LoggerFactory; 069import org.eclipse.aether.spi.log.NullLoggerFactory; 070import org.eclipse.aether.transfer.MetadataNotFoundException; 071import org.eclipse.aether.transfer.MetadataTransferException; 072import org.eclipse.aether.transfer.NoRepositoryConnectorException; 073import org.eclipse.aether.transfer.RepositoryOfflineException; 074import org.eclipse.aether.util.ConfigUtils; 075import org.eclipse.aether.util.concurrency.RunnableErrorForwarder; 076import org.eclipse.aether.util.concurrency.WorkerThreadFactory; 077 078/** 079 */ 080@Named 081public class DefaultMetadataResolver 082 implements MetadataResolver, Service 083{ 084 085 private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads"; 086 087 private Logger logger = NullLoggerFactory.LOGGER; 088 089 private RepositoryEventDispatcher repositoryEventDispatcher; 090 091 private UpdateCheckManager updateCheckManager; 092 093 private RepositoryConnectorProvider repositoryConnectorProvider; 094 095 private RemoteRepositoryManager remoteRepositoryManager; 096 097 private SyncContextFactory syncContextFactory; 098 099 private OfflineController offlineController; 100 101 public DefaultMetadataResolver() 102 { 103 // enables default constructor 104 } 105 106 @Inject 107 DefaultMetadataResolver( RepositoryEventDispatcher repositoryEventDispatcher, 108 UpdateCheckManager updateCheckManager, 109 RepositoryConnectorProvider repositoryConnectorProvider, 110 RemoteRepositoryManager remoteRepositoryManager, SyncContextFactory syncContextFactory, 111 OfflineController offlineController, LoggerFactory loggerFactory ) 112 { 113 setRepositoryEventDispatcher( repositoryEventDispatcher ); 114 setUpdateCheckManager( updateCheckManager ); 115 setRepositoryConnectorProvider( repositoryConnectorProvider ); 116 setRemoteRepositoryManager( remoteRepositoryManager ); 117 setSyncContextFactory( syncContextFactory ); 118 setOfflineController( offlineController ); 119 setLoggerFactory( loggerFactory ); 120 } 121 122 public void initService( ServiceLocator locator ) 123 { 124 setLoggerFactory( locator.getService( LoggerFactory.class ) ); 125 setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) ); 126 setUpdateCheckManager( locator.getService( UpdateCheckManager.class ) ); 127 setRepositoryConnectorProvider( locator.getService( RepositoryConnectorProvider.class ) ); 128 setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) ); 129 setSyncContextFactory( locator.getService( SyncContextFactory.class ) ); 130 setOfflineController( locator.getService( OfflineController.class ) ); 131 } 132 133 public DefaultMetadataResolver setLoggerFactory( LoggerFactory loggerFactory ) 134 { 135 this.logger = NullLoggerFactory.getSafeLogger( loggerFactory, getClass() ); 136 return this; 137 } 138 139 public DefaultMetadataResolver setRepositoryEventDispatcher( RepositoryEventDispatcher repositoryEventDispatcher ) 140 { 141 if ( repositoryEventDispatcher == null ) 142 { 143 throw new IllegalArgumentException( "repository event dispatcher has not been specified" ); 144 } 145 this.repositoryEventDispatcher = repositoryEventDispatcher; 146 return this; 147 } 148 149 public DefaultMetadataResolver setUpdateCheckManager( UpdateCheckManager updateCheckManager ) 150 { 151 if ( updateCheckManager == null ) 152 { 153 throw new IllegalArgumentException( "update check manager has not been specified" ); 154 } 155 this.updateCheckManager = updateCheckManager; 156 return this; 157 } 158 159 public DefaultMetadataResolver setRepositoryConnectorProvider( RepositoryConnectorProvider repositoryConnectorProvider ) 160 { 161 if ( repositoryConnectorProvider == null ) 162 { 163 throw new IllegalArgumentException( "repository connector provider has not been specified" ); 164 } 165 this.repositoryConnectorProvider = repositoryConnectorProvider; 166 return this; 167 } 168 169 public DefaultMetadataResolver setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager ) 170 { 171 if ( remoteRepositoryManager == null ) 172 { 173 throw new IllegalArgumentException( "remote repository manager has not been specified" ); 174 } 175 this.remoteRepositoryManager = remoteRepositoryManager; 176 return this; 177 } 178 179 public DefaultMetadataResolver setSyncContextFactory( SyncContextFactory syncContextFactory ) 180 { 181 if ( syncContextFactory == null ) 182 { 183 throw new IllegalArgumentException( "sync context factory has not been specified" ); 184 } 185 this.syncContextFactory = syncContextFactory; 186 return this; 187 } 188 189 public DefaultMetadataResolver setOfflineController( OfflineController offlineController ) 190 { 191 if ( offlineController == null ) 192 { 193 throw new IllegalArgumentException( "offline controller has not been specified" ); 194 } 195 this.offlineController = offlineController; 196 return this; 197 } 198 199 public List<MetadataResult> resolveMetadata( RepositorySystemSession session, 200 Collection<? extends MetadataRequest> requests ) 201 { 202 SyncContext syncContext = syncContextFactory.newInstance( session, false ); 203 204 try 205 { 206 Collection<Metadata> metadata = new ArrayList<Metadata>( requests.size() ); 207 for ( MetadataRequest request : requests ) 208 { 209 metadata.add( request.getMetadata() ); 210 } 211 212 syncContext.acquire( null, metadata ); 213 214 return resolve( session, requests ); 215 } 216 finally 217 { 218 syncContext.close(); 219 } 220 } 221 222 private List<MetadataResult> resolve( RepositorySystemSession session, 223 Collection<? extends MetadataRequest> requests ) 224 { 225 List<MetadataResult> results = new ArrayList<MetadataResult>( requests.size() ); 226 227 List<ResolveTask> tasks = new ArrayList<ResolveTask>( requests.size() ); 228 229 Map<File, Long> localLastUpdates = new HashMap<File, Long>(); 230 231 for ( MetadataRequest request : requests ) 232 { 233 RequestTrace trace = RequestTrace.newChild( request.getTrace(), request ); 234 235 MetadataResult result = new MetadataResult( request ); 236 results.add( result ); 237 238 Metadata metadata = request.getMetadata(); 239 RemoteRepository repository = request.getRepository(); 240 241 if ( repository == null ) 242 { 243 LocalRepository localRepo = session.getLocalRepositoryManager().getRepository(); 244 245 metadataResolving( session, trace, metadata, localRepo ); 246 247 File localFile = getLocalFile( session, metadata ); 248 249 if ( localFile != null ) 250 { 251 metadata = metadata.setFile( localFile ); 252 result.setMetadata( metadata ); 253 } 254 else 255 { 256 result.setException( new MetadataNotFoundException( metadata, localRepo ) ); 257 } 258 259 metadataResolved( session, trace, metadata, localRepo, result.getException() ); 260 continue; 261 } 262 263 List<RemoteRepository> repositories = getEnabledSourceRepositories( repository, metadata.getNature() ); 264 265 if ( repositories.isEmpty() ) 266 { 267 continue; 268 } 269 270 metadataResolving( session, trace, metadata, repository ); 271 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 272 LocalMetadataRequest localRequest = 273 new LocalMetadataRequest( metadata, repository, request.getRequestContext() ); 274 LocalMetadataResult lrmResult = lrm.find( session, localRequest ); 275 276 File metadataFile = lrmResult.getFile(); 277 278 try 279 { 280 Utils.checkOffline( session, offlineController, repository ); 281 } 282 catch ( RepositoryOfflineException e ) 283 { 284 if ( metadataFile != null ) 285 { 286 metadata = metadata.setFile( metadataFile ); 287 result.setMetadata( metadata ); 288 } 289 else 290 { 291 String msg = 292 "Cannot access " + repository.getId() + " (" + repository.getUrl() 293 + ") in offline mode and the metadata " + metadata 294 + " has not been downloaded from it before"; 295 result.setException( new MetadataNotFoundException( metadata, repository, msg, e ) ); 296 } 297 298 metadataResolved( session, trace, metadata, repository, result.getException() ); 299 continue; 300 } 301 302 Long localLastUpdate = null; 303 if ( request.isFavorLocalRepository() ) 304 { 305 File localFile = getLocalFile( session, metadata ); 306 localLastUpdate = localLastUpdates.get( localFile ); 307 if ( localLastUpdate == null ) 308 { 309 localLastUpdate = localFile != null ? localFile.lastModified() : 0; 310 localLastUpdates.put( localFile, localLastUpdate ); 311 } 312 } 313 314 List<UpdateCheck<Metadata, MetadataTransferException>> checks = 315 new ArrayList<UpdateCheck<Metadata, MetadataTransferException>>(); 316 Exception exception = null; 317 for ( RemoteRepository repo : repositories ) 318 { 319 UpdateCheck<Metadata, MetadataTransferException> check = 320 new UpdateCheck<Metadata, MetadataTransferException>(); 321 check.setLocalLastUpdated( ( localLastUpdate != null ) ? localLastUpdate : 0 ); 322 check.setItem( metadata ); 323 324 // use 'main' installation file for the check (-> use requested repository) 325 File checkFile = 326 new File( 327 session.getLocalRepository().getBasedir(), 328 session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata, repository, 329 request.getRequestContext() ) ); 330 check.setFile( checkFile ); 331 check.setRepository( repository ); 332 check.setAuthoritativeRepository( repo ); 333 check.setPolicy( getPolicy( session, repo, metadata.getNature() ).getUpdatePolicy() ); 334 335 if ( lrmResult.isStale() ) 336 { 337 checks.add( check ); 338 } 339 else 340 { 341 updateCheckManager.checkMetadata( session, check ); 342 if ( check.isRequired() ) 343 { 344 checks.add( check ); 345 } 346 else if ( exception == null ) 347 { 348 exception = check.getException(); 349 } 350 } 351 } 352 353 if ( !checks.isEmpty() ) 354 { 355 RepositoryPolicy policy = getPolicy( session, repository, metadata.getNature() ); 356 357 // install path may be different from lookup path 358 File installFile = 359 new File( 360 session.getLocalRepository().getBasedir(), 361 session.getLocalRepositoryManager().getPathForRemoteMetadata( metadata, 362 request.getRepository(), 363 request.getRequestContext() ) ); 364 365 ResolveTask task = 366 new ResolveTask( session, trace, result, installFile, checks, policy.getChecksumPolicy() ); 367 tasks.add( task ); 368 } 369 else 370 { 371 result.setException( exception ); 372 if ( metadataFile != null ) 373 { 374 metadata = metadata.setFile( metadataFile ); 375 result.setMetadata( metadata ); 376 } 377 metadataResolved( session, trace, metadata, repository, result.getException() ); 378 } 379 } 380 381 if ( !tasks.isEmpty() ) 382 { 383 int threads = ConfigUtils.getInteger( session, 4, CONFIG_PROP_THREADS ); 384 Executor executor = getExecutor( Math.min( tasks.size(), threads ) ); 385 try 386 { 387 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); 388 389 for ( ResolveTask task : tasks ) 390 { 391 executor.execute( errorForwarder.wrap( task ) ); 392 } 393 394 errorForwarder.await(); 395 396 for ( ResolveTask task : tasks ) 397 { 398 task.result.setException( task.exception ); 399 } 400 } 401 finally 402 { 403 shutdown( executor ); 404 } 405 for ( ResolveTask task : tasks ) 406 { 407 Metadata metadata = task.request.getMetadata(); 408 // re-lookup metadata for resolve 409 LocalMetadataRequest localRequest = 410 new LocalMetadataRequest( metadata, task.request.getRepository(), task.request.getRequestContext() ); 411 File metadataFile = session.getLocalRepositoryManager().find( session, localRequest ).getFile(); 412 if ( metadataFile != null ) 413 { 414 metadata = metadata.setFile( metadataFile ); 415 task.result.setMetadata( metadata ); 416 } 417 if ( task.result.getException() == null ) 418 { 419 task.result.setUpdated( true ); 420 } 421 metadataResolved( session, task.trace, metadata, task.request.getRepository(), 422 task.result.getException() ); 423 } 424 } 425 426 return results; 427 } 428 429 private File getLocalFile( RepositorySystemSession session, Metadata metadata ) 430 { 431 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 432 LocalMetadataResult localResult = lrm.find( session, new LocalMetadataRequest( metadata, null, null ) ); 433 File localFile = localResult.getFile(); 434 return localFile; 435 } 436 437 private List<RemoteRepository> getEnabledSourceRepositories( RemoteRepository repository, Metadata.Nature nature ) 438 { 439 List<RemoteRepository> repositories = new ArrayList<RemoteRepository>(); 440 441 if ( repository.isRepositoryManager() ) 442 { 443 for ( RemoteRepository repo : repository.getMirroredRepositories() ) 444 { 445 if ( isEnabled( repo, nature ) ) 446 { 447 repositories.add( repo ); 448 } 449 } 450 } 451 else if ( isEnabled( repository, nature ) ) 452 { 453 repositories.add( repository ); 454 } 455 456 return repositories; 457 } 458 459 private boolean isEnabled( RemoteRepository repository, Metadata.Nature nature ) 460 { 461 if ( !Metadata.Nature.SNAPSHOT.equals( nature ) && repository.getPolicy( false ).isEnabled() ) 462 { 463 return true; 464 } 465 if ( !Metadata.Nature.RELEASE.equals( nature ) && repository.getPolicy( true ).isEnabled() ) 466 { 467 return true; 468 } 469 return false; 470 } 471 472 private RepositoryPolicy getPolicy( RepositorySystemSession session, RemoteRepository repository, 473 Metadata.Nature nature ) 474 { 475 boolean releases = !Metadata.Nature.SNAPSHOT.equals( nature ); 476 boolean snapshots = !Metadata.Nature.RELEASE.equals( nature ); 477 return remoteRepositoryManager.getPolicy( session, repository, releases, snapshots ); 478 } 479 480 private void metadataResolving( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 481 ArtifactRepository repository ) 482 { 483 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVING ); 484 event.setTrace( trace ); 485 event.setMetadata( metadata ); 486 event.setRepository( repository ); 487 488 repositoryEventDispatcher.dispatch( event.build() ); 489 } 490 491 private void metadataResolved( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 492 ArtifactRepository repository, Exception exception ) 493 { 494 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_RESOLVED ); 495 event.setTrace( trace ); 496 event.setMetadata( metadata ); 497 event.setRepository( repository ); 498 event.setException( exception ); 499 event.setFile( metadata.getFile() ); 500 501 repositoryEventDispatcher.dispatch( event.build() ); 502 } 503 504 private void metadataDownloading( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 505 ArtifactRepository repository ) 506 { 507 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADING ); 508 event.setTrace( trace ); 509 event.setMetadata( metadata ); 510 event.setRepository( repository ); 511 512 repositoryEventDispatcher.dispatch( event.build() ); 513 } 514 515 private void metadataDownloaded( RepositorySystemSession session, RequestTrace trace, Metadata metadata, 516 ArtifactRepository repository, File file, Exception exception ) 517 { 518 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_DOWNLOADED ); 519 event.setTrace( trace ); 520 event.setMetadata( metadata ); 521 event.setRepository( repository ); 522 event.setException( exception ); 523 event.setFile( file ); 524 525 repositoryEventDispatcher.dispatch( event.build() ); 526 } 527 528 private Executor getExecutor( int threads ) 529 { 530 if ( threads <= 1 ) 531 { 532 return new Executor() 533 { 534 public void execute( Runnable command ) 535 { 536 command.run(); 537 } 538 }; 539 } 540 else 541 { 542 return new ThreadPoolExecutor( threads, threads, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), 543 new WorkerThreadFactory( null ) ); 544 } 545 } 546 547 private void shutdown( Executor executor ) 548 { 549 if ( executor instanceof ExecutorService ) 550 { 551 ( (ExecutorService) executor ).shutdown(); 552 } 553 } 554 555 class ResolveTask 556 implements Runnable 557 { 558 559 final RepositorySystemSession session; 560 561 final RequestTrace trace; 562 563 final MetadataResult result; 564 565 final MetadataRequest request; 566 567 final File metadataFile; 568 569 final String policy; 570 571 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 572 573 volatile MetadataTransferException exception; 574 575 public ResolveTask( RepositorySystemSession session, RequestTrace trace, MetadataResult result, 576 File metadataFile, List<UpdateCheck<Metadata, MetadataTransferException>> checks, 577 String policy ) 578 { 579 this.session = session; 580 this.trace = trace; 581 this.result = result; 582 this.request = result.getRequest(); 583 this.metadataFile = metadataFile; 584 this.policy = policy; 585 this.checks = checks; 586 } 587 588 public void run() 589 { 590 Metadata metadata = request.getMetadata(); 591 RemoteRepository requestRepository = request.getRepository(); 592 593 metadataDownloading( session, trace, metadata, requestRepository ); 594 595 try 596 { 597 List<RemoteRepository> repositories = new ArrayList<RemoteRepository>(); 598 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks ) 599 { 600 repositories.add( check.getAuthoritativeRepository() ); 601 } 602 603 MetadataDownload download = new MetadataDownload(); 604 download.setMetadata( metadata ); 605 download.setRequestContext( request.getRequestContext() ); 606 download.setFile( metadataFile ); 607 download.setChecksumPolicy( policy ); 608 download.setRepositories( repositories ); 609 download.setListener( SafeTransferListener.wrap( session, logger ) ); 610 download.setTrace( trace ); 611 612 RepositoryConnector connector = 613 repositoryConnectorProvider.newRepositoryConnector( session, requestRepository ); 614 try 615 { 616 connector.get( null, Arrays.asList( download ) ); 617 } 618 finally 619 { 620 connector.close(); 621 } 622 623 exception = download.getException(); 624 625 if ( exception == null ) 626 { 627 628 List<String> contexts = Collections.singletonList( request.getRequestContext() ); 629 LocalMetadataRegistration registration = 630 new LocalMetadataRegistration( metadata, requestRepository, contexts ); 631 632 session.getLocalRepositoryManager().add( session, registration ); 633 } 634 else if ( request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException ) 635 { 636 download.getFile().delete(); 637 } 638 } 639 catch ( NoRepositoryConnectorException e ) 640 { 641 exception = new MetadataTransferException( metadata, requestRepository, e ); 642 } 643 644 /* 645 * NOTE: Touch after registration with local repo to ensure concurrent resolution is not rejected with 646 * "already updated" via session data when actual update to local repo is still pending. 647 */ 648 for ( UpdateCheck<Metadata, MetadataTransferException> check : checks ) 649 { 650 updateCheckManager.touchMetadata( session, check.setException( exception ) ); 651 } 652 653 metadataDownloaded( session, trace, metadata, requestRepository, metadataFile, exception ); 654 } 655 656 } 657 658}