001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.File; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.concurrent.Executor; 033 034import org.eclipse.aether.RepositoryEvent; 035import org.eclipse.aether.RepositoryEvent.EventType; 036import org.eclipse.aether.RepositorySystemSession; 037import org.eclipse.aether.RequestTrace; 038import org.eclipse.aether.SyncContext; 039import org.eclipse.aether.impl.MetadataResolver; 040import org.eclipse.aether.impl.OfflineController; 041import org.eclipse.aether.impl.RemoteRepositoryFilterManager; 042import org.eclipse.aether.impl.RemoteRepositoryManager; 043import org.eclipse.aether.impl.RepositoryConnectorProvider; 044import org.eclipse.aether.impl.RepositoryEventDispatcher; 045import org.eclipse.aether.impl.UpdateCheck; 046import org.eclipse.aether.impl.UpdateCheckManager; 047import org.eclipse.aether.metadata.Metadata; 048import org.eclipse.aether.repository.ArtifactRepository; 049import org.eclipse.aether.repository.LocalMetadataRegistration; 050import org.eclipse.aether.repository.LocalMetadataRequest; 051import org.eclipse.aether.repository.LocalMetadataResult; 052import org.eclipse.aether.repository.LocalRepository; 053import org.eclipse.aether.repository.LocalRepositoryManager; 054import org.eclipse.aether.repository.RemoteRepository; 055import org.eclipse.aether.repository.RepositoryPolicy; 056import org.eclipse.aether.resolution.MetadataRequest; 057import org.eclipse.aether.resolution.MetadataResult; 058import org.eclipse.aether.spi.connector.MetadataDownload; 059import org.eclipse.aether.spi.connector.RepositoryConnector; 060import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter; 061import org.eclipse.aether.spi.locator.Service; 062import org.eclipse.aether.spi.locator.ServiceLocator; 063import org.eclipse.aether.spi.synccontext.SyncContextFactory; 064import org.eclipse.aether.transfer.MetadataNotFoundException; 065import org.eclipse.aether.transfer.MetadataTransferException; 066import org.eclipse.aether.transfer.NoRepositoryConnectorException; 067import org.eclipse.aether.transfer.RepositoryOfflineException; 068import org.eclipse.aether.util.concurrency.ExecutorUtils; 069import org.eclipse.aether.util.concurrency.RunnableErrorForwarder; 070 071import static java.util.Objects.requireNonNull; 072 073/** 074 */ 075@Singleton 076@Named 077public class DefaultMetadataResolver implements MetadataResolver, Service { 078 079 private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads"; 080 081 private RepositoryEventDispatcher repositoryEventDispatcher; 082 083 private UpdateCheckManager updateCheckManager; 084 085 private RepositoryConnectorProvider repositoryConnectorProvider; 086 087 private RemoteRepositoryManager remoteRepositoryManager; 088 089 private SyncContextFactory syncContextFactory; 090 091 private OfflineController offlineController; 092 093 private RemoteRepositoryFilterManager remoteRepositoryFilterManager; 094 095 @Deprecated 096 public DefaultMetadataResolver() { 097 // enables default constructor 098 } 099 100 @Inject 101 public DefaultMetadataResolver( 102 RepositoryEventDispatcher repositoryEventDispatcher, 103 UpdateCheckManager updateCheckManager, 104 RepositoryConnectorProvider repositoryConnectorProvider, 105 RemoteRepositoryManager remoteRepositoryManager, 106 SyncContextFactory syncContextFactory, 107 OfflineController offlineController, 108 RemoteRepositoryFilterManager remoteRepositoryFilterManager) { 109 setRepositoryEventDispatcher(repositoryEventDispatcher); 110 setUpdateCheckManager(updateCheckManager); 111 setRepositoryConnectorProvider(repositoryConnectorProvider); 112 setRemoteRepositoryManager(remoteRepositoryManager); 113 setSyncContextFactory(syncContextFactory); 114 setOfflineController(offlineController); 115 setRemoteRepositoryFilterManager(remoteRepositoryFilterManager); 116 } 117 118 public void initService(ServiceLocator locator) { 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 setRemoteRepositoryFilterManager(locator.getService(RemoteRepositoryFilterManager.class)); 126 } 127 128 public DefaultMetadataResolver setRepositoryEventDispatcher(RepositoryEventDispatcher repositoryEventDispatcher) { 129 this.repositoryEventDispatcher = 130 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null"); 131 return this; 132 } 133 134 public DefaultMetadataResolver setUpdateCheckManager(UpdateCheckManager updateCheckManager) { 135 this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null"); 136 return this; 137 } 138 139 public DefaultMetadataResolver setRepositoryConnectorProvider( 140 RepositoryConnectorProvider repositoryConnectorProvider) { 141 this.repositoryConnectorProvider = 142 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null"); 143 return this; 144 } 145 146 public DefaultMetadataResolver setRemoteRepositoryManager(RemoteRepositoryManager remoteRepositoryManager) { 147 this.remoteRepositoryManager = 148 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null"); 149 return this; 150 } 151 152 public DefaultMetadataResolver setSyncContextFactory(SyncContextFactory syncContextFactory) { 153 this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null"); 154 return this; 155 } 156 157 public DefaultMetadataResolver setOfflineController(OfflineController offlineController) { 158 this.offlineController = requireNonNull(offlineController, "offline controller cannot be null"); 159 return this; 160 } 161 162 public DefaultMetadataResolver setRemoteRepositoryFilterManager( 163 RemoteRepositoryFilterManager remoteRepositoryFilterManager) { 164 this.remoteRepositoryFilterManager = 165 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null"); 166 return this; 167 } 168 169 public List<MetadataResult> resolveMetadata( 170 RepositorySystemSession session, Collection<? extends MetadataRequest> requests) { 171 requireNonNull(session, "session cannot be null"); 172 requireNonNull(requests, "requests cannot be null"); 173 try (SyncContext shared = syncContextFactory.newInstance(session, true); 174 SyncContext exclusive = syncContextFactory.newInstance(session, false)) { 175 Collection<Metadata> metadata = new ArrayList<>(requests.size()); 176 for (MetadataRequest request : requests) { 177 metadata.add(request.getMetadata()); 178 } 179 180 return resolve(shared, exclusive, metadata, session, requests); 181 } 182 } 183 184 @SuppressWarnings("checkstyle:methodlength") 185 private List<MetadataResult> resolve( 186 SyncContext shared, 187 SyncContext exclusive, 188 Collection<Metadata> subjects, 189 RepositorySystemSession session, 190 Collection<? extends MetadataRequest> requests) { 191 SyncContext current = shared; 192 try { 193 while (true) { 194 current.acquire(null, subjects); 195 196 final List<MetadataResult> results = new ArrayList<>(requests.size()); 197 final List<ResolveTask> tasks = new ArrayList<>(requests.size()); 198 final Map<File, Long> localLastUpdates = new HashMap<>(); 199 final RemoteRepositoryFilter remoteRepositoryFilter = 200 remoteRepositoryFilterManager.getRemoteRepositoryFilter(session); 201 202 for (MetadataRequest request : requests) { 203 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 204 205 MetadataResult result = new MetadataResult(request); 206 results.add(result); 207 208 Metadata metadata = request.getMetadata(); 209 RemoteRepository repository = request.getRepository(); 210 211 if (repository == null) { 212 LocalRepository localRepo = 213 session.getLocalRepositoryManager().getRepository(); 214 215 metadataResolving(session, trace, metadata, localRepo); 216 217 File localFile = getLocalFile(session, metadata); 218 219 if (localFile != null) { 220 metadata = metadata.setFile(localFile); 221 result.setMetadata(metadata); 222 } else { 223 result.setException(new MetadataNotFoundException(metadata, localRepo)); 224 } 225 226 metadataResolved(session, trace, metadata, localRepo, result.getException()); 227 continue; 228 } 229 230 if (remoteRepositoryFilter != null) { 231 RemoteRepositoryFilter.Result filterResult = 232 remoteRepositoryFilter.acceptMetadata(repository, metadata); 233 if (!filterResult.isAccepted()) { 234 result.setException( 235 new MetadataNotFoundException(metadata, repository, filterResult.reasoning())); 236 continue; 237 } 238 } 239 240 List<RemoteRepository> repositories = 241 getEnabledSourceRepositories(repository, metadata.getNature()); 242 243 if (repositories.isEmpty()) { 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 Utils.checkOffline(session, offlineController, repository); 257 } catch (RepositoryOfflineException e) { 258 if (metadataFile != null) { 259 metadata = metadata.setFile(metadataFile); 260 result.setMetadata(metadata); 261 } else { 262 String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl() 263 + ") in offline mode and the metadata " + metadata 264 + " has not been downloaded from it before"; 265 result.setException(new MetadataNotFoundException(metadata, repository, msg, e)); 266 } 267 268 metadataResolved(session, trace, metadata, repository, result.getException()); 269 continue; 270 } 271 272 Long localLastUpdate = null; 273 if (request.isFavorLocalRepository()) { 274 File localFile = getLocalFile(session, metadata); 275 localLastUpdate = localLastUpdates.get(localFile); 276 if (localLastUpdate == null) { 277 localLastUpdate = localFile != null ? localFile.lastModified() : 0; 278 localLastUpdates.put(localFile, localLastUpdate); 279 } 280 } 281 282 List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>(); 283 Exception exception = null; 284 for (RemoteRepository repo : repositories) { 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( 298 getPolicy(session, repo, metadata.getNature()).getUpdatePolicy()); 299 300 if (lrmResult.isStale()) { 301 checks.add(check); 302 } else { 303 updateCheckManager.checkMetadata(session, check); 304 if (check.isRequired()) { 305 checks.add(check); 306 } else if (exception == null) { 307 exception = check.getException(); 308 } 309 } 310 } 311 312 if (!checks.isEmpty()) { 313 RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature()); 314 315 // install path may be different from lookup path 316 File installFile = new File( 317 session.getLocalRepository().getBasedir(), 318 session.getLocalRepositoryManager() 319 .getPathForRemoteMetadata( 320 metadata, request.getRepository(), request.getRequestContext())); 321 322 ResolveTask task = new ResolveTask( 323 session, trace, result, installFile, checks, policy.getChecksumPolicy()); 324 tasks.add(task); 325 } else { 326 result.setException(exception); 327 if (metadataFile != null) { 328 metadata = metadata.setFile(metadataFile); 329 result.setMetadata(metadata); 330 } 331 metadataResolved(session, trace, metadata, repository, result.getException()); 332 } 333 } 334 335 if (!tasks.isEmpty() && current == shared) { 336 current.close(); 337 current = exclusive; 338 continue; 339 } 340 341 if (!tasks.isEmpty()) { 342 int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS); 343 Executor executor = ExecutorUtils.executor( 344 Math.min(tasks.size(), threads), getClass().getSimpleName() + '-'); 345 try { 346 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); 347 348 for (ResolveTask task : tasks) { 349 metadataDownloading( 350 task.session, task.trace, task.request.getMetadata(), task.request.getRepository()); 351 352 executor.execute(errorForwarder.wrap(task)); 353 } 354 355 errorForwarder.await(); 356 357 for (ResolveTask task : tasks) { 358 /* 359 * NOTE: Touch after registration with local repo to ensure concurrent resolution is not 360 * rejected with "already updated" via session data when actual update to local repo is 361 * still pending. 362 */ 363 for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) { 364 updateCheckManager.touchMetadata(task.session, check.setException(task.exception)); 365 } 366 367 metadataDownloaded( 368 session, 369 task.trace, 370 task.request.getMetadata(), 371 task.request.getRepository(), 372 task.metadataFile, 373 task.exception); 374 375 task.result.setException(task.exception); 376 } 377 } finally { 378 ExecutorUtils.shutdown(executor); 379 } 380 for (ResolveTask task : tasks) { 381 Metadata metadata = task.request.getMetadata(); 382 // re-lookup metadata for resolve 383 LocalMetadataRequest localRequest = new LocalMetadataRequest( 384 metadata, task.request.getRepository(), task.request.getRequestContext()); 385 File metadataFile = session.getLocalRepositoryManager() 386 .find(session, localRequest) 387 .getFile(); 388 if (metadataFile != null) { 389 metadata = metadata.setFile(metadataFile); 390 task.result.setMetadata(metadata); 391 } 392 if (task.result.getException() == null) { 393 task.result.setUpdated(true); 394 } 395 metadataResolved( 396 session, 397 task.trace, 398 metadata, 399 task.request.getRepository(), 400 task.result.getException()); 401 } 402 } 403 404 return results; 405 } 406 } finally { 407 current.close(); 408 } 409 } 410 411 private File getLocalFile(RepositorySystemSession session, Metadata metadata) { 412 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 413 LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null)); 414 return localResult.getFile(); 415 } 416 417 private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) { 418 List<RemoteRepository> repositories = new ArrayList<>(); 419 420 if (repository.isRepositoryManager()) { 421 for (RemoteRepository repo : repository.getMirroredRepositories()) { 422 if (isEnabled(repo, nature)) { 423 repositories.add(repo); 424 } 425 } 426 } else if (isEnabled(repository, nature)) { 427 repositories.add(repository); 428 } 429 430 return repositories; 431 } 432 433 private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) { 434 if (!Metadata.Nature.SNAPSHOT.equals(nature) 435 && repository.getPolicy(false).isEnabled()) { 436 return true; 437 } 438 if (!Metadata.Nature.RELEASE.equals(nature) 439 && repository.getPolicy(true).isEnabled()) { 440 return true; 441 } 442 return false; 443 } 444 445 private RepositoryPolicy getPolicy( 446 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) { 447 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature); 448 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature); 449 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots); 450 } 451 452 private void metadataResolving( 453 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 454 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING); 455 event.setTrace(trace); 456 event.setMetadata(metadata); 457 event.setRepository(repository); 458 459 repositoryEventDispatcher.dispatch(event.build()); 460 } 461 462 private void metadataResolved( 463 RepositorySystemSession session, 464 RequestTrace trace, 465 Metadata metadata, 466 ArtifactRepository repository, 467 Exception exception) { 468 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED); 469 event.setTrace(trace); 470 event.setMetadata(metadata); 471 event.setRepository(repository); 472 event.setException(exception); 473 event.setFile(metadata.getFile()); 474 475 repositoryEventDispatcher.dispatch(event.build()); 476 } 477 478 private void metadataDownloading( 479 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 480 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING); 481 event.setTrace(trace); 482 event.setMetadata(metadata); 483 event.setRepository(repository); 484 485 repositoryEventDispatcher.dispatch(event.build()); 486 } 487 488 private void metadataDownloaded( 489 RepositorySystemSession session, 490 RequestTrace trace, 491 Metadata metadata, 492 ArtifactRepository repository, 493 File file, 494 Exception exception) { 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 class ResolveTask implements Runnable { 506 final RepositorySystemSession session; 507 508 final RequestTrace trace; 509 510 final MetadataResult result; 511 512 final MetadataRequest request; 513 514 final File metadataFile; 515 516 final String policy; 517 518 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 519 520 volatile MetadataTransferException exception; 521 522 ResolveTask( 523 RepositorySystemSession session, 524 RequestTrace trace, 525 MetadataResult result, 526 File metadataFile, 527 List<UpdateCheck<Metadata, MetadataTransferException>> checks, 528 String policy) { 529 this.session = session; 530 this.trace = trace; 531 this.result = result; 532 this.request = result.getRequest(); 533 this.metadataFile = metadataFile; 534 this.policy = policy; 535 this.checks = checks; 536 } 537 538 public void run() { 539 Metadata metadata = request.getMetadata(); 540 RemoteRepository requestRepository = request.getRepository(); 541 542 try { 543 List<RemoteRepository> repositories = new ArrayList<>(); 544 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) { 545 repositories.add(check.getAuthoritativeRepository()); 546 } 547 548 MetadataDownload download = new MetadataDownload(); 549 download.setMetadata(metadata); 550 download.setRequestContext(request.getRequestContext()); 551 download.setFile(metadataFile); 552 download.setChecksumPolicy(policy); 553 download.setRepositories(repositories); 554 download.setListener(SafeTransferListener.wrap(session)); 555 download.setTrace(trace); 556 557 try (RepositoryConnector connector = 558 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) { 559 connector.get(null, Collections.singletonList(download)); 560 } 561 562 exception = download.getException(); 563 564 if (exception == null) { 565 566 List<String> contexts = Collections.singletonList(request.getRequestContext()); 567 LocalMetadataRegistration registration = 568 new LocalMetadataRegistration(metadata, requestRepository, contexts); 569 570 session.getLocalRepositoryManager().add(session, registration); 571 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) { 572 download.getFile().delete(); 573 } 574 } catch (NoRepositoryConnectorException e) { 575 exception = new MetadataTransferException(metadata, requestRepository, e); 576 } 577 } 578 } 579}