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

org.sonarsource.sonarlint.ls.connected.sync.ServerSynchronizer 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.sync;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine;
import org.sonarsource.sonarlint.core.commons.progress.CanceledException;
import org.sonarsource.sonarlint.core.commons.progress.ClientProgressMonitor;
import org.sonarsource.sonarlint.core.http.HttpClient;
import org.sonarsource.sonarlint.core.serverapi.EndpointParams;
import org.sonarsource.sonarlint.ls.AnalysisScheduler;
import org.sonarsource.sonarlint.ls.backend.BackendServiceFacade;
import org.sonarsource.sonarlint.ls.connected.ProjectBindingManager;
import org.sonarsource.sonarlint.ls.log.LanguageClientLogOutput;
import org.sonarsource.sonarlint.ls.progress.ProgressFacade;
import org.sonarsource.sonarlint.ls.progress.ProgressManager;

public class ServerSynchronizer {

  private final LanguageClient client;
  private final ProgressManager progressManager;
  private final ProjectBindingManager bindingManager;
  private final AnalysisScheduler analysisScheduler;
  private final Timer serverSyncTimer;
  private final BackendServiceFacade backendServiceFacade;
  private final LanguageClientLogOutput logOutput;

  public ServerSynchronizer(LanguageClient client, ProgressManager progressManager, ProjectBindingManager bindingManager,
    AnalysisScheduler analysisScheduler, BackendServiceFacade backendServiceFacade, LanguageClientLogOutput logOutput) {
    this(client, progressManager, bindingManager, analysisScheduler, new Timer("Binding updates checker"), backendServiceFacade, logOutput);
  }

  ServerSynchronizer(LanguageClient client, ProgressManager progressManager, ProjectBindingManager bindingManager,
    AnalysisScheduler analysisScheduler, Timer serverSyncTimer, BackendServiceFacade backendServiceFacade, LanguageClientLogOutput logOutput) {
    this.client = client;
    this.progressManager = progressManager;
    this.bindingManager = bindingManager;
    this.analysisScheduler = analysisScheduler;
    this.backendServiceFacade = backendServiceFacade;
    this.logOutput = logOutput;
    var syncPeriod = Long.parseLong(StringUtils.defaultIfBlank(System.getenv("SONARLINT_INTERNAL_SYNC_PERIOD"), "3600")) * 1000;
    this.serverSyncTimer = serverSyncTimer;
    this.serverSyncTimer.scheduleAtFixedRate(new SyncTask(), syncPeriod, syncPeriod);
  }

  public void updateAllBindings(CancelChecker cancelToken, @Nullable Either workDoneToken) {
    progressManager.doWithProgress("Update bindings", workDoneToken, cancelToken, progress -> {
      // Clear cached bindings to force rebind during next analysis
      bindingManager.clearBindingCache();
      updateBindings(bindingManager.getActiveConnectionsAndProjects(), progress);
      bindingManager.updateAllTaintIssues();
    });
  }

  private void updateBindings(Map>> projectKeyByConnectionIdsToUpdate,
    ProgressFacade progress) {
    var failedConnectionIds = tryUpdateConnectionsAndBoundProjectStorages(projectKeyByConnectionIdsToUpdate, progress);
    showOperationResult(failedConnectionIds);
    triggerAnalysisOfAllOpenFilesInBoundFolders(failedConnectionIds);
  }

  private void triggerAnalysisOfAllOpenFilesInBoundFolders(Set failedConnectionIds) {
    bindingManager.forEachBoundFolder((folder, folderSettings) -> {
      if (!failedConnectionIds.contains(folderSettings.getConnectionId())) {
        analysisScheduler.analyzeAllOpenFilesInFolder(folder);
      }
    });
  }

  private void showOperationResult(Set failedConnectionIds) {
    if (failedConnectionIds.isEmpty()) {
      client.showMessage(new MessageParams(MessageType.Info, "All SonarLint bindings successfully updated"));
    } else {
      var connections = String.join(", ", failedConnectionIds);
      client.showMessage(
        new MessageParams(MessageType.Error, "Binding update failed for the following connection(s): " + connections + ". Look at the SonarLint output for details."));
    }
  }

  private Set tryUpdateConnectionsAndBoundProjectStorages(Map>> projectKeyByConnectionIdsToUpdate, ProgressFacade progress) {
    var failedConnectionIds = new LinkedHashSet();
    projectKeyByConnectionIdsToUpdate.forEach(
      (connectionId, projectKeys) -> {
        var progressFraction = 1.0f / projectKeyByConnectionIdsToUpdate.size();
        var httpClient = backendServiceFacade.getHttpClient(connectionId);
        tryUpdateConnectionAndBoundProjectsStorages(progress, failedConnectionIds, connectionId, projectKeys, progressFraction, httpClient);
      });
    return failedConnectionIds;
  }

