All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonarsource.sonarlint.ls.connected.ProjectBindingManager Maven / Gradle / Ivy

There is a newer version: 3.12.0.75621
Show newest version
/*
 * SonarLint Language Server
 * Copyright (C) 2009-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonarsource.sonarlint.ls.connected;

import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine;
import org.sonarsource.sonarlint.core.clientapi.backend.config.binding.BindingConfigurationDto;
import org.sonarsource.sonarlint.core.clientapi.backend.config.binding.DidUpdateBindingParams;
import org.sonarsource.sonarlint.core.clientapi.backend.connection.validate.ValidateConnectionParams;
import org.sonarsource.sonarlint.core.commons.log.ClientLogOutput;
import org.sonarsource.sonarlint.core.http.HttpClient;
import org.sonarsource.sonarlint.core.serverapi.EndpointParams;
import org.sonarsource.sonarlint.core.serverapi.component.ServerProject;
import org.sonarsource.sonarlint.core.serverconnection.DownloadException;
import org.sonarsource.sonarlint.core.serverconnection.ProjectBinding;
import org.sonarsource.sonarlint.core.serverconnection.issues.ServerTaintIssue;
import org.sonarsource.sonarlint.ls.AnalysisScheduler;
import org.sonarsource.sonarlint.ls.DiagnosticPublisher;
import org.sonarsource.sonarlint.ls.EnginesFactory;
import org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient;
import org.sonarsource.sonarlint.ls.SonarLintExtendedLanguageClient.ConnectionCheckResult;
import org.sonarsource.sonarlint.ls.backend.BackendServiceFacade;
import org.sonarsource.sonarlint.ls.connected.domain.TaintIssue;
import org.sonarsource.sonarlint.ls.folders.WorkspaceFolderWrapper;
import org.sonarsource.sonarlint.ls.folders.WorkspaceFoldersManager;
import org.sonarsource.sonarlint.ls.log.LanguageClientLogOutput;
import org.sonarsource.sonarlint.ls.notebooks.OpenNotebooksCache;
import org.sonarsource.sonarlint.ls.progress.NoOpProgressFacade;
import org.sonarsource.sonarlint.ls.settings.ServerConnectionSettings;
import org.sonarsource.sonarlint.ls.settings.SettingsManager;
import org.sonarsource.sonarlint.ls.settings.WorkspaceFolderSettings;
import org.sonarsource.sonarlint.ls.settings.WorkspaceFolderSettingsChangeListener;
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettings;
import org.sonarsource.sonarlint.ls.settings.WorkspaceSettingsChangeListener;
import org.sonarsource.sonarlint.ls.util.FileUtils;

import static java.util.Objects.requireNonNull;
import static java.util.function.Predicate.not;
import static org.sonarsource.sonarlint.ls.util.FileUtils.getFileRelativePath;
import static org.sonarsource.sonarlint.ls.util.Utils.uriHasFileScheme;

/**
 * Keep a cache of project bindings. Files that are part of a workspace workspaceFolderPath will share the same binding.
 * Files that are opened alone will have their own binding.
 */
public class ProjectBindingManager implements WorkspaceSettingsChangeListener, WorkspaceFolderSettingsChangeListener {
  private final WorkspaceFoldersManager foldersManager;
  private final SettingsManager settingsManager;
  private final ConcurrentMap> folderBindingCache;
  private final LanguageClientLogOutput globalLogOutput;
  private final ConcurrentMap> fileBindingCache = new ConcurrentHashMap<>();
  private final ConcurrentMap> connectedEngineCacheByConnectionId;
  private final SonarLintExtendedLanguageClient client;
  private final EnginesFactory enginesFactory;
  private AnalysisScheduler analysisManager;
  private Function> branchNameForFolderSupplier;
  private final TaintVulnerabilitiesCache taintVulnerabilitiesCache;
  private final DiagnosticPublisher diagnosticPublisher;
  private final BackendServiceFacade backendServiceFacade;
  private final OpenNotebooksCache openNotebooksCache;

  public ProjectBindingManager(EnginesFactory enginesFactory, WorkspaceFoldersManager foldersManager, SettingsManager settingsManager, SonarLintExtendedLanguageClient client,
    LanguageClientLogOutput globalLogOutput, TaintVulnerabilitiesCache taintVulnerabilitiesCache, DiagnosticPublisher diagnosticPublisher,
    BackendServiceFacade backendServiceFacade, OpenNotebooksCache openNotebooksCache) {
    this(enginesFactory, foldersManager, settingsManager, client, new ConcurrentHashMap<>(), globalLogOutput, new ConcurrentHashMap<>(),
      taintVulnerabilitiesCache, diagnosticPublisher, backendServiceFacade,
      openNotebooksCache);
  }

