blob: a6743958a313d456e85ba1ec789f27d04e931295 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.eclipse.aether.internal.impl;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.aether.RepositoryEvent;
import org.eclipse.aether.RepositoryEvent.EventType;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.RequestTrace;
import org.eclipse.aether.SyncContext;
import org.eclipse.aether.impl.MetadataResolver;
import org.eclipse.aether.impl.OfflineController;
import org.eclipse.aether.impl.RemoteRepositoryFilterManager;
import org.eclipse.aether.impl.RemoteRepositoryManager;
import org.eclipse.aether.impl.RepositoryConnectorProvider;
import org.eclipse.aether.impl.RepositoryEventDispatcher;
import org.eclipse.aether.impl.UpdateCheck;
import org.eclipse.aether.impl.UpdateCheckManager;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.repository.LocalMetadataRegistration;
import org.eclipse.aether.repository.LocalMetadataRequest;
import org.eclipse.aether.repository.LocalMetadataResult;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.MetadataRequest;
import org.eclipse.aether.resolution.MetadataResult;
import org.eclipse.aether.spi.connector.MetadataDownload;
import org.eclipse.aether.spi.connector.RepositoryConnector;
import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
import org.eclipse.aether.spi.locator.Service;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.spi.synccontext.SyncContextFactory;
import org.eclipse.aether.transfer.MetadataNotFoundException;
import org.eclipse.aether.transfer.MetadataTransferException;
import org.eclipse.aether.transfer.NoRepositoryConnectorException;
import org.eclipse.aether.transfer.RepositoryOfflineException;
import org.eclipse.aether.util.concurrency.ExecutorUtils;
import org.eclipse.aether.util.concurrency.RunnableErrorForwarder;
import static java.util.Objects.requireNonNull;
/**
*/
@Singleton
@Named
public class DefaultMetadataResolver implements MetadataResolver, Service {
private static final String CONFIG_PROP_THREADS = "aether.metadataResolver.threads";
private RepositoryEventDispatcher repositoryEventDispatcher;
private UpdateCheckManager updateCheckManager;
private RepositoryConnectorProvider repositoryConnectorProvider;
private RemoteRepositoryManager remoteRepositoryManager;
private SyncContextFactory syncContextFactory;
private OfflineController offlineController;
private RemoteRepositoryFilterManager remoteRepositoryFilterManager;
public DefaultMetadataResolver() {
// enables default constructor
}
@Inject
DefaultMetadataResolver(
RepositoryEventDispatcher repositoryEventDispatcher,
UpdateCheckManager updateCheckManager,
RepositoryConnectorProvider repositoryConnectorProvider,
RemoteRepositoryManager remoteRepositoryManager,
SyncContextFactory syncContextFactory,
OfflineController offlineController,
RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
setRepositoryEventDispatcher(repositoryEventDispatcher);
setUpdateCheckManager(updateCheckManager);
setRepositoryConnectorProvider(repositoryConnectorProvider);
setRemoteRepositoryManager(remoteRepositoryManager);
setSyncContextFactory(syncContextFactory);
setOfflineController(offlineController);
setRemoteRepositoryFilterManager(remoteRepositoryFilterManager);
}
public void initService(ServiceLocator locator) {
setRepositoryEventDispatcher(locator.getService(RepositoryEventDispatcher.class));
setUpdateCheckManager(locator.getService(UpdateCheckManager.class));
setRepositoryConnectorProvider(locator.getService(RepositoryConnectorProvider.class));
setRemoteRepositoryManager(locator.getService(RemoteRepositoryManager.class));
setSyncContextFactory(locator.getService(SyncContextFactory.class));
setOfflineController(locator.getService(OfflineController.class));
setRemoteRepositoryFilterManager(locator.getService(RemoteRepositoryFilterManager.class));
}
public DefaultMetadataResolver setRepositoryEventDispatcher(RepositoryEventDispatcher repositoryEventDispatcher) {
this.repositoryEventDispatcher =
requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
return this;
}
public DefaultMetadataResolver setUpdateCheckManager(UpdateCheckManager updateCheckManager) {
this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
return this;
}
public DefaultMetadataResolver setRepositoryConnectorProvider(
RepositoryConnectorProvider repositoryConnectorProvider) {
this.repositoryConnectorProvider =
requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
return this;
}
public DefaultMetadataResolver setRemoteRepositoryManager(RemoteRepositoryManager remoteRepositoryManager) {
this.remoteRepositoryManager =
requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
return this;
}
public DefaultMetadataResolver setSyncContextFactory(SyncContextFactory syncContextFactory) {
this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
return this;
}
public DefaultMetadataResolver setOfflineController(OfflineController offlineController) {
this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
return this;
}
public DefaultMetadataResolver setRemoteRepositoryFilterManager(
RemoteRepositoryFilterManager remoteRepositoryFilterManager) {
this.remoteRepositoryFilterManager =
requireNonNull(remoteRepositoryFilterManager, "remote repository filter manager cannot be null");
return this;
}
public List<MetadataResult> resolveMetadata(
RepositorySystemSession session, Collection<? extends MetadataRequest> requests) {
requireNonNull(session, "session cannot be null");
requireNonNull(requests, "requests cannot be null");
try (SyncContext syncContext = syncContextFactory.newInstance(session, false)) {
Collection<Metadata> metadata = new ArrayList<>(requests.size());
for (MetadataRequest request : requests) {
metadata.add(request.getMetadata());
}
syncContext.acquire(null, metadata);
return resolve(session, requests);
}
}
@SuppressWarnings("checkstyle:methodlength")
private List<MetadataResult> resolve(
RepositorySystemSession session, Collection<? extends MetadataRequest> requests) {
List<MetadataResult> results = new ArrayList<>(requests.size());
List<ResolveTask> tasks = new ArrayList<>(requests.size());
Map<File, Long> localLastUpdates = new HashMap<>();
RemoteRepositoryFilter remoteRepositoryFilter =
remoteRepositoryFilterManager.getRemoteRepositoryFilter(session);
for (MetadataRequest request : requests) {
RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
MetadataResult result = new MetadataResult(request);
results.add(result);
Metadata metadata = request.getMetadata();
RemoteRepository repository = request.getRepository();
if (repository == null) {
LocalRepository localRepo = session.getLocalRepositoryManager().getRepository();
metadataResolving(session, trace, metadata, localRepo);
File localFile = getLocalFile(session, metadata);
if (localFile != null) {
metadata = metadata.setFile(localFile);
result.setMetadata(metadata);
} else {
result.setException(new MetadataNotFoundException(metadata, localRepo));
}
metadataResolved(session, trace, metadata, localRepo, result.getException());
continue;
}
if (remoteRepositoryFilter != null) {
RemoteRepositoryFilter.Result filterResult =
remoteRepositoryFilter.acceptMetadata(repository, metadata);
if (!filterResult.isAccepted()) {
result.setException(new MetadataNotFoundException(metadata, repository, filterResult.reasoning()));
continue;
}
}
List<RemoteRepository> repositories = getEnabledSourceRepositories(repository, metadata.getNature());
if (repositories.isEmpty()) {
continue;
}
metadataResolving(session, trace, metadata, repository);
LocalRepositoryManager lrm = session.getLocalRepositoryManager();
LocalMetadataRequest localRequest =
new LocalMetadataRequest(metadata, repository, request.getRequestContext());
LocalMetadataResult lrmResult = lrm.find(session, localRequest);
File metadataFile = lrmResult.getFile();
try {
Utils.checkOffline(session, offlineController, repository);
} catch (RepositoryOfflineException e) {
if (metadataFile != null) {
metadata = metadata.setFile(metadataFile);
result.setMetadata(metadata);
} else {
String msg = "Cannot access " + repository.getId() + " (" + repository.getUrl()
+ ") in offline mode and the metadata " + metadata
+ " has not been downloaded from it before";
result.setException(new MetadataNotFoundException(metadata, repository, msg, e));
}
metadataResolved(session, trace, metadata, repository, result.getException());
continue;
}
Long localLastUpdate = null;
if (request.isFavorLocalRepository()) {
File localFile = getLocalFile(session, metadata);
localLastUpdate = localLastUpdates.get(localFile);
if (localLastUpdate == null) {
localLastUpdate = localFile != null ? localFile.lastModified() : 0;
localLastUpdates.put(localFile, localLastUpdate);
}
}
List<UpdateCheck<Metadata, MetadataTransferException>> checks = new ArrayList<>();
Exception exception = null;
for (RemoteRepository repo : repositories) {
UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
check.setLocalLastUpdated((localLastUpdate != null) ? localLastUpdate : 0);
check.setItem(metadata);
// use 'main' installation file for the check (-> use requested repository)
File checkFile = new File(
session.getLocalRepository().getBasedir(),
session.getLocalRepositoryManager()
.getPathForRemoteMetadata(metadata, repository, request.getRequestContext()));
check.setFile(checkFile);
check.setRepository(repository);
check.setAuthoritativeRepository(repo);
check.setPolicy(getPolicy(session, repo, metadata.getNature()).getUpdatePolicy());
if (lrmResult.isStale()) {
checks.add(check);
} else {
updateCheckManager.checkMetadata(session, check);
if (check.isRequired()) {
checks.add(check);
} else if (exception == null) {
exception = check.getException();
}
}
}
if (!checks.isEmpty()) {
RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature());
// install path may be different from lookup path
File installFile = new File(
session.getLocalRepository().getBasedir(),
session.getLocalRepositoryManager()
.getPathForRemoteMetadata(
metadata, request.getRepository(), request.getRequestContext()));
metadataDownloading(
session,
trace,
result.getRequest().getMetadata(),
result.getRequest().getRepository());
ResolveTask task =
new ResolveTask(session, trace, result, installFile, checks, policy.getChecksumPolicy());
tasks.add(task);
} else {
result.setException(exception);
if (metadataFile != null) {
metadata = metadata.setFile(metadataFile);
result.setMetadata(metadata);
}
metadataResolved(session, trace, metadata, repository, result.getException());
}
}
if (!tasks.isEmpty()) {
int threads = ExecutorUtils.threadCount(session, 4, CONFIG_PROP_THREADS);
Executor executor = ExecutorUtils.executor(
Math.min(tasks.size(), threads), getClass().getSimpleName() + '-');
try {
RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder();
for (ResolveTask task : tasks) {
executor.execute(errorForwarder.wrap(task));
}
errorForwarder.await();
for (ResolveTask task : tasks) {
/*
* NOTE: Touch after registration with local repo to ensure concurrent resolution is not
* rejected with "already updated" via session data when actual update to local repo is
* still pending.
*/
for (UpdateCheck<Metadata, MetadataTransferException> check : task.checks) {
updateCheckManager.touchMetadata(task.session, check.setException(task.exception));
}
metadataDownloaded(
session,
task.trace,
task.request.getMetadata(),
task.request.getRepository(),
task.metadataFile,
task.exception);
task.result.setException(task.exception);
}
} finally {
ExecutorUtils.shutdown(executor);
}
for (ResolveTask task : tasks) {
Metadata metadata = task.request.getMetadata();
// re-lookup metadata for resolve
LocalMetadataRequest localRequest = new LocalMetadataRequest(
metadata, task.request.getRepository(), task.request.getRequestContext());
File metadataFile = session.getLocalRepositoryManager()
.find(session, localRequest)
.getFile();
if (metadataFile != null) {
metadata = metadata.setFile(metadataFile);
task.result.setMetadata(metadata);
}
if (task.result.getException() == null) {
task.result.setUpdated(true);
}
metadataResolved(
session, task.trace, metadata, task.request.getRepository(), task.result.getException());
}
}
return results;
}
private File getLocalFile(RepositorySystemSession session, Metadata metadata) {
LocalRepositoryManager lrm = session.getLocalRepositoryManager();
LocalMetadataResult localResult = lrm.find(session, new LocalMetadataRequest(metadata, null, null));
return localResult.getFile();
}
private List<RemoteRepository> getEnabledSourceRepositories(RemoteRepository repository, Metadata.Nature nature) {
List<RemoteRepository> repositories = new ArrayList<>();
if (repository.isRepositoryManager()) {
for (RemoteRepository repo : repository.getMirroredRepositories()) {
if (isEnabled(repo, nature)) {
repositories.add(repo);
}
}
} else if (isEnabled(repository, nature)) {
repositories.add(repository);
}
return repositories;
}
private boolean isEnabled(RemoteRepository repository, Metadata.Nature nature) {
if (!Metadata.Nature.SNAPSHOT.equals(nature)
&& repository.getPolicy(false).isEnabled()) {
return true;
}
if (!Metadata.Nature.RELEASE.equals(nature)
&& repository.getPolicy(true).isEnabled()) {
return true;
}
return false;
}
private RepositoryPolicy getPolicy(
RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) {
boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature);
boolean snapshots = !Metadata.Nature.RELEASE.equals(nature);
return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots);
}
private void metadataResolving(
RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING);
event.setTrace(trace);
event.setMetadata(metadata);
event.setRepository(repository);
repositoryEventDispatcher.dispatch(event.build());
}
private void metadataResolved(
RepositorySystemSession session,
RequestTrace trace,
Metadata metadata,
ArtifactRepository repository,
Exception exception) {
RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED);
event.setTrace(trace);
event.setMetadata(metadata);
event.setRepository(repository);
event.setException(exception);
event.setFile(metadata.getFile());
repositoryEventDispatcher.dispatch(event.build());
}
private void metadataDownloading(
RepositorySystemSession session, RequestTrace trace, Metadata metadata, ArtifactRepository repository) {
RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING);
event.setTrace(trace);
event.setMetadata(metadata);
event.setRepository(repository);
repositoryEventDispatcher.dispatch(event.build());
}
private void metadataDownloaded(
RepositorySystemSession session,
RequestTrace trace,
Metadata metadata,
ArtifactRepository repository,
File file,
Exception exception) {
RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED);
event.setTrace(trace);
event.setMetadata(metadata);
event.setRepository(repository);
event.setException(exception);
event.setFile(file);
repositoryEventDispatcher.dispatch(event.build());
}
class ResolveTask implements Runnable {
final RepositorySystemSession session;
final RequestTrace trace;
final MetadataResult result;
final MetadataRequest request;
final File metadataFile;
final String policy;
final List<UpdateCheck<Metadata, MetadataTransferException>> checks;
volatile MetadataTransferException exception;
ResolveTask(
RepositorySystemSession session,
RequestTrace trace,
MetadataResult result,
File metadataFile,
List<UpdateCheck<Metadata, MetadataTransferException>> checks,
String policy) {
this.session = session;
this.trace = trace;
this.result = result;
this.request = result.getRequest();
this.metadataFile = metadataFile;
this.policy = policy;
this.checks = checks;
}
public void run() {
Metadata metadata = request.getMetadata();
RemoteRepository requestRepository = request.getRepository();
try {
List<RemoteRepository> repositories = new ArrayList<>();
for (UpdateCheck<Metadata, MetadataTransferException> check : checks) {
repositories.add(check.getAuthoritativeRepository());
}
MetadataDownload download = new MetadataDownload();
download.setMetadata(metadata);
download.setRequestContext(request.getRequestContext());
download.setFile(metadataFile);
download.setChecksumPolicy(policy);
download.setRepositories(repositories);
download.setListener(SafeTransferListener.wrap(session));
download.setTrace(trace);
try (RepositoryConnector connector =
repositoryConnectorProvider.newRepositoryConnector(session, requestRepository)) {
connector.get(null, Collections.singletonList(download));
}
exception = download.getException();
if (exception == null) {
List<String> contexts = Collections.singletonList(request.getRequestContext());
LocalMetadataRegistration registration =
new LocalMetadataRegistration(metadata, requestRepository, contexts);
session.getLocalRepositoryManager().add(session, registration);
} else if (request.isDeleteLocalCopyIfMissing() && exception instanceof MetadataNotFoundException) {
download.getFile().delete();
}
} catch (NoRepositoryConnectorException e) {
exception = new MetadataTransferException(metadata, requestRepository, e);
}
}
}
}