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