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