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