return Optional.ofNullable(mapper(session).selectByKey(key));
}
+ public Optional<ComponentDto> selectByKeyCaseInsensitive(DbSession session, String key) {
+ return Optional.ofNullable(mapper(session).selectByKeyCaseInsensitive(key));
+ }
+
public Optional<ComponentDto> selectByKeyAndBranch(DbSession session, String key, String branch) {
return Optional.ofNullable(mapper(session).selectBranchByKeyAndBranchKey(key, generateBranchKey(key, branch), branch));
}
public interface ComponentMapper {
@CheckForNull
- ComponentDto selectByKey(String key);
+ ComponentDto selectByKey(@Param("key") String key);
+
+ @CheckForNull
+ ComponentDto selectByKeyCaseInsensitive(@Param("key") String key);
@CheckForNull
ComponentDto selectBranchByKeyAndBranchKey(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch);
ComponentDto selectPrByKeyAndBranchKey(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch);
@CheckForNull
- ComponentDto selectByUuid(String uuid);
+ ComponentDto selectByUuid(@Param("uuid") String uuid);
/**
* Return sub project of component keys
p.kee=#{key,jdbcType=VARCHAR}
</select>
+ <select id="selectByKeyCaseInsensitive" parameterType="String" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM components p
+ where
+ lower(p.kee)=lower(#{key,jdbcType=VARCHAR})
+ </select>
+
<select id="selectBranchByKeyAndBranchKey" parameterType="String" resultType="Component">
select
<include refid="componentColumns"/>
verifyNoInteractions(auditPersister);
}
+ @Test
+ public void selectByKeyCaseInsensitive_shouldFindProject_whenCaseIsDifferent() {
+ String projectKey = randomAlphabetic(5).toLowerCase();
+ db.components().insertPrivateProject(c -> c.setDbKey(projectKey));
+
+ ComponentDto result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey.toUpperCase()).orElse(null);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getKey()).isEqualTo(projectKey);
+ }
+
+ @Test
+ public void selectByKeyCaseInsensitive_shouldNotFindProject_whenKeyIsDifferent() {
+ String projectKey = randomAlphabetic(5).toLowerCase();
+ db.components().insertPrivateProject(c -> c.setDbKey(projectKey));
+
+ Optional<ComponentDto> result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey + randomAlphabetic(1));
+
+ assertThat(result).isEmpty();
+ }
+
private boolean privateFlagOfUuid(String uuid) {
return underTest.selectByUuid(db.getSession(), uuid).get().isPrivate();
}
}
}
- public static BadRequestException create(List<String> errorMessages) {
- checkArgument(!errorMessages.isEmpty(), "At least one error message is required");
- checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty");
- return new BadRequestException(errorMessages);
+ public static void throwBadRequestException(String message, Object... messageArguments) {
+ throw create(format(message, messageArguments));
}
public static BadRequestException create(String... errorMessages) {
return create(asList(errorMessages));
}
+ public static BadRequestException create(List<String> errorMessages) {
+ checkArgument(!errorMessages.isEmpty(), "At least one error message is required");
+ checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty");
+ return new BadRequestException(errorMessages);
+ }
+
public List<String> errors() {
return errors;
}
*/
package org.sonar.server.component;
-import com.google.common.collect.ImmutableSet;
import java.util.Date;
import java.util.Locale;
import java.util.Optional;
import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE;
import static org.sonar.core.component.ComponentKeys.isValidProjectKey;
import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+import static org.sonar.server.exceptions.BadRequestException.throwBadRequestException;
public class ComponentUpdater {
- private static final Set<String> MAIN_BRANCH_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP);
+ private static final Set<String> MAIN_BRANCH_QUALIFIERS = Set.of(Qualifiers.PROJECT, Qualifiers.APP);
+ private static final String KEY_ALREADY_EXISTS_ERROR = "Could not create %s with key: \"%s\". A similar key already exists: \"%s\"";
+ private static final String MALFORMED_KEY_ERROR = "Malformed key for %s: '%s'. %s.";
private final DbClient dbClient;
private final I18n i18n;
return componentDto;
}
+ public void commitAndIndex(DbSession dbSession, ComponentDto componentDto) {
+ projectIndexers.commitAndIndexComponents(dbSession, singletonList(componentDto), Cause.PROJECT_CREATION);
+ }
+
/**
* Create component without committing.
* Don't forget to call commitAndIndex(...) when ready to commit.
@Nullable String userUuid, @Nullable String userLogin, @Nullable String mainBranchName,
Consumer<ComponentDto> componentModifier) {
checkKeyFormat(newComponent.qualifier(), newComponent.key());
+ checkKeyAlreadyExists(dbSession, newComponent);
+
ComponentDto componentDto = createRootComponent(dbSession, newComponent, componentModifier);
if (isRootProject(componentDto)) {
createMainBranch(dbSession, componentDto.uuid(), mainBranchName);
return componentDto;
}
- public void commitAndIndex(DbSession dbSession, ComponentDto componentDto) {
- projectIndexers.commitAndIndexComponents(dbSession, singletonList(componentDto), Cause.PROJECT_CREATION);
+ private void checkKeyFormat(String qualifier, String key) {
+ checkRequest(isValidProjectKey(key), MALFORMED_KEY_ERROR, getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE);
}
- private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, Consumer<ComponentDto> componentModifier) {
- checkRequest(!dbClient.componentDao().selectByKey(session, newComponent.key()).isPresent(),
- "Could not create %s, key already exists: %s", getQualifierToDisplay(newComponent.qualifier()), newComponent.key());
+ private void checkKeyAlreadyExists(DbSession dbSession, NewComponent newComponent) {
+ Optional<ComponentDto> componentDto = newComponent.isProject()
+ ? dbClient.componentDao().selectByKeyCaseInsensitive(dbSession, newComponent.key())
+ : dbClient.componentDao().selectByKey(dbSession, newComponent.key());
+
+ componentDto.map(ComponentDto::getKey)
+ .ifPresent(existingKey -> throwBadRequestException(KEY_ALREADY_EXISTS_ERROR, getQualifierToDisplay(newComponent.qualifier()), newComponent.key(), existingKey));
+ }
+ private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, Consumer<ComponentDto> componentModifier) {
long now = system2.now();
String uuid = uuidFactory.create();
}
}
- private void checkKeyFormat(String qualifier, String key) {
- checkRequest(isValidProjectKey(key), "Malformed key for %s: '%s'. %s.", getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE);
- }
-
private String getQualifierToDisplay(String qualifier) {
return i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project");
}
return description;
}
+ public boolean isProject() {
+ return PROJECT.equals(qualifier);
+ }
+
public static class Builder {
private String description;
private String key;
import org.sonar.api.config.internal.Encryption;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.i18n.I18n;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.project.ProjectDefaultVisibility;
import org.sonar.server.project.Visibility;
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();
+ @Rule
+ public final I18nRule i18n = new I18nRule();
private final AzureDevOpsHttpClient azureDevOpsHttpClient = mock(AzureDevOpsHttpClient.class);
- private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
+ private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory());
private final Encryption encryption = mock(Encryption.class);
assertThatThrownBy(request::execute)
.isInstanceOf(BadRequestException.class)
- .hasMessage("Could not create null, key already exists: " + GENERATED_PROJECT_KEY);
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
}
@Test
import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.i18n.I18n;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.project.ProjectDefaultVisibility;
import org.sonar.server.project.Visibility;
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();
+ @Rule
+ public final I18nRule i18n = new I18nRule();
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
- private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
+ private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory());
private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
assertThatThrownBy(request::execute)
.isInstanceOf(BadRequestException.class)
- .hasMessageContaining("Could not create null, key already exists: " + GENERATED_PROJECT_KEY);
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
}
@Test
import org.sonar.alm.client.bitbucketserver.Repository;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.i18n.I18n;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.project.ProjectDefaultVisibility;
import org.sonar.server.project.Visibility;
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();
+ @Rule
+ public final I18nRule i18n = new I18nRule();
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
private final BitbucketServerRestClient bitbucketServerRestClient = mock(BitbucketServerRestClient.class);
- private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
+ private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory());
private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
.execute();
})
.isInstanceOf(BadRequestException.class)
- .hasMessage("Could not create null, key already exists: " + GENERATED_PROJECT_KEY);
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
}
@Test
import org.sonar.server.permission.PermissionTemplateService;
import static java.util.stream.IntStream.rangeClosed;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
private final TestProjectIndexers projectIndexers = new TestProjectIndexers();
private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
- private ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2,
+ private final ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2,
permissionTemplateService,
new FavoriteUpdater(db.getDbClient()),
projectIndexers, new SequenceUuidFactory());
.build();
assertThatThrownBy(() -> underTest.create(session, newComponent, null, null))
.isInstanceOf(BadRequestException.class)
- .hasMessage("Could not create Project, key already exists: " + existing.getDbKey());
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", existing.getDbKey(), existing.getDbKey());
}
@Test
.isInstanceOf(BadRequestException.class)
.hasMessageContaining("Malformed key for Project: 'roject%Key'");
}
+
+ @Test
+ public void create_shouldFail_whenCreatingProjectWithExistingKeyButDifferentCase() {
+ String existingKey = randomAlphabetic(5).toUpperCase();
+ db.components().insertPrivateProject(component -> component.setDbKey(existingKey));
+ String newKey = existingKey.toLowerCase();
+
+ 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\"", newKey, existingKey);
+ }
}
import org.junit.Test;
import static com.google.common.base.Strings.repeat;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.api.resources.Qualifiers.PROJECT;
assertThat(newComponent.qualifier()).isEqualTo(PROJECT);
}
+ @Test
+ public void isProject_shouldReturnTrue_whenQualifierIsProject() {
+ NewComponent newComponent = underTest.setKey(KEY)
+ .setName(NAME)
+ .setQualifier(PROJECT)
+ .build();
+
+ assertThat(newComponent.isProject()).isTrue();
+ }
+
+ @Test
+ public void isProject_shouldReturnFalse_whenQualifierIsNotProject() {
+ NewComponent newComponent = underTest.setKey(KEY)
+ .setName(NAME)
+ .setQualifier(randomAlphabetic(4))
+ .build();
+
+ assertThat(newComponent.isProject()).isFalse();
+ }
+
private void expectBuildException(Class<? extends Exception> expectedExceptionType, String expectedMessage) {
- assertThatThrownBy(() -> underTest.build())
+ assertThatThrownBy(underTest::build)
.isInstanceOf(expectedExceptionType)
.hasMessageContaining(expectedMessage);
}