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