From fd1ca855caea58c6310c7f19710f223987630e6a Mon Sep 17 00:00:00 2001 From: Guillaume Jambet Date: Wed, 7 Feb 2018 11:20:25 +0100 Subject: [PATCH] SONAR-10345 Create WEBHOOKS table & dao --- .../ComputeEngineContainerImplTest.java | 2 +- .../org/sonar/db/version/schema-h2.ddl | 54 ++-- .../src/main/java/org/sonar/db/DaoModule.java | 3 + .../src/main/java/org/sonar/db/DbClient.java | 19 +- .../src/main/java/org/sonar/db/MyBatis.java | 2 + .../java/org/sonar/db/webhook/WebhookDao.java | 65 +++++ .../java/org/sonar/db/webhook/WebhookDto.java | 110 +++++++++ .../org/sonar/db/webhook/WebhookMapper.java | 37 +++ .../org/sonar/db/webhook/WebhookMapper.xml | 58 +++++ .../test/java/org/sonar/db/DaoModuleTest.java | 2 +- .../org/sonar/db/webhook/WebhookDaoTest.java | 135 ++++++++++ .../version/v71/CreateWebhooksTable.java | 126 ++++++++++ .../db/migration/version/v71/DbVersion71.java | 1 + .../version/v71/CreateWebhooksTableTest.java | 59 +++++ .../version/v71/DbVersion71Test.java | 2 +- .../v71/CreateWebhooksTableTest/empty.sql | 0 .../sonar/server/webhook/ws/CreateAction.java | 232 ++++++++++++++++++ .../server/webhook/ws/WebhookSupport.java | 41 ++++ 18 files changed, 919 insertions(+), 29 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql create mode 100644 server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index fa90b0e6620..3ab7291a64c 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -118,7 +118,7 @@ public class ComputeEngineContainerImplTest { assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION + 26 // level 1 - + 52 // content of DaoModule + + 53 // content of DaoModule + 3 // content of EsSearchModule + 59 // content of CorePropertyDefinitions + 1 // StopFlagContainer diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index 67e6e5bbfbd..e965838d048 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -708,26 +708,6 @@ CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS" ("TOKEN_HASH"); CREATE UNIQUE INDEX "USER_TOKENS_LOGIN_NAME" ON "USER_TOKENS" ("LOGIN", "NAME"); -CREATE TABLE "WEBHOOK_DELIVERIES" ( - "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, - "COMPONENT_UUID" VARCHAR(40) NOT NULL, - "ANALYSIS_UUID" VARCHAR(40), - "CE_TASK_UUID" VARCHAR(40), - "NAME" VARCHAR(100) NOT NULL, - "URL" VARCHAR(2000) NOT NULL, - "SUCCESS" BOOLEAN NOT NULL, - "HTTP_STATUS" INT, - "DURATION_MS" INT, - "PAYLOAD" CLOB NOT NULL, - "ERROR_STACKTRACE" CLOB, - "CREATED_AT" BIGINT NOT NULL -); -CREATE UNIQUE INDEX "PK_WEBHOOK_DELIVERIES" ON "WEBHOOK_DELIVERIES" ("UUID"); -CREATE INDEX "COMPONENT_UUID" ON "WEBHOOK_DELIVERIES" ("COMPONENT_UUID"); -CREATE INDEX "CE_TASK_UUID" ON "WEBHOOK_DELIVERIES" ("CE_TASK_UUID"); -CREATE INDEX "ANALYSIS_UUID" ON "WEBHOOK_DELIVERIES" ("ANALYSIS_UUID"); - - CREATE TABLE "ES_QUEUE" ( "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, "DOC_TYPE" VARCHAR(40) NOT NULL, @@ -771,3 +751,37 @@ CREATE TABLE "ANALYSIS_PROPERTIES" ( "CREATED_AT" BIGINT NOT NULL ); CREATE INDEX "SNAPSHOT_UUID" ON "ANALYSIS_PROPERTIES" ("SNAPSHOT_UUID"); + + +CREATE TABLE "WEBHOOKS" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "NAME" VARCHAR(100) NOT NULL, + "URL" VARCHAR(2000) NOT NULL, + "ORGANIZATION_UUID" VARCHAR(40), + "PROJECT_UUID" VARCHAR(40), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT +); +CREATE UNIQUE INDEX "PK_WEBHOOKS" ON "WEBHOOKS" ("UUID"); +CREATE INDEX "ORGANIZATION_WEBHOOK" ON "WEBHOOKS" ("ORGANIZATION_UUID"); +CREATE INDEX "PROJECT_WEBHOOK" ON "WEBHOOKS" ("PROJECT_UUID"); + + +CREATE TABLE "WEBHOOK_DELIVERIES" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "COMPONENT_UUID" VARCHAR(40) NOT NULL, + "ANALYSIS_UUID" VARCHAR(40), + "CE_TASK_UUID" VARCHAR(40), + "NAME" VARCHAR(100) NOT NULL, + "URL" VARCHAR(2000) NOT NULL, + "SUCCESS" BOOLEAN NOT NULL, + "HTTP_STATUS" INT, + "DURATION_MS" INT, + "PAYLOAD" CLOB NOT NULL, + "ERROR_STACKTRACE" CLOB, + "CREATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_WEBHOOK_DELIVERIES" ON "WEBHOOK_DELIVERIES" ("UUID"); +CREATE INDEX "COMPONENT_UUID" ON "WEBHOOK_DELIVERIES" ("COMPONENT_UUID"); +CREATE INDEX "CE_TASK_UUID" ON "WEBHOOK_DELIVERIES" ("CE_TASK_UUID"); +CREATE INDEX "ANALYSIS_UUID" ON "WEBHOOK_DELIVERIES" ("ANALYSIS_UUID"); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index ef1d147fe33..5db426bc69f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -74,6 +74,7 @@ import org.sonar.db.user.RoleDao; import org.sonar.db.user.UserDao; import org.sonar.db.user.UserGroupDao; import org.sonar.db.user.UserTokenDao; +import org.sonar.db.webhook.WebhookDao; import org.sonar.db.webhook.WebhookDeliveryDao; public class DaoModule extends Module { @@ -106,6 +107,7 @@ public class DaoModule extends Module { InternalPropertiesDao.class, IssueChangeDao.class, IssueDao.class, + LiveMeasureDao.class, MeasureDao.class, MetricDao.class, NotificationQueueDao.class, @@ -132,6 +134,7 @@ public class DaoModule extends Module { UserGroupDao.class, UserPermissionDao.class, UserTokenDao.class, + WebhookDao.class, WebhookDeliveryDao.class)); @Override diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index ee6e3b82eb9..e7b50e95bf8 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -72,6 +72,7 @@ import org.sonar.db.user.RoleDao; import org.sonar.db.user.UserDao; import org.sonar.db.user.UserGroupDao; import org.sonar.db.user.UserTokenDao; +import org.sonar.db.webhook.WebhookDao; import org.sonar.db.webhook.WebhookDeliveryDao; public class DbClient { @@ -123,7 +124,6 @@ public class DbClient { private final ActiveRuleDao activeRuleDao; private final QProfileChangeDao qProfileChangeDao; private final UserPermissionDao userPermissionDao; - private final WebhookDeliveryDao webhookDeliveryDao; private final DefaultQProfileDao defaultQProfileDao; private final EsQueueDao esQueueDao; private final PluginDao pluginDao; @@ -132,6 +132,8 @@ public class DbClient { private final QProfileEditUsersDao qProfileEditUsersDao; private final QProfileEditGroupsDao qProfileEditGroupsDao; private final LiveMeasureDao liveMeasureDao; + private final WebhookDao webhookDao; + private final WebhookDeliveryDao webhookDeliveryDao; public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) { this.database = database; @@ -185,7 +187,6 @@ public class DbClient { activeRuleDao = getDao(map, ActiveRuleDao.class); qProfileChangeDao = getDao(map, QProfileChangeDao.class); userPermissionDao = getDao(map, UserPermissionDao.class); - webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class); defaultQProfileDao = getDao(map, DefaultQProfileDao.class); esQueueDao = getDao(map, EsQueueDao.class); pluginDao = getDao(map, PluginDao.class); @@ -194,6 +195,8 @@ public class DbClient { qProfileEditUsersDao = getDao(map, QProfileEditUsersDao.class); qProfileEditGroupsDao = getDao(map, QProfileEditGroupsDao.class); liveMeasureDao = getDao(map, LiveMeasureDao.class); + webhookDao = getDao(map, WebhookDao.class); + webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class); } public DbSession openSession(boolean batch) { @@ -380,10 +383,6 @@ public class DbClient { return userPermissionDao; } - public WebhookDeliveryDao webhookDeliveryDao() { - return webhookDeliveryDao; - } - public DefaultQProfileDao defaultQProfileDao() { return defaultQProfileDao; } @@ -417,8 +416,16 @@ public class DbClient { } // should be removed. Still used by some old DAO in sonar-server + public MyBatis getMyBatis() { return myBatis; } + public WebhookDao webhookDao() { + return webhookDao ; + } + + public WebhookDeliveryDao webhookDeliveryDao() { + return webhookDeliveryDao; + } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 5559074c5c7..187c5922e21 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -127,6 +127,7 @@ import org.sonar.db.user.UserTokenCount; import org.sonar.db.user.UserTokenDto; import org.sonar.db.user.UserTokenMapper; import org.sonar.db.webhook.WebhookDeliveryMapper; +import org.sonar.db.webhook.WebhookMapper; public class MyBatis implements Startable { @@ -246,6 +247,7 @@ public class MyBatis implements Startable { UserMapper.class, UserPermissionMapper.class, UserTokenMapper.class, + WebhookMapper.class, WebhookDeliveryMapper.class }; confBuilder.loadMappers(mappers); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java new file mode 100644 index 00000000000..9bec0fe477f --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.db.webhook; + +import java.util.List; +import java.util.Optional; +import org.sonar.api.utils.System2; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +import static com.google.common.base.Preconditions.checkState; + +public class WebhookDao implements Dao { + + private final System2 system2; + + public WebhookDao(System2 system2) { + this.system2 = system2; + } + + public Optional selectByUuid(DbSession dbSession, String uuid) { + return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid)); + } + + public void insert(DbSession dbSession, WebhookDto dto) { + + checkState(dto.getOrganizationUuid() != null || dto.getProjectUuid() != null, + "A webhook can not be created if not linked to an organization or a project."); + + checkState(dto.getOrganizationUuid() == null || dto.getProjectUuid() == null, + "A webhook can not be linked to both an organization and a project."); + + mapper(dbSession).insert(dto.setCreatedAt(system2.now())); + } + + public List selectByOrganizationUuid(DbSession dbSession, String organizationUuid) { + return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationUuid); + } + + public List selectByProjectUuid(DbSession dbSession, String projectUuid) { + return mapper(dbSession).selectForProjectUuidOrderedByName(projectUuid); + } + + private static WebhookMapper mapper(DbSession dbSession) { + return dbSession.getMapper(WebhookMapper.class); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java new file mode 100644 index 00000000000..d03e9f08166 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.db.webhook; + +import javax.annotation.Nullable; + +public class WebhookDto { + + /** Technical unique identifier, can't be null */ + protected String uuid; + /** Name, can't be null */ + protected String name; + /** URL, can't be null */ + protected String url; + + @Nullable + protected String organizationUuid; + + @Nullable + protected String projectUuid; + + /** createdAt, can't be null */ + protected Long createdAt; + /** URL, can be null */ + @Nullable + protected Long updatedAt; + + public WebhookDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public WebhookDto setName(String name) { + this.name = name; + return this; + } + + public WebhookDto setUrl(String url) { + this.url = url; + return this; + } + + public WebhookDto setOrganizationUuid(@Nullable String organizationUuid) { + this.organizationUuid = organizationUuid; + return this; + } + + public WebhookDto setProjectUuid(@Nullable String projectUuid) { + this.projectUuid = projectUuid; + return this; + } + + WebhookDto setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + return this; + } + + WebhookDto setUpdatedAt(@Nullable Long updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public String getUuid() { + return uuid; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + @Nullable + public String getOrganizationUuid() { + return organizationUuid; + } + + @Nullable + public String getProjectUuid() { + return projectUuid; + } + + public Long getCreatedAt() { + return createdAt; + } + + @Nullable + public Long getUpdatedAt() { + return updatedAt; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java new file mode 100644 index 00000000000..12499745c35 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.db.webhook; + +import java.util.List; +import javax.annotation.CheckForNull; +import org.apache.ibatis.annotations.Param; + +public interface WebhookMapper { + + @CheckForNull + WebhookDto selectByUuid(@Param("webhookUuid") String webhookUuid); + + List selectForProjectUuidOrderedByName(@Param("projectUuid") String projectUuid); + + List selectForOrganizationUuidOrderedByName(@Param("organizationUuid") String organizationUuid); + + void insert(WebhookDto dto); + +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml new file mode 100644 index 00000000000..c85df463612 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + uuid, + name, + url, + organization_uuid as organizationUuid, + project_uuid as projectUuid, + created_at as createdAt, + updated_at as updatedAt + + + + + + + + + + + insert into webhooks ( + uuid, + name, + url, + organization_uuid, + project_uuid, + created_at, + updated_at + ) values ( + #{uuid,jdbcType=VARCHAR}, + #{name,jdbcType=VARCHAR}, + #{url,jdbcType=VARCHAR}, + #{organizationUuid,jdbcType=VARCHAR}, + #{projectUuid,jdbcType=VARCHAR}, + #{createdAt,jdbcType=BIGINT}, + #{updatedAt,jdbcType=BIGINT} + ) + + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java index 340ad8379fa..89f2e067b9a 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java @@ -30,6 +30,6 @@ public class DaoModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new DaoModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 52); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 53); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java new file mode 100644 index 00000000000..4b52eaae3c7 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.db.webhook; + +import java.util.Date; +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebhookDaoTest { + + @Rule + public final DbTester dbTester = DbTester.create(System2.INSTANCE).setDisableDefaultOrganization(true); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private System2 system2 = System2.INSTANCE; + + private final DbClient dbClient = dbTester.getDbClient(); + private final DbSession dbSession = dbTester.getSession(); + private final WebhookDao underTest = dbClient.webhookDao(); + + @Test + public void selectByUuid_returns_empty_if_uuid_does_not_exist() { + assertThat(underTest.selectByUuid(dbSession, "missing")).isEmpty(); + } + + @Test + public void insert_row_with_organization() { + + WebhookDto dto = new WebhookDto() + .setUuid("UUID_1") + .setName("NAME_1") + .setUrl("URL_1") + .setOrganizationUuid("UUID_2"); + + underTest.insert(dbSession, dto); + + WebhookDto stored = selectByUuid(dto.getUuid()); + + assertThat(stored.getUuid()).isEqualTo(dto.getUuid()); + assertThat(stored.getName()).isEqualTo(dto.getName()); + assertThat(stored.getUrl()).isEqualTo(dto.getUrl()); + assertThat(stored.getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); + assertThat(stored.getProjectUuid()).isNull(); + assertThat(new Date(stored.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); + assertThat(stored.getUpdatedAt()).isNull(); + } + + @Test + public void insert_row_with_project() { + + WebhookDto dto = new WebhookDto() + .setUuid("UUID_1") + .setName("NAME_1") + .setUrl("URL_1") + .setProjectUuid("UUID_2"); + + underTest.insert(dbSession, dto); + + WebhookDto stored = selectByUuid(dto.getUuid()); + + assertThat(stored.getUuid()).isEqualTo(dto.getUuid()); + assertThat(stored.getName()).isEqualTo(dto.getName()); + assertThat(stored.getUrl()).isEqualTo(dto.getUrl()); + assertThat(stored.getOrganizationUuid()).isNull(); + assertThat(stored.getProjectUuid()).isEqualTo(dto.getProjectUuid()); + assertThat(new Date(stored.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); + assertThat(stored.getUpdatedAt()).isNull(); + } + + @Test + public void fail_if_webhook_does_not_have_an_organization_nor_a_project() { + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("A webhook can not be created if not linked to an organization or a project."); + + WebhookDto dto = new WebhookDto() + .setUuid("UUID_1") + .setName("NAME_1") + .setUrl("URL_1"); + + underTest.insert(dbSession, dto); + + } + + @Test + public void fail_if_webhook_have_both_an_organization_nor_a_project() { + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("A webhook can not be linked to both an organization and a project."); + + WebhookDto dto = new WebhookDto() + .setUuid("UUID_1") + .setName("NAME_1") + .setUrl("URL_1") + .setOrganizationUuid("UUID_2") + .setProjectUuid("UUID_3"); + + underTest.insert(dbSession, dto); + + } + + private WebhookDto selectByUuid(String uuid) { + Optional dto = underTest.selectByUuid(dbSession, uuid); + assertThat(dto).isPresent(); + return dto.get(); + } + +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java new file mode 100644 index 00000000000..a82bf22ede6 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.platform.db.migration.version.v71; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class CreateWebhooksTable extends DdlChange { + + private static final String TABLE_NAME = "webhooks"; + + private static final String ORGANIZATION_WEBHOOK_INDEX_NAME = "organization_webhook"; + private static final String PROJECT_WEBHOOK_INDEX_NAME = "project_webhook"; + + private static final VarcharColumnDef UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("uuid") + .setIsNullable(false) + .setLimit(UUID_SIZE) + .build(); + private static final int NAME_COLUMN_SIZE = 100; + private static final VarcharColumnDef NAME_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("name") + .setIsNullable(false) + .setLimit(NAME_COLUMN_SIZE) + .build(); + private static final int URL_COLUMN_SIZE = 2000; + private static final VarcharColumnDef URL_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("url") + .setIsNullable(false) + .setLimit(URL_COLUMN_SIZE) + .build(); + private static final VarcharColumnDef ORGANIZATION_UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("organization_uuid") + .setIsNullable(true) + .setLimit(UUID_SIZE) + .build(); + private static final VarcharColumnDef PROJECT_UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("project_uuid") + .setIsNullable(true) + .setLimit(UUID_SIZE) + .build(); + private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder() + .setColumnName("created_at") + .setIsNullable(false) + .build(); + private static final BigIntegerColumnDef UPDATED_AT_COLUMN = newBigIntegerColumnDefBuilder() + .setColumnName("updated_at") + .setIsNullable(true) + .build(); + + private Database db; + + public CreateWebhooksTable(Database db) { + super(db); + this.db = db; + } + + @Override + public void execute(Context context) throws SQLException { + + if (!tableExists()) { + context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME) + .addPkColumn(UUID_COLUMN) + .addColumn(NAME_COLUMN) + .addColumn(URL_COLUMN) + .addColumn(ORGANIZATION_UUID_COLUMN) + .addColumn(PROJECT_UUID_COLUMN) + .addColumn(CREATED_AT_COLUMN) + .addColumn(UPDATED_AT_COLUMN) + .build() + ); + + context.execute(new CreateIndexBuilder(getDialect()) + .addColumn(ORGANIZATION_UUID_COLUMN) + .setUnique(false) + .setTable(TABLE_NAME) + .setName(ORGANIZATION_WEBHOOK_INDEX_NAME) + .build() + ); + + context.execute(new CreateIndexBuilder(getDialect()) + .addColumn(PROJECT_UUID_COLUMN) + .setUnique(false) + .setTable(TABLE_NAME) + .setName(PROJECT_WEBHOOK_INDEX_NAME) + .build() + ); + + } + + } + + private boolean tableExists() throws SQLException { + try (Connection connection = db.getDataSource().getConnection()) { + return DatabaseUtils.tableExists(TABLE_NAME, connection); + } + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java index 1e18627e81c..d786fe7f4af 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java @@ -40,6 +40,7 @@ public class DbVersion71 implements DbVersion { .add(2010, "Populate table PROJECT_LINKS2", PopulateTableProjectLinks2.class) .add(2011, "Drop table PROJECT_LINKS", DropTableProjectLinks.class) .add(2012, "Rename table PROJECT_LINKS2 to PROJECT_LINKS", RenameTableProjectLinks2ToProjectLinks.class) + .add(2013, "Create WEBHOOKS Table", CreateWebhooksTable.class) ; } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java new file mode 100644 index 00000000000..c6ece1d1319 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.platform.db.migration.version.v71; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.BIGINT; +import static java.sql.Types.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; + +public class CreateWebhooksTableTest { + + private static final String TABLE = "webhooks"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(CreateWebhooksTableTest.class, "empty.sql"); + + private CreateWebhooksTable underTest = new CreateWebhooksTable(db.database()); + + @Test + public void creates_table_on_empty_db() throws SQLException { + underTest.execute(); + + assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0); + + db.assertColumnDefinition(TABLE, "uuid", VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "name", VARCHAR, 100, false); + db.assertColumnDefinition(TABLE, "url", VARCHAR, 2000, false); + + db.assertColumnDefinition(TABLE, "organization_uuid", VARCHAR, 40, true); + db.assertColumnDefinition(TABLE, "project_uuid", VARCHAR, 40, true); + + db.assertColumnDefinition(TABLE, "created_at", BIGINT, null, false); + db.assertColumnDefinition(TABLE, "updated_at", BIGINT, null, true); + + db.assertIndex(TABLE, "organization_webhook", "organization_uuid"); + db.assertIndex(TABLE, "project_webhook", "project_uuid"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java index a93444604fd..bc893e7bd4c 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java @@ -36,7 +36,7 @@ public class DbVersion71Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 13); + verifyMigrationCount(underTest, 14); } } diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql new file mode 100644 index 00000000000..e69de29bb2d diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java new file mode 100644 index 00000000000..029b30666ad --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java @@ -0,0 +1,232 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook.ws; + +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.webhook.WebhookDto; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.Webhooks; + +import static com.google.common.base.Preconditions.checkState; +import static java.lang.String.format; +import static java.util.Locale.ENGLISH; +import static org.apache.commons.lang.StringUtils.isNotBlank; +import static org.sonar.api.web.UserRole.ADMIN; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.ACTION_CREATE; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM_MAXIMUM_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.ORGANIZATION_KEY_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.ORGANIZATION_KEY_PARAM_MAXIMUM_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM_MAXIMUN_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM_MAXIMUM_LENGTH; +import static org.sonar.server.ws.KeyExamples.KEY_ORG_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.NAME_WEBHOOK_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.URL_WEBHOOK_EXAMPLE_001; +import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.Webhooks.CreateWsResponse.Webhook; +import static org.sonarqube.ws.Webhooks.CreateWsResponse.newBuilder; + +public class CreateAction implements WebhooksWsAction { + + private static final int MAX_NUMBER_OF_WEBHOOKS = 10; + + private final DbClient dbClient; + private final UserSession userSession; + private final DefaultOrganizationProvider defaultOrganizationProvider; + private final UuidFactory uuidFactory; + private final System2 system; + + public CreateAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, UuidFactory uuidFactory, System2 system) { + this.dbClient = dbClient; + this.userSession = userSession; + this.defaultOrganizationProvider = defaultOrganizationProvider; + this.uuidFactory = uuidFactory; + this.system = system; + } + + @Override + public void define(WebService.NewController controller) { + + WebService.NewAction action = controller.createAction(ACTION_CREATE) + .setPost(true) + .setDescription("Create a Webhook.
" + + "Requires the global, organization or project permission.") + .setSince("7.1") + .setResponseExample(getClass().getResource("example-webhook-create.json")) + .setHandler(this); + + action.createParam(NAME_PARAM) + .setRequired(true) + .setMaximumLength(NAME_PARAM_MAXIMUM_LENGTH) + .setDescription("The name of the webhook to create") + .setExampleValue(NAME_WEBHOOK_EXAMPLE_001); + + action.createParam(URL_PARAM) + .setRequired(true) + .setMaximumLength(URL_PARAM_MAXIMUM_LENGTH) + .setDescription("The url to be called by the webhook") + .setExampleValue(URL_WEBHOOK_EXAMPLE_001); + + action.createParam(PROJECT_KEY_PARAM) + .setRequired(false) + .setMaximumLength(PROJECT_KEY_PARAM_MAXIMUN_LENGTH) + .setDescription("The key of the project that will own the webhook") + .setExampleValue(KEY_PROJECT_EXAMPLE_001); + + action.createParam(ORGANIZATION_KEY_PARAM) + .setInternal(true) + .setRequired(false) + .setMaximumLength(ORGANIZATION_KEY_PARAM_MAXIMUM_LENGTH) + .setDescription("The key of the organization that will own the webhook") + .setExampleValue(KEY_ORG_EXAMPLE_001); + + } + + @Override + public void handle(Request request, Response response) throws Exception { + + userSession.checkLoggedIn(); + + String name = request.mandatoryParam(NAME_PARAM); + String url = request.mandatoryParam(URL_PARAM); + String projectKey = request.param(PROJECT_KEY_PARAM); + String organizationKey = request.param(ORGANIZATION_KEY_PARAM); + + try (DbSession dbSession = dbClient.openSession(true)) { + + OrganizationDto organizationDto; + if (isNotBlank(organizationKey)) { + Optional dtoOptional = dbClient.organizationDao().selectByKey(dbSession, organizationKey); + organizationDto = checkFoundWithOptional(dtoOptional, "No organization with key '%s'", organizationKey); + } else { + organizationDto = defaultOrganizationDto(dbSession); + } + + ComponentDto projectDto = null; + if (isNotBlank(projectKey)) { + com.google.common.base.Optional dtoOptional = dbClient.componentDao().selectByKey(dbSession, projectKey); + checkFoundWithOptional(dtoOptional, "No project with key '%s'", projectKey); + checkThatProjectBelongsToOrganization(dtoOptional.get(), organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey); + checkUserPermissionOn(dtoOptional.get()); + projectDto = dtoOptional.get(); + } else { + checkUserPermissionOn(organizationDto); + } + + checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url); + + writeResponse(request, response, doHandle(dbSession, organizationDto, projectDto, name, url)); + } + + } + + private WebhookDto doHandle(DbSession dbSession, @Nullable OrganizationDto organization, @Nullable ComponentDto project, String name, String url) { + + checkState(organization != null || project != null, + "A webhook can not be created if not linked to an organization or a project."); + + WebhookDto dto = new WebhookDto() + .setUuid(uuidFactory.create()) + .setName(name) + .setUrl(url) + + if (project != null) { + checkNumberOfWebhook(numberOfWebhookOf(dbSession, project), "Maximum number of webhook reached for project '%s'", project.getKey()); + dto.setProjectUuid(project.projectUuid()); + } else { + checkNumberOfWebhook(numberOfWebhookOf(dbSession, organization), "Maximum number of webhook reached for organization '%s'", organization.getKey()); + dto.setOrganizationUuid(organization.getUuid()); + } + + dbClient.webhookDao().insert(dbSession, dto); + + return dto; + } + + private static void writeResponse(Request request, Response response, WebhookDto element) { + Webhooks.CreateWsResponse.Builder responseBuilder = newBuilder(); + responseBuilder.setWebhook(Webhook.newBuilder() + .setKey(element.getUuid()) + .setName(element.getName()) + .setUrl(element.getUrl())); + + writeProtobuf(responseBuilder.build(), request, response); + } + + private static void checkNumberOfWebhook(int nbOfWebhooks, String message, Object... messageArguments) { + if (nbOfWebhooks >= MAX_NUMBER_OF_WEBHOOKS){ + throw new IllegalArgumentException(format(message, messageArguments)); + } + } + + private int numberOfWebhookOf(DbSession dbSession, OrganizationDto organization) { + return dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid()).size(); + } + + private int numberOfWebhookOf(DbSession dbSession, ComponentDto project) { + return dbClient.webhookDao().selectByProjectUuid(dbSession, project.uuid()).size(); + } + + private void checkUserPermissionOn(ComponentDto componentDto) { + userSession.checkComponentPermission(ADMIN, componentDto); + } + + private void checkUserPermissionOn(OrganizationDto organizationDto) { + userSession.checkPermission(ADMINISTER, organizationDto); + } + + private static void checkThatProjectBelongsToOrganization(ComponentDto componentDto, OrganizationDto organizationDto, String message, Object... messageArguments) { + if (!organizationDto.getUuid().equals(componentDto.getOrganizationUuid())) { + throw new NotFoundException(format(message, messageArguments)); + } + } + + private static void checkUrlPattern(String url, String message, Object... messageArguments) { + if (!url.toLowerCase(ENGLISH).startsWith("http://") && !url.toLowerCase(ENGLISH).startsWith("https://")) { + throw new IllegalArgumentException(format(message, messageArguments)); + } + String sub = url.substring("http://".length()); + if (sub.contains(":") && !sub.substring(sub.indexOf(':')).contains("@")) { + throw new IllegalArgumentException(format(message, messageArguments)); + } + } + + private OrganizationDto defaultOrganizationDto(DbSession dbSession) { + String uuid = defaultOrganizationProvider.get().getUuid(); + return dbClient.organizationDao().selectByUuid(dbSession, uuid).get(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java new file mode 100644 index 00000000000..3201daf29ba --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook.ws; + +import static java.lang.String.format; +import static java.util.Locale.ENGLISH; + +class WebhookSupport { + + private WebhookSupport() { + // only statics + } + + static void checkUrlPattern(String url, String message, Object... messageArguments) { + if (!url.toLowerCase(ENGLISH).startsWith("http://") && !url.toLowerCase(ENGLISH).startsWith("https://")) { + throw new IllegalArgumentException(format(message, messageArguments)); + } + String sub = url.substring("http://".length()); + if (sub.contains(":") && !sub.substring(sub.indexOf(':')).contains("@")) { + throw new IllegalArgumentException(format(message, messageArguments)); + } + } + +} -- 2.39.5