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