  public ProjectBindingManager(EnginesFactory enginesFactory, WorkspaceFoldersManager foldersManager, SettingsManager settingsManager, SonarLintExtendedLanguageClient client,
    ConcurrentMap> folderBindingCache, @Nullable LanguageClientLogOutput globalLogOutput,
    ConcurrentMap> connectedEngineCacheByConnectionId, TaintVulnerabilitiesCache taintVulnerabilitiesCache,
    DiagnosticPublisher diagnosticPublisher, BackendServiceFacade backendServiceFacade,
    OpenNotebooksCache openNotebooksCache) {
    this.enginesFactory = enginesFactory;
    this.foldersManager = foldersManager;
    this.settingsManager = settingsManager;
    this.client = client;
    this.folderBindingCache = folderBindingCache;
    this.globalLogOutput = globalLogOutput;
    this.connectedEngineCacheByConnectionId = connectedEngineCacheByConnectionId;
    this.taintVulnerabilitiesCache = taintVulnerabilitiesCache;
    this.diagnosticPublisher = diagnosticPublisher;
    this.backendServiceFacade = backendServiceFacade;
    this.openNotebooksCache = openNotebooksCache;
  }

  // Can't use constructor injection because of cyclic dependency
  public void setAnalysisManager(AnalysisScheduler analysisManager) {
    this.analysisManager = analysisManager;
  }

  public void clearBindingCache() {
    folderBindingCache.clear();
    fileBindingCache.clear();
  }

  /**
   * Return the binding of the given folder.
   *
   * @return empty if the folder is unbound
   */
  public Optional getBinding(WorkspaceFolderWrapper folder) {
    return getBinding(Optional.of(folder), folder.getUri());
  }

  /**
   * Return the binding of the given file.
   *
   * @return empty if the file is unbound
   */
  public Optional getBinding(URI fileUri) {
    if (!uriHasFileScheme(fileUri) || openNotebooksCache.isNotebook(fileUri)) {
      if (globalLogOutput != null) {
        globalLogOutput.log("Ignoring connected mode settings for unsupported URI: " + fileUri, ClientLogOutput.Level.DEBUG);
      }
      return Optional.empty();
    }
    var folder = foldersManager.findFolderForFile(fileUri);
    var cacheKey = folder.map(WorkspaceFolderWrapper::getUri).orElse(fileUri);
    return getBinding(folder, cacheKey);
  }

  public void getBindingAndRepublishTaints(WorkspaceFolderWrapper folder) {
    getBindingAndRepublishTaints(Optional.of(folder), folder.getUri());
  }

  public Optional getBindingAndRepublishTaints(URI fileUri) {
    var folder = foldersManager.findFolderForFile(fileUri);
    var cacheKey = folder.map(WorkspaceFolderWrapper::getUri).orElse(fileUri);
    return getBindingAndRepublishTaints(folder, cacheKey);
  }

  private Optional getBinding(Optional folder, URI fileUri) {
    var bindingCache = folder.isPresent() ? folderBindingCache : fileBindingCache;
    return bindingCache.computeIfAbsent(fileUri, k -> {
      var settings = folder.map(WorkspaceFolderWrapper::getSettings)
        .orElse(settingsManager.getCurrentDefaultFolderSettings());
      if (!settings.hasBinding()) {
        return Optional.empty();
      } else {
        var folderRoot = folder.map(WorkspaceFolderWrapper::getRootPath).orElse(Paths.get(fileUri).getParent());
        return Optional.ofNullable(computeProjectBinding(settings, folderRoot));
      }
    });
  }

  private Optional getBindingAndRepublishTaints(Optional folder, URI fileUri) {
    var maybeBinding = getBinding(folder, fileUri);
    maybeBinding.ifPresent(binding ->
      folder.ifPresent(actualFolder ->
        updateAllTaintIssuesForOneFolder(actualFolder, binding.getBinding(), binding.getConnectionId())));
    return maybeBinding;
  }

  public Optional getStartedConnectedEngine(String connectionId) {
    return connectedEngineCacheByConnectionId.getOrDefault(connectionId, Optional.empty());
  }

