| /* |
| * 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); |
| } |
| } |
| } |
| } |