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