  @CheckForNull
  private ProjectBindingWrapper computeProjectBinding(WorkspaceFolderSettings settings, Path folderRoot) {
    var connectionId = requireNonNull(settings.getConnectionId());
    var endpointParams = getEndpointParamsFor(connectionId);

    if (endpointParams == null) {
      globalLogOutput.error("Invalid binding for '%s'", folderRoot);
      return null;
    }
    var engineOpt = getOrCreateConnectedEngine(connectionId);
    if (engineOpt.isEmpty()) {
      return null;
    }
    var engine = engineOpt.get();
    var projectKey = requireNonNull(settings.getProjectKey());
    Supplier branchProvider = () -> resolveBranchNameForFolder(folderRoot.toUri(), engine, projectKey);
    var httpClient = backendServiceFacade.getHttpClient(connectionId);
    syncAtStartup(engine, endpointParams, projectKey, branchProvider, httpClient, globalLogOutput);

    var ideFilePaths = FileUtils.allRelativePathsForFilesInTree(folderRoot, globalLogOutput);
    var projectBinding = engine.calculatePathPrefixes(projectKey, ideFilePaths);
    globalLogOutput.debug("Resolved binding %s for folder %s",
      ToStringBuilder.reflectionToString(projectBinding, ToStringStyle.SHORT_PREFIX_STYLE),
      folderRoot);
    var issueTrackerWrapper = new ServerIssueTrackerWrapper(engine, endpointParams, projectBinding, branchProvider, httpClient,
      backendServiceFacade, foldersManager, globalLogOutput);
    return new ProjectBindingWrapper(connectionId, projectBinding, engine, issueTrackerWrapper);
  }

  private static void syncAtStartup(ConnectedSonarLintEngine engine, EndpointParams endpointParams, String projectKey,
    Supplier branchProvider, HttpClient httpClient, LanguageClientLogOutput globalLogOutput) {
    try {
      engine.updateProject(endpointParams, httpClient, projectKey, null);
      engine.sync(endpointParams, httpClient, Set.of(projectKey), null);
      var currentBranchName = branchProvider.get();
      engine.syncServerIssues(endpointParams, httpClient, projectKey, currentBranchName, null);
      engine.syncServerTaintIssues(endpointParams, httpClient, projectKey, currentBranchName, null);
      engine.syncServerHotspots(endpointParams, httpClient, projectKey, currentBranchName, null);
    } catch (Exception exceptionDuringSync) {
      globalLogOutput.warn("Exception happened during initial sync with project " + projectKey, exceptionDuringSync);
    }
  }

  @CheckForNull
  public EndpointParams getEndpointParamsFor(@Nullable String connectionId) {
    return Optional.ofNullable(getServerConnectionSettingsFor(connectionId))
      .map(ServerConnectionSettings::getEndpointParams)
      .orElse(null);
  }

  @CheckForNull
  public ValidateConnectionParams getValidateConnectionParamsFor(@Nullable String connectionId) {
    return Optional.ofNullable(getServerConnectionSettingsFor(connectionId))
      .map(ServerConnectionSettings::getValidateConnectionParams)
      .orElse(null);
  }

  @CheckForNull
  private ServerConnectionSettings getServerConnectionSettingsFor(@Nullable String maybeConnectionId) {
    var connectionId = SettingsManager.connectionIdOrDefault(maybeConnectionId);
    var allConnections = settingsManager.getCurrentSettings().getServerConnections();
    var serverConnectionSettings = allConnections.get(connectionId);
    if (serverConnectionSettings == null) {
      globalLogOutput.error("The specified connection id '%s' doesn't exist.", connectionId);
      return null;
    }
    return serverConnectionSettings;
  }

  public Optional getOrCreateConnectedEngine(String connectionId) {
    return connectedEngineCacheByConnectionId.computeIfAbsent(connectionId,
      s -> Optional.ofNullable(createConnectedEngine(connectionId)));
  }

  @CheckForNull
  private ConnectedSonarLintEngine createConnectedEngine(String connectionId) {
    globalLogOutput.debug("Starting connected SonarLint engine for '%s'...", connectionId);

    ConnectedSonarLintEngine engine;
    try {
      var serverConnectionSettings = settingsManager.getCurrentSettings().getServerConnections().get(connectionId);
      engine = enginesFactory.createConnectedEngine(connectionId, serverConnectionSettings);
    } catch (Exception e) {
      globalLogOutput.error("Error starting connected SonarLint engine for '" + connectionId + "'", e);
      return null;
    }
    return engine;
  }

