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