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.IOException; 026import java.io.UncheckedIOException; 027import java.nio.file.Files; 028import java.nio.file.Path; 029import java.util.ArrayList; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035 036import org.eclipse.aether.ConfigurationProperties; 037import org.eclipse.aether.RepositoryEvent; 038import org.eclipse.aether.RepositoryEvent.EventType; 039import org.eclipse.aether.RepositorySystemSession; 040import org.eclipse.aether.RequestTrace; 041import org.eclipse.aether.SyncContext; 042import org.eclipse.aether.impl.MetadataResolver; 043import org.eclipse.aether.impl.OfflineController; 044import org.eclipse.aether.impl.RemoteRepositoryFilterManager; 045import org.eclipse.aether.impl.RemoteRepositoryManager; 046import org.eclipse.aether.impl.RepositoryConnectorProvider; 047import org.eclipse.aether.impl.RepositoryEventDispatcher; 048import org.eclipse.aether.impl.UpdateCheck; 049import org.eclipse.aether.impl.UpdateCheckManager; 050import org.eclipse.aether.metadata.Metadata; 051import org.eclipse.aether.repository.ArtifactRepository; 052import org.eclipse.aether.repository.LocalMetadataRegistration; 053import org.eclipse.aether.repository.LocalMetadataRequest; 054import org.eclipse.aether.repository.LocalMetadataResult; 055import org.eclipse.aether.repository.LocalRepository; 056import org.eclipse.aether.repository.LocalRepositoryManager; 057import org.eclipse.aether.repository.RemoteRepository; 058import org.eclipse.aether.repository.RepositoryPolicy; 059import org.eclipse.aether.resolution.MetadataRequest; 060import org.eclipse.aether.resolution.MetadataResult; 061import org.eclipse.aether.spi.connector.MetadataDownload; 062import org.eclipse.aether.spi.connector.RepositoryConnector; 063import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter; 064import org.eclipse.aether.spi.io.PathProcessor; 065import org.eclipse.aether.spi.synccontext.SyncContextFactory; 066import org.eclipse.aether.transfer.MetadataNotFoundException; 067import org.eclipse.aether.transfer.MetadataTransferException; 068import org.eclipse.aether.transfer.NoRepositoryConnectorException; 069import org.eclipse.aether.transfer.RepositoryOfflineException; 070import org.eclipse.aether.util.ConfigUtils; 071import org.eclipse.aether.util.concurrency.RunnableErrorForwarder; 072import org.eclipse.aether.util.concurrency.SmartExecutor; 073import org.eclipse.aether.util.concurrency.SmartExecutorUtils; 074 075import static java.util.Objects.requireNonNull; 076 077/** 078 */ 079@Singleton 080@Named 081public class DefaultMetadataResolver implements MetadataResolver { 082 private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "metadataResolver."; 083 084 /** 085 * Number of threads to use in parallel for resolving metadata. 086 * 087 * @since 0.9.0.M4 088 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 089 * @configurationType {@link java.lang.Integer} 090 * @configurationDefaultValue {@link #DEFAULT_THREADS} 091 */ 092 public static final String CONFIG_PROP_THREADS = CONFIG_PROPS_PREFIX + "threads"; 093 094 public static final int DEFAULT_THREADS = 4; 095 096 private final RepositoryEventDispatcher repositoryEventDispatcher; 097 098 private final UpdateCheckManager updateCheckManager; 099 100 private final RepositoryConnectorProvider repositoryConnectorProvider; 101 102 private final RemoteRepositoryManager remoteRepositoryManager; 103 104 private final SyncContextFactory syncContextFactory; 105 106 private final OfflineController offlineController; 107 108 private final RemoteRepositoryFilterManager remoteRepositoryFilterManager; 109 110 private final PathProcessor pathProcessor; 111 112 @SuppressWarnings("checkstyle:parameternumber") 113 @Inject 114 public DefaultMetadataResolver( 115 RepositoryEventDispatcher repositoryEventDispatcher, 116 UpdateCheckManager updateCheckManager, 117 RepositoryConnectorProvider repositoryConnectorProvider, 118 RemoteRepositoryManager remoteRepositoryManager, 119 SyncContextFactory syncContextFactory, 120 OfflineController offlineController, 121 RemoteRepositoryFilterManager remoteRepositoryFilterManager, 122 PathProcessor pathProcessor) { 123 this.repositoryEventDispatcher = 124 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null"); 125 this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null"); 126 this.repositoryConnectorProvider = 127 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null"); 128 this.remoteRepositoryManager = 129 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null"); 130 this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null"); 131 this.offlineController = requireNonNull(offlineController, "offline controller cannot be null"); 132 this.remoteRepositoryFilterManager = 133 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null"); 134 this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null"); 135 } 136 137 @Override 138 public List<MetadataResult> resolveMetadata( 139 RepositorySystemSession session, Collection<? extends MetadataRequest> requests) { 140 requireNonNull(session, "session cannot be null"); 141 requireNonNull(requests, "requests cannot be null"); 142 try (SyncContext shared = syncContextFactory.newInstance(session, true); 143 SyncContext exclusive = syncContextFactory.newInstance(session, false)) { 144 Collection<Metadata> metadata = new ArrayList<>(requests.size()); 145 for (MetadataRequest request : requests) { 146 metadata.add(request.getMetadata()); 147 } 148 149 return resolve(shared, exclusive, metadata, session, requests); 150 } 151 } 152 153 @SuppressWarnings("checkstyle:methodlength") 154 private List<MetadataResult> resolve( 155 SyncContext shared, 156 SyncContext exclusive, 157 Collection<Metadata> subjects, 158 RepositorySystemSession session, 159 Collection<? extends MetadataRequest> requests) { 160 SyncContext current = shared; 161 try { 162 while (true) { 163 current.acquire(null, subjects); 164 165 final List<MetadataResult> results = new ArrayList<>(requests.size()); 166 final List<ResolveTask> tasks = new ArrayList<>(requests.size()); 167 final Map<Path, Long> localLastUpdates = new HashMap<>(); 168 final RemoteRepositoryFilter remoteRepositoryFilter = 169 remoteRepositoryFilterManager.getRemoteRepositoryFilter(session); 170 171 for (MetadataRequest request : requests) { 172 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 173 174 MetadataResult result = new MetadataResult(request); 175 results.add(result); 176 177 Metadata metadata = request.getMetadata(); 178 RemoteRepository repository = request.getRepository(); 179 180 if (repository == null) { 181 LocalRepository localRepo = 182 session.getLocalRepositoryManager().getRepository(); 183 184 metadataResolving(session, trace, metadata, localRepo); 185 186 Path localFile = getLocalFile(session, metadata); 187 188 if (localFile != null) { 189 metadata = metadata.setPath(localFile); 190 result.setMetadata(metadata); 191 } else { 192 result.setException(new MetadataNotFoundException(metadata, localRepo)); 193 } 194 195 metadataResolved(session, trace, metadata, localRepo, result.getException()); 196 continue; 197 } 198 199 if (remoteRepositoryFilter != null) { 200 RemoteRepositoryFilter.Result filterResult = 201 remoteRepositoryFilter.acceptMetadata(repository, metadata); 202 if (!filterResult.isAccepted()) { 203 result.setException( 204 new MetadataNotFoundException(metadata, repository, filterResult.reasoning())); 205 continue; 206 } 207 } 208 209 List<RemoteRepository> repositories = 210 getEnabledSourceRepositories(repository, metadata.getNature()); 211 212 if (repositories.isEmpty()) { 213 continue; 214 } 215 216 metadataResolving(session, trace, metadata, repository); 217 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 218 LocalMetadataRequest localRequest = 219 new LocalMetadataRequest(metadata, repository, request.getRequestContext()); 220 LocalMetadataResult lrmResult = lrm.find(session, localRequest); 221 222 Path metadataPath = lrmResult.getPath(); 223 224 try { 225 Utils.checkOffline(session, offlineController, repository); 226 } catch (RepositoryOfflineException e) { 227 if (metadataPath != null) { 228 metadata = metadata.setPath(metadataPath); 229 result.setMetadata(metadata); 230 } else { 231 String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl() 232 + ") in offline mode and the metadata " + metadata 233 + " has not been downloaded from it before"; 234 result.setException(new MetadataNotFoundException(metadata, repository, msg, e)); 235 } 236 237 metadataResolved(session, trace, metadata, repository, result.getException()); 238 continue; 239 } 240 241 Long localLastUpdate = null; 242 if (request.isFavorLocalRepository()) { 243 Path localPath = getLocalFile(session, metadata); 244 localLastUpdate = localLastUpdates.get(localPath); 245 if (localLastUpdate == null) { 246 localLastUpdate = localPath != null ? pathProcessor.lastModified(localPath, 0L) : 0L; 247 localLastUpdates.put(localPath, localLastUpdate); 248 } 249 } 250 251 List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>(); 252 Exception exception = null; 253 for (RemoteRepository repo : repositories) { 254 RepositoryPolicy policy = getPolicy(session, repo, metadata.getNature()); 255 256 UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>(); 257 check.setLocalLastUpdated((localLastUpdate != null) ? localLastUpdate : 0); 258 check.setItem(metadata); 259 260 // use 'main' installation file for the check (-> use requested repository) 261 Path checkPath = session.getLocalRepositoryManager() 262 .getAbsolutePathForRemoteMetadata(metadata, repository, request.getRequestContext()); 263 check.setPath(checkPath); 264 check.setRepository(repository); 265 check.setAuthoritativeRepository(repo); 266 check.setArtifactPolicy(policy.getArtifactUpdatePolicy()); 267 check.setMetadataPolicy(policy.getMetadataUpdatePolicy()); 268 269 if (lrmResult.isStale()) { 270 checks.add(check); 271 } else { 272 updateCheckManager.checkMetadata(session, check); 273 if (check.isRequired()) { 274 checks.add(check); 275 } else if (exception == null) { 276 exception = check.getException(); 277 } 278 } 279 } 280 281 if (!checks.isEmpty()) { 282 RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature()); 283 284 // install path may be different from lookup path 285 Path installPath = session.getLocalRepositoryManager() 286 .getAbsolutePathForRemoteMetadata( 287 metadata, request.getRepository(), request.getRequestContext()); 288 289 ResolveTask task = new ResolveTask( 290 session, trace, result, installPath, checks, policy.getChecksumPolicy()); 291 tasks.add(task); 292 } else { 293 result.setException(exception); 294 if (metadataPath != null) { 295 metadata = metadata.setPath(metadataPath); 296 result.setMetadata(metadata); 297 } 298 metadataResolved(session, trace, metadata, repository, result.getException()); 299 } 300 } 301 302 if (!tasks.isEmpty() && current == shared) { 303 current.close(); 304 current = exclusive; 305 continue; 306 } 307 308 if (!tasks.isEmpty()) { 309 try (SmartExecutor executor = SmartExecutorUtils.smartExecutor( 310 session, 311 tasks.size(), // we DON'T want global executor; call can be recursive (pool depletion) 312 ConfigUtils.getInteger(session, DEFAULT_THREADS, CONFIG_PROP_THREADS), 313 getClass().getSimpleName() + "-")) { 314 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); 315 316 for (ResolveTask task : tasks) { 317 metadataDownloading( 318 task.session, task.trace, task.request.getMetadata(), task.request.getRepository()); 319 320 executor.submit(errorForwarder.wrap(task)); 321 } 322 323 errorForwarder.await(); 324 325 for (ResolveTask task : tasks) { 326 /* 327 * NOTE: Touch after registration with local repo to ensure concurrent resolution is not 328 * rejected with "already updated" via session data when actual update to local repo is 329 * still pending. 330 */ 331 for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) { 332 updateCheckManager.touchMetadata(task.session, check.setException(task.exception)); 333 } 334 335 metadataDownloaded( 336 session, 337 task.trace, 338 task.request.getMetadata(), 339 task.request.getRepository(), 340 task.metadataPath, 341 task.exception); 342 343 task.result.setException(task.exception); 344 } 345 } 346 for (ResolveTask task : tasks) { 347 Metadata metadata = task.request.getMetadata(); 348 // re-lookup metadata for resolve 349 LocalMetadataRequest localRequest = new LocalMetadataRequest( 350 metadata, task.request.getRepository(), task.request.getRequestContext()); 351 Path metadataPath = session.getLocalRepositoryManager() 352 .find(session, localRequest) 353 .getPath(); 354 if (metadataPath != null) { 355 metadata = metadata.setPath(metadataPath); 356 task.result.setMetadata(metadata); 357 } 358 if (task.result.getException() == null) { 359 task.result.setUpdated(true); 360 } 361 metadataResolved( 362 session, 363 task.trace, 364 metadata, 365 task.request.getRepository(), 366 task.result.getException()); 367 } 368 } 369 370 return results; 371 } 372 } finally { 373 current.close(); 374 } 375 } 376 377 private Path getLocalFile(RepositorySystemSession session, Metadata metadata) { 378 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 379 LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null)); 380 return localResult.getPath(); 381 } 382 383 private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) { 384 List<RemoteRepository> repositories = new ArrayList<>(); 385 386 if (repository.isRepositoryManager()) { 387 for (RemoteRepository repo : repository.getMirroredRepositories()) { 388 if (isEnabled(repo, nature)) { 389 repositories.add(repo); 390 } 391 } 392 } else if (isEnabled(repository, nature)) { 393 repositories.add(repository); 394 } 395 396 return repositories; 397 } 398 399 private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) { 400 if (!Metadata.Nature.SNAPSHOT.equals(nature) 401 && repository.getPolicy(false).isEnabled()) { 402 return true; 403 } 404 return !Metadata.Nature.RELEASE.equals(nature) 405 && repository.getPolicy(true).isEnabled(); 406 } 407 408 private RepositoryPolicy getPolicy( 409 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) { 410 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature); 411 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature); 412 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots); 413 } 414 415 private void metadataResolving( 416 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 417 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING); 418 event.setTrace(trace); 419 event.setMetadata(metadata); 420 event.setRepository(repository); 421 422 repositoryEventDispatcher.dispatch(event.build()); 423 } 424 425 private void metadataResolved( 426 RepositorySystemSession session, 427 RequestTrace trace, 428 Metadata metadata, 429 ArtifactRepository repository, 430 Exception exception) { 431 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED); 432 event.setTrace(trace); 433 event.setMetadata(metadata); 434 event.setRepository(repository); 435 event.setException(exception); 436 event.setPath(metadata.getPath()); 437 438 repositoryEventDispatcher.dispatch(event.build()); 439 } 440 441 private void metadataDownloading( 442 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) { 443 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING); 444 event.setTrace(trace); 445 event.setMetadata(metadata); 446 event.setRepository(repository); 447 448 repositoryEventDispatcher.dispatch(event.build()); 449 } 450 451 private void metadataDownloaded( 452 RepositorySystemSession session, 453 RequestTrace trace, 454 Metadata metadata, 455 ArtifactRepository repository, 456 Path path, 457 Exception exception) { 458 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED); 459 event.setTrace(trace); 460 event.setMetadata(metadata); 461 event.setRepository(repository); 462 event.setException(exception); 463 event.setPath(path); 464 465 repositoryEventDispatcher.dispatch(event.build()); 466 } 467 468 class ResolveTask implements Runnable { 469 final RepositorySystemSession session; 470 471 final RequestTrace trace; 472 473 final MetadataResult result; 474 475 final MetadataRequest request; 476 477 final Path metadataPath; 478 479 final String policy; 480 481 final List<UpdateCheck<Metadata, MetadataTransferException>> checks; 482 483 volatile MetadataTransferException exception; 484 485 ResolveTask( 486 RepositorySystemSession session, 487 RequestTrace trace, 488 MetadataResult result, 489 Path metadataPath, 490 List<UpdateCheck<Metadata, MetadataTransferException>> checks, 491 String policy) { 492 this.session = session; 493 this.trace = trace; 494 this.result = result; 495 this.request = result.getRequest(); 496 this.metadataPath = metadataPath; 497 this.policy = policy; 498 this.checks = checks; 499 } 500 501 public void run() { 502 Metadata metadata = request.getMetadata(); 503 RemoteRepository requestRepository = request.getRepository(); 504 505 try { 506 List<RemoteRepository> repositories = new ArrayList<>(); 507 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) { 508 repositories.add(check.getAuthoritativeRepository()); 509 } 510 511 MetadataDownload download = new MetadataDownload(); 512 download.setMetadata(metadata); 513 download.setRequestContext(request.getRequestContext()); 514 download.setPath(metadataPath); 515 download.setChecksumPolicy(policy); 516 download.setRepositories(repositories); 517 download.setListener(SafeTransferListener.wrap(session)); 518 download.setTrace(trace); 519 520 try (RepositoryConnector connector = 521 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) { 522 connector.get(null, Collections.singletonList(download)); 523 } 524 525 exception = download.getException(); 526 527 if (exception == null) { 528 529 List<String> contexts = Collections.singletonList(request.getRequestContext()); 530 LocalMetadataRegistration registration = 531 new LocalMetadataRegistration(metadata, requestRepository, contexts); 532 533 session.getLocalRepositoryManager().add(session, registration); 534 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) { 535 try { 536 Files.deleteIfExists(download.getPath()); 537 } catch (IOException e) { 538 throw new UncheckedIOException(e); 539 } 540 } 541 } catch (NoRepositoryConnectorException e) { 542 exception = new MetadataTransferException(metadata, requestRepository, e); 543 } 544 } 545 } 546}