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