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 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 }