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