From 3663eaea80abc474d9ba3a2582b3bff033a49d4c Mon Sep 17 00:00:00 2001 From: Matteo Mara Date: Thu, 8 Jun 2023 23:07:58 +0200 Subject: [PATCH] SONAR-19003 Fix error when retrieving multiple components with the same key and different case. --- .../sonar/db/component/ComponentDaoIT.java | 12 +++++------ .../org/sonar/db/component/ComponentDao.java | 5 ++--- .../sonar/db/component/ComponentMapper.java | 2 +- .../server/component/ComponentUpdaterIT.java | 21 +++++++++++++++++++ .../server/component/ComponentUpdater.java | 17 +++++++++++---- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/component/ComponentDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/component/ComponentDaoIT.java index beb63a96a19..b9b9934f3cf 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/component/ComponentDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/component/ComponentDaoIT.java @@ -1796,10 +1796,10 @@ public class ComponentDaoIT { String projectKey = randomAlphabetic(5).toLowerCase(); db.components().insertPrivateProject(c -> c.setKey(projectKey)).getMainBranchComponent(); - ComponentDto result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey.toUpperCase()).orElse(null); + List result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey.toUpperCase()); - assertThat(result).isNotNull(); - assertThat(result.getKey()).isEqualTo(projectKey); + assertThat(result).isNotEmpty(); + assertThat(result.get(0).getKey()).isEqualTo(projectKey); } @Test @@ -1809,9 +1809,9 @@ public class ComponentDaoIT { BranchDto projectBranch = db.components().insertProjectBranch(project); ComponentDto file = db.components().insertFile(projectBranch); - ComponentDto result = underTest.selectByKeyCaseInsensitive(db.getSession(), file.getKey()).orElse(null); + List result = underTest.selectByKeyCaseInsensitive(db.getSession(), file.getKey()); - assertThat(result).isNull(); + assertThat(result).isEmpty(); } @Test @@ -1819,7 +1819,7 @@ public class ComponentDaoIT { String projectKey = randomAlphabetic(5).toLowerCase(); db.components().insertPrivateProject(c -> c.setKey(projectKey)).getMainBranchComponent(); - Optional result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey + randomAlphabetic(1)); + List result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey + randomAlphabetic(1)); assertThat(result).isEmpty(); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java index 22ca24a9df2..7e96d889995 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java @@ -33,7 +33,6 @@ import javax.annotation.Nullable; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; import org.sonar.db.Dao; import org.sonar.db.DbSession; import org.sonar.db.RowNotFoundException; @@ -180,8 +179,8 @@ public class ComponentDao implements Dao { return Optional.ofNullable(mapper(session).selectByKeyAndBranchOrPr(key, null, pullRequestId)); } - public Optional selectByKeyCaseInsensitive(DbSession session, String key) { - return Optional.ofNullable(mapper(session).selectByKeyCaseInsensitive(key)); + public List selectByKeyCaseInsensitive(DbSession session, String key) { + return mapper(session).selectByKeyCaseInsensitive(key); } /** diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java index 1e954409aa0..49f6ad27ec0 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -30,7 +30,7 @@ import org.apache.ibatis.session.RowBounds; public interface ComponentMapper { @CheckForNull - ComponentDto selectByKeyCaseInsensitive(@Param("key") String key); + List selectByKeyCaseInsensitive(@Param("key") String key); @CheckForNull ComponentDto selectByKeyAndBranchOrPr(@Param("key") String key, @Nullable @Param("branch") String branch, @Nullable @Param("pullRequest") String pullRequest); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java index 504e25af0e3..afc40542c2b 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ComponentUpdaterIT.java @@ -20,6 +20,7 @@ package org.sonar.server.component; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -338,6 +339,26 @@ public class ComponentUpdaterIT { .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", newKey, existingKey); } + @Test + public void createComponent_shouldFail_whenCreatingComponentWithMultipleExistingKeyButDifferentCase() { + String existingKey = randomAlphabetic(5).toUpperCase(); + String existingKeyLowerCase = existingKey.toLowerCase(); + db.components().insertPrivateProject(component -> component.setKey(existingKey)); + db.components().insertPrivateProject(component -> component.setKey(existingKeyLowerCase)); + String newKey = StringUtils.capitalize(existingKeyLowerCase); + + NewComponent newComponent = NewComponent.newComponentBuilder() + .setKey(newKey) + .setName(DEFAULT_PROJECT_NAME) + .build(); + + DbSession dbSession = db.getSession(); + assertThatThrownBy(() -> underTest.create(dbSession, newComponent, null, null)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s, %s\"", newKey, existingKey, existingKeyLowerCase); + } + + @Test public void create_createsComponentWithMasterBranchName() { String componentNameAndKey = "createApplicationOrPortfolio"; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java index c905660861a..1126ed1f793 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java @@ -20,11 +20,15 @@ package org.sonar.server.component; import com.google.common.annotations.VisibleForTesting; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Scopes; @@ -167,12 +171,17 @@ public class ComponentUpdater { } private void checkKeyAlreadyExists(DbSession dbSession, NewComponent newComponent) { - Optional componentDto = newComponent.isProject() + List componentDtos = newComponent.isProject() ? dbClient.componentDao().selectByKeyCaseInsensitive(dbSession, newComponent.key()) - : dbClient.componentDao().selectByKey(dbSession, newComponent.key()); + : dbClient.componentDao().selectByKey(dbSession, newComponent.key()).map(Collections::singletonList).orElse(new ArrayList<>()); - componentDto.map(ComponentDto::getKey) - .ifPresent(existingKey -> throwBadRequestException(KEY_ALREADY_EXISTS_ERROR, getQualifierToDisplay(newComponent.qualifier()), newComponent.key(), existingKey)); + if (!componentDtos.isEmpty()) { + String alreadyExistingKeys = componentDtos + .stream() + .map(ComponentDto::getKey) + .collect(Collectors.joining(", ")); + throwBadRequestException(KEY_ALREADY_EXISTS_ERROR, getQualifierToDisplay(newComponent.qualifier()), newComponent.key(), alreadyExistingKeys); + } } private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, Consumer componentModifier, long now) { -- 2.39.5