  public boolean usesConnectedMode() {
    return Stream.concat(folderBindingCache.values().stream(), fileBindingCache.values().stream()).anyMatch(Optional::isPresent);
  }

  public boolean usesSonarCloud() {
    return hasAnyBindingThatMatch(ServerConnectionSettings::isSonarCloudAlias);
  }

  public boolean smartNotificationsDisabled() {
    return hasAnyBindingThatMatch(ServerConnectionSettings::isSmartNotificationsDisabled);
  }

  private boolean hasAnyBindingThatMatch(Predicate predicate) {
    return Stream.concat(folderBindingCache.values().stream(), fileBindingCache.values().stream())
      .flatMap(Optional::stream)
      .map(binding -> settingsManager.getCurrentSettings().getServerConnections().get(binding.getConnectionId()))
      .anyMatch(predicate);
  }

  @Override
  public void onChange(@Nullable WorkspaceFolderWrapper folder, @Nullable WorkspaceFolderSettings oldValue, WorkspaceFolderSettings newValue) {
    if (oldValue == null) {
      return;
    }
    if (oldValue.hasBinding() && !newValue.hasBinding()) {
      unbind(folder);
    } else if (newValue.hasBinding()
      && (!Objects.equals(oldValue.getConnectionId(), newValue.getConnectionId()) || !Objects.equals(oldValue.getProjectKey(), newValue.getProjectKey()))) {
      forceRebindDuringNextAnalysis(folder);
      if (folder == null) return;
      var bindingConfigurationDto = new BindingConfigurationDto(newValue.getConnectionId(), newValue.getProjectKey(), false);
      var params = new DidUpdateBindingParams(folder.getUri().toString(), bindingConfigurationDto);
      backendServiceFacade.getBackendService().updateBinding(params);
    }
  }

  private void forceRebindDuringNextAnalysis(@Nullable WorkspaceFolderWrapper folder) {
    if (folder != null && folderBindingCache.containsKey(folder.getUri())) {
      clearFolderBindingCache(folder);
    } else if (folder == null && !fileBindingCache.isEmpty()) {
      clearFilesBindingCache();
    }
  }

  private void unbind(@Nullable WorkspaceFolderWrapper folder) {
    if (folder != null && folderBindingCache.containsKey(folder.getUri())) {
      unbindFolder(folder);
    } else if (folder == null && !fileBindingCache.isEmpty()) {
      unbindFiles();
    }
  }

  /**
   * Clear cache of binding, so that it gets recomputed during next analysis
   */
  private void clearFilesBindingCache() {
    fileBindingCache.clear();
    stopUnusedEngines();
    analysisManager.analyzeAllOpenFilesInFolder(null);
  }

  private void clearFolderBindingCache(WorkspaceFolderWrapper folder) {
    folderBindingCache.remove(folder.getUri());
    stopUnusedEngines();
    analysisManager.analyzeAllOpenFilesInFolder(folder);
  }

  private void unbindFiles() {
    fileBindingCache.replaceAll((uri, binding) -> Optional.empty());
    globalLogOutput.debug("All files outside workspace are now unbound");
    stopUnusedEngines();
    analysisManager.analyzeAllOpenFilesInFolder(null);
  }

  private void unbindFolder(WorkspaceFolderWrapper folder) {
    folderBindingCache.put(folder.getUri(), Optional.empty());
    globalLogOutput.debug("Workspace '%s' unbound", folder);
    stopUnusedEngines();
    analysisManager.analyzeAllOpenFilesInFolder(folder);
    var bindingConfigurationDto = new BindingConfigurationDto(null, null, false);
    var params = new DidUpdateBindingParams(folder.getUri().toString(), bindingConfigurationDto);
    backendServiceFacade.getBackendService().updateBinding(params);
  }

  private void stopUnusedEngines() {
    var usedServerIds = new HashSet();
    var folderSettings = settingsManager.getCurrentDefaultFolderSettings();
    collectUsedServerId(usedServerIds, folderSettings);
    foldersManager.getAll().forEach(w -> collectUsedServerId(usedServerIds, w.getSettings()));
    var startedEngines = new HashSet<>(connectedEngineCacheByConnectionId.keySet());
    startedEngines.stream()
      .filter(not(usedServerIds::contains))
      .forEach(this::clearCachesAndStopEngine);
  }

