1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.IOException;
26 import java.io.UncheckedIOException;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35
36 import org.eclipse.aether.ConfigurationProperties;
37 import org.eclipse.aether.RepositoryEvent;
38 import org.eclipse.aether.RepositoryEvent.EventType;
39 import org.eclipse.aether.RepositorySystemSession;
40 import org.eclipse.aether.RequestTrace;
41 import org.eclipse.aether.SyncContext;
42 import org.eclipse.aether.impl.MetadataResolver;
43 import org.eclipse.aether.impl.OfflineController;
44 import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
45 import org.eclipse.aether.impl.RemoteRepositoryManager;
46 import org.eclipse.aether.impl.RepositoryConnectorProvider;
47 import org.eclipse.aether.impl.RepositoryEventDispatcher;
48 import org.eclipse.aether.impl.UpdateCheck;
49 import org.eclipse.aether.impl.UpdateCheckManager;
50 import org.eclipse.aether.metadata.Metadata;
51 import org.eclipse.aether.repository.ArtifactRepository;
52 import org.eclipse.aether.repository.LocalMetadataRegistration;
53 import org.eclipse.aether.repository.LocalMetadataRequest;
54 import org.eclipse.aether.repository.LocalMetadataResult;
55 import org.eclipse.aether.repository.LocalRepository;
56 import org.eclipse.aether.repository.LocalRepositoryManager;
57 import org.eclipse.aether.repository.RemoteRepository;
58 import org.eclipse.aether.repository.RepositoryPolicy;
59 import org.eclipse.aether.resolution.MetadataRequest;
60 import org.eclipse.aether.resolution.MetadataResult;
61 import org.eclipse.aether.spi.connector.MetadataDownload;
62 import org.eclipse.aether.spi.connector.RepositoryConnector;
63 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
64 import org.eclipse.aether.spi.io.PathProcessor;
65 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
66 import org.eclipse.aether.transfer.MetadataNotFoundException;
67 import org.eclipse.aether.transfer.MetadataTransferException;
68 import org.eclipse.aether.transfer.NoRepositoryConnectorException;
69 import org.eclipse.aether.transfer.RepositoryOfflineException;
70 import org.eclipse.aether.util.ConfigUtils;
71 import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
72 import org.eclipse.aether.util.concurrency.SmartExecutor;
73 import org.eclipse.aether.util.concurrency.SmartExecutorUtils;
74
75 import static java.util.Objects.requireNonNull;
76
77
78
79 @Singleton
80 @Named
81 public class DefaultMetadataResolver implements MetadataResolver {
82 private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "metadataResolver.";
83
84
85
86
87
88
89
90
91
92 public static final String CONFIG_PROP_THREADS = CONFIG_PROPS_PREFIX + "threads";
93
94 public static final int DEFAULT_THREADS = 4;
95
96 private final RepositoryEventDispatcher repositoryEventDispatcher;
97
98 private final UpdateCheckManager updateCheckManager;
99
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 Collection<Metadata> metadata = new ArrayList<>(requests.size());
143 for (MetadataRequest request : requests) {
144 metadata.add(request.getMetadata());
145 }
146
147 try (SyncContext shared = new CloseOnceSyncContext(syncContextFactory.newInstance(session, true));
148 SyncContext exclusive = new CloseOnceSyncContext(syncContextFactory.newInstance(session, false))) {
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
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
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 SyncContext sharedContext = current;
304 current = exclusive;
305 sharedContext.close();
306 continue;
307 }
308
309 if (!tasks.isEmpty()) {
310 try (SmartExecutor executor = SmartExecutorUtils.smartExecutor(
311 session,
312 tasks.size(),
313 ConfigUtils.getInteger(session, DEFAULT_THREADS, CONFIG_PROP_THREADS),
314 getClass().getSimpleName() + "-")) {
315 RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
316
317 for (ResolveTask task : tasks) {
318 metadataDownloading(
319 task.session, task.trace, task.request.getMetadata(), task.request.getRepository());
320
321 executor.submit(errorForwarder.wrap(task));
322 }
323
324 errorForwarder.await();
325
326 for (ResolveTask task : tasks) {
327
328
329
330
331
332 for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) {
333 updateCheckManager.touchMetadata(task.session, check.setException(task.exception));
334 }
335
336 metadataDownloaded(
337 session,
338 task.trace,
339 task.request.getMetadata(),
340 task.request.getRepository(),
341 task.metadataPath,
342 task.exception);
343
344 task.result.setException(task.exception);
345 }
346 }
347 for (ResolveTask task : tasks) {
348 Metadata metadata = task.request.getMetadata();
349
350 LocalMetadataRequest localRequest = new LocalMetadataRequest(
351 metadata, task.request.getRepository(), task.request.getRequestContext());
352 Path metadataPath = session.getLocalRepositoryManager()
353 .find(session, localRequest)
354 .getPath();
355 if (metadataPath != null) {
356 metadata = metadata.setPath(metadataPath);
357 task.result.setMetadata(metadata);
358 }
359 if (task.result.getException() == null) {
360 task.result.setUpdated(true);
361 }
362 metadataResolved(
363 session,
364 task.trace,
365 metadata,
366 task.request.getRepository(),
367 task.result.getException());
368 }
369 }
370
371 return results;
372 }
373 } finally {
374 try {
375 current.close();
376 } finally {
377 if (current == shared) {
378 exclusive.close();
379 }
380 }
381 }
382 }
383
384 private Path getLocalFile(RepositorySystemSession session, Metadata metadata) {
385 LocalRepositoryManager lrm = session.getLocalRepositoryManager();
386 LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null));
387 return localResult.getPath();
388 }
389
390 private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) {
391 List<RemoteRepository> repositories = new ArrayList<>();
392
393 if (repository.isRepositoryManager()) {
394 for (RemoteRepository repo : repository.getMirroredRepositories()) {
395 if (isEnabled(repo, nature)) {
396 repositories.add(repo);
397 }
398 }
399 } else if (isEnabled(repository, nature)) {
400 repositories.add(repository);
401 }
402
403 return repositories;
404 }
405
406 private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) {
407 if (!Metadata.Nature.SNAPSHOT.equals(nature)
408 && repository.getPolicy(false).isEnabled()) {
409 return true;
410 }
411 return !Metadata.Nature.RELEASE.equals(nature)
412 && repository.getPolicy(true).isEnabled();
413 }
414
415 private RepositoryPolicy getPolicy(
416 RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) {
417 boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature);
418 boolean snapshots = !Metadata.Nature.RELEASE.equals(nature);
419 return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots);
420 }
421
422 private void metadataResolving(
423 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
424 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING);
425 event.setTrace(trace);
426 event.setMetadata(metadata);
427 event.setRepository(repository);
428
429 repositoryEventDispatcher.dispatch(event.build());
430 }
431
432 private void metadataResolved(
433 RepositorySystemSession session,
434 RequestTrace trace,
435 Metadata metadata,
436 ArtifactRepository repository,
437 Exception exception) {
438 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED);
439 event.setTrace(trace);
440 event.setMetadata(metadata);
441 event.setRepository(repository);
442 event.setException(exception);
443 event.setPath(metadata.getPath());
444
445 repositoryEventDispatcher.dispatch(event.build());
446 }
447
448 private void metadataDownloading(
449 RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
450 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING);
451 event.setTrace(trace);
452 event.setMetadata(metadata);
453 event.setRepository(repository);
454
455 repositoryEventDispatcher.dispatch(event.build());
456 }
457
458 private void metadataDownloaded(
459 RepositorySystemSession session,
460 RequestTrace trace,
461 Metadata metadata,
462 ArtifactRepository repository,
463 Path path,
464 Exception exception) {
465 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED);
466 event.setTrace(trace);
467 event.setMetadata(metadata);
468 event.setRepository(repository);
469 event.setException(exception);
470 event.setPath(path);
471
472 repositoryEventDispatcher.dispatch(event.build());
473 }
474
475 class ResolveTask implements Runnable {
476 final RepositorySystemSession session;
477
478 final RequestTrace trace;
479
480 final MetadataResult result;
481
482 final MetadataRequest request;
483
484 final Path metadataPath;
485
486 final String policy;
487
488 final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
489
490 volatile MetadataTransferException exception;
491
492 ResolveTask(
493 RepositorySystemSession session,
494 RequestTrace trace,
495 MetadataResult result,
496 Path metadataPath,
497 List<UpdateCheck<Metadata, MetadataTransferException>> checks,
498 String policy) {
499 this.session = session;
500 this.trace = trace;
501 this.result = result;
502 this.request = result.getRequest();
503 this.metadataPath = metadataPath;
504 this.policy = policy;
505 this.checks = checks;
506 }
507
508 public void run() {
509 Metadata metadata = request.getMetadata();
510 RemoteRepository requestRepository = request.getRepository();
511
512 try {
513 List<RemoteRepository> repositories = new ArrayList<>();
514 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) {
515 repositories.add(check.getAuthoritativeRepository());
516 }
517
518 MetadataDownload download = new MetadataDownload();
519 download.setMetadata(metadata);
520 download.setRequestContext(request.getRequestContext());
521 download.setPath(metadataPath);
522 download.setChecksumPolicy(policy);
523 download.setRepositories(repositories);
524 download.setListener(SafeTransferListener.wrap(session));
525 download.setTrace(trace);
526
527 try (RepositoryConnector connector =
528 repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) {
529 connector.get(null, Collections.singletonList(download));
530 }
531
532 exception = download.getException();
533
534 if (exception == null) {
535
536 List<String> contexts = Collections.singletonList(request.getRequestContext());
537 LocalMetadataRegistration registration =
538 new LocalMetadataRegistration(metadata, requestRepository, contexts);
539
540 session.getLocalRepositoryManager().add(session, registration);
541 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) {
542 try {
543 Files.deleteIfExists(download.getPath());
544 } catch (IOException e) {
545 throw new UncheckedIOException(e);
546 }
547 }
548 } catch (NoRepositoryConnectorException e) {
549 exception = new MetadataTransferException(metadata, requestRepository, e);
550 }
551 }
552 }
553 }