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  import java.util.concurrent.Executor;
36  
37  import org.eclipse.aether.ConfigurationProperties;
38  import org.eclipse.aether.RepositoryEvent;
39  import org.eclipse.aether.RepositoryEvent.EventType;
40  import org.eclipse.aether.RepositorySystemSession;
41  import org.eclipse.aether.RequestTrace;
42  import org.eclipse.aether.SyncContext;
43  import org.eclipse.aether.impl.MetadataResolver;
44  import org.eclipse.aether.impl.OfflineController;
45  import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
46  import org.eclipse.aether.impl.RemoteRepositoryManager;
47  import org.eclipse.aether.impl.RepositoryConnectorProvider;
48  import org.eclipse.aether.impl.RepositoryEventDispatcher;
49  import org.eclipse.aether.impl.UpdateCheck;
50  import org.eclipse.aether.impl.UpdateCheckManager;
51  import org.eclipse.aether.metadata.Metadata;
52  import org.eclipse.aether.repository.ArtifactRepository;
53  import org.eclipse.aether.repository.LocalMetadataRegistration;
54  import org.eclipse.aether.repository.LocalMetadataRequest;
55  import org.eclipse.aether.repository.LocalMetadataResult;
56  import org.eclipse.aether.repository.LocalRepository;
57  import org.eclipse.aether.repository.LocalRepositoryManager;
58  import org.eclipse.aether.repository.RemoteRepository;
59  import org.eclipse.aether.repository.RepositoryPolicy;
60  import org.eclipse.aether.resolution.MetadataRequest;
61  import org.eclipse.aether.resolution.MetadataResult;
62  import org.eclipse.aether.spi.connector.MetadataDownload;
63  import org.eclipse.aether.spi.connector.RepositoryConnector;
64  import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
65  import org.eclipse.aether.spi.io.PathProcessor;
66  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
67  import org.eclipse.aether.transfer.MetadataNotFoundException;
68  import org.eclipse.aether.transfer.MetadataTransferException;
69  import org.eclipse.aether.transfer.NoRepositoryConnectorException;
70  import org.eclipse.aether.transfer.RepositoryOfflineException;
71  import org.eclipse.aether.util.concurrency.ExecutorUtils;
72  import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
73  
74  import static java.util.Objects.requireNonNull;
75  
76  /**
77   */
78  @Singleton
79  @Named
80  public class DefaultMetadataResolver implements MetadataResolver {
81      private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "metadataResolver.";
82  
83      /**
84       * Number of threads to use in parallel for resolving metadata.
85       *
86       * @since 0.9.0.M4
87       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
88       * @configurationType {@link java.lang.Integer}
89       * @configurationDefaultValue {@link #DEFAULT_THREADS}
90       */
91      public static final String CONFIG_PROP_THREADS = CONFIG_PROPS_PREFIX + "threads";
92  
93      public static final int DEFAULT_THREADS = 4;
94  
95      private final RepositoryEventDispatcher repositoryEventDispatcher;
96  
97      private final UpdateCheckManager updateCheckManager;
98  
99      private final RepositoryConnectorProvider repositoryConnectorProvider;
100 
101     private final RemoteRepositoryManager remoteRepositoryManager;
102 
103     private final SyncContextFactory syncContextFactory;
104 
105     private final OfflineController offlineController;
106 
107     private final RemoteRepositoryFilterManager remoteRepositoryFilterManager;
108 
109     private final PathProcessor pathProcessor;
110 
111     @SuppressWarnings("checkstyle:parameternumber")
112     @Inject
113     public DefaultMetadataResolver(
114             RepositoryEventDispatcher repositoryEventDispatcher,
115             UpdateCheckManager updateCheckManager,
116             RepositoryConnectorProvider repositoryConnectorProvider,
117             RemoteRepositoryManager remoteRepositoryManager,
118             SyncContextFactory syncContextFactory,
119             OfflineController offlineController,
120             RemoteRepositoryFilterManager remoteRepositoryFilterManager,
121             PathProcessor pathProcessor) {
122         this.repositoryEventDispatcher =
123                 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
124         this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
125         this.repositoryConnectorProvider =
126                 requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
127         this.remoteRepositoryManager =
128                 requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
129         this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
130         this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
131         this.remoteRepositoryFilterManager =
132                 requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
133         this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null");
134     }
135 
136     @Override
137     public List<MetadataResult> resolveMetadata(
138             RepositorySystemSession session, Collection<? extends MetadataRequest> requests) {
139         requireNonNull(session, "session cannot be null");
140         requireNonNull(requests, "requests cannot be null");
141         try (SyncContext shared = syncContextFactory.newInstance(session, true);
142                 SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
143             Collection<Metadata> metadata = new ArrayList<>(requests.size());
144             for (MetadataRequest request : requests) {
145                 metadata.add(request.getMetadata());
146             }
147 
148             return resolve(shared, exclusive, metadata, session, requests);
149         }
150     }
151 
152     @SuppressWarnings("checkstyle:methodlength")
153     private List<MetadataResult> resolve(
154             SyncContext shared,
155             SyncContext exclusive,
156             Collection<Metadata> subjects,
157             RepositorySystemSession session,
158             Collection<? extends MetadataRequest> requests) {
159         SyncContext current = shared;
160         try {
161             while (true) {
162                 current.acquire(null, subjects);
163 
164                 final List<MetadataResult> results = new ArrayList<>(requests.size());
165                 final List<ResolveTask> tasks = new ArrayList<>(requests.size());
166                 final Map<Path, Long> localLastUpdates = new HashMap<>();
167                 final RemoteRepositoryFilter remoteRepositoryFilter =
168                         remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
169 
170                 for (MetadataRequest request : requests) {
171                     RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
172 
173                     MetadataResult result = new MetadataResult(request);
174                     results.add(result);
175 
176                     Metadata metadata = request.getMetadata();
177                     RemoteRepository repository = request.getRepository();
178 
179                     if (repository == null) {
180                         LocalRepository localRepo =
181                                 session.getLocalRepositoryManager().getRepository();
182 
183                         metadataResolving(session, trace, metadata, localRepo);
184 
185                         Path localFile = getLocalFile(session, metadata);
186 
187                         if (localFile != null) {
188                             metadata = metadata.setPath(localFile);
189                             result.setMetadata(metadata);
190                         } else {
191                             result.setException(new MetadataNotFoundException(metadata, localRepo));
192                         }
193 
194                         metadataResolved(session, trace, metadata, localRepo, result.getException());
195                         continue;
196                     }
197 
198                     if (remoteRepositoryFilter != null) {
199                         RemoteRepositoryFilter.Result filterResult =
200                                 remoteRepositoryFilter.acceptMetadata(repository, metadata);
201                         if (!filterResult.isAccepted()) {
202                             result.setException(
203                                     new MetadataNotFoundException(metadata, repository, filterResult.reasoning()));
204                             continue;
205                         }
206                     }
207 
208                     List<RemoteRepository> repositories =
209                             getEnabledSourceRepositories(repository, metadata.getNature());
210 
211                     if (repositories.isEmpty()) {
212                         continue;
213                     }
214 
215                     metadataResolving(session, trace, metadata, repository);
216                     LocalRepositoryManager lrm = session.getLocalRepositoryManager();
217                     LocalMetadataRequest localRequest =
218                             new LocalMetadataRequest(metadata, repository, request.getRequestContext());
219                     LocalMetadataResult lrmResult = lrm.find(session, localRequest);
220 
221                     Path metadataPath = lrmResult.getPath();
222 
223                     try {
224                         Utils.checkOffline(session, offlineController, repository);
225                     } catch (RepositoryOfflineException e) {
226                         if (metadataPath != null) {
227                             metadata = metadata.setPath(metadataPath);
228                             result.setMetadata(metadata);
229                         } else {
230                             String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl()
231                                     + ") in offline mode and the metadata " + metadata
232                                     + " has not been downloaded from it before";
233                             result.setException(new MetadataNotFoundException(metadata, repository, msg, e));
234                         }
235 
236                         metadataResolved(session, trace, metadata, repository, result.getException());
237                         continue;
238                     }
239 
240                     Long localLastUpdate = null;
241                     if (request.isFavorLocalRepository()) {
242                         Path localPath = getLocalFile(session, metadata);
243                         localLastUpdate = localLastUpdates.get(localPath);
244                         if (localLastUpdate == null) {
245                             localLastUpdate = localPath != null ? pathProcessor.lastModified(localPath, 0L) : 0L;
246                             localLastUpdates.put(localPath, localLastUpdate);
247                         }
248                     }
249 
250                     List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
251                     Exception exception = null;
252                     for (RemoteRepository repo : repositories) {
253                         RepositoryPolicy policy = getPolicy(session, repo, metadata.getNature());
254 
255                         UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
256                         check.setLocalLastUpdated((localLastUpdate != null) ? localLastUpdate : 0);
257                         check.setItem(metadata);
258 
259                         // use 'main' installation file for the check (-> use requested repository)
260                         Path checkPath = session.getLocalRepositoryManager()
261                                 .getAbsolutePathForRemoteMetadata(metadata, repository, request.getRequestContext());
262                         check.setPath(checkPath);
263                         check.setRepository(repository);
264                         check.setAuthoritativeRepository(repo);
265                         check.setArtifactPolicy(policy.getArtifactUpdatePolicy());
266                         check.setMetadataPolicy(policy.getMetadataUpdatePolicy());
267 
268                         if (lrmResult.isStale()) {
269                             checks.add(check);
270                         } else {
271                             updateCheckManager.checkMetadata(session, check);
272                             if (check.isRequired()) {
273                                 checks.add(check);
274                             } else if (exception == null) {
275                                 exception = check.getException();
276                             }
277                         }
278                     }
279 
280                     if (!checks.isEmpty()) {
281                         RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature());
282 
283                         // install path may be different from lookup path
284                         Path installPath = session.getLocalRepositoryManager()
285                                 .getAbsolutePathForRemoteMetadata(
286                                         metadata, request.getRepository(), request.getRequestContext());
287 
288                         ResolveTask task = new ResolveTask(
289                                 session, trace, result, installPath, checks, policy.getChecksumPolicy());
290                         tasks.add(task);
291                     } else {
292                         result.setException(exception);
293                         if (metadataPath != null) {
294                             metadata = metadata.setPath(metadataPath);
295                             result.setMetadata(metadata);
296                         }
297                         metadataResolved(session, trace, metadata, repository, result.getException());
298                     }
299                 }
300 
301                 if (!tasks.isEmpty() && current == shared) {
302                     current.close();
303                     current = exclusive;
304                     continue;
305                 }
306 
307                 if (!tasks.isEmpty()) {
308                     int threads = ExecutorUtils.threadCount(session, DEFAULT_THREADS, CONFIG_PROP_THREADS);
309                     Executor executor = ExecutorUtils.executor(
310                             Math.min(tasks.size(), threads), getClass().getSimpleName() + '-');
311                     try {
312                         RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
313 
314                         for (ResolveTask task : tasks) {
315                             metadataDownloading(
316                                     task.session, task.trace, task.request.getMetadata(), task.request.getRepository());
317 
318                             executor.execute(errorForwarder.wrap(task));
319                         }
320 
321                         errorForwarder.await();
322 
323                         for (ResolveTask task : tasks) {
324                             /*
325                              * NOTE: Touch after registration with local repo to ensure concurrent resolution is not
326                              * rejected with "already updated" via session data when actual update to local repo is
327                              * still pending.
328                              */
329                             for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) {
330                                 updateCheckManager.touchMetadata(task.session, check.setException(task.exception));
331                             }
332 
333                             metadataDownloaded(
334                                     session,
335                                     task.trace,
336                                     task.request.getMetadata(),
337                                     task.request.getRepository(),
338                                     task.metadataPath,
339                                     task.exception);
340 
341                             task.result.setException(task.exception);
342                         }
343                     } finally {
344                         ExecutorUtils.shutdown(executor);
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 }