  private void clearCachesAndStopEngine(String connectionId) {
    folderBindingCache.entrySet().removeIf(e -> e.getValue().isPresent() && e.getValue().get().getConnectionId().equals(connectionId));
    fileBindingCache.entrySet().removeIf(e -> e.getValue().isPresent() && e.getValue().get().getConnectionId().equals(connectionId));
    if (connectedEngineCacheByConnectionId.containsKey(connectionId)) {
      tryStopServer(connectionId, connectedEngineCacheByConnectionId.remove(connectionId));
    }
  }

  private void collectUsedServerId(Set usedConnectionIds, WorkspaceFolderSettings folderSettings) {
    if (folderSettings.hasBinding()) {
      var connectionId = folderSettings.getConnectionId();
      if (connectionId != null && settingsManager.getCurrentSettings().getServerConnections().containsKey(connectionId)) {
        usedConnectionIds.add(connectionId);
      }
    }
  }

  @Override
  public void onChange(@CheckForNull WorkspaceSettings oldValue, WorkspaceSettings newValue) {
    newValue.getServerConnections().forEach((id, value) -> {
      if (oldValue == null) {
        // initial sync
        this.validateConnection(id);
      } else {
        var oldConnection = oldValue.getServerConnections().get(id);
        if (oldConnection != null && !oldConnection.equals(value)) {
          // Settings of the connection have been changed. Remove all cached bindings and force close the engine
          clearCachesAndStopEngine(id);
        }
        if (oldConnection == null || !oldConnection.equals(value)) {
          // New connection or changed settings. Validate connection
          validateConnection(id);
        }
      }
    });
    stopUnusedEngines();
  }

  public void validateConnection(String id) {
    Optional.ofNullable(getValidateConnectionParamsFor(id))
      .map(backendServiceFacade::validateConnection)
      .ifPresent(validationFuture -> validationFuture.thenAccept(validationResult -> {
        var connectionCheckResult = validationResult.isSuccess() ? ConnectionCheckResult.success(id) : ConnectionCheckResult.failure(id, validationResult.getMessage());
        client.reportConnectionCheckResult(connectionCheckResult);
      }));
  }

  public void shutdown() {
    connectedEngineCacheByConnectionId.forEach(this::tryStopServer);
  }

  private void tryStopServer(String connectionId, Optional engine) {
    engine.ifPresent(e -> {
      try {
        e.stop(false);
      } catch (Exception ex) {
        globalLogOutput.error("Unable to stop engine '" + connectionId + "'", ex);
      }
    });
  }

  public Map>> getActiveConnectionsAndProjects() {
    var projectKeyByConnectionIdsToUpdate = new HashMap>>();
    // Update all engines that are already started and cached, even if no folders are bound
    connectedEngineCacheByConnectionId.keySet().forEach(id -> projectKeyByConnectionIdsToUpdate.computeIfAbsent(id, i -> new HashMap<>()));
    // Start and update all engines that used in a folder binding, even if not yet started
    forEachBoundFolder((folder, folderSettings) -> {
      var connectionId = requireNonNull(folderSettings.getConnectionId());
      var projectKey = requireNonNull(folderSettings.getProjectKey());
      getOrCreateConnectedEngine(connectionId).ifPresent(engine -> projectKeyByConnectionIdsToUpdate.computeIfAbsent(connectionId, id -> new HashMap<>())
        .computeIfAbsent(projectKey, k -> new HashSet<>())
        .add(resolveBranchNameForFolder(folder == null ? null : folder.getUri(), engine, projectKey)));
    });
    return projectKeyByConnectionIdsToUpdate;
  }

  public void forEachBoundFolder(BiConsumer boundFolderConsumer) {
    var defaultFolderSettings = settingsManager.getCurrentDefaultFolderSettings();
    if (defaultFolderSettings.hasBinding()) {
      boundFolderConsumer.accept(null, defaultFolderSettings);
    }
    foldersManager.getAll().forEach(f -> {
      var settings = f.getSettings();
      if (settings.hasBinding()) {
        boundFolderConsumer.accept(f, settings);
      }
    });
  }

  public void updateAllTaintIssues() {
    forEachBoundFolder((folder, folderSettings) -> {
      if (folder == null) {
        return;
      }
      getBindingAndRepublishTaints(folder);
    });
  }

