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