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 ResolveTask task = new ResolveTask( 322 session, trace, result, installFile, checks, policy.getChecksumPolicy()); 323 tasks.add(task); 324 } else { 325 result.setException(exception); 326 if (metadataFile != null) { 327 metadata = metadata.setFile(metadataFile); 328 result.setMetadata(metadata); 329 } 330 metadataResolved(session, trace, metadata, repository, result.getException()); 331 } 332 } 333 334 if (!tasks.isEmpty() && current == shared) { 335 current.close(); 336 current = exclusive; 337 continue; 338 } 339 340 if (!tasks.isEmpty()) { 341 int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS); 342 Executor executor = ExecutorUtils.executor( 343 Math.min(tasks.size(), threads), getClass().getSimpleName() + '-'); 344 try { 345 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); 346 347 for (ResolveTask task : tasks) { 348 metadataDownloading( 349 task.session, task.trace, task.request.getMetadata(), task.request.getRepository()); 350 351 executor.execute(errorForwarder.wrap(task)); 352 } 353 354 errorForwarder.await(); 355 356 for (ResolveTask task : tasks) { 357 /* 358 * NOTE: Touch after registration with local repo to ensure concurrent resolution is not 359 * rejected with "already updated" via session data when actual update to local repo is 360 * still pending. 361 */ 362 for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) { 363 updateCheckManager.touchMetadata(task.session, check.setException(task.exception)); 364 } 365 366 metadataDownloaded( 367 session, 368 task.trace, 369 task.request.getMetadata(), 370 task.request.getRepository(), 371 task.metadataFile, 372 task.exception); 373 374 task.result.setException(task.exception); 375 } 376 } finally { 377 ExecutorUtils.shutdown(executor); 378 } 379 for (ResolveTask task : tasks) { 380 Metadata metadata = task.request.getMetadata(); 381 // re-lookup metadata for resolve 382 LocalMetadataRequest localRequest = new LocalMetadataRequest( 383 metadata, task.request.getRepository(), task.request.getRequestContext()); 384 File metadataFile = session.getLocalRepositoryManager() 385 .find(session, localRequest) 386 .getFile(); 387 if (metadataFile != null) { 388 metadata = metadata.setFile(metadataFile); 389 task.result.setMetadata(metadata); 390 } 391 if (task.result.getException() == null) { 392 task.result.setUpdated(true); 393 } 394 metadataResolved( 395 session, 396 task.trace, 397 metadata, 398 task.request.getRepository(), 399 task.result.getException()); 400 } 401 } 402 403 return results; 404 } 405 } finally { 406 current.close(); 407 } 408 } 409 410 private File getLocalFile(RepositorySystemSession session, Metadata metadata) { 411 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 412 LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null)); 413 return localResult.getFile(); 414 } 415 416 private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) { 417 List<RemoteRepository> repositories = new ArrayList<>(); 418 419 if (repository.isRepositoryManager()) { 420 for (RemoteRepository repo : repository.getMirroredRepositories()) { 421 if (isEnabled(repo, nature)) { 422 repositories.add(repo); 423 } 424 } 425 } else if (isEnabled(repository, nature)) { 426 repositories.add(repository); 427 } 428 429 return repositories; 430 } 431 432 private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) { 433 if (!Metadata.Nature.SNAPSHOT.equals(nature) 434 && repository.getPolicy(false).isEnabled()) { 435 return true; 436 } 437 if (!Metadata.Nature.RELEASE.equals(nature) 438 && repository.getPolicy(true).isEnabled()) { 439 return true; 440 } 441 return false; 442 } 443 444 private RepositoryPolicy getPolicy( 445 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) { 446 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature); 447 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature); 448 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots); 449 } 450 451 private void metadataResolving( 452 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 453 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING); 454 event.setTrace(trace); 455 event.setMetadata(metadata); 456 event.setRepository(repository); 457 458 repositoryEventDispatcher.dispatch(event.build()); 459 } 460 461 private void metadataResolved( 462 RepositorySystemSession session, 463 RequestTrace trace, 464 Metadata metadata, 465 ArtifactRepository repository, 466 Exception exception) { 467 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED); 468 event.setTrace(trace); 469 event.setMetadata(metadata); 470 event.setRepository(repository); 471 event.setException(exception); 472 event.setFile(metadata.getFile()); 473 474 repositoryEventDispatcher.dispatch(event.build()); 475 } 476 477 private void metadataDownloading( 478 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 479 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING); 480 event.setTrace(trace); 481 event.setMetadata(metadata); 482 event.setRepository(repository); 483 484 repositoryEventDispatcher.dispatch(event.build()); 485 } 486 487 private void metadataDownloaded( 488 RepositorySystemSession session, 489 RequestTrace trace, 490 Metadata metadata, 491 ArtifactRepository repository, 492 File file, 493 Exception exception) { 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 class ResolveTask implements Runnable { 505 final RepositorySystemSession session; 506 507 final RequestTrace trace; 508 509 final MetadataResult result; 510 511 final MetadataRequest request; 512 513 final File metadataFile; 514 515 final String policy; 516 517 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 518 519 volatile MetadataTransferException exception; 520 521 ResolveTask( 522 RepositorySystemSession session, 523 RequestTrace trace, 524 MetadataResult result, 525 File metadataFile, 526 List<UpdateCheck<Metadata, MetadataTransferException>> checks, 527 String policy) { 528 this.session = session; 529 this.trace = trace; 530 this.result = result; 531 this.request = result.getRequest(); 532 this.metadataFile = metadataFile; 533 this.policy = policy; 534 this.checks = checks; 535 } 536 537 public void run() { 538 Metadata metadata = request.getMetadata(); 539 RemoteRepository requestRepository = request.getRepository(); 540 541 try { 542 List<RemoteRepository> repositories = new ArrayList<>(); 543 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) { 544 repositories.add(check.getAuthoritativeRepository()); 545 } 546 547 MetadataDownload download = new MetadataDownload(); 548 download.setMetadata(metadata); 549 download.setRequestContext(request.getRequestContext()); 550 download.setFile(metadataFile); 551 download.setChecksumPolicy(policy); 552 download.setRepositories(repositories); 553 download.setListener(SafeTransferListener.wrap(session)); 554 download.setTrace(trace); 555 556 try (RepositoryConnector connector = 557 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) { 558 connector.get(null, Collections.singletonList(download)); 559 } 560 561 exception = download.getException(); 562 563 if (exception == null) { 564 565 List<String> contexts = Collections.singletonList(request.getRequestContext()); 566 LocalMetadataRegistration registration = 567 new LocalMetadataRegistration(metadata, requestRepository, contexts); 568 569 session.getLocalRepositoryManager().add(session, registration); 570 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) { 571 download.getFile().delete(); 572 } 573 } catch (NoRepositoryConnectorException e) { 574 exception = new MetadataTransferException(metadata, requestRepository, e); 575 } 576 } 577 } 578}