From 6a60ba7e971f39e8db9c3c4514d5decf3834f1fb Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Tue, 10 Dec 2024 11:03:49 +0100 Subject: [PATCH 1/6] SLCORE-1032 Wrong token error during sync - [New design] --- .../sonarlint/core/ServerApiProvider.java | 54 ++++++++- .../sonarlint/core/SonarProjectsCache.java | 7 +- .../core/VersionSoonUnsupportedHelper.java | 34 +++--- .../core/connection/ConnectionManager.java | 42 +++++++ .../core/connection/ConnectionState.java | 24 ++++ .../core/connection/ServerConnection.java | 103 ++++++++++++++++++ .../server/ShowHotspotRequestHandler.java | 7 +- .../server/ShowIssueRequestHandler.java | 12 +- .../core/file/ServerFilePathsProvider.java | 12 +- .../core/hotspot/HotspotService.java | 17 ++- .../sonarlint/core/issue/IssueService.java | 36 +++--- .../sonarlint/core/rules/RulesService.java | 28 ++--- .../SmartNotifications.java | 3 +- .../sync/HotspotSynchronizationService.java | 8 +- .../sync/IssueSynchronizationService.java | 8 +- ...ProjectBranchesSynchronizationService.java | 8 +- .../core/sync/SynchronizationService.java | 36 ++++-- .../sync/TaintSynchronizationService.java | 2 +- .../core/ServerApiProviderTests.java | 5 +- .../core/SonarProjectsCacheTests.java | 9 +- .../VersionSoonUnsupportedHelperTests.java | 46 +++----- .../server/ShowIssueRequestHandlerTests.java | 15 +-- .../file/ServerFilePathsProviderTest.java | 10 +- .../core/rules/RulesServiceTests.java | 5 +- .../src/test/java/testutils/TestUtils.java | 19 ++++ .../core/serverapi/ServerApiHelper.java | 9 +- .../exception/ForbiddenException.java | 26 +++++ .../exception/UnauthorizedException.java | 26 +++++ .../LocalStorageSynchronizer.java | 1 + .../src/main/proto/sonarlint.proto | 4 + .../client/SonarLintRpcClientDelegate.java | 3 + .../rpc/client/SonarLintRpcClientImpl.java | 6 + .../mediumtest/EffectiveRulesMediumTests.java | 2 +- .../NotebookLanguageMediumTests.java | 19 ++-- .../ConnectionSyncMediumTests.java | 34 +++++- .../core/rpc/protocol/SonarLintRpcClient.java | 5 + .../client/sync/InvalidTokenParams.java | 33 ++++++ .../test/utils/SonarLintBackendFixture.java | 10 ++ .../core/test/utils/server/ServerFixture.java | 47 +++++--- 39 files changed, 587 insertions(+), 188 deletions(-) create mode 100644 backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java create mode 100644 backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionState.java create mode 100644 backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java create mode 100644 backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ForbiddenException.java create mode 100644 backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnauthorizedException.java create mode 100644 rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/InvalidTokenParams.java diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java index d2b598f784..3032ba5141 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java @@ -19,19 +19,25 @@ */ package org.sonarsource.sonarlint.core; +import com.google.common.annotations.VisibleForTesting; import java.net.URI; import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; import javax.annotation.Nullable; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; +import org.sonarsource.sonarlint.core.connection.ConnectionManager; +import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider; import org.sonarsource.sonarlint.core.http.HttpClient; import org.sonarsource.sonarlint.core.http.HttpClientProvider; import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository; +import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient; import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto; @@ -47,19 +53,21 @@ @Named @Singleton -public class ServerApiProvider { +public class ServerApiProvider implements ConnectionManager { private static final SonarLintLogger LOG = SonarLintLogger.get(); private final ConnectionConfigurationRepository connectionRepository; private final ConnectionAwareHttpClientProvider awareHttpClientProvider; private final HttpClientProvider httpClientProvider; + private final SonarLintRpcClient client; private final URI sonarCloudUri; public ServerApiProvider(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider, - SonarCloudActiveEnvironment sonarCloudActiveEnvironment) { + SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarLintRpcClient client) { this.connectionRepository = connectionRepository; this.awareHttpClientProvider = awareHttpClientProvider; this.httpClientProvider = httpClientProvider; + this.client = client; this.sonarCloudUri = sonarCloudActiveEnvironment.getUri(); } @@ -100,7 +108,7 @@ public ServerApi getServerApi(String baseUrl, @Nullable String organization, Str return new ServerApi(params, httpClientProvider.getHttpClientWithPreemptiveAuth(token, isBearerSupported)); } - public ServerApi getServerApiOrThrow(String connectionId) { + private ServerApi getServerApiOrThrow(String connectionId) { var params = connectionRepository.getEndpointParams(connectionId); if (params.isEmpty()) { var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, "Connection '" + connectionId + "' is gone", connectionId); @@ -137,4 +145,44 @@ private HttpClient getClientFor(EndpointParams params, Either httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword())); } + @Override + public ServerConnection getConnectionOrThrow(String connectionId) { + var serverApi = getServerApiOrThrow(connectionId); + return new ServerConnection(connectionId, serverApi, client); + } + + public Optional tryGetConnection(String connectionId) { + return getServerApi(connectionId) + .map(serverApi -> new ServerConnection(connectionId, serverApi, client)); + } + + public Optional tryGetConnectionWithoutCredentials(String connectionId) { + return getServerApiWithoutCredentials(connectionId) + .map(serverApi -> new ServerConnection(connectionId, serverApi, client)); + } + + @Override + public ServerApi getTransientConnection(String token,@Nullable String organization, String baseUrl) { + return getServerApi(baseUrl, organization, token); + } + + @Override + public void withValidConnection(String connectionId, Consumer serverApiConsumer) { + getValidConnection(connectionId).ifPresent(connection -> connection.withClientApi(serverApiConsumer)); + } + + @Override + public Optional withValidConnectionAndReturn(String connectionId, Function serverApiConsumer) { + return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)); + } + + @Override + public Optional withValidConnectionFlatMapOptionalAndReturn(String connectionId, Function> serverApiConsumer) { + return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)).flatMap(Function.identity()); + } + + @VisibleForTesting + public Optional getValidConnection(String connectionId) { + return tryGetConnection(connectionId).filter(ServerConnection::isValid); + } } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java index d18f76073a..e0e8171f69 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java @@ -120,7 +120,8 @@ public Optional getSonarProject(String connectionId, String sonar return singleProjectsCache.get(new SonarProjectKey(connectionId, sonarProjectKey), () -> { LOG.debug("Query project '{}' on connection '{}'...", sonarProjectKey, connectionId); try { - return serverApiProvider.getServerApi(connectionId).flatMap(s -> s.component().getProject(sonarProjectKey, cancelMonitor)); + return serverApiProvider.withValidConnectionAndReturn(connectionId, + s -> s.component().getProject(sonarProjectKey, cancelMonitor)).orElse(Optional.empty()); } catch (Exception e) { LOG.error("Error while querying project '{}' from connection '{}'", sonarProjectKey, connectionId, e); return Optional.empty(); @@ -137,7 +138,9 @@ public TextSearchIndex getTextSearchIndex(String connectionId, So LOG.debug("Load projects from connection '{}'...", connectionId); List projects; try { - projects = serverApiProvider.getServerApi(connectionId).map(s -> s.component().getAllProjects(cancelMonitor)).orElse(List.of()); + projects = serverApiProvider.withValidConnectionAndReturn(connectionId, + s -> s.component().getAllProjects(cancelMonitor)) + .orElse(List.of()); } catch (Exception e) { LOG.error("Error while querying projects from connection '{}'", connectionId, e); return new TextSearchIndex<>(); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java index 749cf2a105..d237cd70d9 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java @@ -107,23 +107,23 @@ private void queueCheckIfSoonUnsupported(String connectionId, String configScope try { var connection = connectionRepository.getConnectionById(connectionId); if (connection != null && connection.getKind() == ConnectionKind.SONARQUBE) { - var serverApi = serverApiProvider.getServerApiWithoutCredentials(connectionId); - if (serverApi.isPresent()) { - var version = synchronizationService.getServerConnection(connectionId, serverApi.get()).readOrSynchronizeServerVersion(serverApi.get(), cancelMonitor); - var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0; - if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) { - client.showSoonUnsupportedMessage( - new ShowSoonUnsupportedMessageParams( - String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()), - configScopeId, - String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts()) - ) - ); - LOG.debug(String.format("Connection '%s' with version '%s' is detected to be soon unsupported", - connection.getConnectionId(), version.getName())); - } - cacheConnectionIdPerVersion.put(connectionId, version); - } + serverApiProvider.tryGetConnectionWithoutCredentials(connectionId) + .ifPresent(serverConnection -> serverConnection.withClientApi(serverApi -> { + var version = synchronizationService.readOrSynchronizeServerVersion(connectionId, serverApi, cancelMonitor); + var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0; + if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) { + client.showSoonUnsupportedMessage( + new ShowSoonUnsupportedMessageParams( + String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()), + configScopeId, + String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts()) + ) + ); + LOG.debug(String.format("Connection '%s' with version '%s' is detected to be soon unsupported", + connection.getConnectionId(), version.getName())); + } + cacheConnectionIdPerVersion.put(connectionId, version); + })); } } catch (Exception e) { LOG.error("Error while checking if soon unsupported", e); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java new file mode 100644 index 0000000000..144b5ba5db --- /dev/null +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java @@ -0,0 +1,42 @@ +/* + * SonarLint Core - Implementation + * Copyright (C) 2016-2025 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.core.connection; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.sonarsource.sonarlint.core.serverapi.ServerApi; + +public interface ConnectionManager { + ServerConnection getConnectionOrThrow(String connectionId); + /** + * Having dedicated TransientConnection class makes sense only if we handle the connection errors from there. + * Which brings up the problem of managing global state for notifications because we don't know the connection ID.

+ * On other hand providing ServerApis directly, all Web API calls from transient ServerApi are not protected by checks for connection state. + * So we still can spam server with unprotected requests. + * It's not a big problem because we don't use such requests during scheduled sync. + * They are mostly related to setting up the connection or other user-triggered actions. + */ + ServerApi getTransientConnection(String token, @Nullable String organization, String baseUrl); + void withValidConnection(String connectionId, Consumer serverApiConsumer); + Optional withValidConnectionAndReturn(String connectionId, Function serverApiConsumer); + Optional withValidConnectionFlatMapOptionalAndReturn(String connectionId, Function> serverApiConsumer); +} diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionState.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionState.java new file mode 100644 index 0000000000..29fdf3defe --- /dev/null +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionState.java @@ -0,0 +1,24 @@ +/* + * SonarLint Core - Implementation + * Copyright (C) 2016-2025 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.core.connection; + +public enum ConnectionState { + ACTIVE, INVALID_CREDENTIALS, MISSING_PERMISSION +} diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java new file mode 100644 index 0000000000..c4dc56603c --- /dev/null +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java @@ -0,0 +1,103 @@ +/* + * SonarLint Core - Implementation + * Copyright (C) 2016-2025 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.core.connection; + +import java.time.Instant; +import java.time.Period; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient; +import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams; +import org.sonarsource.sonarlint.core.serverapi.ServerApi; +import org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException; +import org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException; + +public class ServerConnection { + + private static final Period WRONG_TOKEN_NOTIFICATION_INTERVAL = Period.ofDays(1); + private final String connectionId; + private final ServerApi serverApi; + private final SonarLintRpcClient client; + private ConnectionState state = ConnectionState.ACTIVE; + @Nullable + private Instant lastNotificationTime; + + public ServerConnection(String connectionId, ServerApi serverApi, SonarLintRpcClient client) { + this.connectionId = connectionId; + this.serverApi = serverApi; + this.client = client; + } + + public boolean isSonarCloud() { + return serverApi.isSonarCloud(); + } + + public boolean isValid() { + return state == ConnectionState.ACTIVE; + } + + public T withClientApiAndReturn(Function serverApiConsumer) { + try { + var result = serverApiConsumer.apply(serverApi); + state = ConnectionState.ACTIVE; + lastNotificationTime = null; + return result; + } catch (ForbiddenException e) { + state = ConnectionState.INVALID_CREDENTIALS; + notifyClientAboutWrongTokenIfNeeded(); + } catch (UnauthorizedException e) { + state = ConnectionState.MISSING_PERMISSION; + notifyClientAboutWrongTokenIfNeeded(); + } + return null; + } + + public void withClientApi(Consumer serverApiConsumer) { + try { + serverApiConsumer.accept(serverApi); + state = ConnectionState.ACTIVE; + lastNotificationTime = null; + } catch (ForbiddenException e) { + state = ConnectionState.INVALID_CREDENTIALS; + notifyClientAboutWrongTokenIfNeeded(); + } catch (UnauthorizedException e) { + state = ConnectionState.MISSING_PERMISSION; + notifyClientAboutWrongTokenIfNeeded(); + } + } + + private boolean shouldNotifyAboutWrongToken() { + if (state != ConnectionState.INVALID_CREDENTIALS && state != ConnectionState.MISSING_PERMISSION) { + return false; + } + if (lastNotificationTime == null) { + return true; + } + return lastNotificationTime.plus(WRONG_TOKEN_NOTIFICATION_INTERVAL).isBefore(Instant.now()); + } + + private void notifyClientAboutWrongTokenIfNeeded() { + if (shouldNotifyAboutWrongToken()) { + client.invalidToken(new InvalidTokenParams(connectionId)); + lastNotificationTime = Instant.now(); + } + } +} diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java index ccb4dab53d..17f711a190 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java @@ -105,12 +105,7 @@ private void showHotspotForScope(String connectionId, String configurationScopeI } private Optional tryFetchHotspot(String connectionId, String hotspotKey, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getServerApi(connectionId); - if (serverApi.isEmpty()) { - // should not happen since we found the connection just before, improve the design ? - return Optional.empty(); - } - return serverApi.get().hotspot().fetch(hotspotKey, cancelMonitor); + return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(connectionId,api -> api.hotspot().fetch(hotspotKey, cancelMonitor)); } private static HotspotDetailsDto adapt(String hotspotKey, ServerHotspotDetails hotspot, FilePathTranslation translation) { diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java index 0c9bb7e1a5..2fb9d6976e 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java @@ -188,18 +188,14 @@ static boolean isIssueTaint(String ruleKey) { private Optional tryFetchIssue(String connectionId, String issueKey, String projectKey, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getServerApiOrThrow(connectionId); - return serverApi.issue().fetchServerIssue(issueKey, projectKey, branch, pullRequest, cancelMonitor); + return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(connectionId, + serverApi -> serverApi.issue().fetchServerIssue(issueKey, projectKey, branch, pullRequest, cancelMonitor)); } private Optional tryFetchCodeSnippet(String connectionId, String fileKey, Common.TextRange textRange, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getServerApi(connectionId); - if (serverApi.isEmpty() || fileKey.isEmpty()) { - // should not happen since we found the connection just before, improve the design ? - return Optional.empty(); - } - return serverApi.get().issue().getCodeSnippet(fileKey, textRange, branch, pullRequest, cancelMonitor); + return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(connectionId, + api -> api.issue().getCodeSnippet(fileKey, textRange, branch, pullRequest, cancelMonitor)); } @VisibleForTesting diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java index d84bd6f147..2d6cf955ad 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java @@ -103,15 +103,17 @@ private Optional> getPathsFromFileCache(Binding binding) { } private Optional> fetchPathsFromServer(Binding binding, SonarLintCancelMonitor cancelMonitor) { - var serverApiOpt = serverApiProvider.getServerApi(binding.getConnectionId()); - if (serverApiOpt.isEmpty()) { + var connectionOpt = serverApiProvider.tryGetConnection(binding.getConnectionId()); + if (connectionOpt.isEmpty()) { LOG.debug("Connection '{}' does not exist", binding.getConnectionId()); return Optional.empty(); } try { - List paths = fetchPathsFromServer(serverApiOpt.get(), binding.getSonarProjectKey(), cancelMonitor); - cacheServerPaths(binding, paths); - return Optional.of(paths); + return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(binding.getConnectionId(), serverApi -> { + List paths = fetchPathsFromServer(serverApi, binding.getSonarProjectKey(), cancelMonitor); + cacheServerPaths(binding, paths); + return Optional.of(paths); + }); } catch (CancellationException e) { throw e; } catch (Exception e) { diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java index 25884e41b6..f908f48a83 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java @@ -128,8 +128,8 @@ public CheckLocalDetectionSupportedResponse checkLocalDetectionSupported(String public CheckStatusChangePermittedResponse checkStatusChangePermitted(String connectionId, String hotspotKey, SonarLintCancelMonitor cancelMonitor) { // fixme add getConnectionByIdOrThrow var connection = connectionRepository.getConnectionById(connectionId); - var serverApi = serverApiProvider.getServerApiOrThrow(connectionId); - var r = serverApi.hotspot().show(hotspotKey, cancelMonitor); + var r = serverApiProvider.getConnectionOrThrow(connectionId) + .withClientApiAndReturn(serverApi -> serverApi.hotspot().show(hotspotKey, cancelMonitor)); var allowedStatuses = HotspotReviewStatus.allowedStatusesOn(connection.getKind()); // canChangeStatus is false when the 'Administer Hotspots' permission is missing // normally the 'Browse' permission is also required, but we assume it's present as the client knows the hotspot key @@ -151,14 +151,11 @@ public void changeStatus(String configurationScopeId, String hotspotKey, Hotspot LOG.debug("No binding for config scope {}", configurationScopeId); return; } - var connectionOpt = serverApiProvider.getServerApi(effectiveBindingOpt.get().getConnectionId()); - if (connectionOpt.isEmpty()) { - LOG.debug("Connection {} is gone", effectiveBindingOpt.get().getConnectionId()); - return; - } - connectionOpt.get().hotspot().changeStatus(hotspotKey, newStatus, cancelMonitor); - saveStatusInStorage(effectiveBindingOpt.get(), hotspotKey, newStatus); - telemetryService.hotspotStatusChanged(); + serverApiProvider.withValidConnection(effectiveBindingOpt.get().getConnectionId(), serverApi -> { + serverApi.hotspot().changeStatus(hotspotKey, newStatus, cancelMonitor); + saveStatusInStorage(effectiveBindingOpt.get(), hotspotKey, newStatus); + telemetryService.hotspotStatusChanged(); + }); } private void saveStatusInStorage(Binding binding, String hotspotKey, HotspotReviewStatus newStatus) { diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java index bf0929fdb5..9f7f7b8a62 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java @@ -129,12 +129,12 @@ public IssueService(ConfigurationRepository configurationRepository, ServerApiPr public void changeStatus(String configurationScopeId, String issueKey, ResolutionStatus newStatus, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) { var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverApi = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); + var serverConnection = serverApiProvider.getConnectionOrThrow(binding.getConnectionId()); var reviewStatus = transitionByResolutionStatus.get(newStatus); var projectServerIssueStore = storageService.binding(binding).findings(); boolean isServerIssue = projectServerIssueStore.containsIssue(issueKey); if (isServerIssue) { - serverApi.issue().changeStatus(issueKey, reviewStatus, cancelMonitor); + serverConnection.withClientApi(serverApi -> serverApi.issue().changeStatus(issueKey, reviewStatus, cancelMonitor)); projectServerIssueStore.updateIssueResolutionStatus(issueKey, isTaintIssue, true) .ifPresent(issue -> eventPublisher.publishEvent(new ServerIssueStatusChangedEvent(binding.getConnectionId(), binding.getSonarProjectKey(), issue))); } else { @@ -147,7 +147,8 @@ public void changeStatus(String configurationScopeId, String issueKey, Resolutio var issue = localIssueOpt.get(); issue.resolve(coreStatus); var localOnlyIssueStore = localOnlyIssueStorageService.get(); - serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), concat(localOnlyIssueStore.loadAll(configurationScopeId), issue), cancelMonitor); + serverConnection.withClientApi(serverApi -> serverApi.issue() + .anticipatedTransitions(binding.getSonarProjectKey(), concat(localOnlyIssueStore.loadAll(configurationScopeId), issue), cancelMonitor)); localOnlyIssueStore.storeLocalOnlyIssue(configurationScopeId, issue); eventPublisher.publishEvent(new LocalOnlyIssueStatusChangedEvent(issue)); } @@ -166,8 +167,8 @@ private static List subtract(List allIssues, Lis public boolean checkAnticipatedStatusChangeSupported(String configScopeId) { var binding = configurationRepository.getEffectiveBindingOrThrow(configScopeId); var connectionId = binding.getConnectionId(); - var serverApi = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); - return checkAnticipatedStatusChangeSupported(serverApi, connectionId); + return serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + .withClientApiAndReturn(serverApi -> checkAnticipatedStatusChangeSupported(serverApi, connectionId)); } /** @@ -184,8 +185,7 @@ private boolean checkAnticipatedStatusChangeSupported(ServerApi api, String conn } public CheckStatusChangePermittedResponse checkStatusChangePermitted(String connectionId, String issueKey, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getServerApiOrThrow(connectionId); - return asUUID(issueKey) + return serverApiProvider.getConnectionOrThrow(connectionId).withClientApiAndReturn(serverApi -> asUUID(issueKey) .flatMap(localOnlyIssueRepository::findByKey) .map(r -> { // For anticipated issues we currently don't get the information from SonarQube (as there is no web API @@ -202,7 +202,7 @@ public CheckStatusChangePermittedResponse checkStatusChangePermitted(String conn .orElseGet(() -> { var issue = serverApi.issue().searchByKey(issueKey, cancelMonitor); return toResponse(getAdministerIssueTransitions(issue), STATUS_CHANGE_PERMISSION_MISSING_REASON); - }); + })); } /** @@ -260,11 +260,11 @@ public void addComment(String configurationScopeId, String issueKey, String text public boolean reopenIssue(String configurationScopeId, String issueId, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) { var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverApiConnection = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); var projectServerIssueStore = storageService.binding(binding).findings(); boolean isServerIssue = projectServerIssueStore.containsIssue(issueId); if (isServerIssue) { - return reopenServerIssue(serverApiConnection, binding, issueId, projectServerIssueStore, isTaintIssue, cancelMonitor); + return serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + .withClientApiAndReturn(serverApi -> reopenServerIssue(serverApi, binding, issueId, projectServerIssueStore, isTaintIssue, cancelMonitor)); } else { return reopenLocalIssue(issueId, configurationScopeId, cancelMonitor); } @@ -284,8 +284,8 @@ private void removeAllIssuesForFile(XodusLocalOnlyIssueStore localOnlyIssueStore var issuesForFile = localOnlyIssueStore.loadForFile(configurationScopeId, filePath); var issuesToSync = subtract(allIssues, issuesForFile); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverConnection = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); - serverConnection.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor); + serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor)); } private void removeIssueOnServer(XodusLocalOnlyIssueStore localOnlyIssueStore, @@ -293,8 +293,8 @@ private void removeIssueOnServer(XodusLocalOnlyIssueStore localOnlyIssueStore, var allIssues = localOnlyIssueStore.loadAll(configurationScopeId); var issuesToSync = allIssues.stream().filter(it -> !it.getId().equals(issueId)).collect(Collectors.toList()); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverConnection = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); - serverConnection.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor); + serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor)); } private void setCommentOnLocalOnlyIssue(String configurationScopeId, UUID issueId, String comment, SonarLintCancelMonitor cancelMonitor) { @@ -308,8 +308,8 @@ private void setCommentOnLocalOnlyIssue(String configurationScopeId, UUID issueI var issuesToSync = localOnlyIssueStore.loadAll(configurationScopeId); issuesToSync.replaceAll(issue -> issue.getId().equals(issueId) ? commentedIssue : issue); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverApi = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); - serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor); + serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor)); localOnlyIssueStore.storeLocalOnlyIssue(configurationScopeId, commentedIssue); } } else { @@ -324,8 +324,8 @@ private static ResponseErrorException issueNotFoundException(String issueId) { private void addCommentOnServerIssue(String configurationScopeId, String issueKey, String comment, SonarLintCancelMonitor cancelMonitor) { var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverApi = serverApiProvider.getServerApiOrThrow(binding.getConnectionId()); - serverApi.issue().addComment(issueKey, comment, cancelMonitor); + serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + .withClientApi(serverApi -> serverApi.issue().addComment(issueKey, comment, cancelMonitor)); } private boolean reopenServerIssue(ServerApi connection, Binding binding, String issueId, ProjectServerIssueStore projectServerIssueStore, boolean isTaintIssue, diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java index cbb56d5503..77b537974e 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java @@ -47,7 +47,6 @@ import org.sonarsource.sonarlint.core.mode.SeverityModeService; import org.sonarsource.sonarlint.core.reporting.FindingReportingService; import org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository; -import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository; import org.sonarsource.sonarlint.core.repository.rules.RulesRepository; import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode; import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams; @@ -93,20 +92,18 @@ public class RulesService { private final Map standaloneRuleConfig = new ConcurrentHashMap<>(); private FindingReportingService findingReportingService; private final SeverityModeService severityModeService; - private final ConnectionConfigurationRepository connectionConfigurationRepository; @Inject public RulesService(ServerApiProvider serverApiProvider, ConfigurationRepository configurationRepository, RulesRepository rulesRepository, StorageService storageService, InitializeParams params, ApplicationEventPublisher eventPublisher, - SeverityModeService severityModeService, ConnectionConfigurationRepository connectionConfigurationRepository) { + SeverityModeService severityModeService) { this(serverApiProvider, configurationRepository, rulesRepository, storageService, eventPublisher, - params.getStandaloneRuleConfigByKey(), severityModeService, connectionConfigurationRepository); + params.getStandaloneRuleConfigByKey(), severityModeService); } RulesService(ServerApiProvider serverApiProvider, ConfigurationRepository configurationRepository, RulesRepository rulesRepository, StorageService storageService, ApplicationEventPublisher eventPublisher, - @Nullable Map standaloneRuleConfigByKey, SeverityModeService severityModeService, - ConnectionConfigurationRepository connectionConfigurationRepository) { + @Nullable Map standaloneRuleConfigByKey, SeverityModeService severityModeService) { this.serverApiProvider = serverApiProvider; this.configurationRepository = configurationRepository; this.rulesRepository = rulesRepository; @@ -116,7 +113,6 @@ public RulesService(ServerApiProvider serverApiProvider, ConfigurationRepository if (standaloneRuleConfigByKey != null) { this.standaloneRuleConfig.putAll(standaloneRuleConfigByKey); } - this.connectionConfigurationRepository = connectionConfigurationRepository; } public EffectiveRuleDetailsDto getEffectiveRuleDetails(String configurationScopeId, String ruleKey, @Nullable String contextKey, @@ -142,11 +138,7 @@ public RuleDetails getRuleDetails(String configurationScopeId, String ruleKey, S public RuleDetails getActiveRuleForBinding(String ruleKey, Binding binding, SonarLintCancelMonitor cancelMonitor) { var connectionId = binding.getConnectionId(); - - var endpointParams = connectionConfigurationRepository.getEndpointParams(connectionId); - if (endpointParams.isEmpty()) { - throw unknownConnection(connectionId); - } + serverApiProvider.getConnectionOrThrow(connectionId); var serverUsesStandardSeverityMode = !severityModeService.isMQRModeForConnection(connectionId); @@ -175,16 +167,16 @@ private Optional findServerActiveRuleInStorage(Binding binding private RuleDetails hydrateDetailsWithServer(String connectionId, ServerActiveRule activeRuleFromStorage, boolean skipCleanCodeTaxonomy, SonarLintCancelMonitor cancelMonitor) { var ruleKey = activeRuleFromStorage.getRuleKey(); var templateKey = activeRuleFromStorage.getTemplateKey(); - var serverApi = serverApiProvider.getServerApiOrThrow(connectionId); + var serverConnection = serverApiProvider.getConnectionOrThrow(connectionId); if (StringUtils.isNotBlank(templateKey)) { var templateRule = rulesRepository.getRule(connectionId, templateKey); if (templateRule.isEmpty()) { throw ruleDefinitionNotFound(templateKey); } - var serverRule = fetchRuleFromServer(connectionId, ruleKey, serverApi, cancelMonitor); + var serverRule = serverConnection.withClientApiAndReturn(serverApi -> fetchRuleFromServer(connectionId, ruleKey, serverApi, cancelMonitor)); return RuleDetails.merging(activeRuleFromStorage, serverRule, templateRule.get(), skipCleanCodeTaxonomy); } else { - var serverRule = fetchRuleFromServer(connectionId, ruleKey, serverApi, cancelMonitor); + var serverRule = serverConnection.withClientApiAndReturn(serverApi -> fetchRuleFromServer(connectionId, ruleKey, serverApi, cancelMonitor)); var ruleDefFromPluginOpt = rulesRepository.getRule(connectionId, ruleKey); return ruleDefFromPluginOpt .map(ruleDefFromPlugin -> RuleDetails.merging(serverRule, ruleDefFromPlugin, skipCleanCodeTaxonomy)) @@ -192,12 +184,6 @@ private RuleDetails hydrateDetailsWithServer(String connectionId, ServerActiveRu } } - @NotNull - private static ResponseErrorException unknownConnection(String connectionId) { - var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, "Connection with ID '" + connectionId + "' does not exist", connectionId); - return new ResponseErrorException(error); - } - private static ServerRule fetchRuleFromServer(String connectionId, String ruleKey, ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) { return serverApi.rules().getRule(ruleKey, cancelMonitor).orElseThrow(() -> ruleNotFoundOnServer(ruleKey, connectionId)); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java index 82303f8b08..a2b1590566 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java @@ -101,7 +101,8 @@ private void poll(SonarLintCancelMonitor cancelMonitor) { boundScopeByConnectionAndSonarProject.forEach((connectionId, boundScopesByProject) -> { var connection = connectionRepository.getConnectionById(connectionId); if (connection != null && !connection.isDisableNotifications() && !shouldSkipPolling(connection)) { - serverApiProvider.getServerApi(connectionId).ifPresent(serverApi -> manageNotificationsForConnection(serverApi, boundScopesByProject, connection, cancelMonitor)); + serverApiProvider.withValidConnection(connectionId, + serverApi -> manageNotificationsForConnection(serverApi, boundScopesByProject, connection, cancelMonitor)); } }); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java index a8e2ec1652..636c81f1b9 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java @@ -73,8 +73,8 @@ private static Version getSonarServerVersion(ServerApi serverApi, ConnectionStor } public void fetchProjectHotspots(Binding binding, String activeBranch, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.getServerApi(binding.getConnectionId()) - .ifPresent(serverApi -> downloadAllServerHotspots(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), activeBranch, cancelMonitor)); + serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + downloadAllServerHotspots(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), activeBranch, cancelMonitor)); } private void downloadAllServerHotspots(String connectionId, ServerApi serverApi, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) { @@ -87,8 +87,8 @@ private void downloadAllServerHotspots(String connectionId, ServerApi serverApi, } public void fetchFileHotspots(Binding binding, String activeBranch, Path serverFilePath, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.getServerApi(binding.getConnectionId()) - .ifPresent(serverApi -> downloadAllServerHotspotsForFile(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), serverFilePath, activeBranch, cancelMonitor)); + serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + downloadAllServerHotspotsForFile(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), serverFilePath, activeBranch, cancelMonitor)); } private void downloadAllServerHotspotsForFile(String connectionId, ServerApi serverApi, String projectKey, Path serverRelativeFilePath, String branchName, diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java index 53bcff828b..dd897db526 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java @@ -65,8 +65,8 @@ public void syncServerIssuesForProject(ServerApi serverApi, String connectionId, } public void fetchProjectIssues(Binding binding, String activeBranch, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.getServerApi(binding.getConnectionId()) - .ifPresent(serverApi -> downloadServerIssuesForProject(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), activeBranch, cancelMonitor)); + serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + downloadServerIssuesForProject(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), activeBranch, cancelMonitor)); } private void downloadServerIssuesForProject(String connectionId, ServerApi serverApi, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) { @@ -78,8 +78,8 @@ private void downloadServerIssuesForProject(String connectionId, ServerApi serve } public void fetchFileIssues(Binding binding, Path serverFileRelativePath, String activeBranch, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.getServerApi(binding.getConnectionId()) - .ifPresent(serverApi -> downloadServerIssuesForFile(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), serverFileRelativePath, activeBranch, cancelMonitor)); + serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + downloadServerIssuesForFile(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), serverFileRelativePath, activeBranch, cancelMonitor)); } public void downloadServerIssuesForFile(String connectionId, ServerApi serverApi, String projectKey, Path serverFileRelativePath, String branchName, diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java index 4d1605cb69..2177630f53 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java @@ -52,7 +52,7 @@ public SonarProjectBranchesSynchronizationService(StorageService storageService, } public void sync(String connectionId, String sonarProjectKey, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.getServerApi(connectionId).ifPresent(serverApi -> { + serverApiProvider.withValidConnection(connectionId, serverApi -> { var branchesStorage = storageService.getStorageFacade().connection(connectionId).project(sonarProjectKey).branches(); Optional oldBranches = Optional.empty(); if (branchesStorage.exists()) { @@ -81,9 +81,9 @@ public String findMainBranch(String connectionId, String projectKey, SonarLintCa var storedBranches = branchesStorage.read(); return storedBranches.getMainBranchName(); } else { - var serverApi = serverApiProvider.getServerApiOrThrow(connectionId); - var branches = getProjectBranches(serverApi, projectKey, cancelMonitor); - return branches.getMainBranchName(); + return serverApiProvider.withValidConnectionAndReturn(connectionId, + serverApi -> getProjectBranches(serverApi, projectKey, cancelMonitor)) + .map(ProjectBranches::getMainBranchName).orElseThrow(); } } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java index e24339abcf..beacfc13a4 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -33,16 +34,18 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Named; import javax.inject.Singleton; -import org.jetbrains.annotations.NotNull; import org.sonarsource.sonarlint.core.ServerApiProvider; import org.sonarsource.sonarlint.core.branch.MatchedSonarProjectBranchChangedEvent; import org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.BoundScope; +import org.sonarsource.sonarlint.core.commons.Version; +import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; import org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; @@ -59,7 +62,10 @@ import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.DidSynchronizeConfigurationScopeParams; import org.sonarsource.sonarlint.core.serverapi.ServerApi; -import org.sonarsource.sonarlint.core.serverconnection.ServerConnection; +import org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException; +import org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException; +import org.sonarsource.sonarlint.core.serverconnection.LocalStorageSynchronizer; +import org.sonarsource.sonarlint.core.serverconnection.ServerInfoSynchronizer; import org.sonarsource.sonarlint.core.serverconnection.SonarServerSettingsChangedEvent; import org.sonarsource.sonarlint.core.storage.StorageService; import org.springframework.context.ApplicationEventPublisher; @@ -178,7 +184,7 @@ private void synchronizeProjectsOfTheSameConnection(String connectionId, Map { + serverApiProvider.withValidConnection(connectionId, serverApi -> { var subProgressGap = progressGap / boundScopeBySonarProject.size(); var subProgress = progress; for (var entry : boundScopeBySonarProject.entrySet()) { @@ -207,10 +213,9 @@ private void synchronizeProjectWithProgress(ServerApi serverApi, String connecti })); } - @NotNull - public ServerConnection getServerConnection(String connectionId, ServerApi serverApi) { - return new ServerConnection(storageService.getStorageFacade(), connectionId, serverApi.isSonarCloud(), - languageSupportRepository.getEnabledLanguagesInConnectedMode(), connectedModeEmbeddedPluginKeys); + public Version readOrSynchronizeServerVersion(String connectionId, ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) { + var serverInfoSynchronizer = new ServerInfoSynchronizer(storageService.getStorageFacade().connection(connectionId)); + return serverInfoSynchronizer.readOrSynchronizeServerInfo(serverApi, cancelMonitor).getVersion(); } @EventListener @@ -291,8 +296,8 @@ public void onConnectionCredentialsChanged(ConnectionCredentialsChangedEvent eve private void synchronizeConnectionAndProjectsIfNeededAsync(String connectionId, Collection boundScopes) { var cancelMonitor = new SonarLintCancelMonitor(); cancelMonitor.watchForShutdown(scheduledSynchronizer); - scheduledSynchronizer.submit(() -> serverApiProvider.getServerApi(connectionId) - .ifPresent(serverApi -> synchronizeConnectionAndProjectsIfNeededSync(connectionId, serverApi, boundScopes, cancelMonitor))); + scheduledSynchronizer.submit(() -> serverApiProvider.withValidConnection(connectionId, serverApi -> + synchronizeConnectionAndProjectsIfNeededSync(connectionId, serverApi, boundScopes, cancelMonitor))); } private void synchronizeConnectionAndProjectsIfNeededSync(String connectionId, ServerApi serverApi, Collection boundScopes, SonarLintCancelMonitor cancelMonitor) { @@ -303,10 +308,14 @@ private void synchronizeConnectionAndProjectsIfNeededSync(String connectionId, S scopesToSync.forEach(scope -> scopeSynchronizationTimestampRepository.setLastSynchronizationTimestampToNow(scope.getConfigScopeId())); // We will already trigger a sync of the project storage so we can temporarily ignore branch changed event for these config scopes ignoreBranchEventForScopes.addAll(scopesToSync.stream().map(BoundScope::getConfigScopeId).collect(toSet())); - var serverConnection = getServerConnection(connectionId, serverApi); + var enabledLanguagesToSync = languageSupportRepository.getEnabledLanguagesInConnectedMode().stream() + .filter(SonarLanguage::shouldSyncInConnectedMode).collect(Collectors.toCollection(LinkedHashSet::new)); + var storage = storageService.getStorageFacade().connection(connectionId); + var serverInfoSynchronizer = new ServerInfoSynchronizer(storage); + var storageSynchronizer = new LocalStorageSynchronizer(enabledLanguagesToSync, connectedModeEmbeddedPluginKeys, serverInfoSynchronizer, storage); try { LOG.debug("Synchronizing storage of connection '{}'", connectionId); - var summary = serverConnection.sync(serverApi, cancelMonitor); + var summary = storageSynchronizer.synchronizeServerInfosAndPlugins(serverApi, cancelMonitor); if (summary.anyPluginSynchronized()) { // TODO re-review this solution before merging pluginsRepository.unload(connectionId); @@ -319,7 +328,7 @@ private void synchronizeConnectionAndProjectsIfNeededSync(String connectionId, S scopesPerProjectKey.forEach((projectKey, configScopeIds) -> { bindingSynchronizationTimestampRepository.setLastSynchronizationTimestampToNow(new Binding(connectionId, projectKey)); LOG.debug("Synchronizing storage of Sonar project '{}' for connection '{}'", projectKey, connectionId); - var analyzerConfigUpdateSummary = serverConnection.sync(serverApi, projectKey, cancelMonitor); + var analyzerConfigUpdateSummary = storageSynchronizer.synchronizeAnalyzerConfig(serverApi, projectKey, cancelMonitor); // XXX we might want to group those 2 events under one if (!analyzerConfigUpdateSummary.getUpdatedSettingsValueByKey().isEmpty()) { applicationEventPublisher.publishEvent( @@ -334,6 +343,9 @@ private void synchronizeConnectionAndProjectsIfNeededSync(String connectionId, S cancelMonitor); } catch (Exception e) { LOG.error("Error during synchronization", e); + if (e instanceof UnauthorizedException || e instanceof ForbiddenException) { + throw e; + } } finally { ignoreBranchEventForScopes.removeAll(scopesToSync.stream().map(BoundScope::getConfigScopeId).collect(toSet())); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java index 2130a7aa8b..4a82bb1d79 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java @@ -66,7 +66,7 @@ public TaintSynchronizationService(ConfigurationRepository configurationReposito } public void synchronizeTaintVulnerabilities(String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.getServerApi(connectionId).ifPresent(serverApi -> { + serverApiProvider.withValidConnection(connectionId, serverApi -> { var allScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey); var allScopesByOptBranch = allScopes.stream() .collect(groupingBy(b -> branchTrackingService.awaitEffectiveSonarProjectBranch(b.getConfigScopeId()))); diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java index dfbd963806..792c4e2c2a 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java @@ -30,6 +30,7 @@ import org.sonarsource.sonarlint.core.http.HttpClientProvider; import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository; import org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration; +import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient; import org.sonarsource.sonarlint.core.serverapi.EndpointParams; import static org.assertj.core.api.Assertions.assertThat; @@ -43,7 +44,9 @@ class ServerApiProviderTests { private final ConnectionConfigurationRepository connectionRepository = mock(ConnectionConfigurationRepository.class); private final ConnectionAwareHttpClientProvider awareHttpClientProvider = mock(ConnectionAwareHttpClientProvider.class); private final HttpClientProvider httpClientProvider = mock(HttpClientProvider.class); - private final ServerApiProvider underTest = new ServerApiProvider(connectionRepository, awareHttpClientProvider, httpClientProvider, SonarCloudActiveEnvironment.prod()); + private final SonarLintRpcClient client = mock(SonarLintRpcClient.class); + private final ServerApiProvider underTest = new ServerApiProvider(connectionRepository, awareHttpClientProvider, httpClientProvider, + SonarCloudActiveEnvironment.prod(), client); @Test void getServerApi_for_sonarqube() { diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java index 3ddcb74802..a98271aeb6 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java @@ -27,6 +27,7 @@ import org.mockito.Mockito; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; +import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.event.ConnectionConfigurationRemovedEvent; import org.sonarsource.sonarlint.core.event.ConnectionConfigurationUpdatedEvent; import org.sonarsource.sonarlint.core.serverapi.ServerApi; @@ -35,10 +36,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static testutils.TestUtils.mockServerApiProvider; class SonarProjectsCacheTests { @RegisterExtension @@ -82,13 +85,15 @@ public String getName() { return PROJECT_NAME_2; } }; - private final ServerApiProvider serverApiProvider = mock(ServerApiProvider.class); + private final ServerApiProvider serverApiProvider = mockServerApiProvider(); private final ServerApi serverApi = mock(ServerApi.class, Mockito.RETURNS_DEEP_STUBS); private final SonarProjectsCache underTest = new SonarProjectsCache(serverApiProvider); @BeforeEach public void setup() { - when(serverApiProvider.getServerApi(SQ_1)).thenReturn(Optional.of(serverApi)); + doReturn(Optional.of(serverApi)).when(serverApiProvider).getServerApi(SQ_1); + var serverConnection = new ServerConnection(SQ_1, serverApi, null); + doReturn(Optional.of(serverConnection)).when(serverApiProvider).tryGetConnection(SQ_1); } @Test diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java index fbc12dba28..62b7a70339 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java @@ -29,6 +29,7 @@ import org.sonarsource.sonarlint.core.commons.log.LogOutput; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; +import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.event.BindingConfigChangedEvent; import org.sonarsource.sonarlint.core.event.ConfigurationScopesAddedEvent; import org.sonarsource.sonarlint.core.repository.config.BindingConfiguration; @@ -39,7 +40,6 @@ import org.sonarsource.sonarlint.core.repository.connection.SonarQubeConnectionConfiguration; import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient; import org.sonarsource.sonarlint.core.serverapi.ServerApi; -import org.sonarsource.sonarlint.core.serverconnection.ServerConnection; import org.sonarsource.sonarlint.core.serverconnection.VersionUtils; import org.sonarsource.sonarlint.core.sync.SynchronizationService; @@ -87,10 +87,9 @@ void should_trigger_notification_when_new_binding_to_previous_lts_detected_on_co configRepository.addOrReplace(new ConfigurationScope(CONFIG_SCOPE_ID_2, null, false, ""), bindingConfiguration); connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID)).thenReturn(Optional.of(serverApi)); - var serverConnection = mock(ServerConnection.class); - when(serverConnection.readOrSynchronizeServerVersion(eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID, serverApi)).thenReturn(serverConnection); + when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); + underTest.configurationScopesAdded(new ConfigurationScopesAddedEvent(Set.of(CONFIG_SCOPE_ID, CONFIG_SCOPE_ID_2))); @@ -108,14 +107,10 @@ void should_trigger_multiple_notification_when_new_bindings_to_previous_lts_dete connectionRepository.addOrReplace(SQ_CONNECTION_2); var serverApi = mock(ServerApi.class); var serverApi2 = mock(ServerApi.class); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID)).thenReturn(Optional.of(serverApi)); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID_2)).thenReturn(Optional.of(serverApi2)); - var serverConnection = mock(ServerConnection.class); - when(serverConnection.readOrSynchronizeServerVersion(eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); - var serverConnection2 = mock(ServerConnection.class); - when(serverConnection2.readOrSynchronizeServerVersion(eq(serverApi2), any(SonarLintCancelMonitor.class))).thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion() + ".9")); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID, serverApi)).thenReturn(serverConnection); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID_2, serverApi2)).thenReturn(serverConnection2); + when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(serverApiProvider.tryGetConnection(CONFIG_SCOPE_ID_2)).thenReturn(Optional.of(new ServerConnection(CONFIG_SCOPE_ID_2, serverApi2, null))); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID_2), eq(serverApi2), any(SonarLintCancelMonitor.class))).thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion() + ".9")); underTest.configurationScopesAdded(new ConfigurationScopesAddedEvent(Set.of(CONFIG_SCOPE_ID, CONFIG_SCOPE_ID_2))); @@ -137,10 +132,9 @@ void should_not_trigger_notification_when_config_scope_has_no_effective_binding( void should_trigger_notification_when_new_binding_to_previous_lts_detected() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID)).thenReturn(Optional.of(serverApi)); - var serverConnection = mock(ServerConnection.class); - when(serverConnection.readOrSynchronizeServerVersion(eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID, serverApi)).thenReturn(serverConnection); + when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))) + .thenReturn(VersionUtils.getMinimalSupportedVersion()); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, new BindingConfiguration(SQ_CONNECTION_ID, "", false))); @@ -153,10 +147,8 @@ void should_trigger_notification_when_new_binding_to_previous_lts_detected() { void should_trigger_once_when_same_binding_to_previous_lts_detected_twice() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID)).thenReturn(Optional.of(serverApi)); - var serverConnection = mock(ServerConnection.class); - when(serverConnection.readOrSynchronizeServerVersion(eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID, serverApi)).thenReturn(serverConnection); + when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, new BindingConfiguration(SQ_CONNECTION_ID, "", false))); @@ -171,10 +163,8 @@ void should_trigger_once_when_same_binding_to_previous_lts_detected_twice() { void should_trigger_notification_when_new_binding_to_in_between_lts_detected() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID)).thenReturn(Optional.of(serverApi)); - var serverConnection = mock(ServerConnection.class); - when(serverConnection.readOrSynchronizeServerVersion(eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion().getName() + ".9")); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID, serverApi)).thenReturn(serverConnection); + when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion().getName() + ".9")); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, new BindingConfiguration(SQ_CONNECTION_ID, "", false))); @@ -187,10 +177,8 @@ void should_trigger_notification_when_new_binding_to_in_between_lts_detected() { void should_not_trigger_notification_when_new_binding_to_current_lts_detected() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.getServerApi(SQ_CONNECTION_ID)).thenReturn(Optional.of(serverApi)); - var serverConnection = mock(ServerConnection.class); - when(serverConnection.readOrSynchronizeServerVersion(eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getCurrentLts()); - when(synchronizationService.getServerConnection(SQ_CONNECTION_ID, serverApi)).thenReturn(serverConnection); + when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getCurrentLts()); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, new BindingConfiguration(SQ_CONNECTION_ID, "", false))); diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandlerTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandlerTests.java index 768d5b67ee..0651ba3670 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandlerTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandlerTests.java @@ -43,11 +43,11 @@ import org.mockito.ArgumentCaptor; import org.sonarsource.sonarlint.core.BindingCandidatesFinder; import org.sonarsource.sonarlint.core.BindingSuggestionProvider; -import org.sonarsource.sonarlint.core.ServerApiProvider; import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment; import org.sonarsource.sonarlint.core.commons.BoundScope; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; +import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.file.FilePathTranslation; import org.sonarsource.sonarlint.core.file.PathTranslationService; import org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository; @@ -83,6 +83,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment.PRODUCTION_URI; +import static testutils.TestUtils.mockServerApiProvider; class ShowIssueRequestHandlerTests { @@ -95,7 +96,6 @@ class ShowIssueRequestHandlerTests { private ProjectBranchesStorage branchesStorage; private IssueApi issueApi; private TelemetryService telemetryService; - @BeforeEach void setup() { connectionConfigurationRepository = mock(ConnectionConfigurationRepository.class); @@ -112,15 +112,16 @@ void setup() { issueApi = mock(IssueApi.class); var serverApi = mock(ServerApi.class); when(serverApi.issue()).thenReturn(issueApi); - var serverApiProvider = mock(ServerApiProvider.class); - when(serverApiProvider.getServerApiOrThrow(any())).thenReturn(serverApi); - when(serverApiProvider.getServerApi(any())).thenReturn(Optional.of(serverApi)); + var connection = new ServerConnection("connectionId", serverApi, sonarLintRpcClient); + var serverApiProvider = mockServerApiProvider(); + doReturn(Optional.of(connection)).when(serverApiProvider).tryGetConnection(any()); + doReturn(connection).when(serverApiProvider).getConnectionOrThrow(any()); + doReturn(Optional.of(serverApi)).when(serverApiProvider).getServerApi(any()); branchesStorage = mock(ProjectBranchesStorage.class); var storageService = mock(StorageService.class); var sonarStorage = mock(SonarProjectStorage.class); var eventPublisher = mock(ApplicationEventPublisher.class); - var sonarProjectBranchesSynchronizationService = spy(new SonarProjectBranchesSynchronizationService(storageService, serverApiProvider - , eventPublisher)); + var sonarProjectBranchesSynchronizationService = spy(new SonarProjectBranchesSynchronizationService(storageService, serverApiProvider, eventPublisher)); doReturn(new ProjectBranches(Set.of(), "main")).when(sonarProjectBranchesSynchronizationService).getProjectBranches(any(), any(), any()); when(storageService.binding(any())).thenReturn(sonarStorage); diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java index 3f7c66f400..fd8efa639c 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java @@ -41,6 +41,7 @@ import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; +import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.serverapi.ServerApi; import org.sonarsource.sonarlint.core.serverapi.component.ComponentApi; @@ -52,6 +53,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static testutils.TestUtils.mockServerApiProvider; class ServerFilePathsProviderTest { @RegisterExtension @@ -62,7 +64,7 @@ class ServerFilePathsProviderTest { public static final String PROJECT_KEY = "projectKey"; private Path cacheDirectory; - private final ServerApiProvider serverApiProvider = mock(ServerApiProvider.class); + private final ServerApiProvider serverApiProvider = mockServerApiProvider(); private final ServerApi serverApi_A = mock(ServerApi.class); private final ServerApi serverApi_B = mock(ServerApi.class); private final SonarLintCancelMonitor cancelMonitor =mock(SonarLintCancelMonitor.class); @@ -77,6 +79,12 @@ void before(@TempDir Path storageDir) throws IOException { when(serverApiProvider.getServerApi(CONNECTION_A)).thenReturn(Optional.of(serverApi_A)); when(serverApiProvider.getServerApi(CONNECTION_B)).thenReturn(Optional.of(serverApi_B)); + var serverConnectionA = new ServerConnection(CONNECTION_A, serverApi_A, null); + var serverConnectionB = new ServerConnection(CONNECTION_B, serverApi_B, null); + doReturn(Optional.of(serverConnectionA)).when(serverApiProvider).getValidConnection(CONNECTION_A); + doReturn(Optional.of(serverConnectionB)).when(serverApiProvider).getValidConnection(CONNECTION_B); + doReturn(Optional.of(serverConnectionA)).when(serverApiProvider).tryGetConnection(CONNECTION_A); + doReturn(Optional.of(serverConnectionB)).when(serverApiProvider).tryGetConnection(CONNECTION_B); when(serverApi_A.component()).thenReturn(componentApi_A); when(serverApi_B.component()).thenReturn(componentApi_B); mockServerFilePaths(componentApi_A, "pathA", "pathB"); diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RulesServiceTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RulesServiceTests.java index ecc02c044c..b35b09eef7 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RulesServiceTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/rules/RulesServiceTests.java @@ -26,7 +26,6 @@ import org.sonarsource.sonarlint.core.commons.ImpactSeverity; import org.sonarsource.sonarlint.core.commons.SoftwareQuality; import org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository; -import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository; import org.sonarsource.sonarlint.core.repository.rules.RulesRepository; import org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RuleDefinitionDto; import org.sonarsource.sonarlint.core.serverapi.push.parsing.common.ImpactPayload; @@ -42,20 +41,18 @@ class RulesServiceTests { private RulesRepository rulesRepository; private RulesExtractionHelper extractionHelper; private ConfigurationRepository configurationRepository; - private ConnectionConfigurationRepository connectionConfigurationRepository; @BeforeEach void prepare() { extractionHelper = mock(RulesExtractionHelper.class); configurationRepository = mock(ConfigurationRepository.class); - connectionConfigurationRepository = mock(ConnectionConfigurationRepository.class); rulesRepository = new RulesRepository(extractionHelper, configurationRepository); } @Test void it_should_return_all_embedded_rules_from_the_repository() { when(extractionHelper.extractEmbeddedRules()).thenReturn(List.of(aRule())); - var rulesService = new RulesService(null, null, rulesRepository, null, null, Map.of(), null, connectionConfigurationRepository); + var rulesService = new RulesService(null, null, rulesRepository, null, null, Map.of(), null); var embeddedRules = rulesService.listAllStandaloneRulesDefinitions().values(); diff --git a/backend/core/src/test/java/testutils/TestUtils.java b/backend/core/src/test/java/testutils/TestUtils.java index be0061736a..15efc5d74b 100644 --- a/backend/core/src/test/java/testutils/TestUtils.java +++ b/backend/core/src/test/java/testutils/TestUtils.java @@ -20,6 +20,15 @@ package testutils; import java.lang.management.ManagementFactory; +import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment; +import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider; +import org.sonarsource.sonarlint.core.http.HttpClientProvider; +import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository; +import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; public class TestUtils { @@ -48,4 +57,14 @@ public static void printThreadDump() { System.out.println(generateThreadDump()); } + public static ServerApiProvider mockServerApiProvider() { + var connectionRepository = mock(ConnectionConfigurationRepository.class); + var awareHttpClientProvider = mock(ConnectionAwareHttpClientProvider.class); + var httpClientProvider = mock(HttpClientProvider.class); + var sonarCloudActiveEnvironment = mock(SonarCloudActiveEnvironment.class); + var client = mock(SonarLintRpcClient.class); + var obj = new ServerApiProvider(connectionRepository, awareHttpClientProvider, httpClientProvider, + sonarCloudActiveEnvironment, client); + return spy(obj); + } } diff --git a/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/ServerApiHelper.java b/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/ServerApiHelper.java index af17e4e03a..afe5939611 100644 --- a/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/ServerApiHelper.java +++ b/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/ServerApiHelper.java @@ -43,8 +43,10 @@ import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; import org.sonarsource.sonarlint.core.http.HttpClient; import org.sonarsource.sonarlint.core.http.HttpConnectionListener; +import org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException; import org.sonarsource.sonarlint.core.serverapi.exception.NotFoundException; import org.sonarsource.sonarlint.core.serverapi.exception.ServerErrorException; +import org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException; /** * Wrapper around HttpClient to avoid repetitive code, like support of pagination, and log timing of requests @@ -138,11 +140,11 @@ public static String concat(String baseUrl, String relativePath) { public static RuntimeException handleError(HttpClient.Response toBeClosed) { try (var failedResponse = toBeClosed) { if (failedResponse.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { - return new IllegalStateException("Not authorized. Please check server credentials."); + return new UnauthorizedException("Not authorized. Please check server credentials."); } if (failedResponse.code() == HttpURLConnection.HTTP_FORBIDDEN) { // Details are in response content - return new IllegalStateException(tryParseAsJsonError(failedResponse)); + return new ForbiddenException(tryParseAsJsonError(failedResponse)); } if (failedResponse.code() == HttpURLConnection.HTTP_NOT_FOUND) { return new NotFoundException(formatHttpFailedResponse(failedResponse, null)); @@ -169,6 +171,9 @@ private static String tryParseAsJsonError(HttpClient.Response response) { } var obj = JsonParser.parseString(content).getAsJsonObject(); var errors = obj.getAsJsonArray("errors"); + if (errors == null) { + return null; + } List errorMessages = new ArrayList<>(); for (JsonElement e : errors) { errorMessages.add(e.getAsJsonObject().get("msg").getAsString()); diff --git a/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ForbiddenException.java b/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ForbiddenException.java new file mode 100644 index 0000000000..5b970cf5d0 --- /dev/null +++ b/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/ForbiddenException.java @@ -0,0 +1,26 @@ +/* + * SonarLint Core - Server API + * Copyright (C) 2016-2025 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.core.serverapi.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnauthorizedException.java b/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnauthorizedException.java new file mode 100644 index 0000000000..f937b78009 --- /dev/null +++ b/backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/exception/UnauthorizedException.java @@ -0,0 +1,26 @@ +/* + * SonarLint Core - Server API + * Copyright (C) 2016-2025 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.core.serverapi.exception; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/LocalStorageSynchronizer.java b/backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/LocalStorageSynchronizer.java index 3739aff9ad..ae7cd12a5c 100644 --- a/backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/LocalStorageSynchronizer.java +++ b/backend/server-connection/src/main/java/org/sonarsource/sonarlint/core/serverconnection/LocalStorageSynchronizer.java @@ -89,6 +89,7 @@ public AnalyzerSettingsUpdateSummary synchronizeAnalyzerConfig(ServerApi serverA private AnalyzerConfiguration downloadAnalyzerConfig(ServerApi serverApi, String projectKey, SonarLintCancelMonitor cancelMonitor) { LOG.info("[SYNC] Synchronizing analyzer configuration for project '{}'", projectKey); + LOG.info("[SYNC] Languages enabled for synchronization: {}", enabledLanguageKeys); Map currentRuleSets; int currentSchemaVersion; try { diff --git a/backend/server-connection/src/main/proto/sonarlint.proto b/backend/server-connection/src/main/proto/sonarlint.proto index f0aea08ce0..597f9de2e5 100644 --- a/backend/server-connection/src/main/proto/sonarlint.proto +++ b/backend/server-connection/src/main/proto/sonarlint.proto @@ -100,6 +100,10 @@ message LastEventPolling { int64 last_event_polling = 1; } +message LastWebApiErrorNotification { + int64 last_wrong_token_notification = 1; +} + enum NewCodeDefinitionMode { UNKNOWN = 0; NUMBER_OF_DAYS = 1; diff --git a/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientDelegate.java b/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientDelegate.java index 26d5adbb41..ffbdb8ad83 100644 --- a/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientDelegate.java +++ b/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientDelegate.java @@ -209,4 +209,7 @@ default Map getInferredAnalysisProperties(String configurationSc default Set getFileExclusions(String configurationScopeId) throws ConfigScopeNotFoundException { return Collections.emptySet(); } + + default void invalidToken(String connectionId) { + } } diff --git a/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientImpl.java b/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientImpl.java index d248aedcf9..7819016e0e 100644 --- a/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientImpl.java +++ b/client/rpc-java-client/src/main/java/org/sonarsource/sonarlint/core/rpc/client/SonarLintRpcClientImpl.java @@ -83,6 +83,7 @@ import org.sonarsource.sonarlint.core.rpc.protocol.client.promotion.PromoteExtraEnabledLanguagesInConnectedModeParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.DidSynchronizeConfigurationScopeParams; +import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse; @@ -417,4 +418,9 @@ public CompletableFuture getFileExclusions(GetFileExc } }); } + + @Override + public void invalidToken(InvalidTokenParams params) { + notify(() -> delegate.invalidToken(params.getConnectionId())); + } } diff --git a/medium-tests/src/test/java/mediumtest/EffectiveRulesMediumTests.java b/medium-tests/src/test/java/mediumtest/EffectiveRulesMediumTests.java index 3f38aa8870..a09f074638 100644 --- a/medium-tests/src/test/java/mediumtest/EffectiveRulesMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/EffectiveRulesMediumTests.java @@ -237,7 +237,7 @@ void it_should_fail_to_merge_rule_from_storage_and_server_when_connection_is_unk assertThat(futureResponse).failsWithin(1, TimeUnit.SECONDS) .withThrowableOfType(ExecutionException.class) .withCauseInstanceOf(ResponseErrorException.class) - .withMessageContaining("Connection with ID 'connectionId' does not exist"); + .withMessageContaining("Connection 'connectionId' is gone"); } @SonarLintTest diff --git a/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java b/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java index 1e76f79e7f..8e56db6f94 100644 --- a/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java @@ -22,14 +22,15 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.extension.RegisterExtension; -import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; -import org.sonarsource.sonarlint.core.serverconnection.ServerConnection; +import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams; +import org.sonarsource.sonarlint.core.rpc.protocol.common.Language; import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest; import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness; import utils.TestPlugin; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; class NotebookLanguageMediumTests { @RegisterExtension @@ -40,18 +41,22 @@ class NotebookLanguageMediumTests { @SonarLintTest void should_not_enable_sync_for_notebook_python_language(SonarLintTestHarness harness) { - var fakeClient = harness.newFakeClient() + client = harness.newFakeClient() .build(); var backend = harness.newBackend() .withStorage(CONNECTION_ID, s -> s.withPlugins(TestPlugin.JAVASCRIPT, TestPlugin.JAVA) .withProject("test-project") .withProject(JAVA_MODULE_KEY)) - .build(fakeClient); + .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY) + .withEnabledLanguageInStandaloneMode(Language.JAVA) + .withEnabledLanguageInStandaloneMode(Language.JS) + .withEnabledLanguageInStandaloneMode(Language.IPYTHON) + .withFullSynchronization() + .build(client); - var serverConnection = new ServerConnection(backend.getStorageRoot(), CONNECTION_ID, false, Set.of(SonarLanguage.JAVA, SonarLanguage.JS, - SonarLanguage.IPYTHON), Set.of(), backend.getWorkDir()); + backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID)); - assertThat(serverConnection.getEnabledLanguagesToSync()).containsOnly(SonarLanguage.JAVA, SonarLanguage.JS); + await().untilAsserted(() -> assertThat(client.getLogMessages()).contains("[SYNC] Languages enabled for synchronization: [java, js]")); } } diff --git a/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java b/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java index d73b9675d2..174affe947 100644 --- a/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java @@ -23,6 +23,11 @@ import java.time.temporal.ChronoUnit; import java.util.List; import java.util.concurrent.ExecutionException; +import mediumtest.fixtures.TestPlugin; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.sonarsource.sonarlint.core.commons.RuleType; import org.sonarsource.sonarlint.core.commons.api.TextRange; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams; @@ -108,7 +113,7 @@ void it_should_sync_when_credentials_are_updated(SonarLintTestHarness harness) { RuleType.VULNERABILITY))) .start(); - server.getMockServer().stubFor(get("/api/system/status").willReturn(aResponse().withStatus(401))); + server.getMockServer().stubFor(get("/api/system/status").willReturn(aResponse().withStatus(404))); var backend = harness.newBackend() .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA)) @@ -128,6 +133,33 @@ void it_should_sync_when_credentials_are_updated(SonarLintTestHarness harness) { "Synchronizing project branches for project 'projectKey'")); } + @ParameterizedTest + @ValueSource(ints = {401, 403}) + void it_should_notify_client_if_invalid_token(Integer status) { + var client = newFakeClient() + .withCredentials(CONNECTION_ID, "user", "pw") + .build(); + when(client.getClientLiveDescription()).thenReturn(this.getClass().getName()); + + var server = newSonarQubeServer() + .withProject("projectKey", project -> project.withBranch("main")) + .withResponseCode(status) + .start(); + + backend = newBackend() + .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA).withProject("projectKey")) + .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, "projectKey") + .withEnabledLanguageInStandaloneMode(JAVA) + .withProjectSynchronization() + .withFullSynchronization() + .build(client); + await().untilAsserted(() -> assertThat(client.getLogMessages()).contains("Error during synchronization")); + + backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID)); + + await().untilAsserted(() -> assertThat(client.getConnectionIdsWithInvalidToken()).containsExactly(CONNECTION_ID)); + } + private EffectiveRuleDetailsDto getEffectiveRuleDetails(SonarLintTestRpcServer backend, String configScopeId, String ruleKey) { try { return backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams(configScopeId, ruleKey, null)).get().details(); diff --git a/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcClient.java b/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcClient.java index b413a8832c..fdf055601a 100644 --- a/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcClient.java +++ b/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/SonarLintRpcClient.java @@ -72,6 +72,7 @@ import org.sonarsource.sonarlint.core.rpc.protocol.client.promotion.PromoteExtraEnabledLanguagesInConnectedModeParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.smartnotification.ShowSmartNotificationParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.DidSynchronizeConfigurationScopeParams; +import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.taint.vulnerability.DidChangeTaintVulnerabilitiesParams; import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.TelemetryClientLiveAttributesResponse; @@ -333,4 +334,8 @@ default void promoteExtraEnabledLanguagesInConnectedMode(PromoteExtraEnabledLang */ @JsonRequest CompletableFuture getFileExclusions(GetFileExclusionsParams params); + + @JsonNotification + default void invalidToken(InvalidTokenParams params) { + } } diff --git a/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/InvalidTokenParams.java b/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/InvalidTokenParams.java new file mode 100644 index 0000000000..7d012ff6a8 --- /dev/null +++ b/rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/client/sync/InvalidTokenParams.java @@ -0,0 +1,33 @@ +/* + * SonarLint Core - RPC Protocol + * Copyright (C) 2016-2025 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.core.rpc.protocol.client.sync; + +public class InvalidTokenParams { + + String connectionId; + + public InvalidTokenParams(String connectionId) { + this.connectionId = connectionId; + } + + public String getConnectionId() { + return connectionId; + } +} diff --git a/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintBackendFixture.java b/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintBackendFixture.java index d594b438ae..bbf7087114 100644 --- a/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintBackendFixture.java +++ b/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintBackendFixture.java @@ -643,6 +643,7 @@ public static class FakeSonarLintRpcClient implements SonarLintRpcClientDelegate Map>> raisedHotspotsByScopeId = new HashMap<>(); Map> inferredAnalysisPropertiesByScopeId = new HashMap<>(); Map analysisReadinessPerScopeId = new HashMap<>(); + Set connectionIdsWithInvalidToken = new HashSet<>(); public FakeSonarLintRpcClient(Map> credentialsByConnectionId, boolean printLogsToStdOut, Map matchedBranchPerScopeId, Map baseDirsByConfigScope, Map> initialFilesByConfigScope, @@ -715,6 +716,11 @@ public void reportProgress(ReportProgressParams params) { } } + @Override + public void invalidToken(String connectionId) { + connectionIdsWithInvalidToken.add(connectionId); + } + public Map getProgressReportsByTaskId() { return progressReportsByTaskId; } @@ -923,6 +929,10 @@ public void waitForSynchronization() { verify(this, timeout(5000)).didSynchronizeConfigurationScopes(any()); } + public Set getConnectionIdsWithInvalidToken() { + return connectionIdsWithInvalidToken; + } + public static class ProgressReport { @CheckForNull private final String configurationScopeId; diff --git a/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/ServerFixture.java b/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/ServerFixture.java index fc0fc2a6e1..4721a3872c 100644 --- a/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/ServerFixture.java +++ b/test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/ServerFixture.java @@ -37,6 +37,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import java.util.Set; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -132,6 +133,7 @@ public static class ServerBuilder { private ServerStatus serverStatus = ServerStatus.UP; private boolean smartNotificationsSupported; private final List tokensRegistered = new ArrayList<>(); + private Integer statusCode = 200; public ServerBuilder(@Nullable Consumer onStart, ServerKind serverKind, @Nullable String organizationKey, @Nullable String version) { this.onStart = onStart; @@ -186,9 +188,15 @@ public ServerBuilder withVersion(String version) { return this; } + public ServerBuilder withResponseCode(Integer status) { + this.statusCode = status; + return this; + } + public Server start() { - var server = new Server(serverKind, serverStatus, organizationKey, version, projectByProjectKey, smartNotificationsSupported, pluginsByKey, qualityProfilesByKey, - tokensRegistered); + var server = new Server(serverKind, serverStatus, organizationKey, version, projectByProjectKey, smartNotificationsSupported, pluginsByKey, + qualityProfilesByKey, + tokensRegistered, statusCode); server.start(); if (onStart != null) { onStart.accept(server); @@ -538,11 +546,12 @@ public static class Server { private final Map pluginsByKey; private final Map qualityProfilesByKey; private final List tokensRegistered; + private final Integer statusCode; public Server(ServerKind serverKind, ServerStatus serverStatus, @Nullable String organizationKey, @Nullable String version, Map projectsByProjectKey, boolean smartNotificationsSupported, Map pluginsByKey, - Map qualityProfilesByKey, List tokensRegistered) { + Map qualityProfilesByKey, List tokensRegistered, Integer statusCode) { this.serverKind = serverKind; this.serverStatus = serverStatus; this.organizationKey = organizationKey; @@ -552,6 +561,7 @@ public Server(ServerKind serverKind, ServerStatus serverStatus, @Nullable String this.pluginsByKey = pluginsByKey; this.qualityProfilesByKey = qualityProfilesByKey; this.tokensRegistered = tokensRegistered; + this.statusCode = statusCode; } public void start() { @@ -590,8 +600,11 @@ private void registerComponentApiResponses() { } public void registerSystemApiResponses() { + // API is public, so it can't return 401 or 403 status + var statusesToSkip = Set.of(401, 403); + var status = statusesToSkip.contains(statusCode) ? 200 : statusCode; mockServer.stubFor(get("/api/system/status") - .willReturn(aResponse().withStatus(200).withBody("{\"id\": \"20160308094653\",\"version\": \"" + version + "\",\"status\": " + + .willReturn(aResponse().withStatus(status).withBody("{\"id\": \"20160308094653\",\"version\": \"" + version + "\",\"status\": " + "\"" + serverStatus + "\"}"))); } @@ -602,7 +615,7 @@ private void registerPluginsApiResponses() { private void registerPluginsInstalledResponses() { mockServer.stubFor(get("/api/plugins/installed") - .willReturn(aResponse().withStatus(200).withBody("{\"plugins\": [" + + .willReturn(aResponse().withStatus(statusCode).withBody("{\"plugins\": [" + pluginsByKey.entrySet().stream().map( entry -> { var pluginKey = entry.getKey(); @@ -620,7 +633,7 @@ private void registerPluginsDownloadResponses() { try { var pluginContent = Files.exists(plugin.jarPath) ? Files.readAllBytes(plugin.jarPath) : new byte[0]; mockServer.stubFor(get("/api/plugins/download?plugin=" + pluginKey) - .willReturn(aResponse().withStatus(200).withBody(pluginContent))); + .willReturn(aResponse().withStatus(statusCode).withBody(pluginContent))); } catch (IOException e) { throw new RuntimeException(e); } @@ -634,7 +647,7 @@ private void registerQualityProfilesApiResponses() { urlBuilder.append("&organization=").append(organizationKey); } mockServer.stubFor(get(urlBuilder.toString()) - .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Qualityprofiles.SearchWsResponse.newBuilder().addAllProfiles( + .willReturn(aResponse().withStatus(statusCode).withResponseBody(protobufBody(Qualityprofiles.SearchWsResponse.newBuilder().addAllProfiles( project.qualityProfileKeys.stream().map(qualityProfileKey -> { var qualityProfile = qualityProfilesByKey.get(qualityProfileKey); return Qualityprofiles.SearchWsResponse.QualityProfile.newBuilder() @@ -659,7 +672,7 @@ private void registerRulesApiResponses() { } url += "&activation=true&f=templateKey,actives&types=CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT&s=key&ps=500&p=1"; mockServer.stubFor(get(url) - .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.SearchResponse.newBuilder() + .willReturn(aResponse().withStatus(statusCode).withResponseBody(protobufBody(Rules.SearchResponse.newBuilder() .addAllRules(qualityProfile.activeRulesByKey.entrySet().stream().map(entry -> Rules.Rule.newBuilder() .setKey(entry.getKey()) .setSeverity(entry.getValue().issueSeverity.name()) @@ -679,7 +692,7 @@ private void registerRulesApiResponses() { } url += "&f=repo&s=key&ps=500&p=1"; mockServer.stubFor(get(url) - .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.SearchResponse.newBuilder() + .willReturn(aResponse().withStatus(statusCode).withResponseBody(protobufBody(Rules.SearchResponse.newBuilder() .addAllRules(taintActiveRulesByKey.entrySet().stream().map(entry -> Rules.Rule.newBuilder() .setKey(entry.getKey()) .setSeverity(entry.getValue().issueSeverity.name()) @@ -697,7 +710,7 @@ private void registerRulesApiResponses() { var rule = entry.getValue(); var rulesShowUrl = "/api/rules/show.protobuf?key=" + ruleKey; mockServer.stubFor(get(rulesShowUrl) - .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder() + .willReturn(aResponse().withStatus(statusCode).withResponseBody(protobufBody(Rules.ShowResponse.newBuilder() .setRule(Rules.Rule.newBuilder() .setKey(ruleKey) .setName("fakeName") @@ -717,7 +730,7 @@ private void registerRulesApiResponses() { private void registerProjectBranchesApiResponses() { projectsByProjectKey.forEach((projectKey, project) -> mockServer.stubFor(get("/api/project_branches/list.protobuf?project=" + projectKey) - .willReturn(aResponse().withStatus(200).withResponseBody(protobufBody(ProjectBranches.ListWsResponse.newBuilder() + .willReturn(aResponse().withStatus(statusCode).withResponseBody(protobufBody(ProjectBranches.ListWsResponse.newBuilder() .addAllBranches(project.branchesByName.keySet().stream() .filter(Objects::nonNull) .map(branchName -> ProjectBranches.Branch.newBuilder().setName(branchName).setIsMain(project.mainBranchName.equals(branchName)).setType(Common.BranchType.LONG).build()) @@ -935,7 +948,7 @@ private void registerHotspotsShowApiResponses() { } private void registerHotspotsStatusChangeApiResponses() { - mockServer.stubFor(post("/api/hotspots/change_status").willReturn(aResponse().withStatus(200))); + mockServer.stubFor(post("/api/hotspots/change_status").willReturn(aResponse().withStatus(statusCode))); } private void registerIssuesApiResponses() { @@ -986,11 +999,11 @@ private void registerBatchIssuesResponses() { } private void registerIssuesStatusChangeApiResponses() { - mockServer.stubFor(post("/api/issues/do_transition").willReturn(aResponse().withStatus(200))); + mockServer.stubFor(post("/api/issues/do_transition").willReturn(aResponse().withStatus(statusCode))); } private void registerAddIssueCommentApiResponses() { - mockServer.stubFor(post("/api/issues/add_comment").willReturn(aResponse().withStatus(200))); + mockServer.stubFor(post("/api/issues/add_comment").willReturn(aResponse().withStatus(statusCode))); } private void registerApiIssuesPullResponses() { @@ -1054,7 +1067,7 @@ private void registerApiIssuesPullTaintResponses() { } private void registerIssueAnticipateTransitionResponses() { - mockServer.stubFor(post("/api/issues/anticipated_transitions?projectKey=projectKey").willReturn(aResponse().withStatus(200))); + mockServer.stubFor(post("/api/issues/anticipated_transitions?projectKey=projectKey").willReturn(aResponse().withStatus(statusCode))); } private void registerSourceApiResponses() { @@ -1076,7 +1089,7 @@ private void registerSourceApiResponses() { private void registerDevelopersApiResponses() { if (smartNotificationsSupported) { - mockServer.stubFor(get("/api/developers/search_events?projects=&from=").willReturn(aResponse().withStatus(200))); + mockServer.stubFor(get("/api/developers/search_events?projects=&from=").willReturn(aResponse().withStatus(statusCode))); } } @@ -1167,7 +1180,7 @@ private void registerSettingsApiResponses() { private void registerTokenApiResponse() { tokensRegistered.forEach( - tokenName -> mockServer.stubFor(post("/api/user_tokens/revoke").withRequestBody(WireMock.containing("name=" + tokenName)).willReturn(aResponse().withStatus(200)))); + tokenName -> mockServer.stubFor(post("/api/user_tokens/revoke").withRequestBody(WireMock.containing("name=" + tokenName)).willReturn(aResponse().withStatus(statusCode)))); } public void shutdown() { From 79e8b554fadd0f8e297fa99c047bd1e3d02c93a4 Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Mon, 20 Jan 2025 11:21:13 +0100 Subject: [PATCH 2/6] PR feedback --- ...piProvider.java => ConnectionManager.java} | 22 ++++------ .../sonarlint/core/ConnectionService.java | 18 ++++---- .../sonarlint/core/OrganizationsCache.java | 10 ++--- .../sonarlint/core/SonarProjectsCache.java | 10 ++--- .../core/VersionSoonUnsupportedHelper.java | 8 ++-- .../core/connection/ConnectionManager.java | 42 ------------------- .../server/ShowHotspotRequestHandler.java | 10 ++--- .../server/ShowIssueRequestHandler.java | 12 +++--- .../core/file/ServerFilePathsProvider.java | 12 +++--- .../core/hotspot/HotspotService.java | 12 +++--- .../sonarlint/core/issue/IssueService.java | 24 +++++------ .../sonarlint/core/rules/RulesService.java | 24 +++++------ .../server/event/ServerEventsService.java | 10 ++--- .../server/event/SonarQubeEventStream.java | 10 ++--- .../SmartNotifications.java | 10 ++--- .../core/spring/SonarLintSpringAppConfig.java | 4 +- .../sync/HotspotSynchronizationService.java | 12 +++--- .../sync/IssueSynchronizationService.java | 12 +++--- ...ProjectBranchesSynchronizationService.java | 12 +++--- .../core/sync/SynchronizationService.java | 12 +++--- .../sync/TaintSynchronizationService.java | 12 +++--- .../core/usertoken/UserTokenService.java | 10 ++--- ...Tests.java => ConnectionManagerTests.java} | 27 +++++++++++- .../core/SonarProjectsCacheTests.java | 8 ++-- .../VersionSoonUnsupportedHelperTests.java | 18 ++++---- .../file/ServerFilePathsProviderTest.java | 18 ++++---- .../src/test/java/testutils/TestUtils.java | 6 +-- 27 files changed, 180 insertions(+), 205 deletions(-) rename backend/core/src/main/java/org/sonarsource/sonarlint/core/{ServerApiProvider.java => ConnectionManager.java} (94%) delete mode 100644 backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java rename backend/core/src/test/java/org/sonarsource/sonarlint/core/{ServerApiProviderTests.java => ConnectionManagerTests.java} (85%) diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java similarity index 94% rename from backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java rename to backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java index 3032ba5141..c2bc552016 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java @@ -19,7 +19,6 @@ */ package org.sonarsource.sonarlint.core; -import com.google.common.annotations.VisibleForTesting; import java.net.URI; import java.util.Optional; import java.util.function.Consumer; @@ -30,7 +29,6 @@ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; -import org.sonarsource.sonarlint.core.connection.ConnectionManager; import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider; @@ -53,7 +51,7 @@ @Named @Singleton -public class ServerApiProvider implements ConnectionManager { +public class ConnectionManager { private static final SonarLintLogger LOG = SonarLintLogger.get(); private final ConnectionConfigurationRepository connectionRepository; @@ -62,8 +60,8 @@ public class ServerApiProvider implements ConnectionManager { private final SonarLintRpcClient client; private final URI sonarCloudUri; - public ServerApiProvider(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider, - SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarLintRpcClient client) { + public ConnectionManager(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider, + SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarLintRpcClient client) { this.connectionRepository = connectionRepository; this.awareHttpClientProvider = awareHttpClientProvider; this.httpClientProvider = httpClientProvider; @@ -145,7 +143,6 @@ private HttpClient getClientFor(EndpointParams params, Either httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword())); } - @Override public ServerConnection getConnectionOrThrow(String connectionId) { var serverApi = getServerApiOrThrow(connectionId); return new ServerConnection(connectionId, serverApi, client); @@ -161,28 +158,27 @@ public Optional tryGetConnectionWithoutCredentials(String conn .map(serverApi -> new ServerConnection(connectionId, serverApi, client)); } - @Override public ServerApi getTransientConnection(String token,@Nullable String organization, String baseUrl) { return getServerApi(baseUrl, organization, token); } - @Override public void withValidConnection(String connectionId, Consumer serverApiConsumer) { getValidConnection(connectionId).ifPresent(connection -> connection.withClientApi(serverApiConsumer)); } - @Override public Optional withValidConnectionAndReturn(String connectionId, Function serverApiConsumer) { return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)); } - @Override public Optional withValidConnectionFlatMapOptionalAndReturn(String connectionId, Function> serverApiConsumer) { return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)).flatMap(Function.identity()); } - @VisibleForTesting - public Optional getValidConnection(String connectionId) { - return tryGetConnection(connectionId).filter(ServerConnection::isValid); + private Optional getValidConnection(String connectionId) { + return tryGetConnection(connectionId).filter(ServerConnection::isValid) + .or(() -> { + LOG.debug("Connection '{}' is invalid", connectionId); + return Optional.empty(); + }); } } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionService.java index 94fb9dd4af..1410c5d577 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionService.java @@ -63,23 +63,23 @@ public class ConnectionService { private final ApplicationEventPublisher applicationEventPublisher; private final ConnectionConfigurationRepository repository; private final URI sonarCloudUri; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final TokenGeneratorHelper tokenGeneratorHelper; @Inject public ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository, InitializeParams params, - SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, ServerApiProvider serverApiProvider) { - this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, serverApiProvider, + SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, ConnectionManager connectionManager) { + this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, connectionManager, tokenGeneratorHelper); } ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository, @Nullable List initSonarQubeConnections, @Nullable List initSonarCloudConnections, - SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ServerApiProvider serverApiProvider, TokenGeneratorHelper tokenGeneratorHelper) { + SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ConnectionManager connectionManager, TokenGeneratorHelper tokenGeneratorHelper) { this.applicationEventPublisher = applicationEventPublisher; this.repository = repository; this.sonarCloudUri = sonarCloudActiveEnvironment.getUri(); - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.tokenGeneratorHelper = tokenGeneratorHelper; if (initSonarQubeConnections != null) { initSonarQubeConnections.forEach(c -> repository.addOrReplace(adapt(c))); @@ -157,7 +157,7 @@ private void updateConnection(AbstractConnectionConfiguration connectionConfigur public ValidateConnectionResponse validateConnection(Either transientConnection, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getForTransientConnection(transientConnection); + var serverApi = connectionManager.getForTransientConnection(transientConnection); var serverChecker = new ServerVersionAndStatusChecker(serverApi); try { serverChecker.checkVersionAndStatus(cancelMonitor); @@ -179,7 +179,7 @@ public ValidateConnectionResponse validateConnection(Either transientConnection, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getForTransientConnection(transientConnection); + var serverApi = connectionManager.getForTransientConnection(transientConnection); var developersApi = serverApi.developers(); return developersApi.isSupported(cancelMonitor); } @@ -189,7 +189,7 @@ public HelpGenerateUserTokenResponse helpGenerateUserToken(String serverUrl, Son } public List getAllProjects(Either transientConnection, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getForTransientConnection(transientConnection); + var serverApi = connectionManager.getForTransientConnection(transientConnection); return serverApi.component().getAllProjects(cancelMonitor) .stream().map(serverProject -> new SonarProjectDto(serverProject.getKey(), serverProject.getName())) .collect(Collectors.toList()); @@ -197,7 +197,7 @@ public List getAllProjects(Either getProjectNamesByKey(Either transientConnection, List projectKeys, SonarLintCancelMonitor cancelMonitor) { - var serverApi = serverApiProvider.getForTransientConnection(transientConnection); + var serverApi = connectionManager.getForTransientConnection(transientConnection); var projectNamesByKey = new HashMap(); projectKeys.forEach(key -> { var projectName = serverApi.component().getProject(key, cancelMonitor).map(ServerProject::getName).orElse(null); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/OrganizationsCache.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/OrganizationsCache.java index 0f6113546a..487d008e9f 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/OrganizationsCache.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/OrganizationsCache.java @@ -47,14 +47,14 @@ public class OrganizationsCache { private static final SonarLintLogger LOG = SonarLintLogger.get(); - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final Cache, TextSearchIndex> textSearchIndexCacheByCredentials = CacheBuilder.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .build(); - public OrganizationsCache(ServerApiProvider serverApiProvider) { - this.serverApiProvider = serverApiProvider; + public OrganizationsCache(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; } public List fuzzySearchOrganizations(Either credentials, String searchText, SonarLintCancelMonitor cancelMonitor) { @@ -74,7 +74,7 @@ public TextSearchIndex getTextSearchIndex(Either orgs; try { - var serverApi = serverApiProvider.getForSonarCloudNoOrg(credentials); + var serverApi = connectionManager.getForSonarCloudNoOrg(credentials); var serverOrganizations = serverApi.organization().listUserOrganizations(cancelMonitor); orgs = serverOrganizations.stream().map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).collect(Collectors.toList()); } catch (Exception e) { @@ -103,7 +103,7 @@ public List listUserOrganizations(Either credentials, String organizationKey, SonarLintCancelMonitor cancelMonitor) { - var helper = serverApiProvider.getForSonarCloudNoOrg(credentials); + var helper = connectionManager.getForSonarCloudNoOrg(credentials); var serverOrganization = helper.organization().getOrganization(organizationKey, cancelMonitor); return serverOrganization.map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).orElse(null); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java index e0e8171f69..e18b7d91e7 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java @@ -46,7 +46,7 @@ public class SonarProjectsCache { private static final SonarLintLogger LOG = SonarLintLogger.get(); - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final Cache> textSearchIndexCacheByConnectionId = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) @@ -94,8 +94,8 @@ public int hashCode() { } } - public SonarProjectsCache(ServerApiProvider serverApiProvider) { - this.serverApiProvider = serverApiProvider; + public SonarProjectsCache(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; } @EventListener @@ -120,7 +120,7 @@ public Optional getSonarProject(String connectionId, String sonar return singleProjectsCache.get(new SonarProjectKey(connectionId, sonarProjectKey), () -> { LOG.debug("Query project '{}' on connection '{}'...", sonarProjectKey, connectionId); try { - return serverApiProvider.withValidConnectionAndReturn(connectionId, + return connectionManager.withValidConnectionAndReturn(connectionId, s -> s.component().getProject(sonarProjectKey, cancelMonitor)).orElse(Optional.empty()); } catch (Exception e) { LOG.error("Error while querying project '{}' from connection '{}'", sonarProjectKey, connectionId, e); @@ -138,7 +138,7 @@ public TextSearchIndex getTextSearchIndex(String connectionId, So LOG.debug("Load projects from connection '{}'...", connectionId); List projects; try { - projects = serverApiProvider.withValidConnectionAndReturn(connectionId, + projects = connectionManager.withValidConnectionAndReturn(connectionId, s -> s.component().getAllProjects(cancelMonitor)) .orElse(List.of()); } catch (Exception e) { diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java index d237cd70d9..533db9b929 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java @@ -56,17 +56,17 @@ public class VersionSoonUnsupportedHelper { private final SonarLintRpcClient client; private final ConfigurationRepository configRepository; private final ConnectionConfigurationRepository connectionRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final SynchronizationService synchronizationService; private final Map cacheConnectionIdPerVersion = new ConcurrentHashMap<>(); private final ExecutorServiceShutdownWatchable executorService; - public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, ServerApiProvider serverApiProvider, + public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, ConnectionManager connectionManager, ConnectionConfigurationRepository connectionRepository, SynchronizationService synchronizationService) { this.client = client; this.configRepository = configRepository; this.connectionRepository = connectionRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.synchronizationService = synchronizationService; this.executorService = new ExecutorServiceShutdownWatchable<>(new ThreadPoolExecutor(0, 1, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, "Version Soon Unsupported Helper"))); @@ -107,7 +107,7 @@ private void queueCheckIfSoonUnsupported(String connectionId, String configScope try { var connection = connectionRepository.getConnectionById(connectionId); if (connection != null && connection.getKind() == ConnectionKind.SONARQUBE) { - serverApiProvider.tryGetConnectionWithoutCredentials(connectionId) + connectionManager.tryGetConnectionWithoutCredentials(connectionId) .ifPresent(serverConnection -> serverConnection.withClientApi(serverApi -> { var version = synchronizationService.readOrSynchronizeServerVersion(connectionId, serverApi, cancelMonitor); var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0; diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java deleted file mode 100644 index 144b5ba5db..0000000000 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ConnectionManager.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarLint Core - Implementation - * Copyright (C) 2016-2025 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.core.connection; - -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; -import javax.annotation.Nullable; -import org.sonarsource.sonarlint.core.serverapi.ServerApi; - -public interface ConnectionManager { - ServerConnection getConnectionOrThrow(String connectionId); - /** - * Having dedicated TransientConnection class makes sense only if we handle the connection errors from there. - * Which brings up the problem of managing global state for notifications because we don't know the connection ID.

- * On other hand providing ServerApis directly, all Web API calls from transient ServerApi are not protected by checks for connection state. - * So we still can spam server with unprotected requests. - * It's not a big problem because we don't use such requests during scheduled sync. - * They are mostly related to setting up the connection or other user-triggered actions. - */ - ServerApi getTransientConnection(String token, @Nullable String organization, String baseUrl); - void withValidConnection(String connectionId, Consumer serverApiConsumer); - Optional withValidConnectionAndReturn(String connectionId, Function serverApiConsumer); - Optional withValidConnectionFlatMapOptionalAndReturn(String connectionId, Function> serverApiConsumer); -} diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java index 17f711a190..6ac4c6fab3 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowHotspotRequestHandler.java @@ -35,7 +35,7 @@ import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.net.URIBuilder; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.api.TextRange; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; import org.sonarsource.sonarlint.core.file.FilePathTranslation; @@ -57,15 +57,15 @@ @Singleton public class ShowHotspotRequestHandler implements HttpRequestHandler { private final SonarLintRpcClient client; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final TelemetryService telemetryService; private final RequestHandlerBindingAssistant requestHandlerBindingAssistant; private final PathTranslationService pathTranslationService; - public ShowHotspotRequestHandler(SonarLintRpcClient client, ServerApiProvider serverApiProvider, TelemetryService telemetryService, + public ShowHotspotRequestHandler(SonarLintRpcClient client, ConnectionManager connectionManager, TelemetryService telemetryService, RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService) { this.client = client; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.telemetryService = telemetryService; this.requestHandlerBindingAssistant = requestHandlerBindingAssistant; this.pathTranslationService = pathTranslationService; @@ -105,7 +105,7 @@ private void showHotspotForScope(String connectionId, String configurationScopeI } private Optional tryFetchHotspot(String connectionId, String hotspotKey, SonarLintCancelMonitor cancelMonitor) { - return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(connectionId,api -> api.hotspot().fetch(hotspotKey, cancelMonitor)); + return connectionManager.withValidConnectionFlatMapOptionalAndReturn(connectionId, api -> api.hotspot().fetch(hotspotKey, cancelMonitor)); } private static HotspotDetailsDto adapt(String hotspotKey, ServerHotspotDetails hotspot, FilePathTranslation translation) { diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java index 2fb9d6976e..4ce8e2f468 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/embedded/server/ShowIssueRequestHandler.java @@ -41,7 +41,7 @@ import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.net.URIBuilder; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; import org.sonarsource.sonarlint.core.file.FilePathTranslation; @@ -73,18 +73,18 @@ public class ShowIssueRequestHandler implements HttpRequestHandler { private final SonarLintRpcClient client; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final TelemetryService telemetryService; private final RequestHandlerBindingAssistant requestHandlerBindingAssistant; private final PathTranslationService pathTranslationService; private final String sonarCloudUrl; private final SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService; - public ShowIssueRequestHandler(SonarLintRpcClient client, ServerApiProvider serverApiProvider, TelemetryService telemetryService, + public ShowIssueRequestHandler(SonarLintRpcClient client, ConnectionManager connectionManager, TelemetryService telemetryService, RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService, SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService) { this.client = client; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.telemetryService = telemetryService; this.requestHandlerBindingAssistant = requestHandlerBindingAssistant; this.pathTranslationService = pathTranslationService; @@ -188,13 +188,13 @@ static boolean isIssueTaint(String ruleKey) { private Optional tryFetchIssue(String connectionId, String issueKey, String projectKey, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) { - return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(connectionId, + return connectionManager.withValidConnectionFlatMapOptionalAndReturn(connectionId, serverApi -> serverApi.issue().fetchServerIssue(issueKey, projectKey, branch, pullRequest, cancelMonitor)); } private Optional tryFetchCodeSnippet(String connectionId, String fileKey, Common.TextRange textRange, String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) { - return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(connectionId, + return connectionManager.withValidConnectionFlatMapOptionalAndReturn(connectionId, api -> api.issue().getCodeSnippet(fileKey, textRange, branch, pullRequest, cancelMonitor)); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java index 2d6cf955ad..64b77068b9 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProvider.java @@ -42,7 +42,7 @@ import javax.inject.Singleton; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; @@ -55,13 +55,13 @@ public class ServerFilePathsProvider { private static final SonarLintLogger LOG = SonarLintLogger.get(); - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final Map cachedResponseFilePathByBinding = new HashMap<>(); private final Path cacheDirectoryPath; private final Cache> temporaryInMemoryFilePathCacheByBinding; - public ServerFilePathsProvider(ServerApiProvider serverApiProvider, Path storageRoot) { - this.serverApiProvider = serverApiProvider; + public ServerFilePathsProvider(ConnectionManager connectionManager, Path storageRoot) { + this.connectionManager = connectionManager; this.cacheDirectoryPath = storageRoot.resolve("cache"); this.temporaryInMemoryFilePathCacheByBinding = CacheBuilder.newBuilder() .expireAfterWrite(Duration.of(1, ChronoUnit.MINUTES)) @@ -103,13 +103,13 @@ private Optional> getPathsFromFileCache(Binding binding) { } private Optional> fetchPathsFromServer(Binding binding, SonarLintCancelMonitor cancelMonitor) { - var connectionOpt = serverApiProvider.tryGetConnection(binding.getConnectionId()); + var connectionOpt = connectionManager.tryGetConnection(binding.getConnectionId()); if (connectionOpt.isEmpty()) { LOG.debug("Connection '{}' does not exist", binding.getConnectionId()); return Optional.empty(); } try { - return serverApiProvider.withValidConnectionFlatMapOptionalAndReturn(binding.getConnectionId(), serverApi -> { + return connectionManager.withValidConnectionFlatMapOptionalAndReturn(binding.getConnectionId(), serverApi -> { List paths = fetchPathsFromServer(serverApi, binding.getSonarProjectKey(), cancelMonitor); cacheServerPaths(binding, paths); return Optional.of(paths); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java index f908f48a83..f061c50a3b 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/hotspot/HotspotService.java @@ -25,7 +25,7 @@ import javax.inject.Singleton; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.HotspotReviewStatus; @@ -66,20 +66,20 @@ public class HotspotService { private final ConfigurationRepository configurationRepository; private final ConnectionConfigurationRepository connectionRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final TelemetryService telemetryService; private final SonarProjectBranchTrackingService branchTrackingService; private final FindingReportingService findingReportingService; private final StorageService storageService; public HotspotService(SonarLintRpcClient client, StorageService storageService, ConfigurationRepository configurationRepository, - ConnectionConfigurationRepository connectionRepository, ServerApiProvider serverApiProvider, TelemetryService telemetryService, + ConnectionConfigurationRepository connectionRepository, ConnectionManager connectionManager, TelemetryService telemetryService, SonarProjectBranchTrackingService branchTrackingService, FindingReportingService findingReportingService) { this.client = client; this.storageService = storageService; this.configurationRepository = configurationRepository; this.connectionRepository = connectionRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.telemetryService = telemetryService; this.branchTrackingService = branchTrackingService; this.findingReportingService = findingReportingService; @@ -128,7 +128,7 @@ public CheckLocalDetectionSupportedResponse checkLocalDetectionSupported(String public CheckStatusChangePermittedResponse checkStatusChangePermitted(String connectionId, String hotspotKey, SonarLintCancelMonitor cancelMonitor) { // fixme add getConnectionByIdOrThrow var connection = connectionRepository.getConnectionById(connectionId); - var r = serverApiProvider.getConnectionOrThrow(connectionId) + var r = connectionManager.getConnectionOrThrow(connectionId) .withClientApiAndReturn(serverApi -> serverApi.hotspot().show(hotspotKey, cancelMonitor)); var allowedStatuses = HotspotReviewStatus.allowedStatusesOn(connection.getKind()); // canChangeStatus is false when the 'Administer Hotspots' permission is missing @@ -151,7 +151,7 @@ public void changeStatus(String configurationScopeId, String hotspotKey, Hotspot LOG.debug("No binding for config scope {}", configurationScopeId); return; } - serverApiProvider.withValidConnection(effectiveBindingOpt.get().getConnectionId(), serverApi -> { + connectionManager.withValidConnection(effectiveBindingOpt.get().getConnectionId(), serverApi -> { serverApi.hotspot().changeStatus(hotspotKey, newStatus, cancelMonitor); saveStatusInStorage(effectiveBindingOpt.get(), hotspotKey, newStatus); telemetryService.hotspotStatusChanged(); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java index 9f7f7b8a62..bb8844e00b 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/issue/IssueService.java @@ -35,7 +35,7 @@ import javax.inject.Singleton; import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.ImpactSeverity; import org.sonarsource.sonarlint.core.commons.LocalOnlyIssue; @@ -99,7 +99,7 @@ public class IssueService { ResolutionStatus.FALSE_POSITIVE, Transition.FALSE_POSITIVE); private final ConfigurationRepository configurationRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final StorageService storageService; private final LocalOnlyIssueStorageService localOnlyIssueStorageService; private final LocalOnlyIssueRepository localOnlyIssueRepository; @@ -110,12 +110,12 @@ public class IssueService { private final RulesService rulesService; private final TaintVulnerabilityTrackingService taintVulnerabilityTrackingService; - public IssueService(ConfigurationRepository configurationRepository, ServerApiProvider serverApiProvider, StorageService storageService, + public IssueService(ConfigurationRepository configurationRepository, ConnectionManager connectionManager, StorageService storageService, LocalOnlyIssueStorageService localOnlyIssueStorageService, LocalOnlyIssueRepository localOnlyIssueRepository, ApplicationEventPublisher eventPublisher, FindingReportingService findingReportingService, SeverityModeService severityModeService, NewCodeService newCodeService, RulesService rulesService, TaintVulnerabilityTrackingService taintVulnerabilityTrackingService) { this.configurationRepository = configurationRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.storageService = storageService; this.localOnlyIssueStorageService = localOnlyIssueStorageService; this.localOnlyIssueRepository = localOnlyIssueRepository; @@ -129,7 +129,7 @@ public IssueService(ConfigurationRepository configurationRepository, ServerApiPr public void changeStatus(String configurationScopeId, String issueKey, ResolutionStatus newStatus, boolean isTaintIssue, SonarLintCancelMonitor cancelMonitor) { var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - var serverConnection = serverApiProvider.getConnectionOrThrow(binding.getConnectionId()); + var serverConnection = connectionManager.getConnectionOrThrow(binding.getConnectionId()); var reviewStatus = transitionByResolutionStatus.get(newStatus); var projectServerIssueStore = storageService.binding(binding).findings(); boolean isServerIssue = projectServerIssueStore.containsIssue(issueKey); @@ -167,7 +167,7 @@ private static List subtract(List allIssues, Lis public boolean checkAnticipatedStatusChangeSupported(String configScopeId) { var binding = configurationRepository.getEffectiveBindingOrThrow(configScopeId); var connectionId = binding.getConnectionId(); - return serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + return connectionManager.getConnectionOrThrow(binding.getConnectionId()) .withClientApiAndReturn(serverApi -> checkAnticipatedStatusChangeSupported(serverApi, connectionId)); } @@ -185,7 +185,7 @@ private boolean checkAnticipatedStatusChangeSupported(ServerApi api, String conn } public CheckStatusChangePermittedResponse checkStatusChangePermitted(String connectionId, String issueKey, SonarLintCancelMonitor cancelMonitor) { - return serverApiProvider.getConnectionOrThrow(connectionId).withClientApiAndReturn(serverApi -> asUUID(issueKey) + return connectionManager.getConnectionOrThrow(connectionId).withClientApiAndReturn(serverApi -> asUUID(issueKey) .flatMap(localOnlyIssueRepository::findByKey) .map(r -> { // For anticipated issues we currently don't get the information from SonarQube (as there is no web API @@ -263,7 +263,7 @@ public boolean reopenIssue(String configurationScopeId, String issueId, boolean var projectServerIssueStore = storageService.binding(binding).findings(); boolean isServerIssue = projectServerIssueStore.containsIssue(issueId); if (isServerIssue) { - return serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + return connectionManager.getConnectionOrThrow(binding.getConnectionId()) .withClientApiAndReturn(serverApi -> reopenServerIssue(serverApi, binding, issueId, projectServerIssueStore, isTaintIssue, cancelMonitor)); } else { return reopenLocalIssue(issueId, configurationScopeId, cancelMonitor); @@ -284,7 +284,7 @@ private void removeAllIssuesForFile(XodusLocalOnlyIssueStore localOnlyIssueStore var issuesForFile = localOnlyIssueStore.loadForFile(configurationScopeId, filePath); var issuesToSync = subtract(allIssues, issuesForFile); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + connectionManager.getConnectionOrThrow(binding.getConnectionId()) .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor)); } @@ -293,7 +293,7 @@ private void removeIssueOnServer(XodusLocalOnlyIssueStore localOnlyIssueStore, var allIssues = localOnlyIssueStore.loadAll(configurationScopeId); var issuesToSync = allIssues.stream().filter(it -> !it.getId().equals(issueId)).collect(Collectors.toList()); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + connectionManager.getConnectionOrThrow(binding.getConnectionId()) .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor)); } @@ -308,7 +308,7 @@ private void setCommentOnLocalOnlyIssue(String configurationScopeId, UUID issueI var issuesToSync = localOnlyIssueStore.loadAll(configurationScopeId); issuesToSync.replaceAll(issue -> issue.getId().equals(issueId) ? commentedIssue : issue); var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + connectionManager.getConnectionOrThrow(binding.getConnectionId()) .withClientApi(serverApi -> serverApi.issue().anticipatedTransitions(binding.getSonarProjectKey(), issuesToSync, cancelMonitor)); localOnlyIssueStore.storeLocalOnlyIssue(configurationScopeId, commentedIssue); } @@ -324,7 +324,7 @@ private static ResponseErrorException issueNotFoundException(String issueId) { private void addCommentOnServerIssue(String configurationScopeId, String issueKey, String comment, SonarLintCancelMonitor cancelMonitor) { var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId); - serverApiProvider.getConnectionOrThrow(binding.getConnectionId()) + connectionManager.getConnectionOrThrow(binding.getConnectionId()) .withClientApi(serverApi -> serverApi.issue().addComment(issueKey, comment, cancelMonitor)); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java index 77b537974e..64a9ef5039 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/rules/RulesService.java @@ -36,7 +36,7 @@ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; import org.jetbrains.annotations.NotNull; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.analysis.RuleDetailsForAnalysis; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.BoundScope; @@ -83,7 +83,7 @@ public class RulesService { private static final SonarLintLogger LOG = SonarLintLogger.get(); public static final String IN_EMBEDDED_RULES = "' in embedded rules"; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final ConfigurationRepository configurationRepository; private final RulesRepository rulesRepository; private final StorageService storageService; @@ -94,17 +94,17 @@ public class RulesService { private final SeverityModeService severityModeService; @Inject - public RulesService(ServerApiProvider serverApiProvider, ConfigurationRepository configurationRepository, RulesRepository rulesRepository, - StorageService storageService, InitializeParams params, ApplicationEventPublisher eventPublisher, - SeverityModeService severityModeService) { - this(serverApiProvider, configurationRepository, rulesRepository, storageService, eventPublisher, + public RulesService(ConnectionManager connectionManager, ConfigurationRepository configurationRepository, RulesRepository rulesRepository, + StorageService storageService, InitializeParams params, ApplicationEventPublisher eventPublisher, + SeverityModeService severityModeService) { + this(connectionManager, configurationRepository, rulesRepository, storageService, eventPublisher, params.getStandaloneRuleConfigByKey(), severityModeService); } - RulesService(ServerApiProvider serverApiProvider, ConfigurationRepository configurationRepository, RulesRepository rulesRepository, - StorageService storageService, ApplicationEventPublisher eventPublisher, - @Nullable Map standaloneRuleConfigByKey, SeverityModeService severityModeService) { - this.serverApiProvider = serverApiProvider; + RulesService(ConnectionManager connectionManager, ConfigurationRepository configurationRepository, RulesRepository rulesRepository, + StorageService storageService, ApplicationEventPublisher eventPublisher, + @Nullable Map standaloneRuleConfigByKey, SeverityModeService severityModeService) { + this.connectionManager = connectionManager; this.configurationRepository = configurationRepository; this.rulesRepository = rulesRepository; this.storageService = storageService; @@ -138,7 +138,7 @@ public RuleDetails getRuleDetails(String configurationScopeId, String ruleKey, S public RuleDetails getActiveRuleForBinding(String ruleKey, Binding binding, SonarLintCancelMonitor cancelMonitor) { var connectionId = binding.getConnectionId(); - serverApiProvider.getConnectionOrThrow(connectionId); + connectionManager.getConnectionOrThrow(connectionId); var serverUsesStandardSeverityMode = !severityModeService.isMQRModeForConnection(connectionId); @@ -167,7 +167,7 @@ private Optional findServerActiveRuleInStorage(Binding binding private RuleDetails hydrateDetailsWithServer(String connectionId, ServerActiveRule activeRuleFromStorage, boolean skipCleanCodeTaxonomy, SonarLintCancelMonitor cancelMonitor) { var ruleKey = activeRuleFromStorage.getRuleKey(); var templateKey = activeRuleFromStorage.getTemplateKey(); - var serverConnection = serverApiProvider.getConnectionOrThrow(connectionId); + var serverConnection = connectionManager.getConnectionOrThrow(connectionId); if (StringUtils.isNotBlank(templateKey)) { var templateRule = rulesRepository.getRule(connectionId, templateKey); if (templateRule.isEmpty()) { diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/ServerEventsService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/ServerEventsService.java index 33348ce342..b28488a1f5 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/ServerEventsService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/ServerEventsService.java @@ -30,7 +30,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.annotation.PreDestroy; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.ConnectionKind; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; @@ -58,7 +58,7 @@ public class ServerEventsService { private static final SonarLintLogger LOG = SonarLintLogger.get(); private final ConfigurationRepository configurationRepository; private final ConnectionConfigurationRepository connectionConfigurationRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final LanguageSupportRepository languageSupportRepository; private final boolean shouldManageServerSentEvents; private final ApplicationEventPublisher eventPublisher; @@ -66,10 +66,10 @@ public class ServerEventsService { private final ExecutorService executorService = Executors.newSingleThreadExecutor(r -> new Thread(r, "sonarlint-server-sent-events-subscriber")); public ServerEventsService(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionConfigurationRepository, - ServerApiProvider serverApiProvider, LanguageSupportRepository languageSupportRepository, InitializeParams initializeParams, ApplicationEventPublisher eventPublisher) { + ConnectionManager connectionManager, LanguageSupportRepository languageSupportRepository, InitializeParams initializeParams, ApplicationEventPublisher eventPublisher) { this.configurationRepository = configurationRepository; this.connectionConfigurationRepository = connectionConfigurationRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.languageSupportRepository = languageSupportRepository; this.shouldManageServerSentEvents = initializeParams.getFeatureFlags().shouldManageServerSentEvents(); this.eventPublisher = eventPublisher; @@ -178,7 +178,7 @@ private void subscribe(String connectionId, Set possiblyNewProjectKeys) } private SonarQubeEventStream openStream(String connectionId) { - return new SonarQubeEventStream(languageSupportRepository.getEnabledLanguagesInConnectedMode(), connectionId, serverApiProvider, + return new SonarQubeEventStream(languageSupportRepository.getEnabledLanguagesInConnectedMode(), connectionId, connectionManager, e -> eventPublisher.publishEvent(new SonarServerEventReceivedEvent(connectionId, e))); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/SonarQubeEventStream.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/SonarQubeEventStream.java index cfbc8eb625..ec58e52549 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/SonarQubeEventStream.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/server/event/SonarQubeEventStream.java @@ -22,7 +22,7 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Consumer; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; import org.sonarsource.sonarlint.core.serverapi.push.SonarServerEvent; import org.sonarsource.sonarlint.core.serverapi.stream.EventStream; @@ -32,13 +32,13 @@ public class SonarQubeEventStream { private final Set subscribedProjectKeys = new LinkedHashSet<>(); private final Set enabledLanguages; private final String connectionId; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final Consumer eventConsumer; - public SonarQubeEventStream(Set enabledLanguages, String connectionId, ServerApiProvider serverApiProvider, Consumer eventConsumer) { + public SonarQubeEventStream(Set enabledLanguages, String connectionId, ConnectionManager connectionManager, Consumer eventConsumer) { this.enabledLanguages = enabledLanguages; this.connectionId = connectionId; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.eventConsumer = eventConsumer; } @@ -67,7 +67,7 @@ public synchronized void unsubscribe(String projectKey) { private void attemptSubscription(Set projectKeys) { if (!enabledLanguages.isEmpty()) { - serverApiProvider.getServerApi(connectionId) + connectionManager.getServerApi(connectionId) .ifPresent(serverApi -> eventStream = serverApi.push().subscribe(projectKeys, enabledLanguages, e -> notifyHandlers(e, eventConsumer))); } } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java index a2b1590566..067c3f2277 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/smartnotifications/SmartNotifications.java @@ -34,7 +34,7 @@ import javax.annotation.PreDestroy; import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.BoundScope; import org.sonarsource.sonarlint.core.commons.ConnectionKind; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; @@ -63,7 +63,7 @@ public class SmartNotifications { private final ConfigurationRepository configurationRepository; private final ConnectionConfigurationRepository connectionRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final SonarLintRpcClient client; private final TelemetryService telemetryService; private final WebSocketService webSocketService; @@ -72,11 +72,11 @@ public class SmartNotifications { private final LastEventPolling lastEventPollingService; private ExecutorServiceShutdownWatchable smartNotificationsPolling; - public SmartNotifications(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionRepository, ServerApiProvider serverApiProvider, + public SmartNotifications(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionRepository, ConnectionManager connectionManager, SonarLintRpcClient client, StorageService storageService, TelemetryService telemetryService, WebSocketService webSocketService, InitializeParams params) { this.configurationRepository = configurationRepository; this.connectionRepository = connectionRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.client = client; this.telemetryService = telemetryService; this.webSocketService = webSocketService; @@ -101,7 +101,7 @@ private void poll(SonarLintCancelMonitor cancelMonitor) { boundScopeByConnectionAndSonarProject.forEach((connectionId, boundScopesByProject) -> { var connection = connectionRepository.getConnectionById(connectionId); if (connection != null && !connection.isDisableNotifications() && !shouldSkipPolling(connection)) { - serverApiProvider.withValidConnection(connectionId, + connectionManager.withValidConnection(connectionId, serverApi -> manageNotificationsForConnection(serverApi, boundScopesByProject, connection, cancelMonitor)); } }); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java index c0c9c54aef..5407bc07b0 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java @@ -38,7 +38,7 @@ import org.sonarsource.sonarlint.core.ConnectionService; import org.sonarsource.sonarlint.core.ConnectionSuggestionProvider; import org.sonarsource.sonarlint.core.OrganizationsCache; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.SharedConnectedModeSettingsProvider; import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment; import org.sonarsource.sonarlint.core.SonarProjectsCache; @@ -131,7 +131,7 @@ ConfigurationService.class, ConfigurationRepository.class, RulesService.class, - ServerApiProvider.class, + ConnectionManager.class, ConnectionConfigurationRepository.class, RulesRepository.class, RulesExtractionHelper.class, diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java index 636c81f1b9..df3a9ed3b5 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/HotspotSynchronizationService.java @@ -24,7 +24,7 @@ import java.util.stream.Collectors; import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.Version; import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; @@ -45,12 +45,12 @@ public class HotspotSynchronizationService { private static final SonarLintLogger LOG = SonarLintLogger.get(); private final StorageService storageService; private final LanguageSupportRepository languageSupportRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; - public HotspotSynchronizationService(StorageService storageService, LanguageSupportRepository languageSupportRepository, ServerApiProvider serverApiProvider) { + public HotspotSynchronizationService(StorageService storageService, LanguageSupportRepository languageSupportRepository, ConnectionManager connectionManager) { this.storageService = storageService; this.languageSupportRepository = languageSupportRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; } public void syncServerHotspotsForProject(ServerApi serverApi, String connectionId, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) { @@ -73,7 +73,7 @@ private static Version getSonarServerVersion(ServerApi serverApi, ConnectionStor } public void fetchProjectHotspots(Binding binding, String activeBranch, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + connectionManager.withValidConnection(binding.getConnectionId(), serverApi -> downloadAllServerHotspots(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), activeBranch, cancelMonitor)); } @@ -87,7 +87,7 @@ private void downloadAllServerHotspots(String connectionId, ServerApi serverApi, } public void fetchFileHotspots(Binding binding, String activeBranch, Path serverFilePath, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + connectionManager.withValidConnection(binding.getConnectionId(), serverApi -> downloadAllServerHotspotsForFile(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), serverFilePath, activeBranch, cancelMonitor)); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java index dd897db526..78c5001031 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/IssueSynchronizationService.java @@ -24,7 +24,7 @@ import java.util.stream.Collectors; import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; @@ -42,13 +42,13 @@ public class IssueSynchronizationService { private static final SonarLintLogger LOG = SonarLintLogger.get(); private final StorageService storageService; private final LanguageSupportRepository languageSupportRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; public IssueSynchronizationService(StorageService storageService, LanguageSupportRepository languageSupportRepository, - ServerApiProvider serverApiProvider) { + ConnectionManager connectionManager) { this.storageService = storageService; this.languageSupportRepository = languageSupportRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; } public void syncServerIssuesForProject(ServerApi serverApi, String connectionId, String projectKey, String branchName, SonarLintCancelMonitor cancelMonitor) { @@ -65,7 +65,7 @@ public void syncServerIssuesForProject(ServerApi serverApi, String connectionId, } public void fetchProjectIssues(Binding binding, String activeBranch, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + connectionManager.withValidConnection(binding.getConnectionId(), serverApi -> downloadServerIssuesForProject(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), activeBranch, cancelMonitor)); } @@ -78,7 +78,7 @@ private void downloadServerIssuesForProject(String connectionId, ServerApi serve } public void fetchFileIssues(Binding binding, Path serverFileRelativePath, String activeBranch, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.withValidConnection(binding.getConnectionId(), serverApi -> + connectionManager.withValidConnection(binding.getConnectionId(), serverApi -> downloadServerIssuesForFile(binding.getConnectionId(), serverApi, binding.getSonarProjectKey(), serverFileRelativePath, activeBranch, cancelMonitor)); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java index 2177630f53..90b17e9cd5 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SonarProjectBranchesSynchronizationService.java @@ -22,7 +22,7 @@ import java.util.Optional; import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; @@ -42,17 +42,17 @@ public class SonarProjectBranchesSynchronizationService { private static final SonarLintLogger LOG = SonarLintLogger.get(); private final StorageService storageService; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final ApplicationEventPublisher eventPublisher; - public SonarProjectBranchesSynchronizationService(StorageService storageService, ServerApiProvider serverApiProvider, ApplicationEventPublisher eventPublisher) { + public SonarProjectBranchesSynchronizationService(StorageService storageService, ConnectionManager connectionManager, ApplicationEventPublisher eventPublisher) { this.storageService = storageService; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.eventPublisher = eventPublisher; } public void sync(String connectionId, String sonarProjectKey, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.withValidConnection(connectionId, serverApi -> { + connectionManager.withValidConnection(connectionId, serverApi -> { var branchesStorage = storageService.getStorageFacade().connection(connectionId).project(sonarProjectKey).branches(); Optional oldBranches = Optional.empty(); if (branchesStorage.exists()) { @@ -81,7 +81,7 @@ public String findMainBranch(String connectionId, String projectKey, SonarLintCa var storedBranches = branchesStorage.read(); return storedBranches.getMainBranchName(); } else { - return serverApiProvider.withValidConnectionAndReturn(connectionId, + return connectionManager.withValidConnectionAndReturn(connectionId, serverApi -> getProjectBranches(serverApi, projectKey, cancelMonitor)) .map(ProjectBranches::getMainBranchName).orElseThrow(); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java index beacfc13a4..ae8ed7dc3a 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/SynchronizationService.java @@ -39,7 +39,7 @@ import javax.annotation.PreDestroy; import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.branch.MatchedSonarProjectBranchChangedEvent; import org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService; import org.sonarsource.sonarlint.core.commons.Binding; @@ -86,7 +86,7 @@ public class SynchronizationService { private final SonarLintRpcClient client; private final ConfigurationRepository configurationRepository; private final LanguageSupportRepository languageSupportRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final TaskManager taskManager; private final StorageService storageService; private final Set connectedModeEmbeddedPluginKeys; @@ -107,14 +107,14 @@ public class SynchronizationService { private final Set ignoreBranchEventForScopes = ConcurrentHashMap.newKeySet(); public SynchronizationService(SonarLintRpcClient client, ConfigurationRepository configurationRepository, LanguageSupportRepository languageSupportRepository, - ServerApiProvider serverApiProvider, StorageService storageService, InitializeParams params, TaintSynchronizationService taintSynchronizationService, + ConnectionManager connectionManager, StorageService storageService, InitializeParams params, TaintSynchronizationService taintSynchronizationService, IssueSynchronizationService issueSynchronizationService, HotspotSynchronizationService hotspotSynchronizationService, SonarProjectBranchesSynchronizationService sonarProjectBranchesSynchronizationService, SonarProjectBranchTrackingService sonarProjectBranchTrackingService, PluginsRepository pluginsRepository, ApplicationEventPublisher applicationEventPublisher) { this.client = client; this.configurationRepository = configurationRepository; this.languageSupportRepository = languageSupportRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.taskManager = new TaskManager(client); this.storageService = storageService; this.connectedModeEmbeddedPluginKeys = params.getConnectedModeEmbeddedPluginPathsByKey().keySet(); @@ -184,7 +184,7 @@ private void synchronizeProjectsOfTheSameConnection(String connectionId, Map { + connectionManager.withValidConnection(connectionId, serverApi -> { var subProgressGap = progressGap / boundScopeBySonarProject.size(); var subProgress = progress; for (var entry : boundScopeBySonarProject.entrySet()) { @@ -296,7 +296,7 @@ public void onConnectionCredentialsChanged(ConnectionCredentialsChangedEvent eve private void synchronizeConnectionAndProjectsIfNeededAsync(String connectionId, Collection boundScopes) { var cancelMonitor = new SonarLintCancelMonitor(); cancelMonitor.watchForShutdown(scheduledSynchronizer); - scheduledSynchronizer.submit(() -> serverApiProvider.withValidConnection(connectionId, serverApi -> + scheduledSynchronizer.submit(() -> connectionManager.withValidConnection(connectionId, serverApi -> synchronizeConnectionAndProjectsIfNeededSync(connectionId, serverApi, boundScopes, cancelMonitor))); } diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java index 4a82bb1d79..e63b129441 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/sync/TaintSynchronizationService.java @@ -23,7 +23,7 @@ import java.util.stream.Collectors; import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService; import org.sonarsource.sonarlint.core.commons.api.SonarLanguage; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; @@ -51,22 +51,22 @@ public class TaintSynchronizationService { private final SonarProjectBranchTrackingService branchTrackingService; private final StorageService storageService; private final LanguageSupportRepository languageSupportRepository; - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; private final ApplicationEventPublisher eventPublisher; public TaintSynchronizationService(ConfigurationRepository configurationRepository, SonarProjectBranchTrackingService branchTrackingService, - StorageService storageService, LanguageSupportRepository languageSupportRepository, - ServerApiProvider serverApiProvider, ApplicationEventPublisher eventPublisher) { + StorageService storageService, LanguageSupportRepository languageSupportRepository, + ConnectionManager connectionManager, ApplicationEventPublisher eventPublisher) { this.configurationRepository = configurationRepository; this.branchTrackingService = branchTrackingService; this.storageService = storageService; this.languageSupportRepository = languageSupportRepository; - this.serverApiProvider = serverApiProvider; + this.connectionManager = connectionManager; this.eventPublisher = eventPublisher; } public void synchronizeTaintVulnerabilities(String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) { - serverApiProvider.withValidConnection(connectionId, serverApi -> { + connectionManager.withValidConnection(connectionId, serverApi -> { var allScopes = configurationRepository.getBoundScopesToConnectionAndSonarProject(connectionId, projectKey); var allScopesByOptBranch = allScopes.stream() .collect(groupingBy(b -> branchTrackingService.awaitEffectiveSonarProjectBranch(b.getConfigScopeId()))); diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/usertoken/UserTokenService.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/usertoken/UserTokenService.java index b957d2c4d6..34d0c77d8a 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/usertoken/UserTokenService.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/usertoken/UserTokenService.java @@ -21,7 +21,7 @@ import javax.inject.Named; import javax.inject.Singleton; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.auth.RevokeTokenParams; @@ -32,15 +32,15 @@ public class UserTokenService { private static final SonarLintLogger LOG = SonarLintLogger.get(); - private final ServerApiProvider serverApiProvider; + private final ConnectionManager connectionManager; - public UserTokenService(ServerApiProvider serverApiProvider) { - this.serverApiProvider = serverApiProvider; + public UserTokenService(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; } public void revokeToken(RevokeTokenParams params, SonarLintCancelMonitor cancelMonitor) { LOG.debug(String.format("Revoking token '%s'", params.getTokenName())); - serverApiProvider.getServerApi(params.getBaseUrl(), null, params.getTokenValue()) + connectionManager.getServerApi(params.getBaseUrl(), null, params.getTokenValue()) .userTokens() .revoke(params.getTokenName(), cancelMonitor); } diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/ConnectionManagerTests.java similarity index 85% rename from backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java rename to backend/core/src/test/java/org/sonarsource/sonarlint/core/ConnectionManagerTests.java index 792c4e2c2a..6fb9315331 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/ServerApiProviderTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/ConnectionManagerTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; +import org.sonarsource.sonarlint.core.connection.ServerConnection; import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider; import org.sonarsource.sonarlint.core.http.HttpClient; import org.sonarsource.sonarlint.core.http.HttpClientProvider; @@ -32,12 +33,16 @@ import org.sonarsource.sonarlint.core.repository.connection.SonarCloudConnectionConfiguration; import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient; import org.sonarsource.sonarlint.core.serverapi.EndpointParams; +import org.sonarsource.sonarlint.core.serverapi.ServerApi; +import org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -class ServerApiProviderTests { +class ConnectionManagerTests { @RegisterExtension private static final SonarLintLogTester logTester = new SonarLintLogTester(); @@ -45,7 +50,7 @@ class ServerApiProviderTests { private final ConnectionAwareHttpClientProvider awareHttpClientProvider = mock(ConnectionAwareHttpClientProvider.class); private final HttpClientProvider httpClientProvider = mock(HttpClientProvider.class); private final SonarLintRpcClient client = mock(SonarLintRpcClient.class); - private final ServerApiProvider underTest = new ServerApiProvider(connectionRepository, awareHttpClientProvider, httpClientProvider, + private final ConnectionManager underTest = new ConnectionManager(connectionRepository, awareHttpClientProvider, httpClientProvider, SonarCloudActiveEnvironment.prod(), client); @Test @@ -136,4 +141,22 @@ void getServerApi_returns_empty_if_client_cant_provide_httpclient() { assertThat(serverApi).isEmpty(); } + + @Test + void should_log_invalid_connection() { + var connectionId = "connectionId"; + ConnectionManager spy = spy(underTest); + var serverApi = mock(ServerApi.class); + var serverConnection = new ServerConnection(connectionId, serverApi, client); + doReturn(Optional.of(serverConnection)).when(spy).tryGetConnection(connectionId); + + // switch connection to invalid state + spy.withValidConnection(connectionId, api -> { + throw new ForbiddenException("401"); + }); + // attempt to get connection + spy.withValidConnection(connectionId, api -> {}); + + assertThat(logTester.logs()).contains("Connection 'connectionId' is invalid"); + } } diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java index a98271aeb6..82b505a069 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/SonarProjectsCacheTests.java @@ -85,15 +85,15 @@ public String getName() { return PROJECT_NAME_2; } }; - private final ServerApiProvider serverApiProvider = mockServerApiProvider(); + private final ConnectionManager connectionManager = mockServerApiProvider(); private final ServerApi serverApi = mock(ServerApi.class, Mockito.RETURNS_DEEP_STUBS); - private final SonarProjectsCache underTest = new SonarProjectsCache(serverApiProvider); + private final SonarProjectsCache underTest = new SonarProjectsCache(connectionManager); @BeforeEach public void setup() { - doReturn(Optional.of(serverApi)).when(serverApiProvider).getServerApi(SQ_1); + doReturn(Optional.of(serverApi)).when(connectionManager).getServerApi(SQ_1); var serverConnection = new ServerConnection(SQ_1, serverApi, null); - doReturn(Optional.of(serverConnection)).when(serverApiProvider).tryGetConnection(SQ_1); + doReturn(Optional.of(serverConnection)).when(connectionManager).tryGetConnection(SQ_1); } @Test diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java index 62b7a70339..70c0e57485 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelperTests.java @@ -66,7 +66,7 @@ class VersionSoonUnsupportedHelperTests { private static final SonarCloudConnectionConfiguration SC_CONNECTION = new SonarCloudConnectionConfiguration(SonarCloudActiveEnvironment.PRODUCTION_URI, SC_CONNECTION_ID, "https://sonarcloud.com", true); private final SonarLintRpcClient client = mock(SonarLintRpcClient.class); - private final ServerApiProvider serverApiProvider = mock(ServerApiProvider.class); + private final ConnectionManager connectionManager = mock(ConnectionManager.class); private final SynchronizationService synchronizationService = mock(SynchronizationService.class); private ConfigurationRepository configRepository; @@ -77,7 +77,7 @@ class VersionSoonUnsupportedHelperTests { void init() { configRepository = new ConfigurationRepository(); connectionRepository = new ConnectionConfigurationRepository(); - underTest = new VersionSoonUnsupportedHelper(client, configRepository, serverApiProvider, connectionRepository, synchronizationService); + underTest = new VersionSoonUnsupportedHelper(client, configRepository, connectionManager, connectionRepository, synchronizationService); } @Test @@ -87,7 +87,7 @@ void should_trigger_notification_when_new_binding_to_previous_lts_detected_on_co configRepository.addOrReplace(new ConfigurationScope(CONFIG_SCOPE_ID_2, null, false, ""), bindingConfiguration); connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(connectionManager.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); @@ -107,8 +107,8 @@ void should_trigger_multiple_notification_when_new_bindings_to_previous_lts_dete connectionRepository.addOrReplace(SQ_CONNECTION_2); var serverApi = mock(ServerApi.class); var serverApi2 = mock(ServerApi.class); - when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); - when(serverApiProvider.tryGetConnection(CONFIG_SCOPE_ID_2)).thenReturn(Optional.of(new ServerConnection(CONFIG_SCOPE_ID_2, serverApi2, null))); + when(connectionManager.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(connectionManager.tryGetConnection(CONFIG_SCOPE_ID_2)).thenReturn(Optional.of(new ServerConnection(CONFIG_SCOPE_ID_2, serverApi2, null))); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID_2), eq(serverApi2), any(SonarLintCancelMonitor.class))).thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion() + ".9")); @@ -132,7 +132,7 @@ void should_not_trigger_notification_when_config_scope_has_no_effective_binding( void should_trigger_notification_when_new_binding_to_previous_lts_detected() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(connectionManager.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))) .thenReturn(VersionUtils.getMinimalSupportedVersion()); @@ -147,7 +147,7 @@ void should_trigger_notification_when_new_binding_to_previous_lts_detected() { void should_trigger_once_when_same_binding_to_previous_lts_detected_twice() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(connectionManager.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getMinimalSupportedVersion()); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, @@ -163,7 +163,7 @@ void should_trigger_once_when_same_binding_to_previous_lts_detected_twice() { void should_trigger_notification_when_new_binding_to_in_between_lts_detected() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(connectionManager.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(Version.create(VersionUtils.getMinimalSupportedVersion().getName() + ".9")); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, @@ -177,7 +177,7 @@ void should_trigger_notification_when_new_binding_to_in_between_lts_detected() { void should_not_trigger_notification_when_new_binding_to_current_lts_detected() { connectionRepository.addOrReplace(SQ_CONNECTION); var serverApi = mock(ServerApi.class); - when(serverApiProvider.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); + when(connectionManager.tryGetConnection(SQ_CONNECTION_ID)).thenReturn(Optional.of(new ServerConnection(SQ_CONNECTION_ID, serverApi, null))); when(synchronizationService.readOrSynchronizeServerVersion(eq(SQ_CONNECTION_ID), eq(serverApi), any(SonarLintCancelMonitor.class))).thenReturn(VersionUtils.getCurrentLts()); underTest.bindingConfigChanged(new BindingConfigChangedEvent(CONFIG_SCOPE_ID, null, diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java index fd8efa639c..3e99ca15c2 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java @@ -37,7 +37,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.commons.Binding; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor; @@ -64,7 +64,7 @@ class ServerFilePathsProviderTest { public static final String PROJECT_KEY = "projectKey"; private Path cacheDirectory; - private final ServerApiProvider serverApiProvider = mockServerApiProvider(); + private final ConnectionManager connectionManager = mockServerApiProvider(); private final ServerApi serverApi_A = mock(ServerApi.class); private final ServerApi serverApi_B = mock(ServerApi.class); private final SonarLintCancelMonitor cancelMonitor =mock(SonarLintCancelMonitor.class); @@ -77,20 +77,18 @@ void before(@TempDir Path storageDir) throws IOException { cacheDirectory = storageDir.resolve("cache"); Files.createDirectories(cacheDirectory); - when(serverApiProvider.getServerApi(CONNECTION_A)).thenReturn(Optional.of(serverApi_A)); - when(serverApiProvider.getServerApi(CONNECTION_B)).thenReturn(Optional.of(serverApi_B)); + when(connectionManager.getServerApi(CONNECTION_A)).thenReturn(Optional.of(serverApi_A)); + when(connectionManager.getServerApi(CONNECTION_B)).thenReturn(Optional.of(serverApi_B)); var serverConnectionA = new ServerConnection(CONNECTION_A, serverApi_A, null); var serverConnectionB = new ServerConnection(CONNECTION_B, serverApi_B, null); - doReturn(Optional.of(serverConnectionA)).when(serverApiProvider).getValidConnection(CONNECTION_A); - doReturn(Optional.of(serverConnectionB)).when(serverApiProvider).getValidConnection(CONNECTION_B); - doReturn(Optional.of(serverConnectionA)).when(serverApiProvider).tryGetConnection(CONNECTION_A); - doReturn(Optional.of(serverConnectionB)).when(serverApiProvider).tryGetConnection(CONNECTION_B); + doReturn(Optional.of(serverConnectionA)).when(connectionManager).tryGetConnection(CONNECTION_A); + doReturn(Optional.of(serverConnectionB)).when(connectionManager).tryGetConnection(CONNECTION_B); when(serverApi_A.component()).thenReturn(componentApi_A); when(serverApi_B.component()).thenReturn(componentApi_B); mockServerFilePaths(componentApi_A, "pathA", "pathB"); mockServerFilePaths(componentApi_B, "pathC", "pathD"); - underTest = new ServerFilePathsProvider(serverApiProvider, storageDir); + underTest = new ServerFilePathsProvider(connectionManager, storageDir); cacheDirectory = storageDir.resolve("cache"); } @@ -108,7 +106,7 @@ void clear_cache_directory_after_initialization(@TempDir Path storageDir) throws @Test void log_when_connection_not_exist() { - when(serverApiProvider.getServerApi(anyString())).thenReturn(Optional.empty()); + when(connectionManager.getServerApi(anyString())).thenReturn(Optional.empty()); underTest.getServerPaths(new Binding("conId", null), cancelMonitor); diff --git a/backend/core/src/test/java/testutils/TestUtils.java b/backend/core/src/test/java/testutils/TestUtils.java index 15efc5d74b..b98e4127ce 100644 --- a/backend/core/src/test/java/testutils/TestUtils.java +++ b/backend/core/src/test/java/testutils/TestUtils.java @@ -20,7 +20,7 @@ package testutils; import java.lang.management.ManagementFactory; -import org.sonarsource.sonarlint.core.ServerApiProvider; +import org.sonarsource.sonarlint.core.ConnectionManager; import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment; import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider; import org.sonarsource.sonarlint.core.http.HttpClientProvider; @@ -57,13 +57,13 @@ public static void printThreadDump() { System.out.println(generateThreadDump()); } - public static ServerApiProvider mockServerApiProvider() { + public static ConnectionManager mockServerApiProvider() { var connectionRepository = mock(ConnectionConfigurationRepository.class); var awareHttpClientProvider = mock(ConnectionAwareHttpClientProvider.class); var httpClientProvider = mock(HttpClientProvider.class); var sonarCloudActiveEnvironment = mock(SonarCloudActiveEnvironment.class); var client = mock(SonarLintRpcClient.class); - var obj = new ServerApiProvider(connectionRepository, awareHttpClientProvider, httpClientProvider, + var obj = new ConnectionManager(connectionRepository, awareHttpClientProvider, httpClientProvider, sonarCloudActiveEnvironment, client); return spy(obj); } From 37022a51607caf21d69528a85605fab06198baa9 Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Mon, 20 Jan 2025 12:14:37 +0100 Subject: [PATCH 3/6] rebase --- .../core/file/ServerFilePathsProviderTest.java | 4 +--- .../mediumtest/NotebookLanguageMediumTests.java | 13 +++---------- .../synchronization/ConnectionSyncMediumTests.java | 8 ++++---- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java b/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java index 3e99ca15c2..9a83d6b07c 100644 --- a/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java +++ b/backend/core/src/test/java/org/sonarsource/sonarlint/core/file/ServerFilePathsProviderTest.java @@ -19,7 +19,6 @@ */ package org.sonarsource.sonarlint.core.file; -import ch.qos.logback.classic.spi.ILoggingEvent; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -110,8 +109,7 @@ void log_when_connection_not_exist() { underTest.getServerPaths(new Binding("conId", null), cancelMonitor); - assertThat(logTester.logs()) - .containsExactly("Connection 'conId' does not exist"); + assertThat(logTester.logs()).contains("Connection 'conId' does not exist"); } @Test diff --git a/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java b/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java index 8e56db6f94..1aafa76e3d 100644 --- a/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java @@ -19,12 +19,10 @@ */ package mediumtest; -import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.extension.RegisterExtension; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams; -import org.sonarsource.sonarlint.core.rpc.protocol.common.Language; import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest; import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness; import utils.TestPlugin; @@ -41,22 +39,17 @@ class NotebookLanguageMediumTests { @SonarLintTest void should_not_enable_sync_for_notebook_python_language(SonarLintTestHarness harness) { - client = harness.newFakeClient() + var fakeClient = harness.newFakeClient() .build(); var backend = harness.newBackend() .withStorage(CONNECTION_ID, s -> s.withPlugins(TestPlugin.JAVASCRIPT, TestPlugin.JAVA) .withProject("test-project") .withProject(JAVA_MODULE_KEY)) - .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY) - .withEnabledLanguageInStandaloneMode(Language.JAVA) - .withEnabledLanguageInStandaloneMode(Language.JS) - .withEnabledLanguageInStandaloneMode(Language.IPYTHON) - .withFullSynchronization() - .build(client); + .build(fakeClient); backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID)); - await().untilAsserted(() -> assertThat(client.getLogMessages()).contains("[SYNC] Languages enabled for synchronization: [java, js]")); + await().untilAsserted(() -> assertThat(fakeClient.getLogMessages()).contains("[SYNC] Languages enabled for synchronization: [java, js]")); } } diff --git a/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java b/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java index 174affe947..8fd095daf0 100644 --- a/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java @@ -23,9 +23,6 @@ import java.time.temporal.ChronoUnit; import java.util.List; import java.util.concurrent.ExecutionException; -import mediumtest.fixtures.TestPlugin; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.sonarsource.sonarlint.core.commons.RuleType; @@ -45,6 +42,9 @@ import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.when; import static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA; +import static org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.newBackend; +import static org.sonarsource.sonarlint.core.test.utils.SonarLintBackendFixture.newFakeClient; +import static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.newSonarQubeServer; class ConnectionSyncMediumTests { public static final String CONNECTION_ID = "connectionId"; @@ -146,7 +146,7 @@ void it_should_notify_client_if_invalid_token(Integer status) { .withResponseCode(status) .start(); - backend = newBackend() + var backend = newBackend() .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage.withPlugin(TestPlugin.JAVA).withProject("projectKey")) .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, "projectKey") .withEnabledLanguageInStandaloneMode(JAVA) From d07e793dfc05d9a7f98735442e720c8f710cf99e Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Mon, 20 Jan 2025 13:15:55 +0100 Subject: [PATCH 4/6] fix test --- .../NotebookLanguageMediumTests.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java b/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java index 1aafa76e3d..bcf976230e 100644 --- a/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/NotebookLanguageMediumTests.java @@ -20,31 +20,40 @@ package mediumtest; import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams; +import org.sonarsource.sonarlint.core.rpc.protocol.common.Language; import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest; import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness; import utils.TestPlugin; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.sonarsource.sonarlint.core.test.utils.server.ServerFixture.newSonarQubeServer; class NotebookLanguageMediumTests { - @RegisterExtension - private static final SonarLintLogTester logTester = new SonarLintLogTester(); private static final String CONNECTION_ID = StringUtils.repeat("very-long-id", 30); private static final String JAVA_MODULE_KEY = "test-project-2"; + public static final String SCOPE_ID = "scopeId"; @SonarLintTest void should_not_enable_sync_for_notebook_python_language(SonarLintTestHarness harness) { var fakeClient = harness.newFakeClient() .build(); + var server = newSonarQubeServer() + .withProject(JAVA_MODULE_KEY, project -> project.withBranch("main")) + .start(); var backend = harness.newBackend() - .withStorage(CONNECTION_ID, s -> s.withPlugins(TestPlugin.JAVASCRIPT, TestPlugin.JAVA) - .withProject("test-project") + .withSonarQubeConnection(CONNECTION_ID, server, storage -> storage + .withPlugin(TestPlugin.JAVA) + .withPlugin(TestPlugin.JAVASCRIPT) + .withPlugin(TestPlugin.PYTHON) .withProject(JAVA_MODULE_KEY)) + .withBoundConfigScope(SCOPE_ID, CONNECTION_ID, JAVA_MODULE_KEY) + .withEnabledLanguageInStandaloneMode(Language.JAVA) + .withEnabledLanguageInStandaloneMode(Language.JS) + .withEnabledLanguageInStandaloneMode(Language.IPYTHON) + .withFullSynchronization() .build(fakeClient); backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams(CONNECTION_ID)); From c988d92b83be7025f4e165bc6e2237a2747d9344 Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Mon, 20 Jan 2025 13:52:13 +0100 Subject: [PATCH 5/6] PR feedback --- .../sonarsource/sonarlint/core/ConnectionManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java index c2bc552016..9529fbd7f9 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java @@ -143,16 +143,25 @@ private HttpClient getClientFor(EndpointParams params, Either httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword())); } + /** + * Throws ResponseErrorException if connection with provided ID is not found in ConnectionConfigurationRepository + */ public ServerConnection getConnectionOrThrow(String connectionId) { var serverApi = getServerApiOrThrow(connectionId); return new ServerConnection(connectionId, serverApi, client); } + /** + * Returns empty Optional if connection with provided ID is not found in ConnectionConfigurationRepository + */ public Optional tryGetConnection(String connectionId) { return getServerApi(connectionId) .map(serverApi -> new ServerConnection(connectionId, serverApi, client)); } + /** + * Should be used for WebAPI requests without an authentication + */ public Optional tryGetConnectionWithoutCredentials(String connectionId) { return getServerApiWithoutCredentials(connectionId) .map(serverApi -> new ServerConnection(connectionId, serverApi, client)); From 5ebeeafc8f44874a19d563d6e83ee9532b1e9d61 Mon Sep 17 00:00:00 2001 From: Kirill Knize Date: Mon, 20 Jan 2025 16:16:51 +0100 Subject: [PATCH 6/6] PR feedback, polishing --- API_CHANGES.md | 5 +++++ .../sonarlint/core/connection/ServerConnection.java | 7 ------- backend/server-connection/src/main/proto/sonarlint.proto | 4 ---- .../synchronization/ConnectionSyncMediumTests.java | 8 +++----- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/API_CHANGES.md b/API_CHANGES.md index 2cb07812fc..f0bc6e495a 100644 --- a/API_CHANGES.md +++ b/API_CHANGES.md @@ -2,6 +2,11 @@ ## Breaking changes +* Add new method `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#invalidToken` to notify client that WebAPI calls to SQS/SQC fails due to wrong token + * Client can implement this method to offer user to change credentials for the connection to fix the problem + * For now notification is being sent only for 403 Forbidden HTTP response code since it's corresponds to malformed/wrong token and ignores 401 Unauthorized response code since it's a user permissions problem that has to be addressed on the server + * Also once notification sent, backend doesn't attempt to send any requests to server anymore until credentials changed + * Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#didRaiseIssue` and associated types. See `raiseIssues` and `raiseHotspots` instead. * Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getIssueTrackingService` and associated types. Tracking is managed by the backend. * Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getSecurityHotspotMatchingService` and associated types. Tracking is managed by the backend. diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java index c4dc56603c..269fc341e4 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/connection/ServerConnection.java @@ -28,7 +28,6 @@ import org.sonarsource.sonarlint.core.rpc.protocol.client.sync.InvalidTokenParams; import org.sonarsource.sonarlint.core.serverapi.ServerApi; import org.sonarsource.sonarlint.core.serverapi.exception.ForbiddenException; -import org.sonarsource.sonarlint.core.serverapi.exception.UnauthorizedException; public class ServerConnection { @@ -63,9 +62,6 @@ public T withClientApiAndReturn(Function serverApiConsumer) { } catch (ForbiddenException e) { state = ConnectionState.INVALID_CREDENTIALS; notifyClientAboutWrongTokenIfNeeded(); - } catch (UnauthorizedException e) { - state = ConnectionState.MISSING_PERMISSION; - notifyClientAboutWrongTokenIfNeeded(); } return null; } @@ -78,9 +74,6 @@ public void withClientApi(Consumer serverApiConsumer) { } catch (ForbiddenException e) { state = ConnectionState.INVALID_CREDENTIALS; notifyClientAboutWrongTokenIfNeeded(); - } catch (UnauthorizedException e) { - state = ConnectionState.MISSING_PERMISSION; - notifyClientAboutWrongTokenIfNeeded(); } } diff --git a/backend/server-connection/src/main/proto/sonarlint.proto b/backend/server-connection/src/main/proto/sonarlint.proto index 597f9de2e5..f0aea08ce0 100644 --- a/backend/server-connection/src/main/proto/sonarlint.proto +++ b/backend/server-connection/src/main/proto/sonarlint.proto @@ -100,10 +100,6 @@ message LastEventPolling { int64 last_event_polling = 1; } -message LastWebApiErrorNotification { - int64 last_wrong_token_notification = 1; -} - enum NewCodeDefinitionMode { UNKNOWN = 0; NUMBER_OF_DAYS = 1; diff --git a/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java b/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java index 8fd095daf0..926fc5f479 100644 --- a/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java +++ b/medium-tests/src/test/java/mediumtest/synchronization/ConnectionSyncMediumTests.java @@ -23,8 +23,6 @@ import java.time.temporal.ChronoUnit; import java.util.List; import java.util.concurrent.ExecutionException; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.sonarsource.sonarlint.core.commons.RuleType; import org.sonarsource.sonarlint.core.commons.api.TextRange; import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.config.DidChangeCredentialsParams; @@ -133,9 +131,9 @@ void it_should_sync_when_credentials_are_updated(SonarLintTestHarness harness) { "Synchronizing project branches for project 'projectKey'")); } - @ParameterizedTest - @ValueSource(ints = {401, 403}) - void it_should_notify_client_if_invalid_token(Integer status) { + @SonarLintTest + void it_should_notify_client_if_invalid_token() { + var status = 403; var client = newFakeClient() .withCredentials(CONNECTION_ID, "user", "pw") .build();