View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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.File;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.concurrent.Executor;
33  
34  import org.eclipse.aether.RepositoryEvent;
35  import org.eclipse.aether.RepositoryEvent.EventType;
36  import org.eclipse.aether.RepositorySystemSession;
37  import org.eclipse.aether.RequestTrace;
38  import org.eclipse.aether.SyncContext;
39  import org.eclipse.aether.impl.MetadataResolver;
40  import org.eclipse.aether.impl.OfflineController;
41  import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
42  import org.eclipse.aether.impl.RemoteRepositoryManager;
43  import org.eclipse.aether.impl.RepositoryConnectorProvider;
44  import org.eclipse.aether.impl.RepositoryEventDispatcher;
45  import org.eclipse.aether.impl.UpdateCheck;
46  import org.eclipse.aether.impl.UpdateCheckManager;
47  import org.eclipse.aether.metadata.Metadata;
48  import org.eclipse.aether.repository.ArtifactRepository;
49  import org.eclipse.aether.repository.LocalMetadataRegistration;
50  import org.eclipse.aether.repository.LocalMetadataRequest;
51  import org.eclipse.aether.repository.LocalMetadataResult;
52  import org.eclipse.aether.repository.LocalRepository;
53  import org.eclipse.aether.repository.LocalRepositoryManager;
54  import org.eclipse.aether.repository.RemoteRepository;
55  import org.eclipse.aether.repository.RepositoryPolicy;
56  import org.eclipse.aether.resolution.MetadataRequest;
57  import org.eclipse.aether.resolution.MetadataResult;
58  import org.eclipse.aether.spi.connector.MetadataDownload;
59  import org.eclipse.aether.spi.connector.RepositoryConnector;
60  import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
61  import org.eclipse.aether.spi.locator.Service;
62  import org.eclipse.aether.spi.locator.ServiceLocator;
63  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
64  import org.eclipse.aether.transfer.MetadataNotFoundException;
65  import org.eclipse.aether.transfer.MetadataTransferException;
66  import org.eclipse.aether.transfer.NoRepositoryConnectorException;
67  import org.eclipse.aether.transfer.RepositoryOfflineException;
68  import org.eclipse.aether.util.concurrency.ExecutorUtils;
69  import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
70  
71  import static java.util.Objects.requireNonNull;
72  
73  /**
74   */
75  @Singleton
76  @Named
77  public class DefaultMetadataResolver implements MetadataResolver, Service {
78  
79      private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
80  
81      private RepositoryEventDispatcher repositoryEventDispatcher;
82  
83      private UpdateCheckManager updateCheckManager;
84  
85      private RepositoryConnectorProvider repositoryConnectorProvider;
86  
87      private RemoteRepositoryManager remoteRepositoryManager;
88  
89      private SyncContextFactory syncContextFactory;
90  
91      private OfflineController offlineController;
92  
93      private RemoteRepositoryFilterManager remoteRepositoryFilterManager;
94  
95      public DefaultMetadataResolver() {
96          // enables default constructor
97      }
98  
99      @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 }