@@ -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 |
@@ -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"); |
@@ -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 |
@@ -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; | |||
} | |||
} |
@@ -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); |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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> |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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) | |||
; | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -36,7 +36,7 @@ public class DbVersion71Test { | |||
@Test | |||
public void verify_migration_count() { | |||
verifyMigrationCount(underTest, 13); | |||
verifyMigrationCount(underTest, 14); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} | |||
} |