]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10345 Create WEBHOOKS table & dao
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>
Wed, 7 Feb 2018 10:20:25 +0000 (11:20 +0100)
committerGuillaume Jambet <guillaume.jambet@gmail.com>
Thu, 1 Mar 2018 14:21:05 +0000 (15:21 +0100)
18 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java [new file with mode: 0644]

index fa90b0e662044774a41ecd6ba54d86300367fcbb..3ab7291a64c709b07385c4b475d293e3ef406cae 100644 (file)
@@ -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
index 67e6e5bbfbd23ff0e64f74f46b0c0121fde43f86..e965838d04821f76cf1e597b8e44c6928c7abc3e 100644 (file)
@@ -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");
index ef1d147fe339b0a6949004105ebac224bf114853..5db426bc69f34793eb74c933d165ce89bfc50531 100644 (file)
@@ -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
index ee6e3b82eb9b66665c208d0a48b8eafadbc7ecde..e7b50e95bf8addd625d41565bfd88da1c92d19cb 100644 (file)
@@ -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;
+  }
 }
index 5559074c5c747636d2e3f63056eb61234df68304..187c5922e21d4b79ae90c5eba2e26e9b6721d7c0 100644 (file)
@@ -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 (file)
index 0000000..9bec0fe
--- /dev/null
@@ -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<WebhookDto> 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<WebhookDto> selectByOrganizationUuid(DbSession dbSession, String organizationUuid) {
+    return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationUuid);
+  }
+
+  public List<WebhookDto> 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 (file)
index 0000000..d03e9f0
--- /dev/null
@@ -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 (file)
index 0000000..1249974
--- /dev/null
@@ -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<WebhookDto> selectForProjectUuidOrderedByName(@Param("projectUuid") String projectUuid);
+
+  List<WebhookDto> 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 (file)
index 0000000..c85df46
--- /dev/null
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.webhook.WebhookMapper">
+
+  <sql id="sqlLiteColumns">
+    uuid,
+    name,
+    url,
+    organization_uuid as organizationUuid,
+    project_uuid as projectUuid,
+    created_at as createdAt,
+    updated_at as updatedAt
+  </sql>
+
+
+  <select id="selectByUuid" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
+    select <include refid="sqlLiteColumns" />
+    from webhooks
+    where uuid = #{webhookUuid,jdbcType=VARCHAR}
+  </select>
+
+  <select id="selectForOrganizationUuidOrderedByName" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
+    select <include refid="sqlLiteColumns" />
+    from webhooks
+    where organization_uuid = #{organizationUuid,jdbcType=VARCHAR}
+    order by name asc
+  </select>
+
+  <select id="selectForProjectUuidOrderedByName" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
+    select <include refid="sqlLiteColumns" />
+    from webhooks
+    where project_uuid = #{projectUuid,jdbcType=VARCHAR}
+    order by name asc
+  </select>
+
+  <insert id="insert" parameterType="org.sonar.db.webhook.WebhookDto" useGeneratedKeys="false">
+    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}
+    )
+  </insert>
+
+</mapper>
index 340ad8379fa4cf3d7b5457eecfe53a0c7003d1e6..89f2e067b9af4f7ae7e1d5fc969fb69d42a5f814 100644 (file)
@@ -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 (file)
index 0000000..4b52eaa
--- /dev/null
@@ -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<WebhookDto> 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 (file)
index 0000000..a82bf22
--- /dev/null
@@ -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);
+    }
+  }
+}
index 1e18627e81c51a36115894c402829f8a94eb7fd0..d786fe7f4af4e038268babca8ed39ca54e8c4e26 100644 (file)
@@ -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 (file)
index 0000000..c6ece1d
--- /dev/null
@@ -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");
+  }
+}
index a93444604fd9588112a88d5c70252ccc0928f0a9..bc893e7bd4cd7a26ff92b3d112806473e35fd0e7 100644 (file)
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..029b306
--- /dev/null
@@ -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.<br>" +
+        "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<OrganizationDto> 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<ComponentDto> 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 (file)
index 0000000..3201daf
--- /dev/null
@@ -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));
+    }
+  }
+
+}