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 exclusive = syncContextFactory.newInstance(session, false)) { 173 Collection<Metadata> metadata = new ArrayList<>(requests.size()); 174 for (MetadataRequest request : requests) { 175 metadata.add(request.getMetadata()); 176 } 177 178 return resolve(exclusive, metadata, session, requests); 179 } 180 } 181 182 @SuppressWarnings("checkstyle:methodlength") 183 private List<MetadataResult> resolve( 184 SyncContext syncContext, 185 Collection<Metadata> subjects, 186 RepositorySystemSession session, 187 Collection<? extends MetadataRequest> requests) { 188 try { 189 syncContext.acquire(null, subjects); 190 191 final List<MetadataResult> results = new ArrayList<>(requests.size()); 192 final List<ResolveTask> tasks = new ArrayList<>(requests.size()); 193 final Map<File, Long> localLastUpdates = new HashMap<>(); 194 final RemoteRepositoryFilter remoteRepositoryFilter = 195 remoteRepositoryFilterManager.getRemoteRepositoryFilter(session); 196 197 for (MetadataRequest request : requests) { 198 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 199 200 MetadataResult result = new MetadataResult(request); 201 results.add(result); 202 203 Metadata metadata = request.getMetadata(); 204 RemoteRepository repository = request.getRepository(); 205 206 if (repository == null) { 207 LocalRepository localRepo = 208 session.getLocalRepositoryManager().getRepository(); 209 210 metadataResolving(session, trace, metadata, localRepo); 211 212 File localFile = getLocalFile(session, metadata); 213 214 if (localFile != null) { 215 metadata = metadata.setFile(localFile); 216 result.setMetadata(metadata); 217 } else { 218 result.setException(new MetadataNotFoundException(metadata, localRepo)); 219 } 220 221 metadataResolved(session, trace, metadata, localRepo, result.getException()); 222 continue; 223 } 224 225 if (remoteRepositoryFilter != null) { 226 RemoteRepositoryFilter.Result filterResult = 227 remoteRepositoryFilter.acceptMetadata(repository, metadata); 228 if (!filterResult.isAccepted()) { 229 result.setException( 230 new MetadataNotFoundException(metadata, repository, filterResult.reasoning())); 231 continue; 232 } 233 } 234 235 List<RemoteRepository> repositories = getEnabledSourceRepositories(repository, metadata.getNature()); 236 237 if (repositories.isEmpty()) { 238 continue; 239 } 240 241 metadataResolving(session, trace, metadata, repository); 242 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 243 LocalMetadataRequest localRequest = 244 new LocalMetadataRequest(metadata, repository, request.getRequestContext()); 245 LocalMetadataResult lrmResult = lrm.find(session, localRequest); 246 247 File metadataFile = lrmResult.getFile(); 248 249 try { 250 Utils.checkOffline(session, offlineController, repository); 251 } catch (RepositoryOfflineException e) { 252 if (metadataFile != null) { 253 metadata = metadata.setFile(metadataFile); 254 result.setMetadata(metadata); 255 } else { 256 String msg = "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 File localFile = getLocalFile(session, metadata); 269 localLastUpdate = localLastUpdates.get(localFile); 270 if (localLastUpdate == null) { 271 localLastUpdate = localFile != null ? localFile.lastModified() : 0; 272 localLastUpdates.put(localFile, localLastUpdate); 273 } 274 } 275 276 List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>(); 277 Exception exception = null; 278 for (RemoteRepository repo : repositories) { 279 UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>(); 280 check.setLocalLastUpdated((localLastUpdate != null) ? localLastUpdate : 0); 281 check.setItem(metadata); 282 283 // use 'main' installation file for the check (-> use requested repository) 284 File checkFile = new File( 285 session.getLocalRepository().getBasedir(), 286 session.getLocalRepositoryManager() 287 .getPathForRemoteMetadata(metadata, repository, request.getRequestContext())); 288 check.setFile(checkFile); 289 check.setRepository(repository); 290 check.setAuthoritativeRepository(repo); 291 check.setPolicy( 292 getPolicy(session, repo, metadata.getNature()).getUpdatePolicy()); 293 294 if (lrmResult.isStale()) { 295 checks.add(check); 296 } else { 297 updateCheckManager.checkMetadata(session, check); 298 if (check.isRequired()) { 299 checks.add(check); 300 } else if (exception == null) { 301 exception = check.getException(); 302 } 303 } 304 } 305 306 if (!checks.isEmpty()) { 307 RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature()); 308 309 // install path may be different from lookup path 310 File installFile = new File( 311 session.getLocalRepository().getBasedir(), 312 session.getLocalRepositoryManager() 313 .getPathForRemoteMetadata( 314 metadata, request.getRepository(), request.getRequestContext())); 315 316 ResolveTask task = 317 new ResolveTask(session, trace, result, installFile, checks, policy.getChecksumPolicy()); 318 tasks.add(task); 319 } else { 320 result.setException(exception); 321 if (metadataFile != null) { 322 metadata = metadata.setFile(metadataFile); 323 result.setMetadata(metadata); 324 } 325 metadataResolved(session, trace, metadata, repository, result.getException()); 326 } 327 } 328 329 if (!tasks.isEmpty()) { 330 int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS); 331 Executor executor = ExecutorUtils.executor( 332 Math.min(tasks.size(), threads), getClass().getSimpleName() + '-'); 333 try { 334 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); 335 336 for (ResolveTask task : tasks) { 337 metadataDownloading( 338 task.session, task.trace, task.request.getMetadata(), task.request.getRepository()); 339 340 executor.execute(errorForwarder.wrap(task)); 341 } 342 343 errorForwarder.await(); 344 345 for (ResolveTask task : tasks) { 346 /* 347 * NOTE: Touch after registration with local repo to ensure concurrent resolution is not 348 * rejected with "already updated" via session data when actual update to local repo is 349 * still pending. 350 */ 351 for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) { 352 updateCheckManager.touchMetadata(task.session, check.setException(task.exception)); 353 } 354 355 metadataDownloaded( 356 session, 357 task.trace, 358 task.request.getMetadata(), 359 task.request.getRepository(), 360 task.metadataFile, 361 task.exception); 362 363 task.result.setException(task.exception); 364 } 365 } finally { 366 ExecutorUtils.shutdown(executor); 367 } 368 for (ResolveTask task : tasks) { 369 Metadata metadata = task.request.getMetadata(); 370 // re-lookup metadata for resolve 371 LocalMetadataRequest localRequest = new LocalMetadataRequest( 372 metadata, task.request.getRepository(), task.request.getRequestContext()); 373 File metadataFile = session.getLocalRepositoryManager() 374 .find(session, localRequest) 375 .getFile(); 376 if (metadataFile != null) { 377 metadata = metadata.setFile(metadataFile); 378 task.result.setMetadata(metadata); 379 } 380 if (task.result.getException() == null) { 381 task.result.setUpdated(true); 382 } 383 metadataResolved( 384 session, task.trace, metadata, task.request.getRepository(), task.result.getException()); 385 } 386 } 387 388 return results; 389 } finally { 390 syncContext.close(); 391 } 392 } 393 394 private File getLocalFile(RepositorySystemSession session, Metadata metadata) { 395 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 396 LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null)); 397 return localResult.getFile(); 398 } 399 400 private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) { 401 List<RemoteRepository> repositories = new ArrayList<>(); 402 403 if (repository.isRepositoryManager()) { 404 for (RemoteRepository repo : repository.getMirroredRepositories()) { 405 if (isEnabled(repo, nature)) { 406 repositories.add(repo); 407 } 408 } 409 } else if (isEnabled(repository, nature)) { 410 repositories.add(repository); 411 } 412 413 return repositories; 414 } 415 416 private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) { 417 if (!Metadata.Nature.SNAPSHOT.equals(nature) 418 && repository.getPolicy(false).isEnabled()) { 419 return true; 420 } 421 if (!Metadata.Nature.RELEASE.equals(nature) 422 && repository.getPolicy(true).isEnabled()) { 423 return true; 424 } 425 return false; 426 } 427 428 private RepositoryPolicy getPolicy( 429 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) { 430 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature); 431 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature); 432 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots); 433 } 434 435 private void metadataResolving( 436 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 437 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING); 438 event.setTrace(trace); 439 event.setMetadata(metadata); 440 event.setRepository(repository); 441 442 repositoryEventDispatcher.dispatch(event.build()); 443 } 444 445 private void metadataResolved( 446 RepositorySystemSession session, 447 RequestTrace trace, 448 Metadata metadata, 449 ArtifactRepository repository, 450 Exception exception) { 451 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED); 452 event.setTrace(trace); 453 event.setMetadata(metadata); 454 event.setRepository(repository); 455 event.setException(exception); 456 event.setFile(metadata.getFile()); 457 458 repositoryEventDispatcher.dispatch(event.build()); 459 } 460 461 private void metadataDownloading( 462 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 463 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING); 464 event.setTrace(trace); 465 event.setMetadata(metadata); 466 event.setRepository(repository); 467 468 repositoryEventDispatcher.dispatch(event.build()); 469 } 470 471 private void metadataDownloaded( 472 RepositorySystemSession session, 473 RequestTrace trace, 474 Metadata metadata, 475 ArtifactRepository repository, 476 File file, 477 Exception exception) { 478 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED); 479 event.setTrace(trace); 480 event.setMetadata(metadata); 481 event.setRepository(repository); 482 event.setException(exception); 483 event.setFile(file); 484 485 repositoryEventDispatcher.dispatch(event.build()); 486 } 487 488 class ResolveTask implements Runnable { 489 final RepositorySystemSession session; 490 491 final RequestTrace trace; 492 493 final MetadataResult result; 494 495 final MetadataRequest request; 496 497 final File metadataFile; 498 499 final String policy; 500 501 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 502 503 volatile MetadataTransferException exception; 504 505 ResolveTask( 506 RepositorySystemSession session, 507 RequestTrace trace, 508 MetadataResult result, 509 File metadataFile, 510 List<UpdateCheck<Metadata, MetadataTransferException>> checks, 511 String policy) { 512 this.session = session; 513 this.trace = trace; 514 this.result = result; 515 this.request = result.getRequest(); 516 this.metadataFile = metadataFile; 517 this.policy = policy; 518 this.checks = checks; 519 } 520 521 public void run() { 522 Metadata metadata = request.getMetadata(); 523 RemoteRepository requestRepository = request.getRepository(); 524 525 try { 526 List<RemoteRepository> repositories = new ArrayList<>(); 527 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) { 528 repositories.add(check.getAuthoritativeRepository()); 529 } 530 531 MetadataDownload download = new MetadataDownload(); 532 download.setMetadata(metadata); 533 download.setRequestContext(request.getRequestContext()); 534 download.setFile(metadataFile); 535 download.setChecksumPolicy(policy); 536 download.setRepositories(repositories); 537 download.setListener(SafeTransferListener.wrap(session)); 538 download.setTrace(trace); 539 540 try (RepositoryConnector connector = 541 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) { 542 connector.get(null, Collections.singletonList(download)); 543 } 544 545 exception = download.getException(); 546 547 if (exception == null) { 548 549 List<String> contexts = Collections.singletonList(request.getRequestContext()); 550 LocalMetadataRegistration registration = 551 new LocalMetadataRegistration(metadata, requestRepository, contexts); 552 553 session.getLocalRepositoryManager().add(session, registration); 554 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) { 555 download.getFile().delete(); 556 } 557 } catch (NoRepositoryConnectorException e) { 558 exception = new MetadataTransferException(metadata, requestRepository, e); 559 } 560 } 561 } 562}