  private void updateAllTaintIssuesForOneFolder(@Nullable WorkspaceFolderWrapper folder, ProjectBinding binding, String connectionId) {
    getStartedConnectedEngine(connectionId).ifPresent(engine -> {
      var branchName = resolveBranchNameForFolder(folder == null ? null : folder.getUri(), engine, binding.projectKey());
      engine.getAllServerTaintIssues(binding, branchName)
        .stream()
        .map(ServerTaintIssue::getFilePath)
        .distinct()
        .forEach(this::updateTaintIssueCacheFromStorageForServerPath);
    });
  }

  private void updateTaintIssueCacheFromStorageForServerPath(String filePathFromEvent) {
    globalLogOutput.debug("Re-publishing taint vulnerabilities for \"%s\"", filePathFromEvent);
    serverPathToFileUri(filePathFromEvent).ifPresent(this::updateTaintIssueCacheFromStorageForFile);
  }

  public void updateTaintIssueCacheFromStorageForFile(URI fileUri) {
    var workspaceFolder = foldersManager.findFolderForFile(fileUri);
    if (workspaceFolder.isPresent()) {
      var baseDir = workspaceFolder.get().getUri();
      var filePath = FileUtils.toSonarQubePath(getFileRelativePath(Paths.get(baseDir), fileUri, globalLogOutput));
      Optional folderBinding = folderBindingCache.get(baseDir);
      if (folderBinding.isPresent()) {
        ProjectBindingWrapper bindingWrapper = folderBinding.get();
        var engine = bindingWrapper.getEngine();
        var folder = foldersManager.findFolderForFile(fileUri);
        var folderUri = folder.isPresent() ? folder.get().getUri() : URI.create("");
        var branchName = this.resolveBranchNameForFolder(folderUri, engine, bindingWrapper.getBinding().projectKey());
        var serverTaintIssues = engine.getServerTaintIssues(bindingWrapper.getBinding(), branchName, filePath, false);
        var connectionSettings = settingsManager.getCurrentSettings().getServerConnections().get(bindingWrapper.getConnectionId());
        var isSonarCloud = connectionSettings != null && connectionSettings.isSonarCloudAlias();
        taintVulnerabilitiesCache.reload(fileUri, TaintIssue.from(serverTaintIssues, isSonarCloud));
        diagnosticPublisher.publishDiagnostics(fileUri, false);
      }
    }
  }

  public Optional serverPathToFileUri(String serverPath) {
    return folderBindingCache.entrySet().stream()
      .filter(e -> e.getValue().isPresent())
      .map(e -> tryResolveLocalFile(serverPath, e.getKey(), e.getValue().get()))
      .flatMap(Optional::stream)
      .map(File::toPath)
      .map(Path::toUri)
      .findFirst();
  }

  private static Optional tryResolveLocalFile(String serverPath, URI folderUri, ProjectBindingWrapper binding) {
    return binding.getBinding()
      .serverPathToIdePath(serverPath)
      // Try to resolve local path in matching folder
      .map(Paths.get(folderUri)::resolve)
      .map(Path::toFile)
      .filter(File::exists);
  }

  public void setBranchResolver(Function> getReferenceBranchNameForFolder) {
    this.branchNameForFolderSupplier = getReferenceBranchNameForFolder;
  }

  public String resolveBranchNameForFolder(@Nullable URI folder, ConnectedSonarLintEngine engine, String projectKey) {
    return branchNameForFolderSupplier.apply(folder).orElse(engine.getServerBranches(projectKey).getMainBranchName());
  }

  public Map getRemoteProjects(@Nullable String maybeConnectionId) {
    var connectionId = SettingsManager.connectionIdOrDefault(maybeConnectionId);
    var endpointParams = getEndpointParamsFor(connectionId);
    if (endpointParams == null) {
      throw new IllegalArgumentException(String.format("No server configuration found with ID '%s'", connectionId));
    }
    var progress = new NoOpProgressFacade();
    var engine = getOrCreateConnectedEngine(connectionId)
      .orElseThrow(() -> new IllegalArgumentException(String.format("No connected engine found with ID '%s'", connectionId)));
    try {
      var httpClient = backendServiceFacade.getHttpClient(connectionId);
      return engine.downloadAllProjects(endpointParams, httpClient, progress.asCoreMonitor())
        .values()
        .stream()
        .collect(Collectors.toMap(ServerProject::getKey, ServerProject::getName));
    } catch (DownloadException downloadFailed) {
      throw new IllegalStateException(String.format("Failed to fetch list of projects from '%s'", connectionId), downloadFailed);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy