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 public DefaultMetadataResolver() { 096 // enables default constructor 097 } 098 099 @Inject 100 DefaultMetadataResolver( 101 RepositoryEventDispatcher repositoryEventDispatcher, 102 UpdateCheckManager updateCheckManager, 103 RepositoryConnectorProvider repositoryConnectorProvider, 104 RemoteRepositoryManager remoteRepositoryManager, 105 SyncContextFactory syncContextFactory, 106 OfflineController offlineController, 107 RemoteRepositoryFilterManager remoteRepositoryFilterManager) { 108 setRepositoryEventDispatcher(repositoryEventDispatcher); 109 setUpdateCheckManager(updateCheckManager); 110 setRepositoryConnectorProvider(repositoryConnectorProvider); 111 setRemoteRepositoryManager(remoteRepositoryManager); 112 setSyncContextFactory(syncContextFactory); 113 setOfflineController(offlineController); 114 setRemoteRepositoryFilterManager(remoteRepositoryFilterManager); 115 } 116 117 public void initService(ServiceLocator locator) { 118 setRepositoryEventDispatcher(locator.getService(RepositoryEventDispatcher.class)); 119 setUpdateCheckManager(locator.getService(UpdateCheckManager.class)); 120 setRepositoryConnectorProvider(locator.getService(RepositoryConnectorProvider.class)); 121 setRemoteRepositoryManager(locator.getService(RemoteRepositoryManager.class)); 122 setSyncContextFactory(locator.getService(SyncContextFactory.class)); 123 setOfflineController(locator.getService(OfflineController.class)); 124 setRemoteRepositoryFilterManager(locator.getService(RemoteRepositoryFilterManager.class)); 125 } 126 127 public DefaultMetadataResolver setRepositoryEventDispatcher(RepositoryEventDispatcher repositoryEventDispatcher) { 128 this.repositoryEventDispatcher = 129 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null"); 130 return this; 131 } 132 133 public DefaultMetadataResolver setUpdateCheckManager(UpdateCheckManager updateCheckManager) { 134 this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null"); 135 return this; 136 } 137 138 public DefaultMetadataResolver setRepositoryConnectorProvider( 139 RepositoryConnectorProvider repositoryConnectorProvider) { 140 this.repositoryConnectorProvider = 141 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null"); 142 return this; 143 } 144 145 public DefaultMetadataResolver setRemoteRepositoryManager(RemoteRepositoryManager remoteRepositoryManager) { 146 this.remoteRepositoryManager = 147 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null"); 148 return this; 149 } 150 151 public DefaultMetadataResolver setSyncContextFactory(SyncContextFactory syncContextFactory) { 152 this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null"); 153 return this; 154 } 155 156 public DefaultMetadataResolver setOfflineController(OfflineController offlineController) { 157 this.offlineController = requireNonNull(offlineController, "offline controller cannot be null"); 158 return this; 159 } 160 161 public DefaultMetadataResolver setRemoteRepositoryFilterManager( 162 RemoteRepositoryFilterManager remoteRepositoryFilterManager) { 163 this.remoteRepositoryFilterManager = 164 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null"); 165 return this; 166 } 167 168 public List<MetadataResult> resolveMetadata( 169 RepositorySystemSession session, Collection<? extends MetadataRequest> requests) { 170 requireNonNull(session, "session cannot be null"); 171 requireNonNull(requests, "requests cannot be null"); 172 try (SyncContext shared = syncContextFactory.newInstance(session, true); 173 SyncContext exclusive = syncContextFactory.newInstance(session, false)) { 174 Collection<Metadata> metadata = new ArrayList<>(requests.size()); 175 for (MetadataRequest request : requests) { 176 metadata.add(request.getMetadata()); 177 } 178 179 return resolve(shared, exclusive, metadata, session, requests); 180 } 181 } 182 183 @SuppressWarnings("checkstyle:methodlength") 184 private List<MetadataResult> resolve( 185 SyncContext shared, 186 SyncContext exclusive, 187 Collection<Metadata> subjects, 188 RepositorySystemSession session, 189 Collection<? extends MetadataRequest> requests) { 190 SyncContext current = shared; 191 try { 192 while (true) { 193 current.acquire(null, subjects); 194 195 final List<MetadataResult> results = new ArrayList<>(requests.size()); 196 final List<ResolveTask> tasks = new ArrayList<>(requests.size()); 197 final Map<File, Long> localLastUpdates = new HashMap<>(); 198 final RemoteRepositoryFilter remoteRepositoryFilter = 199 remoteRepositoryFilterManager.getRemoteRepositoryFilter(session); 200 201 for (MetadataRequest request : requests) { 202 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 203 204 MetadataResult result = new MetadataResult(request); 205 results.add(result); 206 207 Metadata metadata = request.getMetadata(); 208 RemoteRepository repository = request.getRepository(); 209 210 if (repository == null) { 211 LocalRepository localRepo = 212 session.getLocalRepositoryManager().getRepository(); 213 214 metadataResolving(session, trace, metadata, localRepo); 215 216 File localFile = getLocalFile(session, metadata); 217 218 if (localFile != null) { 219 metadata = metadata.setFile(localFile); 220 result.setMetadata(metadata); 221 } else { 222 result.setException(new MetadataNotFoundException(metadata, localRepo)); 223 } 224 225 metadataResolved(session, trace, metadata, localRepo, result.getException()); 226 continue; 227 } 228 229 if (remoteRepositoryFilter != null) { 230 RemoteRepositoryFilter.Result filterResult = 231 remoteRepositoryFilter.acceptMetadata(repository, metadata); 232 if (!filterResult.isAccepted()) { 233 result.setException( 234 new MetadataNotFoundException(metadata, repository, filterResult.reasoning())); 235 continue; 236 } 237 } 238 239 List<RemoteRepository> repositories = 240 getEnabledSourceRepositories(repository, metadata.getNature()); 241 242 if (repositories.isEmpty()) { 243 continue; 244 } 245 246 metadataResolving(session, trace, metadata, repository); 247 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 248 LocalMetadataRequest localRequest = 249 new LocalMetadataRequest(metadata, repository, request.getRequestContext()); 250 LocalMetadataResult lrmResult = lrm.find(session, localRequest); 251 252 File metadataFile = lrmResult.getFile(); 253 254 try { 255 Utils.checkOffline(session, offlineController, repository); 256 } catch (RepositoryOfflineException e) { 257 if (metadataFile != null) { 258 metadata = metadata.setFile(metadataFile); 259 result.setMetadata(metadata); 260 } else { 261 String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl() 262 + ") in offline mode and the metadata " + metadata 263 + " has not been downloaded from it before"; 264 result.setException(new MetadataNotFoundException(metadata, repository, msg, e)); 265 } 266 267 metadataResolved(session, trace, metadata, repository, result.getException()); 268 continue; 269 } 270 271 Long localLastUpdate = null; 272 if (request.isFavorLocalRepository()) { 273 File localFile = getLocalFile(session, metadata); 274 localLastUpdate = localLastUpdates.get(localFile); 275 if (localLastUpdate == null) { 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 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( 297 getPolicy(session, repo, metadata.getNature()).getUpdatePolicy()); 298 299 if (lrmResult.isStale()) { 300 checks.add(check); 301 } else { 302 updateCheckManager.checkMetadata(session, check); 303 if (check.isRequired()) { 304 checks.add(check); 305 } else if (exception == null) { 306 exception = check.getException(); 307 } 308 } 309 } 310 311 if (!checks.isEmpty()) { 312 RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature()); 313 314 // install path may be different from lookup path 315 File installFile = new File( 316 session.getLocalRepository().getBasedir(), 317 session.getLocalRepositoryManager() 318 .getPathForRemoteMetadata( 319 metadata, request.getRepository(), request.getRequestContext())); 320 321 metadataDownloading( 322 session, 323 trace, 324 result.getRequest().getMetadata(), 325 result.getRequest().getRepository()); 326 327 ResolveTask task = new ResolveTask( 328 session, trace, result, installFile, checks, policy.getChecksumPolicy()); 329 tasks.add(task); 330 } else { 331 result.setException(exception); 332 if (metadataFile != null) { 333 metadata = metadata.setFile(metadataFile); 334 result.setMetadata(metadata); 335 } 336 metadataResolved(session, trace, metadata, repository, result.getException()); 337 } 338 } 339 340 if (!tasks.isEmpty() && current == shared) { 341 current.close(); 342 current = exclusive; 343 continue; 344 } 345 346 if (!tasks.isEmpty()) { 347 int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS); 348 Executor executor = ExecutorUtils.executor( 349 Math.min(tasks.size(), threads), getClass().getSimpleName() + '-'); 350 try { 351 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); 352 353 for (ResolveTask task : tasks) { 354 executor.execute(errorForwarder.wrap(task)); 355 } 356 357 errorForwarder.await(); 358 359 for (ResolveTask task : tasks) { 360 /* 361 * NOTE: Touch after registration with local repo to ensure concurrent resolution is not 362 * rejected with "already updated" via session data when actual update to local repo is 363 * still pending. 364 */ 365 for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) { 366 updateCheckManager.touchMetadata(task.session, check.setException(task.exception)); 367 } 368 369 metadataDownloaded( 370 session, 371 task.trace, 372 task.request.getMetadata(), 373 task.request.getRepository(), 374 task.metadataFile, 375 task.exception); 376 377 task.result.setException(task.exception); 378 } 379 } finally { 380 ExecutorUtils.shutdown(executor); 381 } 382 for (ResolveTask task : tasks) { 383 Metadata metadata = task.request.getMetadata(); 384 // re-lookup metadata for resolve 385 LocalMetadataRequest localRequest = new LocalMetadataRequest( 386 metadata, task.request.getRepository(), task.request.getRequestContext()); 387 File metadataFile = session.getLocalRepositoryManager() 388 .find(session, localRequest) 389 .getFile(); 390 if (metadataFile != null) { 391 metadata = metadata.setFile(metadataFile); 392 task.result.setMetadata(metadata); 393 } 394 if (task.result.getException() == null) { 395 task.result.setUpdated(true); 396 } 397 metadataResolved( 398 session, 399 task.trace, 400 metadata, 401 task.request.getRepository(), 402 task.result.getException()); 403 } 404 } 405 406 return results; 407 } 408 } finally { 409 current.close(); 410 } 411 } 412 413 private File getLocalFile(RepositorySystemSession session, Metadata metadata) { 414 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 415 LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null)); 416 return localResult.getFile(); 417 } 418 419 private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) { 420 List<RemoteRepository> repositories = new ArrayList<>(); 421 422 if (repository.isRepositoryManager()) { 423 for (RemoteRepository repo : repository.getMirroredRepositories()) { 424 if (isEnabled(repo, nature)) { 425 repositories.add(repo); 426 } 427 } 428 } else if (isEnabled(repository, nature)) { 429 repositories.add(repository); 430 } 431 432 return repositories; 433 } 434 435 private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) { 436 if (!Metadata.Nature.SNAPSHOT.equals(nature) 437 && repository.getPolicy(false).isEnabled()) { 438 return true; 439 } 440 if (!Metadata.Nature.RELEASE.equals(nature) 441 && repository.getPolicy(true).isEnabled()) { 442 return true; 443 } 444 return false; 445 } 446 447 private RepositoryPolicy getPolicy( 448 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) { 449 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature); 450 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature); 451 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots); 452 } 453 454 private void metadataResolving( 455 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 456 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING); 457 event.setTrace(trace); 458 event.setMetadata(metadata); 459 event.setRepository(repository); 460 461 repositoryEventDispatcher.dispatch(event.build()); 462 } 463 464 private void metadataResolved( 465 RepositorySystemSession session, 466 RequestTrace trace, 467 Metadata metadata, 468 ArtifactRepository repository, 469 Exception exception) { 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( 481 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 482 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING); 483 event.setTrace(trace); 484 event.setMetadata(metadata); 485 event.setRepository(repository); 486 487 repositoryEventDispatcher.dispatch(event.build()); 488 } 489 490 private void metadataDownloaded( 491 RepositorySystemSession session, 492 RequestTrace trace, 493 Metadata metadata, 494 ArtifactRepository repository, 495 File file, 496 Exception exception) { 497 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED); 498 event.setTrace(trace); 499 event.setMetadata(metadata); 500 event.setRepository(repository); 501 event.setException(exception); 502 event.setFile(file); 503 504 repositoryEventDispatcher.dispatch(event.build()); 505 } 506 507 class ResolveTask implements Runnable { 508 final RepositorySystemSession session; 509 510 final RequestTrace trace; 511 512 final MetadataResult result; 513 514 final MetadataRequest request; 515 516 final File metadataFile; 517 518 final String policy; 519 520 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 521 522 volatile MetadataTransferException exception; 523 524 ResolveTask( 525 RepositorySystemSession session, 526 RequestTrace trace, 527 MetadataResult result, 528 File metadataFile, 529 List<UpdateCheck<Metadata, MetadataTransferException>> checks, 530 String policy) { 531 this.session = session; 532 this.trace = trace; 533 this.result = result; 534 this.request = result.getRequest(); 535 this.metadataFile = metadataFile; 536 this.policy = policy; 537 this.checks = checks; 538 } 539 540 public void run() { 541 Metadata metadata = request.getMetadata(); 542 RemoteRepository requestRepository = request.getRepository(); 543 544 try { 545 List<RemoteRepository> repositories = new ArrayList<>(); 546 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) { 547 repositories.add(check.getAuthoritativeRepository()); 548 } 549 550 MetadataDownload download = new MetadataDownload(); 551 download.setMetadata(metadata); 552 download.setRequestContext(request.getRequestContext()); 553 download.setFile(metadataFile); 554 download.setChecksumPolicy(policy); 555 download.setRepositories(repositories); 556 download.setListener(SafeTransferListener.wrap(session)); 557 download.setTrace(trace); 558 559 try (RepositoryConnector connector = 560 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) { 561 connector.get(null, Collections.singletonList(download)); 562 } 563 564 exception = download.getException(); 565 566 if (exception == null) { 567 568 List<String> contexts = Collections.singletonList(request.getRequestContext()); 569 LocalMetadataRegistration registration = 570 new LocalMetadataRegistration(metadata, requestRepository, contexts); 571 572 session.getLocalRepositoryManager().add(session, registration); 573 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) { 574 download.getFile().delete(); 575 } 576 } catch (NoRepositoryConnectorException e) { 577 exception = new MetadataTransferException(metadata, requestRepository, e); 578 } 579 } 580 } 581}