Преглед изворни кода

SONAR-10345 Create WEBHOOKS table & dao

tags/7.5
Guillaume Jambet пре 6 година
родитељ
комит
fd1ca855ca
18 измењених фајлова са 919 додато и 29 уклоњено
  1. 1
    1
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  2. 34
    20
      server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
  3. 3
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
  4. 13
    6
      server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
  5. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
  6. 65
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java
  7. 110
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java
  8. 37
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java
  9. 58
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml
  10. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
  11. 135
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java
  12. 126
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java
  13. 1
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java
  14. 59
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest.java
  15. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java
  16. 0
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql
  17. 232
    0
      server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
  18. 41
    0
      server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java

+ 1
- 1
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

+ 34
- 20
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");

+ 3
- 0
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

+ 13
- 6
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;
}
}

+ 2
- 0
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);

+ 65
- 0
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);
}

}

+ 110
- 0
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;
}
}

+ 37
- 0
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);

}

+ 58
- 0
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>

+ 1
- 1
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);
}
}

+ 135
- 0
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();
}

}

+ 126
- 0
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);
}
}
}

+ 1
- 0
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)
;
}
}

+ 59
- 0
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");
}
}

+ 1
- 1
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);
}

}

+ 0
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTableTest/empty.sql Прегледај датотеку


+ 232
- 0
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();
}
}

+ 41
- 0
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));
}
}

}

Loading…
Откажи
Сачувај