diff options
10 files changed, 211 insertions, 223 deletions
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateBranchesLiveMeasuresToMeasuresIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateBranchesLiveMeasuresToMeasuresIT.java index e02264134a6..6418fdeddc4 100644 --- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateBranchesLiveMeasuresToMeasuresIT.java +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateBranchesLiveMeasuresToMeasuresIT.java @@ -22,6 +22,7 @@ package org.sonar.server.platform.db.migration.version.v108; import com.google.gson.Gson; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -268,6 +269,64 @@ class MigrateBranchesLiveMeasuresToMeasuresIT { "\"HIGH\":4,\"total\":98723987498723987429874928748748}"); } + @Test + void should_not_migrate_large_measures() throws SQLException { + String nclocMetricUuid = insertMetric("ncloc", "INT"); + String metricWithDataUuid = insertMetric("metric_with_data", "DATA"); + String metricWithLargeDataUuid = insertMetric("metric_with_large_data", "DATA"); + + String branch1 = "branch_1"; + insertNotMigratedBranch(branch1); + String component1 = uuidFactory.create(); + insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120)); + byte[] largeValue = createLargeValue(999_999); + insertMeasure(branch1, component1, metricWithDataUuid, Map.of("measure_data", largeValue)); + insertMeasure(branch1, component1, metricWithLargeDataUuid, Map.of("measure_data", createLargeValue(1_000_000))); + + underTest.execute(); + + assertBranchMigrated(branch1); + assertThat(db.countRowsOfTable("measures")).isEqualTo(1); + + assertThat(db.select(format(SELECT_MEASURE, component1))) + .hasSize(1) + .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), + t -> t.get("json_value"), t -> t.get("json_value_hash")) + .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0,\"metric_with_data\":\"" + + new String(largeValue, StandardCharsets.UTF_8) + "\"}", -8442559321192521885L)); + } + + @Test + void should_not_migrate_not_persisted_metrics() throws SQLException { + String devCostMetricUuid = insertMetric(CoreMetrics.DEVELOPMENT_COST_KEY, "STRING"); + String nclocDataMetricUuid = insertMetric(CoreMetrics.NCLOC_DATA_KEY, "STRING"); + String executableLinesDataMetricUuid = insertMetric(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, "STRING"); + + String branch1 = "branch_1"; + insertNotMigratedBranch(branch1); + String component1 = uuidFactory.create(); + insertMeasure(branch1, component1, devCostMetricUuid, Map.of("text_value", "123")); + insertMeasure(branch1, component1, nclocDataMetricUuid, Map.of("text_value", "456")); + insertMeasure(branch1, component1, executableLinesDataMetricUuid, Map.of("text_value", "789")); + + underTest.execute(); + + assertBranchMigrated(branch1); + assertThat(db.countRowsOfTable("measures")).isEqualTo(1); + + assertThat(db.select(format(SELECT_MEASURE, component1))) + .hasSize(1) + .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), + t -> t.get("json_value"), t -> t.get("json_value_hash")) + .containsOnly(tuple(component1, branch1, "{\"development_cost\":\"123\"}", -4081454374503046534L)); + } + + private byte[] createLargeValue(int size) { + byte[] value = new byte[size]; + Arrays.fill(value, (byte) 'a'); + return value; + } + private void assertBranchMigrated(String branch) { List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from project_branches where uuid = '%s'", MEASURES_MIGRATED_COLUMN, branch)); assertThat(result) diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java index ef338b918e2..e1cac143632 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java @@ -34,6 +34,10 @@ import org.slf4j.LoggerFactory; import org.sonar.api.utils.System2; import org.sonar.db.Database; import org.sonar.db.DatabaseUtils; +import org.sonar.db.dialect.H2; +import org.sonar.db.dialect.MsSql; +import org.sonar.db.dialect.Oracle; +import org.sonar.db.dialect.PostgreSql; import org.sonar.server.platform.db.migration.step.DataChange; import org.sonar.server.platform.db.migration.step.MassUpdate; import org.sonar.server.platform.db.migration.step.Select; @@ -58,6 +62,8 @@ public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange { FROM live_measures lm INNER JOIN metrics m ON m.uuid = lm.metric_uuid WHERE lm.project_uuid = ? + AND (lm.measure_data is null OR %s(lm.measure_data) < 1000000) + AND m.name NOT IN ('executable_lines_data', 'ncloc_data') ORDER BY lm.component_uuid """; @@ -119,9 +125,11 @@ public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange { LOGGER.info("Starting the migration of {} {}s (total number of {}s: {})", uuids.size(), item, item, total); int migrated = 0; + String selectQuery = String.format(SELECT_QUERY, getByteLengthFunction()); + for (String uuid : uuids) { try { - migrateItem(uuid, context); + migrateItem(uuid, context, selectQuery); } catch (Exception e) { LOGGER.error(format("Migration of %s %s failed", item, uuid)); throw e; @@ -134,14 +142,24 @@ public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange { } } - private void migrateItem(String uuid, Context context) throws SQLException { + private String getByteLengthFunction() { + return switch (getDialect().getId()) { + case PostgreSql.ID -> "OCTET_LENGTH"; + case MsSql.ID -> "DATALENGTH"; + case Oracle.ID -> "DBMS_LOB.GETLENGTH"; + case H2.ID -> "LENGTH"; + default -> throw new IllegalStateException("Unsupported dialect: " + getDialect().getId()); + }; + } + + private void migrateItem(String uuid, Context context, String selectQuery) throws SQLException { LOGGER.debug("Migrating {} {}...", item, uuid); Map<String, Object> measureValues = new HashMap<>(); AtomicReference<String> componentUuid = new AtomicReference<>(null); MassUpdate massUpdate = context.prepareMassUpdate(); - massUpdate.select(SELECT_QUERY).setString(1, uuid); + massUpdate.select(selectQuery).setString(1, uuid); massUpdate.update(INSERT_QUERY); massUpdate.execute((row, update) -> { boolean shouldUpdate = false; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/HealthController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/HealthController.java index 8d45e96db24..669296482a4 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/HealthController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/HealthController.java @@ -19,6 +19,7 @@ */ package org.sonar.server.v2.api.system.controller; +import javax.annotation.Nullable; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ServerException; import org.sonar.server.health.Health; @@ -26,6 +27,7 @@ import org.sonar.server.health.HealthChecker; import org.sonar.server.platform.NodeInformation; import org.sonar.server.user.SystemPasscode; import org.sonar.server.user.UserSession; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -47,18 +49,15 @@ public class HealthController { private final NodeInformation nodeInformation; private final UserSession userSession; - public HealthController(HealthChecker healthChecker, SystemPasscode systemPasscode, NodeInformation nodeInformation, - UserSession userSession) { + @Autowired(required=true) + public HealthController(HealthChecker healthChecker, SystemPasscode systemPasscode, @Nullable NodeInformation nodeInformation, + @Nullable UserSession userSession) { this.healthChecker = healthChecker; this.systemPasscode = systemPasscode; this.nodeInformation = nodeInformation; this.userSession = userSession; } - public HealthController(HealthChecker healthChecker, SystemPasscode systemPasscode) { - this(healthChecker, systemPasscode, null, null); - } - @GetMapping public Health getHealth(@RequestHeader(value = "X-Sonar-Passcode", required = false) String requestPassCode) { if (systemPasscode.isValidPasscode(requestPassCode) || isSystemAdmin()) { diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java index bd010589ae6..962682037d7 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java @@ -19,75 +19,30 @@ */ package org.sonar.server.v2.config; -import org.sonar.api.platform.Server; -import org.sonar.api.resources.Languages; -import org.sonar.db.Database; -import org.sonar.db.DbClient; -import org.sonar.server.common.email.config.EmailConfigurationService; -import org.sonar.server.common.github.config.GithubConfigurationService; -import org.sonar.server.common.gitlab.config.GitlabConfigurationService; -import org.sonar.server.common.group.service.GroupMembershipService; -import org.sonar.server.common.group.service.GroupService; -import org.sonar.server.common.management.ManagedInstanceChecker; -import org.sonar.server.common.platform.LivenessChecker; -import org.sonar.server.common.project.ImportProjectService; -import org.sonar.server.common.projectbindings.service.ProjectBindingsService; -import org.sonar.server.common.rule.service.RuleService; -import org.sonar.server.common.text.MacroInterpreter; -import org.sonar.server.common.user.service.UserService; -import org.sonar.server.health.HealthChecker; -import org.sonar.server.notification.NotificationManager; -import org.sonar.server.platform.NodeInformation; -import org.sonar.server.platform.ServerFileSystem; -import org.sonar.server.platform.db.migration.DatabaseMigrationState; -import org.sonar.server.platform.db.migration.version.DatabaseVersion; -import org.sonar.server.qualitygate.QualityGateConditionsValidator; import org.sonar.server.rule.ActiveRuleService; -import org.sonar.server.rule.RuleDescriptionFormatter; -import org.sonar.server.setting.SettingsChangeNotifier; -import org.sonar.server.user.SystemPasscode; import org.sonar.server.user.UserSession; -import org.sonar.server.v2.api.analysis.controller.ActiveRulesController; import org.sonar.server.v2.api.analysis.controller.DefaultActiveRulesController; import org.sonar.server.v2.api.analysis.controller.DefaultJresController; import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController; import org.sonar.server.v2.api.analysis.controller.DefaultVersionController; -import org.sonar.server.v2.api.analysis.controller.JresController; -import org.sonar.server.v2.api.analysis.controller.ScannerEngineController; -import org.sonar.server.v2.api.analysis.controller.VersionController; -import org.sonar.server.v2.api.analysis.service.ActiveRulesHandler; import org.sonar.server.v2.api.analysis.service.ActiveRulesHandlerImpl; -import org.sonar.server.v2.api.analysis.service.JresHandler; import org.sonar.server.v2.api.analysis.service.JresHandlerImpl; -import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler; import org.sonar.server.v2.api.analysis.service.ScannerEngineHandlerImpl; import org.sonar.server.v2.api.dop.controller.DefaultDopSettingsController; -import org.sonar.server.v2.api.dop.controller.DopSettingsController; import org.sonar.server.v2.api.email.config.controller.DefaultEmailConfigurationController; -import org.sonar.server.v2.api.email.config.controller.EmailConfigurationController; import org.sonar.server.v2.api.github.config.controller.DefaultGithubConfigurationController; -import org.sonar.server.v2.api.github.config.controller.GithubConfigurationController; import org.sonar.server.v2.api.gitlab.config.controller.DefaultGitlabConfigurationController; -import org.sonar.server.v2.api.gitlab.config.controller.GitlabConfigurationController; import org.sonar.server.v2.api.group.controller.DefaultGroupController; -import org.sonar.server.v2.api.group.controller.GroupController; import org.sonar.server.v2.api.membership.controller.DefaultGroupMembershipController; -import org.sonar.server.v2.api.membership.controller.GroupMembershipController; import org.sonar.server.v2.api.mode.controller.DefaultModeController; -import org.sonar.server.v2.api.mode.controller.ModeController; import org.sonar.server.v2.api.projectbindings.controller.DefaultProjectBindingsController; -import org.sonar.server.v2.api.projectbindings.controller.ProjectBindingsController; -import org.sonar.server.v2.api.projects.controller.BoundProjectsController; import org.sonar.server.v2.api.projects.controller.DefaultBoundProjectsController; import org.sonar.server.v2.api.rule.controller.DefaultRuleController; -import org.sonar.server.v2.api.rule.controller.RuleController; import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator; import org.sonar.server.v2.api.system.controller.DatabaseMigrationsController; import org.sonar.server.v2.api.system.controller.DefaultLivenessController; import org.sonar.server.v2.api.system.controller.HealthController; -import org.sonar.server.v2.api.system.controller.LivenessController; import org.sonar.server.v2.api.user.controller.DefaultUserController; -import org.sonar.server.v2.api.user.controller.UserController; import org.sonar.server.v2.api.user.converter.UsersSearchRestResponseGenerator; import org.sonar.server.v2.common.DeprecatedHandler; import org.springframework.context.annotation.Bean; @@ -97,60 +52,35 @@ import org.springframework.context.annotation.Primary; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @Configuration -@Import(CommonWebConfig.class) +@Import({ + ActiveRulesHandlerImpl.class, + ActiveRuleService.class, + CommonWebConfig.class, + DatabaseMigrationsController.class, + DefaultActiveRulesController.class, + DefaultBoundProjectsController.class, + DefaultDopSettingsController.class, + DefaultEmailConfigurationController.class, + DefaultGithubConfigurationController.class, + DefaultGitlabConfigurationController.class, + DefaultGroupController.class, + DefaultGroupMembershipController.class, + DefaultJresController.class, + DefaultLivenessController.class, + DefaultModeController.class, + DefaultProjectBindingsController.class, + DefaultRuleController.class, + DefaultScannerEngineController.class, + DefaultUserController.class, + DefaultVersionController.class, + HealthController.class, + JresHandlerImpl.class, + ScannerEngineHandlerImpl.class, + UsersSearchRestResponseGenerator.class, + RuleRestResponseGenerator.class +}) public class PlatformLevel4WebConfig { - @Bean - public LivenessController livenessController(LivenessChecker livenessChecker, UserSession userSession, SystemPasscode systemPasscode) { - return new DefaultLivenessController(livenessChecker, systemPasscode, userSession); - } - - @Bean - public HealthController healthController(HealthChecker healthChecker, SystemPasscode systemPasscode, NodeInformation nodeInformation, - UserSession userSession) { - return new HealthController(healthChecker, systemPasscode, nodeInformation, userSession); - } - - @Bean - public DatabaseMigrationsController databaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, - Database database) { - return new DatabaseMigrationsController(databaseVersion, databaseMigrationState, database); - } - - @Bean - public UsersSearchRestResponseGenerator usersSearchResponseGenerator(UserSession userSession) { - return new UsersSearchRestResponseGenerator(userSession); - } - - @Bean - public UserController userController( - UserSession userSession, - UsersSearchRestResponseGenerator usersSearchResponseGenerator, - UserService userService) { - return new DefaultUserController(userSession, userService, usersSearchResponseGenerator); - } - - @Bean - public GroupController groupController(UserSession userSession, DbClient dbClient, GroupService groupService, ManagedInstanceChecker managedInstanceChecker) { - return new DefaultGroupController(userSession, dbClient, groupService, managedInstanceChecker); - } - - @Bean - public GroupMembershipController groupMembershipsController(UserSession userSession, - GroupMembershipService groupMembershipService, ManagedInstanceChecker managedInstanceChecker) { - return new DefaultGroupMembershipController(userSession, groupMembershipService, managedInstanceChecker); - } - - @Bean - public RuleRestResponseGenerator ruleRestResponseGenerator(Languages languages, MacroInterpreter macroInterpreter, RuleDescriptionFormatter ruleDescriptionFormatter) { - return new RuleRestResponseGenerator(languages, macroInterpreter, ruleDescriptionFormatter); - } - - @Bean - public RuleController ruleController(UserSession userSession, RuleService ruleService, RuleRestResponseGenerator ruleRestResponseGenerator) { - return new DefaultRuleController(userSession, ruleService, ruleRestResponseGenerator); - } - @Primary @Bean("org.sonar.server.v2.config.PlatformLevel4WebConfig.requestMappingHandlerMapping") public RequestMappingHandlerMapping requestMappingHandlerMapping(UserSession userSession) { @@ -159,80 +89,4 @@ public class PlatformLevel4WebConfig { return handlerMapping; } - @Bean - public GitlabConfigurationController gitlabConfigurationController(UserSession userSession, GitlabConfigurationService gitlabConfigurationService) { - return new DefaultGitlabConfigurationController(userSession, gitlabConfigurationService); - } - - @Bean - public GithubConfigurationController githubConfigurationController(UserSession userSession, GithubConfigurationService githubConfigurationService) { - return new DefaultGithubConfigurationController(userSession, githubConfigurationService); - } - - @Bean - public BoundProjectsController importedProjectsController(UserSession userSession, ImportProjectService importProjectService) { - return new DefaultBoundProjectsController(userSession, importProjectService); - } - - @Bean - public DopSettingsController dopSettingsController(UserSession userSession, DbClient dbClient) { - return new DefaultDopSettingsController(userSession, dbClient); - } - - @Bean - public ProjectBindingsController projectBindingsController(UserSession userSession, ProjectBindingsService projectBindingsService) { - return new DefaultProjectBindingsController(userSession, projectBindingsService); - } - - @Bean - public VersionController versionController(Server server) { - return new DefaultVersionController(server); - } - - @Bean - public JresHandler jresHandler() { - return new JresHandlerImpl(); - } - - @Bean - public JresController jresController(JresHandler jresHandler) { - return new DefaultJresController(jresHandler); - } - - @Bean - public ScannerEngineHandler scannerEngineHandler(ServerFileSystem serverFileSystem) { - return new ScannerEngineHandlerImpl(serverFileSystem); - } - - @Bean - public ScannerEngineController scannerEngineController(ScannerEngineHandler scannerEngineHandler) { - return new DefaultScannerEngineController(scannerEngineHandler); - } - - @Bean - public EmailConfigurationController emailConfigurationController(UserSession userSession, EmailConfigurationService emailConfigurationService) { - return new DefaultEmailConfigurationController(userSession, emailConfigurationService); - } - - @Bean - public ModeController modeController(UserSession userSession, org.sonar.api.config.Configuration configuration, DbClient dbClient, - SettingsChangeNotifier settingsChangeNotifier, NotificationManager notificationManager, QualityGateConditionsValidator qualityGateConditionsValidator) { - return new DefaultModeController(userSession, dbClient, configuration, settingsChangeNotifier, notificationManager, qualityGateConditionsValidator); - } - - @Bean - public ActiveRuleService activeRuleService(DbClient dbClient, Languages languages) { - return new ActiveRuleService(dbClient, languages); - } - - @Bean - public ActiveRulesHandler activeRulesHandler(DbClient dbClient, ActiveRuleService activeRuleService) { - return new ActiveRulesHandlerImpl(dbClient, activeRuleService); - } - - @Bean - public ActiveRulesController activeRulesController(ActiveRulesHandler activeRulesHandler) { - return new DefaultActiveRulesController(activeRulesHandler); - } - } diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java index 1810e0dbbc9..db4cf56e25f 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java @@ -19,11 +19,8 @@ */ package org.sonar.server.v2.config; -import org.sonar.db.Database; import org.sonar.server.common.platform.LivenessChecker; import org.sonar.server.health.HealthChecker; -import org.sonar.server.platform.db.migration.DatabaseMigrationState; -import org.sonar.server.platform.db.migration.version.DatabaseVersion; import org.sonar.server.user.SystemPasscode; import org.sonar.server.v2.api.system.controller.DatabaseMigrationsController; import org.sonar.server.v2.api.system.controller.DefaultLivenessController; @@ -36,7 +33,10 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableWebMvc -@Import(CommonWebConfig.class) +@Import({ + CommonWebConfig.class, + DatabaseMigrationsController.class +}) public class SafeModeWebConfig { @Bean @@ -46,12 +46,7 @@ public class SafeModeWebConfig { @Bean public HealthController healthController(HealthChecker healthChecker, SystemPasscode systemPasscode) { - return new HealthController(healthChecker, systemPasscode); + return new HealthController(healthChecker, systemPasscode, null, null); } - @Bean - public DatabaseMigrationsController databaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, - Database database) { - return new DatabaseMigrationsController(databaseVersion, databaseMigrationState, database); - } } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java index 27c05bc9e3f..dc2e42ad426 100644 --- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java @@ -23,15 +23,8 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.sonar.api.platform.Server; -import org.sonar.server.platform.ServerFileSystem; -import org.sonar.server.v2.api.analysis.controller.DefaultJresController; -import org.sonar.server.v2.api.analysis.controller.DefaultVersionController; -import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController; -import org.sonar.server.v2.api.analysis.service.JresHandler; -import org.sonar.server.v2.api.analysis.service.JresHandlerImpl; -import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler; -import org.sonar.server.v2.api.analysis.service.ScannerEngineHandlerImpl; +import org.sonar.server.user.UserSession; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -43,17 +36,12 @@ class PlatformLevel4WebConfigTest { private static Stream<Arguments> components() { return Stream.of( - arguments(platformLevel4WebConfig.versionController(mock(Server.class)), DefaultVersionController.class), - arguments(platformLevel4WebConfig.jresHandler(), JresHandlerImpl.class), - arguments(platformLevel4WebConfig.jresController(mock(JresHandler.class)), DefaultJresController.class), - arguments(platformLevel4WebConfig.scannerEngineHandler(mock(ServerFileSystem.class)), ScannerEngineHandlerImpl.class), - arguments(platformLevel4WebConfig.scannerEngineController(mock(ScannerEngineHandler.class)), DefaultScannerEngineController.class) - ); + arguments(platformLevel4WebConfig.requestMappingHandlerMapping(mock(UserSession.class)), RequestMappingHandlerMapping.class)); } @ParameterizedTest @MethodSource("components") - void components_shouldBeInjectedInPlatformLevel4WebConfig(Object component, Class<?> instanceClass) { + void custom_components_shouldBeInjectedInPlatformLevel4WebConfig(Object component, Class<?> instanceClass) { assertThat(component).isNotNull().isInstanceOf(instanceClass); } } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/SafeModeWebConfigTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/SafeModeWebConfigTest.java new file mode 100644 index 00000000000..a9d87095b5a --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/SafeModeWebConfigTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-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.sonar.server.v2.config; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.sonar.server.common.platform.LivenessChecker; +import org.sonar.server.health.HealthChecker; +import org.sonar.server.user.SystemPasscode; +import org.sonar.server.v2.api.system.controller.DefaultLivenessController; +import org.sonar.server.v2.api.system.controller.HealthController; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.Mockito.mock; + +class SafeModeWebConfigTest { + + private static final SafeModeWebConfig safeModeWebConfig = new SafeModeWebConfig(); + + private static Stream<Arguments> components() { + return Stream.of( + arguments(safeModeWebConfig.livenessController(mock(LivenessChecker.class), mock(SystemPasscode.class)), DefaultLivenessController.class), + arguments(safeModeWebConfig.healthController(mock(HealthChecker.class), mock(SystemPasscode.class)), HealthController.class)); + } + + @ParameterizedTest + @MethodSource("components") + void custom_components_shouldBeInjectedInSafeModeWebConfig(Object component, Class<?> instanceClass) { + assertThat(component).isNotNull().isInstanceOf(instanceClass); + } +} diff --git a/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarQubeIdeConnectionFilterIT.java index 9ae4f71f988..302eff253ab 100644 --- a/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java +++ b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarQubeIdeConnectionFilterIT.java @@ -40,7 +40,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class SonarLintConnectionFilterIT { +public class SonarQubeIdeConnectionFilterIT { private static final String LOGIN = "user1"; private final TestSystem2 system2 = new TestSystem2(); @@ -52,6 +52,24 @@ public class SonarLintConnectionFilterIT { system2.setNow(10_000_000L); addUser(LOGIN, 1_000_000L); + runFilter(LOGIN, "SonarQube for IDE"); + assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); + } + + @Test + public void update_with_rebranding_transitional_agent() throws IOException { + system2.setNow(10_000_000L); + addUser(LOGIN, 1_000_000L); + + runFilter(LOGIN, "SonarQube for IDE (SonarLint)"); + assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); + } + + @Test + public void update_with_legacy_sonarlint_agent() throws IOException { + system2.setNow(10_000_000L); + addUser(LOGIN, 1_000_000L); + runFilter(LOGIN, "SonarLint for IntelliJ"); assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); } @@ -67,7 +85,7 @@ public class SonarLintConnectionFilterIT { @Test public void only_applies_to_api() { - SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ThreadLocalUserSession.class), system2); + SonarQubeIdeConnectionFilter underTest = new SonarQubeIdeConnectionFilter(dbTester.getDbClient(), mock(ThreadLocalUserSession.class), system2); assertThat(underTest.doGetPattern().matches("/api/test")).isTrue(); assertThat(underTest.doGetPattern().matches("/test")).isFalse(); @@ -94,10 +112,12 @@ public class SonarLintConnectionFilterIT { @Test public void dont_fail_if_no_user_set() throws IOException { - SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), new ThreadLocalUserSession(), system2); - HttpServletRequest httpRequest = mock(HttpServletRequest.class); + var userSession = new ThreadLocalUserSession(); + userSession.unload(); + var underTest = new SonarQubeIdeConnectionFilter(dbTester.getDbClient(), userSession, system2); + var httpRequest = mock(HttpServletRequest.class); when(httpRequest.getHeader("User-Agent")).thenReturn("sonarlint"); - FilterChain chain = mock(FilterChain.class); + var chain = mock(FilterChain.class); underTest.doFilter(new JakartaHttpRequest(httpRequest), mock(HttpResponse.class), chain); verify(chain).doFilter(any(), any()); } @@ -124,7 +144,7 @@ public class SonarLintConnectionFilterIT { UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser); ThreadLocalUserSession session = new ThreadLocalUserSession(); session.set(new ServerUserSession(dbTester.getDbClient(), user, false)); - SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2); + SonarQubeIdeConnectionFilter underTest = new SonarQubeIdeConnectionFilter(dbTester.getDbClient(), session, system2); HttpServletRequest httpRequest = mock(HttpServletRequest.class); when(httpRequest.getHeader("User-Agent")).thenReturn(agent); FilterChain chain = mock(FilterChain.class); diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index a9c53d053e9..6e677fcfd0b 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -206,7 +206,7 @@ import org.sonar.server.platform.telemetry.TelemetrySubportfolioSelectionModePro import org.sonar.server.platform.telemetry.TelemetryUserEnabledProvider; import org.sonar.server.platform.telemetry.TelemetryVersionProvider; import org.sonar.server.platform.web.ActionDeprecationLoggerInterceptor; -import org.sonar.server.platform.web.SonarLintConnectionFilter; +import org.sonar.server.platform.web.SonarQubeIdeConnectionFilter; import org.sonar.server.platform.web.WebServiceFilter; import org.sonar.server.platform.web.WebServiceReroutingFilter; import org.sonar.server.platform.web.requestid.HttpRequestIdModule; @@ -422,7 +422,7 @@ public class PlatformLevel4 extends PlatformLevel { ActionDeprecationLoggerInterceptor.class, WebServiceEngine.class, new WebServicesWsModule(), - SonarLintConnectionFilter.class, + SonarQubeIdeConnectionFilter.class, WebServiceFilter.class, WebServiceReroutingFilter.class, diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarQubeIdeConnectionFilter.java index 27c6b6fa117..d77c3f650fd 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarQubeIdeConnectionFilter.java @@ -21,7 +21,6 @@ package org.sonar.server.platform.web; import java.io.IOException; import java.util.Locale; -import java.util.Optional; import org.sonar.api.server.http.HttpRequest; import org.sonar.api.server.http.HttpResponse; import org.sonar.api.utils.System2; @@ -35,7 +34,7 @@ import org.sonar.server.ws.ServletRequest; import static java.util.concurrent.TimeUnit.HOURS; -public class SonarLintConnectionFilter extends HttpFilter { +public class SonarQubeIdeConnectionFilter extends HttpFilter { private static final UrlPattern URL_PATTERN = UrlPattern.builder() .includes("/api/*") .excludes("/api/v2/*") @@ -44,7 +43,7 @@ public class SonarLintConnectionFilter extends HttpFilter { private final ThreadLocalUserSession userSession; private final System2 system2; - public SonarLintConnectionFilter(DbClient dbClient, ThreadLocalUserSession userSession, System2 system2) { + public SonarQubeIdeConnectionFilter(DbClient dbClient, ThreadLocalUserSession userSession, System2 system2) { this.dbClient = dbClient; this.userSession = userSession; this.system2 = system2; @@ -57,15 +56,20 @@ public class SonarLintConnectionFilter extends HttpFilter { @Override public void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws IOException { - ServletRequest wsRequest = new ServletRequest(request); - - Optional<String> agent = wsRequest.header("User-Agent"); - if (agent.isPresent() && agent.get().toLowerCase(Locale.ENGLISH).contains("sonarlint")) { + if (isComingFromSonarQubeIde(request)) { update(); } chain.doFilter(request, response); } + private static boolean isComingFromSonarQubeIde(HttpRequest request) { + ServletRequest wsRequest = new ServletRequest(request); + return wsRequest.header("User-Agent") + .map(agent -> agent.toLowerCase(Locale.ENGLISH)) + .filter(agent -> agent.contains("sonarlint") || agent.contains("sonarqube for ide")) + .isPresent(); + } + public void update() { if (shouldUpdate()) { try (DbSession session = dbClient.openSession(false)) { |