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