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