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 shared = syncContextFactory.newInstance(session, true);
173                 SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
174             Collection<Metadata> metadata = new ArrayList<>(requests.size());
175             for (MetadataRequest request : requests) {
176                 metadata.add(request.getMetadata());
177             }
178 
179             return resolve(shared, exclusive, metadata, session, requests);
180         }
181     }
182 
183     @SuppressWarnings("checkstyle:methodlength")
184     private List<MetadataResult> resolve(
185             SyncContext shared,
186             SyncContext exclusive,
187             Collection<Metadata> subjects,
188             RepositorySystemSession session,
189             Collection<? extends MetadataRequest> requests) {
190         SyncContext current = shared;
191         try {
192             while (true) {
193                 current.acquire(null, subjects);
194 
195                 final List<MetadataResult> results = new ArrayList<>(requests.size());
196                 final List<ResolveTask> tasks = new ArrayList<>(requests.size());
197                 final Map<File, Long> localLastUpdates = new HashMap<>();
198                 final RemoteRepositoryFilter remoteRepositoryFilter =
199                         remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
200 
201                 for (MetadataRequest request : requests) {
202                     RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
203 
204                     MetadataResult result = new MetadataResult(request);
205                     results.add(result);
206 
207                     Metadata metadata = request.getMetadata();
208                     RemoteRepository repository = request.getRepository();
209 
210                     if (repository == null) {
211                         LocalRepository localRepo =
212                                 session.getLocalRepositoryManager().getRepository();
213 
214                         metadataResolving(session, trace, metadata, localRepo);
215 
216                         File localFile = getLocalFile(session, metadata);
217 
218                         if (localFile != null) {
219                             metadata = metadata.setFile(localFile);
220                             result.setMetadata(metadata);
221                         } else {
222                             result.setException(new MetadataNotFoundException(metadata, localRepo));
223                         }
224 
225                         metadataResolved(session, trace, metadata, localRepo, result.getException());
226                         continue;
227                     }
228 
229                     if (remoteRepositoryFilter != null) {
230                         RemoteRepositoryFilter.Result filterResult =
231                                 remoteRepositoryFilter.acceptMetadata(repository, metadata);
232                         if (!filterResult.isAccepted()) {
233                             result.setException(
234                                     new MetadataNotFoundException(metadata, repository, filterResult.reasoning()));
235                             continue;
236                         }
237                     }
238 
239                     List<RemoteRepository> repositories =
240                             getEnabledSourceRepositories(repository, metadata.getNature());
241 
242                     if (repositories.isEmpty()) {
243                         continue;
244                     }
245 
246                     metadataResolving(session, trace, metadata, repository);
247                     LocalRepositoryManager lrm = session.getLocalRepositoryManager();
248                     LocalMetadataRequest localRequest =
249                             new LocalMetadataRequest(metadata, repository, request.getRequestContext());
250                     LocalMetadataResult lrmResult = lrm.find(session, localRequest);
251 
252                     File metadataFile = lrmResult.getFile();
253 
254                     try {
255                         Utils.checkOffline(session, offlineController, repository);
256                     } catch (RepositoryOfflineException e) {
257                         if (metadataFile != null) {
258                             metadata = metadata.setFile(metadataFile);
259                             result.setMetadata(metadata);
260                         } else {
261                             String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl()
262                                     + ") in offline mode and the metadata " + metadata
263                                     + " has not been downloaded from it before";
264                             result.setException(new MetadataNotFoundException(metadata, repository, msg, e));
265                         }
266 
267                         metadataResolved(session, trace, metadata, repository, result.getException());
268                         continue;
269                     }
270 
271                     Long localLastUpdate = null;
272                     if (request.isFavorLocalRepository()) {
273                         File localFile = getLocalFile(session, metadata);
274                         localLastUpdate = localLastUpdates.get(localFile);
275                         if (localLastUpdate == null) {
276                             localLastUpdate = localFile != null ? localFile.lastModified() : 0;
277                             localLastUpdates.put(localFile, localLastUpdate);
278                         }
279                     }
280 
281                     List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
282                     Exception exception = null;
283                     for (RemoteRepository repo : repositories) {
284                         UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
285                         check.setLocalLastUpdated((localLastUpdate != null) ? localLastUpdate : 0);
286                         check.setItem(metadata);
287 
288                         // use 'main' installation file for the check (-> use requested repository)
289                         File checkFile = new File(
290                                 session.getLocalRepository().getBasedir(),
291                                 session.getLocalRepositoryManager()
292                                         .getPathForRemoteMetadata(metadata, repository, request.getRequestContext()));
293                         check.setFile(checkFile);
294                         check.setRepository(repository);
295                         check.setAuthoritativeRepository(repo);
296                         check.setPolicy(
297                                 getPolicy(session, repo, metadata.getNature()).getUpdatePolicy());
298 
299                         if (lrmResult.isStale()) {
300                             checks.add(check);
301                         } else {
302                             updateCheckManager.checkMetadata(session, check);
303                             if (check.isRequired()) {
304                                 checks.add(check);
305                             } else if (exception == null) {
306                                 exception = check.getException();
307                             }
308                         }
309                     }
310 
311                     if (!checks.isEmpty()) {
312                         RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature());
313 
314                         // install path may be different from lookup path
315                         File installFile = new File(
316                                 session.getLocalRepository().getBasedir(),
317                                 session.getLocalRepositoryManager()
318                                         .getPathForRemoteMetadata(
319                                                 metadata, request.getRepository(), request.getRequestContext()));
320 
321                         ResolveTask task = new ResolveTask(
322                                 session, trace, result, installFile, checks, policy.getChecksumPolicy());
323                         tasks.add(task);
324                     } else {
325                         result.setException(exception);
326                         if (metadataFile != null) {
327                             metadata = metadata.setFile(metadataFile);
328                             result.setMetadata(metadata);
329                         }
330                         metadataResolved(session, trace, metadata, repository, result.getException());
331                     }
332                 }
333 
334                 if (!tasks.isEmpty() && current == shared) {
335                     current.close();
336                     current = exclusive;
337                     continue;
338                 }
339 
340                 if (!tasks.isEmpty()) {
341                     int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS);
342                     Executor executor = ExecutorUtils.executor(
343                             Math.min(tasks.size(), threads), getClass().getSimpleName() + '-');
344                     try {
345                         RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
346 
347                         for (ResolveTask task : tasks) {
348                             metadataDownloading(
349                                     task.session, task.trace, task.request.getMetadata(), task.request.getRepository());
350 
351                             executor.execute(errorForwarder.wrap(task));
352                         }
353 
354                         errorForwarder.await();
355 
356                         for (ResolveTask task : tasks) {
357                             /*
358                              * NOTE: Touch after registration with local repo to ensure concurrent resolution is not
359                              * rejected with "already updated" via session data when actual update to local repo is
360                              * still pending.
361                              */
362                             for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) {
363                                 updateCheckManager.touchMetadata(task.session, check.setException(task.exception));
364                             }
365 
366                             metadataDownloaded(
367                                     session,
368                                     task.trace,
369                                     task.request.getMetadata(),
370                                     task.request.getRepository(),
371                                     task.metadataFile,
372                                     task.exception);
373 
374                             task.result.setException(task.exception);
375                         }
376                     } finally {
377                         ExecutorUtils.shutdown(executor);
378                     }
379                     for (ResolveTask task : tasks) {
380                         Metadata metadata = task.request.getMetadata();
381                         // re-lookup metadata for resolve
382                         LocalMetadataRequest localRequest = new LocalMetadataRequest(
383                                 metadata, task.request.getRepository(), task.request.getRequestContext());
384                         File metadataFile = session.getLocalRepositoryManager()
385                                 .find(session, localRequest)
386                                 .getFile();
387                         if (metadataFile != null) {
388                             metadata = metadata.setFile(metadataFile);
389                             task.result.setMetadata(metadata);
390                         }
391                         if (task.result.getException() == null) {
392                             task.result.setUpdated(true);
393                         }
394                         metadataResolved(
395                                 session,
396                                 task.trace,
397                                 metadata,
398                                 task.request.getRepository(),
399                                 task.result.getException());
400                     }
401                 }
402 
403                 return results;
404             }
405         } finally {
406             current.close();
407         }
408     }
409 
410     private File getLocalFile(RepositorySystemSession session, Metadata metadata) {
411         LocalRepositoryManager lrm = session.getLocalRepositoryManager();
412         LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null));
413         return localResult.getFile();
414     }
415 
416     private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) {
417         List<RemoteRepository> repositories = new ArrayList<>();
418 
419         if (repository.isRepositoryManager()) {
420             for (RemoteRepository repo : repository.getMirroredRepositories()) {
421                 if (isEnabled(repo, nature)) {
422                     repositories.add(repo);
423                 }
424             }
425         } else if (isEnabled(repository, nature)) {
426             repositories.add(repository);
427         }
428 
429         return repositories;
430     }
431 
432     private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) {
433         if (!Metadata.Nature.SNAPSHOT.equals(nature)
434                 && repository.getPolicy(false).isEnabled()) {
435             return true;
436         }
437         if (!Metadata.Nature.RELEASE.equals(nature)
438                 && repository.getPolicy(true).isEnabled()) {
439             return true;
440         }
441         return false;
442     }
443 
444     private RepositoryPolicy getPolicy(
445             RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) {
446         boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature);
447         boolean snapshots = !Metadata.Nature.RELEASE.equals(nature);
448         return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots);
449     }
450 
451     private void metadataResolving(
452             RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
453         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING);
454         event.setTrace(trace);
455         event.setMetadata(metadata);
456         event.setRepository(repository);
457 
458         repositoryEventDispatcher.dispatch(event.build());
459     }
460 
461     private void metadataResolved(
462             RepositorySystemSession session,
463             RequestTrace trace,
464             Metadata metadata,
465             ArtifactRepository repository,
466             Exception exception) {
467         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED);
468         event.setTrace(trace);
469         event.setMetadata(metadata);
470         event.setRepository(repository);
471         event.setException(exception);
472         event.setFile(metadata.getFile());
473 
474         repositoryEventDispatcher.dispatch(event.build());
475     }
476 
477     private void metadataDownloading(
478             RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
479         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING);
480         event.setTrace(trace);
481         event.setMetadata(metadata);
482         event.setRepository(repository);
483 
484         repositoryEventDispatcher.dispatch(event.build());
485     }
486 
487     private void metadataDownloaded(
488             RepositorySystemSession session,
489             RequestTrace trace,
490             Metadata metadata,
491             ArtifactRepository repository,
492             File file,
493             Exception exception) {
494         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED);
495         event.setTrace(trace);
496         event.setMetadata(metadata);
497         event.setRepository(repository);
498         event.setException(exception);
499         event.setFile(file);
500 
501         repositoryEventDispatcher.dispatch(event.build());
502     }
503 
504     class ResolveTask implements Runnable {
505         final RepositorySystemSession session;
506 
507         final RequestTrace trace;
508 
509         final MetadataResult result;
510 
511         final MetadataRequest request;
512 
513         final File metadataFile;
514 
515         final String policy;
516 
517         final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
518 
519         volatile MetadataTransferException exception;
520 
521         ResolveTask(
522                 RepositorySystemSession session,
523                 RequestTrace trace,
524                 MetadataResult result,
525                 File metadataFile,
526                 List<UpdateCheck<Metadata, MetadataTransferException>> checks,
527                 String policy) {
528             this.session = session;
529             this.trace = trace;
530             this.result = result;
531             this.request = result.getRequest();
532             this.metadataFile = metadataFile;
533             this.policy = policy;
534             this.checks = checks;
535         }
536 
537         public void run() {
538             Metadata metadata = request.getMetadata();
539             RemoteRepository requestRepository = request.getRepository();
540 
541             try {
542                 List<RemoteRepository> repositories = new ArrayList<>();
543                 for (UpdateCheck<Metadata, MetadataTransferException> check : checks) {
544                     repositories.add(check.getAuthoritativeRepository());
545                 }
546 
547                 MetadataDownload download = new MetadataDownload();
548                 download.setMetadata(metadata);
549                 download.setRequestContext(request.getRequestContext());
550                 download.setFile(metadataFile);
551                 download.setChecksumPolicy(policy);
552                 download.setRepositories(repositories);
553                 download.setListener(SafeTransferListener.wrap(session));
554                 download.setTrace(trace);
555 
556                 try (RepositoryConnector connector =
557                         repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) {
558                     connector.get(null, Collections.singletonList(download));
559                 }
560 
561                 exception = download.getException();
562 
563                 if (exception == null) {
564 
565                     List<String> contexts = Collections.singletonList(request.getRequestContext());
566                     LocalMetadataRegistration registration =
567                             new LocalMetadataRegistration(metadata, requestRepository, contexts);
568 
569                     session.getLocalRepositoryManager().add(session, registration);
570                 } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) {
571                     download.getFile().delete();
572                 }
573             } catch (NoRepositoryConnectorException e) {
574                 exception = new MetadataTransferException(metadata, requestRepository, e);
575             }
576         }
577     }
578 }