  private void tryUpdateConnectionAndBoundProjectsStorages(ProgressFacade progress, Set failedConnectionIds, String connectionId,
    Map> branchNamesByProjectKey, float progressFraction, HttpClient httpClient) {
    progress.doInSubProgress(connectionId, progressFraction, subProgress -> {
      var endpointParams = bindingManager.getEndpointParamsFor(connectionId);
      if (endpointParams == null) {
        failedConnectionIds.add(connectionId);
        return;
      }
      var engineOpt = bindingManager.getOrCreateConnectedEngine(connectionId);
      if (engineOpt.isEmpty()) {
        failedConnectionIds.add(connectionId);
        return;
      }
      subProgress.doInSubProgress("Update projects storages", 0.5f, s -> tryUpdateBoundProjectsStorage(
        branchNamesByProjectKey.keySet(), endpointParams, engineOpt.get(), httpClient, s));
      subProgress.doInSubProgress("Sync projects storages", 0.5f, s -> syncOneEngine(
        connectionId, branchNamesByProjectKey, engineOpt.get(), httpClient, s));
    });
  }

  private void tryUpdateBoundProjectsStorage(Set projectKeys, EndpointParams endpointParams,
    ConnectedSonarLintEngine engine, HttpClient httpClient, ProgressFacade progress) {
    projectKeys.forEach(projectKey -> progress.doInSubProgress(projectKey, 1.0f / projectKeys.size(), subProgress -> {
      try {
        engine.updateProject(endpointParams, httpClient, projectKey, subProgress.asCoreMonitor());
      } catch (CanceledException e) {
        throw e;
      } catch (Exception updateFailed) {
        logOutput.error("Binding update failed for project key '%s'", projectKey, updateFailed);
      }
    }));
  }

  private void syncOneEngine(String connectionId, Map> branchNamesByProjectKey, ConnectedSonarLintEngine engine,
  HttpClient httpClient, @Nullable ProgressFacade progress) {
    try {
      var endpointParams = bindingManager.getEndpointParamsFor(connectionId);
      if (endpointParams == null) {
        return;
      }
      var progressMonitor = progress != null ? progress.asCoreMonitor() : null;
      engine.sync(endpointParams, httpClient, branchNamesByProjectKey.keySet(), progressMonitor);
      syncIssues(engine, endpointParams, branchNamesByProjectKey, httpClient, progressMonitor);
    } catch (Exception e) {
      logOutput.error("Error while synchronizing storage", e);
    }
  }

  private static void syncIssues(ConnectedSonarLintEngine engine, EndpointParams endpointParams,
    Map> branchNamesByProjectKey,
    HttpClient httpClient,
    @Nullable ClientProgressMonitor progressMonitor) {
    branchNamesByProjectKey
      .forEach((projectKey, branchNames) -> branchNames.forEach(branchName -> syncIssuesForBranch(engine, endpointParams, projectKey, branchName, httpClient, progressMonitor)));
  }

  private static void syncIssuesForBranch(ConnectedSonarLintEngine engine, EndpointParams endpointParams, String projectKey,
    String branchName, HttpClient httpClient, @Nullable ClientProgressMonitor progressMonitor) {
    engine.syncServerIssues(endpointParams, httpClient, projectKey, branchName, progressMonitor);
    engine.syncServerTaintIssues(endpointParams, httpClient, projectKey, branchName, progressMonitor);
    engine.syncServerHotspots(endpointParams, httpClient, projectKey, branchName, progressMonitor);
  }

  public void shutdown() {
    serverSyncTimer.cancel();
  }

  private class SyncTask extends TimerTask {
    @Override
    public void run() {
      syncBoundProjects();
    }

    private void syncBoundProjects() {
      var projectsToSynchronize = bindingManager.getActiveConnectionsAndProjects();
      if (!projectsToSynchronize.isEmpty()) {

        logOutput.debug("Synchronizing storages...");
        projectsToSynchronize.forEach((connectionId, branchNamesByProjectKey) -> bindingManager.getStartedConnectedEngine(connectionId)
          .ifPresent(engine -> {
            var httpClient = backendServiceFacade.getHttpClient(connectionId);
            syncOneEngine(connectionId, branchNamesByProjectKey, engine, httpClient, null);
          })
        );
        bindingManager.updateAllTaintIssues();
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy