001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.internal.impl;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023import javax.inject.Singleton;
024
025import java.io.File;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.concurrent.Executor;
033
034import org.eclipse.aether.RepositoryEvent;
035import org.eclipse.aether.RepositoryEvent.EventType;
036import org.eclipse.aether.RepositorySystemSession;
037import org.eclipse.aether.RequestTrace;
038import org.eclipse.aether.SyncContext;
039import org.eclipse.aether.impl.MetadataResolver;
040import org.eclipse.aether.impl.OfflineController;
041import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
042import org.eclipse.aether.impl.RemoteRepositoryManager;
043import org.eclipse.aether.impl.RepositoryConnectorProvider;
044import org.eclipse.aether.impl.RepositoryEventDispatcher;
045import org.eclipse.aether.impl.UpdateCheck;
046import org.eclipse.aether.impl.UpdateCheckManager;
047import org.eclipse.aether.metadata.Metadata;
048import org.eclipse.aether.repository.ArtifactRepository;
049import org.eclipse.aether.repository.LocalMetadataRegistration;
050import org.eclipse.aether.repository.LocalMetadataRequest;
051import org.eclipse.aether.repository.LocalMetadataResult;
052import org.eclipse.aether.repository.LocalRepository;
053import org.eclipse.aether.repository.LocalRepositoryManager;
054import org.eclipse.aether.repository.RemoteRepository;
055import org.eclipse.aether.repository.RepositoryPolicy;
056import org.eclipse.aether.resolution.MetadataRequest;
057import org.eclipse.aether.resolution.MetadataResult;
058import org.eclipse.aether.spi.connector.MetadataDownload;
059import org.eclipse.aether.spi.connector.RepositoryConnector;
060import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
061import org.eclipse.aether.spi.locator.Service;
062import org.eclipse.aether.spi.locator.ServiceLocator;
063import org.eclipse.aether.spi.synccontext.SyncContextFactory;
064import org.eclipse.aether.transfer.MetadataNotFoundException;
065import org.eclipse.aether.transfer.MetadataTransferException;
066import org.eclipse.aether.transfer.NoRepositoryConnectorException;
067import org.eclipse.aether.transfer.RepositoryOfflineException;
068import org.eclipse.aether.util.concurrency.ExecutorUtils;
069import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
070
071import static java.util.Objects.requireNonNull;
072
073/**
074 */
075@Singleton
076@Named
077public class DefaultMetadataResolver implements MetadataResolver, Service {
078
079    private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
080
081    private RepositoryEventDispatcher repositoryEventDispatcher;
082
083    private UpdateCheckManager updateCheckManager;
084
085    private RepositoryConnectorProvider repositoryConnectorProvider;
086
087    private RemoteRepositoryManager remoteRepositoryManager;
088
089    private SyncContextFactory syncContextFactory;
090
091    private OfflineController offlineController;
092
093    private RemoteRepositoryFilterManager remoteRepositoryFilterManager;
094
095    public DefaultMetadataResolver() {
096        // enables default constructor
097    }
098
099    @Inject
100    DefaultMetadataResolver(
101            RepositoryEventDispatcher repositoryEventDispatcher,
102            UpdateCheckManager updateCheckManager,
103            RepositoryConnectorProvider repositoryConnectorProvider,
104            RemoteRepositoryManager remoteRepositoryManager,
105            SyncContextFactory syncContextFactory,
106            OfflineController offlineController,
107            RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
108        setRepositoryEventDispatcher(repositoryEventDispatcher);
109        setUpdateCheckManager(updateCheckManager);
110        setRepositoryConnectorProvider(repositoryConnectorProvider);
111        setRemoteRepositoryManager(remoteRepositoryManager);
112        setSyncContextFactory(syncContextFactory);
113        setOfflineController(offlineController);
114        setRemoteRepositoryFilterManager(remoteRepositoryFilterManager);
115    }
116
117    public void initService(ServiceLocator locator) {
118        setRepositoryEventDispatcher(locator.getService(RepositoryEventDispatcher.class));
119        setUpdateCheckManager(locator.getService(UpdateCheckManager.class));
120        setRepositoryConnectorProvider(locator.getService(RepositoryConnectorProvider.class));
121        setRemoteRepositoryManager(locator.getService(RemoteRepositoryManager.class));
122        setSyncContextFactory(locator.getService(SyncContextFactory.class));
123        setOfflineController(locator.getService(OfflineController.class));
124        setRemoteRepositoryFilterManager(locator.getService(RemoteRepositoryFilterManager.class));
125    }
126
127    public DefaultMetadataResolver setRepositoryEventDispatcher(RepositoryEventDispatcher repositoryEventDispatcher) {
128        this.repositoryEventDispatcher =
129                requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
130        return this;
131    }
132
133    public DefaultMetadataResolver setUpdateCheckManager(UpdateCheckManager updateCheckManager) {
134        this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
135        return this;
136    }
137
138    public DefaultMetadataResolver setRepositoryConnectorProvider(
139            RepositoryConnectorProvider repositoryConnectorProvider) {
140        this.repositoryConnectorProvider =
141                requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
142        return this;
143    }
144
145    public DefaultMetadataResolver setRemoteRepositoryManager(RemoteRepositoryManager remoteRepositoryManager) {
146        this.remoteRepositoryManager =
147                requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
148        return this;
149    }
150
151    public DefaultMetadataResolver setSyncContextFactory(SyncContextFactory syncContextFactory) {
152        this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
153        return this;
154    }
155
156    public DefaultMetadataResolver setOfflineController(OfflineController offlineController) {
157        this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
158        return this;
159    }
160
161    public DefaultMetadataResolver setRemoteRepositoryFilterManager(
162            RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
163        this.remoteRepositoryFilterManager =
164                requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
165        return this;
166    }
167
168    public List<MetadataResult> resolveMetadata(
169            RepositorySystemSession session, Collection<? extends MetadataRequest> requests) {
170        requireNonNull(session, "session cannot be null");
171        requireNonNull(requests, "requests cannot be null");
172        try (SyncContext exclusive = syncContextFactory.newInstance(session, false)) {
173            Collection<Metadata> metadata = new ArrayList<>(requests.size());
174            for (MetadataRequest request : requests) {
175                metadata.add(request.getMetadata());
176            }
177
178            return resolve(exclusive, metadata, session, requests);
179        }
180    }
181
182    @SuppressWarnings("checkstyle:methodlength")
183    private List<MetadataResult> resolve(
184            SyncContext syncContext,
185            Collection<Metadata> subjects,
186            RepositorySystemSession session,
187            Collection<? extends MetadataRequest> requests) {
188        try {
189            syncContext.acquire(null, subjects);
190
191            final List<MetadataResult> results = new ArrayList<>(requests.size());
192            final List<ResolveTask> tasks = new ArrayList<>(requests.size());
193            final Map<File, Long> localLastUpdates = new HashMap<>();
194            final RemoteRepositoryFilter remoteRepositoryFilter =
195                    remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
196
197            for (MetadataRequest request : requests) {
198                RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
199
200                MetadataResult result = new MetadataResult(request);
201                results.add(result);
202
203                Metadata metadata = request.getMetadata();
204                RemoteRepository repository = request.getRepository();
205
206                if (repository == null) {
207                    LocalRepository localRepo =
208                            session.getLocalRepositoryManager().getRepository();
209
210                    metadataResolving(session, trace, metadata, localRepo);
211
212                    File localFile = getLocalFile(session, metadata);
213
214                    if (localFile != null) {
215                        metadata = metadata.setFile(localFile);
216                        result.setMetadata(metadata);
217                    } else {
218                        result.setException(new MetadataNotFoundException(metadata, localRepo));
219                    }
220
221                    metadataResolved(session, trace, metadata, localRepo, result.getException());
222                    continue;
223                }
224
225                if (remoteRepositoryFilter != null) {
226                    RemoteRepositoryFilter.Result filterResult =
227                            remoteRepositoryFilter.acceptMetadata(repository, metadata);
228                    if (!filterResult.isAccepted()) {
229                        result.setException(
230                                new MetadataNotFoundException(metadata, repository, filterResult.reasoning()));
231                        continue;
232                    }
233                }
234
235                List<RemoteRepository> repositories = getEnabledSourceRepositories(repository, metadata.getNature());
236
237                if (repositories.isEmpty()) {
238                    continue;
239                }
240
241                metadataResolving(session, trace, metadata, repository);
242                LocalRepositoryManager lrm = session.getLocalRepositoryManager();
243                LocalMetadataRequest localRequest =
244                        new LocalMetadataRequest(metadata, repository, request.getRequestContext());
245                LocalMetadataResult lrmResult = lrm.find(session, localRequest);
246
247                File metadataFile = lrmResult.getFile();
248
249                try {
250                    Utils.checkOffline(session, offlineController, repository);
251                } catch (RepositoryOfflineException e) {
252                    if (metadataFile != null) {
253                        metadata = metadata.setFile(metadataFile);
254                        result.setMetadata(metadata);
255                    } else {
256                        String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl()
257                                + ") in offline mode and the metadata " + metadata
258                                + " has not been downloaded from it before";
259                        result.setException(new MetadataNotFoundException(metadata, repository, msg, e));
260                    }
261
262                    metadataResolved(session, trace, metadata, repository, result.getException());
263                    continue;
264                }
265
266                Long localLastUpdate = null;
267                if (request.isFavorLocalRepository()) {
268                    File localFile = getLocalFile(session, metadata);
269                    localLastUpdate = localLastUpdates.get(localFile);
270                    if (localLastUpdate == null) {
271                        localLastUpdate = localFile != null ? localFile.lastModified() : 0;
272                        localLastUpdates.put(localFile, localLastUpdate);
273                    }
274                }
275
276                List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
277                Exception exception = null;
278                for (RemoteRepository repo : repositories) {
279                    UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
280                    check.setLocalLastUpdated((localLastUpdate != null) ? localLastUpdate : 0);
281                    check.setItem(metadata);
282
283                    // use 'main' installation file for the check (-> use requested repository)
284                    File checkFile = new File(
285                            session.getLocalRepository().getBasedir(),
286                            session.getLocalRepositoryManager()
287                                    .getPathForRemoteMetadata(metadata, repository, request.getRequestContext()));
288                    check.setFile(checkFile);
289                    check.setRepository(repository);
290                    check.setAuthoritativeRepository(repo);
291                    check.setPolicy(
292                            getPolicy(session, repo, metadata.getNature()).getUpdatePolicy());
293
294                    if (lrmResult.isStale()) {
295                        checks.add(check);
296                    } else {
297                        updateCheckManager.checkMetadata(session, check);
298                        if (check.isRequired()) {
299                            checks.add(check);
300                        } else if (exception == null) {
301                            exception = check.getException();
302                        }
303                    }
304                }
305
306                if (!checks.isEmpty()) {
307                    RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature());
308
309                    // install path may be different from lookup path
310                    File installFile = new File(
311                            session.getLocalRepository().getBasedir(),
312                            session.getLocalRepositoryManager()
313                                    .getPathForRemoteMetadata(
314                                            metadata, request.getRepository(), request.getRequestContext()));
315
316                    ResolveTask task =
317                            new ResolveTask(session, trace, result, installFile, checks, policy.getChecksumPolicy());
318                    tasks.add(task);
319                } else {
320                    result.setException(exception);
321                    if (metadataFile != null) {
322                        metadata = metadata.setFile(metadataFile);
323                        result.setMetadata(metadata);
324                    }
325                    metadataResolved(session, trace, metadata, repository, result.getException());
326                }
327            }
328
329            if (!tasks.isEmpty()) {
330                int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS);
331                Executor executor = ExecutorUtils.executor(
332                        Math.min(tasks.size(), threads), getClass().getSimpleName() + '-');
333                try {
334                    RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
335
336                    for (ResolveTask task : tasks) {
337                        metadataDownloading(
338                                task.session, task.trace, task.request.getMetadata(), task.request.getRepository());
339
340                        executor.execute(errorForwarder.wrap(task));
341                    }
342
343                    errorForwarder.await();
344
345                    for (ResolveTask task : tasks) {
346                        /*
347                         * NOTE: Touch after registration with local repo to ensure concurrent resolution is not
348                         * rejected with "already updated" via session data when actual update to local repo is
349                         * still pending.
350                         */
351                        for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) {
352                            updateCheckManager.touchMetadata(task.session, check.setException(task.exception));
353                        }
354
355                        metadataDownloaded(
356                                session,
357                                task.trace,
358                                task.request.getMetadata(),
359                                task.request.getRepository(),
360                                task.metadataFile,
361                                task.exception);
362
363                        task.result.setException(task.exception);
364                    }
365                } finally {
366                    ExecutorUtils.shutdown(executor);
367                }
368                for (ResolveTask task : tasks) {
369                    Metadata metadata = task.request.getMetadata();
370                    // re-lookup metadata for resolve
371                    LocalMetadataRequest localRequest = new LocalMetadataRequest(
372                            metadata, task.request.getRepository(), task.request.getRequestContext());
373                    File metadataFile = session.getLocalRepositoryManager()
374                            .find(session, localRequest)
375                            .getFile();
376                    if (metadataFile != null) {
377                        metadata = metadata.setFile(metadataFile);
378                        task.result.setMetadata(metadata);
379                    }
380                    if (task.result.getException() == null) {
381                        task.result.setUpdated(true);
382                    }
383                    metadataResolved(
384                            session, task.trace, metadata, task.request.getRepository(), task.result.getException());
385                }
386            }
387
388            return results;
389        } finally {
390            syncContext.close();
391        }
392    }
393
394    private File getLocalFile(RepositorySystemSession session, Metadata metadata) {
395        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
396        LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null));
397        return localResult.getFile();
398    }
399
400    private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) {
401        List<RemoteRepository> repositories = new ArrayList<>();
402
403        if (repository.isRepositoryManager()) {
404            for (RemoteRepository repo : repository.getMirroredRepositories()) {
405                if (isEnabled(repo, nature)) {
406                    repositories.add(repo);
407                }
408            }
409        } else if (isEnabled(repository, nature)) {
410            repositories.add(repository);
411        }
412
413        return repositories;
414    }
415
416    private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) {
417        if (!Metadata.Nature.SNAPSHOT.equals(nature)
418                && repository.getPolicy(false).isEnabled()) {
419            return true;
420        }
421        if (!Metadata.Nature.RELEASE.equals(nature)
422                && repository.getPolicy(true).isEnabled()) {
423            return true;
424        }
425        return false;
426    }
427
428    private RepositoryPolicy getPolicy(
429            RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) {
430        boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature);
431        boolean snapshots = !Metadata.Nature.RELEASE.equals(nature);
432        return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots);
433    }
434
435    private void metadataResolving(
436            RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
437        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING);
438        event.setTrace(trace);
439        event.setMetadata(metadata);
440        event.setRepository(repository);
441
442        repositoryEventDispatcher.dispatch(event.build());
443    }
444
445    private void metadataResolved(
446            RepositorySystemSession session,
447            RequestTrace trace,
448            Metadata metadata,
449            ArtifactRepository repository,
450            Exception exception) {
451        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED);
452        event.setTrace(trace);
453        event.setMetadata(metadata);
454        event.setRepository(repository);
455        event.setException(exception);
456        event.setFile(metadata.getFile());
457
458        repositoryEventDispatcher.dispatch(event.build());
459    }
460
461    private void metadataDownloading(
462            RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
463        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING);
464        event.setTrace(trace);
465        event.setMetadata(metadata);
466        event.setRepository(repository);
467
468        repositoryEventDispatcher.dispatch(event.build());
469    }
470
471    private void metadataDownloaded(
472            RepositorySystemSession session,
473            RequestTrace trace,
474            Metadata metadata,
475            ArtifactRepository repository,
476            File file,
477            Exception exception) {
478        RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED);
479        event.setTrace(trace);
480        event.setMetadata(metadata);
481        event.setRepository(repository);
482        event.setException(exception);
483        event.setFile(file);
484
485        repositoryEventDispatcher.dispatch(event.build());
486    }
487
488    class ResolveTask implements Runnable {
489        final RepositorySystemSession session;
490
491        final RequestTrace trace;
492
493        final MetadataResult result;
494
495        final MetadataRequest request;
496
497        final File metadataFile;
498
499        final String policy;
500
501        final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
502
503        volatile MetadataTransferException exception;
504
505        ResolveTask(
506                RepositorySystemSession session,
507                RequestTrace trace,
508                MetadataResult result,
509                File metadataFile,
510                List<UpdateCheck<Metadata, MetadataTransferException>> checks,
511                String policy) {
512            this.session = session;
513            this.trace = trace;
514            this.result = result;
515            this.request = result.getRequest();
516            this.metadataFile = metadataFile;
517            this.policy = policy;
518            this.checks = checks;
519        }
520
521        public void run() {
522            Metadata metadata = request.getMetadata();
523            RemoteRepository requestRepository = request.getRepository();
524
525            try {
526                List<RemoteRepository> repositories = new ArrayList<>();
527                for (UpdateCheck<Metadata, MetadataTransferException> check : checks) {
528                    repositories.add(check.getAuthoritativeRepository());
529                }
530
531                MetadataDownload download = new MetadataDownload();
532                download.setMetadata(metadata);
533                download.setRequestContext(request.getRequestContext());
534                download.setFile(metadataFile);
535                download.setChecksumPolicy(policy);
536                download.setRepositories(repositories);
537                download.setListener(SafeTransferListener.wrap(session));
538                download.setTrace(trace);
539
540                try (RepositoryConnector connector =
541                        repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) {
542                    connector.get(null, Collections.singletonList(download));
543                }
544
545                exception = download.getException();
546
547                if (exception == null) {
548
549                    List<String> contexts = Collections.singletonList(request.getRequestContext());
550                    LocalMetadataRegistration registration =
551                            new LocalMetadataRegistration(metadata, requestRepository, contexts);
552
553                    session.getLocalRepositoryManager().add(session, registration);
554                } else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) {
555                    download.getFile().delete();
556                }
557            } catch (NoRepositoryConnectorException e) {
558                exception = new MetadataTransferException(metadata, requestRepository, e);
559            }
560        }
561    }
562}