aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>2018-02-07 11:20:25 +0100
committerGuillaume Jambet <guillaume.jambet@gmail.com>2018-03-01 15:21:05 +0100
commitfd1ca855caea58c6310c7f19710f223987630e6a (patch)
tree6ceed78505472fb45ae208107b2dabc4080fc9fb /server
parent827381f0e4e80b69ba67cd0128f47166eda916be (diff)
downloadsonarqube-fd1ca855caea58c6310c7f19710f223987630e6a.tar.gz
sonarqube-fd1ca855caea58c6310c7f19710f223987630e6a.zip
SONAR-10345 Create WEBHOOKS table & dao
Diffstat (limited to 'server')
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java2
-rw-r--r--server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl54
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java3
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java19
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java65
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java110
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java37
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml58
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java2
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java135
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java126
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java1
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java59
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java2
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql0
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java232
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java41
18 files changed, 919 insertions, 29 deletions
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<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
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<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
index 00000000000..c85df463612
--- /dev/null
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml
@@ -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>
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<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
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
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql
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.<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
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));
+ }
+ }
+
+}