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 return !Metadata.Nature.RELEASE.equals(nature) 439 && repository.getPolicy(true).isEnabled(); 440 } 441 442 private RepositoryPolicy getPolicy( 443 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) { 444 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature); 445 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature); 446 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots); 447 } 448 449 private void metadataResolving( 450 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 451 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING); 452 event.setTrace(trace); 453 event.setMetadata(metadata); 454 event.setRepository(repository); 455 456 repositoryEventDispatcher.dispatch(event.build()); 457 } 458 459 private void metadataResolved( 460 RepositorySystemSession session, 461 RequestTrace trace, 462 Metadata metadata, 463 ArtifactRepository repository, 464 Exception exception) { 465 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED); 466 event.setTrace(trace); 467 event.setMetadata(metadata); 468 event.setRepository(repository); 469 event.setException(exception); 470 event.setFile(metadata.getFile()); 471 472 repositoryEventDispatcher.dispatch(event.build()); 473 } 474 475 private void metadataDownloading( 476 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 477 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING); 478 event.setTrace(trace); 479 event.setMetadata(metadata); 480 event.setRepository(repository); 481 482 repositoryEventDispatcher.dispatch(event.build()); 483 } 484 485 private void metadataDownloaded( 486 RepositorySystemSession session, 487 RequestTrace trace, 488 Metadata metadata, 489 ArtifactRepository repository, 490 File file, 491 Exception exception) { 492 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED); 493 event.setTrace(trace); 494 event.setMetadata(metadata); 495 event.setRepository(repository); 496 event.setException(exception); 497 event.setFile(file); 498 499 repositoryEventDispatcher.dispatch(event.build()); 500 } 501 502 class ResolveTask implements Runnable { 503 final RepositorySystemSession session; 504 505 final RequestTrace trace; 506 507 final MetadataResult result; 508 509 final MetadataRequest request; 510 511 final File metadataFile; 512 513 final String policy; 514 515 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 516 517 volatile MetadataTransferException exception; 518 519 ResolveTask( 520 RepositorySystemSession session, 521 RequestTrace trace, 522 MetadataResult result, 523 File metadataFile, 524 List<UpdateCheck<Metadata, MetadataTransferException>> checks, 525 String policy) { 526 this.session = session; 527 this.trace = trace; 528 this.result = result; 529 this.request = result.getRequest(); 530 this.metadataFile = metadataFile; 531 this.policy = policy; 532 this.checks = checks; 533 } 534 535 public void run() { 536 Metadata metadata = request.getMetadata(); 537 RemoteRepository requestRepository = request.getRepository(); 538 539 try { 540 List<RemoteRepository> repositories = new ArrayList<>(); 541 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) { 542 repositories.add(check.getAuthoritativeRepository()); 543 } 544 545 MetadataDownload download = new MetadataDownload(); 546 download.setMetadata(metadata); 547 download.setRequestContext(request.getRequestContext()); 548 download.setFile(metadataFile); 549 download.setChecksumPolicy(policy); 550 download.setRepositories(repositories); 551 download.setListener(SafeTransferListener.wrap(session)); 552 download.setTrace(trace); 553 554 try (RepositoryConnector connector = 555 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) { 556 connector.get(null, Collections.singletonList(download)); 557 } 558 559 exception = download.getException(); 560 561 if (exception == null) { 562 563 List<String> contexts = Collections.singletonList(request.getRequestContext()); 564 LocalMetadataRegistration registration = 565 new LocalMetadataRegistration(metadata, requestRepository, contexts); 566 567 session.getLocalRepositoryManager().add(session, registration); 568 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) { 569 download.getFile().delete(); 570 } 571 } catch (NoRepositoryConnectorException e) { 572 exception = new MetadataTransferException(metadata, requestRepository, e); 573 } 574 } 575 } 576}