From: Teryk Bellahsene Date: Mon, 22 Aug 2016 16:10:08 +0000 (+0200) Subject: SONAR-7970 Create WS api/settings/set persist a simple value setting X-Git-Tag: 6.1-RC1~332 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=8a098434b7d84a74256b643f2b291f00abe505df;p=sonarqube.git SONAR-7970 Create WS api/settings/set persist a simple value setting --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SetAction.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SetAction.java new file mode 100644 index 00000000000..1e1b7ed9828 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SetAction.java @@ -0,0 +1,138 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.settings.ws; + +import java.util.Optional; +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.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.property.PropertyDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; +import org.sonar.server.ws.KeyExamples; +import org.sonarqube.ws.client.setting.SetRequest; + +public class SetAction implements SettingsWsAction { + private final DbClient dbClient; + private final ComponentFinder componentFinder; + private final UserSession userSession; + + public SetAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + this.dbClient = dbClient; + this.componentFinder = componentFinder; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("set") + .setDescription("Update a setting value.
" + + "Either '%s' or '%s' can be provided, not both.
" + + "Requires one of the following permissions: " + + "", "componentId", "componentKey") + .setSince("6.1") + .setPost(true) + .setHandler(this); + + action.createParam("key") + .setDescription("Setting key") + .setExampleValue("sonar.links.scm") + .setRequired(true); + + action.createParam("value") + .setDescription("Setting value. To reset a value, please use the reset web service.") + .setExampleValue("git@github.com:SonarSource/sonarqube.git") + .setRequired(true); + + action.createParam("componentId") + .setDescription("Component id") + .setExampleValue(Uuids.UUID_EXAMPLE_01); + + action.createParam("componentKey") + .setDescription("Component key") + .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); + } + + @Override + public void handle(Request request, Response response) throws Exception { + DbSession dbSession = dbClient.openSession(false); + try { + SetRequest setRequest = toWsRequest(request); + Optional component = searchComponent(dbSession, setRequest); + checkPermissions(component); + + dbClient.propertiesDao().insertProperty(dbSession, toProperty(setRequest, component)); + dbSession.commit(); + } finally { + dbClient.closeSession(dbSession); + } + + response.noContent(); + } + + private void checkPermissions(Optional component) { + if (component.isPresent()) { + userSession.checkComponentUuidPermission(UserRole.ADMIN, component.get().uuid()); + } else { + userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN); + } + } + + private static SetRequest toWsRequest(Request request) { + return SetRequest.builder() + .setKey(request.mandatoryParam("key")) + .setValue(request.mandatoryParam("value")) + .setComponentId(request.param("componentId")) + .setComponentKey(request.param("componentKey")) + .build(); + } + + private Optional searchComponent(DbSession dbSession, SetRequest request) { + if (request.getComponentId() == null && request.getComponentKey() == null) { + return Optional.empty(); + } + + ComponentDto project = componentFinder.getByUuidOrKey(dbSession, request.getComponentId(), request.getComponentKey(), ComponentFinder.ParamNames.COMPONENT_ID_AND_KEY); + + return Optional.of(project); + } + + private static PropertyDto toProperty(SetRequest request, Optional component) { + PropertyDto property = new PropertyDto() + .setKey(request.getKey()) + .setValue(request.getValue()); + + if (component.isPresent()) { + property.setResourceId(component.get().getId()); + } + + return property; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsModule.java index fb0e42953f7..006dc678b46 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsModule.java @@ -26,6 +26,7 @@ public class SettingsWsModule extends Module { protected void configureModule() { add( SettingsWs.class, + SetAction.class, ListDefinitionsAction.class); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SetActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SetActionTest.java new file mode 100644 index 00000000000..5a5b8eae291 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SetActionTest.java @@ -0,0 +1,251 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.settings.ws; + +import java.net.HttpURLConnection; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.property.PropertyDbTester; +import org.sonar.db.property.PropertyDto; +import org.sonar.db.property.PropertyQuery; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.sonar.db.property.PropertyTesting.newGlobalProperty; +import static org.sonar.db.property.PropertyTesting.newProjectProperty; + +public class SetActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone() + .setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + PropertyDbTester propertyDb = new PropertyDbTester(db); + ComponentDbTester componentDb = new ComponentDbTester(db); + DbClient dbClient = db.getDbClient(); + DbSession dbSession = db.getSession(); + ComponentFinder componentFinder = new ComponentFinder(dbClient); + + SetAction underTest = new SetAction(dbClient, componentFinder, userSession); + + WsActionTester ws = new WsActionTester(underTest); + + @Test + public void empty_204_response() { + TestResponse result = ws.newRequest() + .setParam("key", "my.key") + .setParam("value", "my value") + .execute(); + + assertThat(result.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT); + assertThat(result.getInput()).isEmpty(); + } + + @Test + public void persist_new_global_property() { + callForGlobalProperty("my.key", "my value"); + + assertGlobalProperty("my.key", "my value"); + } + + @Test + public void update_existing_global_property() { + propertyDb.insertProperty(newGlobalProperty("my.key", "my value")); + assertGlobalProperty("my.key", "my value"); + + callForGlobalProperty("my.key", "my new value"); + + assertGlobalProperty("my.key", "my new value"); + } + + @Test + public void persist_new_project_property() { + propertyDb.insertProperty(newGlobalProperty("my.key", "my global value")); + ComponentDto project = componentDb.insertProject(); + + callForProjectPropertyByUuid("my.key", "my project value", project.uuid()); + + assertGlobalProperty("my.key", "my global value"); + assertProjectProperty("my.key", "my project value", project.getId()); + } + + @Test + public void persist_project_property_with_project_admin_permission() { + ComponentDto project = componentDb.insertProject(); + userSession.anonymous().addProjectUuidPermissions(UserRole.ADMIN, project.uuid()); + + callForProjectPropertyByUuid("my.key", "my value", project.uuid()); + + assertProjectProperty("my.key", "my value", project.getId()); + } + + @Test + public void update_existing_project_property() { + propertyDb.insertProperty(newGlobalProperty("my.key", "my global value")); + ComponentDto project = componentDb.insertProject(); + propertyDb.insertProperty(newProjectProperty("my.key", "my project value", project.getId())); + assertProjectProperty("my.key", "my project value", project.getId()); + + callForProjectPropertyByKey("my.key", "my new project value", project.key()); + + assertProjectProperty("my.key", "my new project value", project.getId()); + } + + @Test + public void user_property_is_not_updated() { + propertyDb.insertProperty(newGlobalProperty("my.key", "my user value").setUserId(42L)); + propertyDb.insertProperty(newGlobalProperty("my.key", "my global value")); + + callForGlobalProperty("my.key", "my new global value"); + + assertGlobalProperty("my.key", "my new global value"); + assertUserProperty("my.key", "my user value", 42L); + } + + @Test + public void fail_when_no_key() { + expectedException.expect(IllegalArgumentException.class); + + callForGlobalProperty(null, "my value"); + } + + @Test + public void fail_when_empty_key_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Setting key is mandatory and must not be empty."); + + callForGlobalProperty(" ", "my value"); + } + + @Test + public void fail_when_no_value() { + expectedException.expect(IllegalArgumentException.class); + + callForGlobalProperty("my.key", null); + } + + @Test + public void fail_when_empty_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Setting value is mandatory and must not be empty."); + + callForGlobalProperty("my.key", ""); + } + + @Test + public void fail_when_insufficient_privileges() { + userSession.anonymous().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); + expectedException.expect(ForbiddenException.class); + + callForGlobalProperty("my.key", "my value"); + } + + @Test + public void definition() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.key()).isEqualTo("set"); + assertThat(definition.isPost()).isTrue(); + assertThat(definition.since()).isEqualTo("6.1"); + assertThat(definition.params()).extracting(Param::key) + .containsOnlyOnce("key", "value"); + } + + private void assertGlobalProperty(String key, String value) { + PropertyDto result = dbClient.propertiesDao().selectGlobalProperty(key); + + assertThat(result) + .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getResourceId) + .containsExactly(key, value, null); + } + + private void assertUserProperty(String key, String value, long userId) { + List result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setKey(key).setUserId((int) userId).build(), dbSession); + + assertThat(result).hasSize(1) + .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getUserId) + .containsExactly(tuple(key, value, userId)); + } + + private void assertProjectProperty(String key, String value, long componentId) { + PropertyDto result = dbClient.propertiesDao().selectProjectProperty(componentId, key); + + assertThat(result) + .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getResourceId) + .containsExactly(key, value, componentId); + } + + private void callForGlobalProperty(@Nullable String key, @Nullable String value) { + call(key, value, null, null); + } + + private void callForProjectPropertyByUuid(@Nullable String key, @Nullable String value, @Nullable String componentUuid) { + call(key, value, componentUuid, null); + } + + private void callForProjectPropertyByKey(@Nullable String key, @Nullable String value, @Nullable String componentKey) { + call(key, value, null, componentKey); + } + + private void call(@Nullable String key, @Nullable String value, @Nullable String componentUuid, @Nullable String componentKey) { + TestRequest request = ws.newRequest(); + + if (key != null) { + request.setParam("key", key); + } + + if (value != null) { + request.setParam("value", value); + } + + if (componentUuid != null) { + request.setParam("componentId", componentUuid); + } + + if (componentKey != null) { + request.setParam("componentKey", componentKey); + } + + request.execute(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsWsModuleTest.java index e37e98e1523..d2f1b858c0b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsWsModuleTest.java @@ -29,6 +29,6 @@ public class SettingsWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new SettingsWsModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 2); + assertThat(container.size()).isEqualTo(3 + 2); } } diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertyDbTester.java b/sonar-db/src/test/java/org/sonar/db/property/PropertyDbTester.java new file mode 100644 index 00000000000..9b486e3f29a --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/property/PropertyDbTester.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.property; + +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +public class PropertyDbTester { + private final DbTester db; + private final DbClient dbClient; + private final DbSession dbSession; + + public PropertyDbTester(DbTester db) { + this.db = db; + this.dbClient = db.getDbClient(); + this.dbSession = db.getSession(); + } + + public PropertyDto insertProperty(PropertyDto property) { + dbClient.propertiesDao().insertProperty(dbSession, property); + db.commit(); + + return property; + } +} diff --git a/sonar-db/src/test/java/org/sonar/db/property/PropertyTesting.java b/sonar-db/src/test/java/org/sonar/db/property/PropertyTesting.java new file mode 100644 index 00000000000..cb7a0897663 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/property/PropertyTesting.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.property; + +public class PropertyTesting { + + public static PropertyDto newGlobalProperty(String key, String value) { + return new PropertyDto().setKey(key).setValue(value); + } + + public static PropertyDto newProjectProperty(String key, String value, long componentId) { + return new PropertyDto().setKey(key).setValue(value).setResourceId(componentId); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/setting/SetRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/setting/SetRequest.java new file mode 100644 index 00000000000..ee19ef6fc1d --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/setting/SetRequest.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.sonarqube.ws.client.setting; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; + +public class SetRequest { + private final String key; + private final String value; + private final String componentId; + private final String componentKey; + + public SetRequest(Builder builder) { + this.key = builder.key; + this.value = builder.value; + this.componentId = builder.componentId; + this.componentKey = builder.componentKey; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + @CheckForNull + public String getComponentId() { + return componentId; + } + + @CheckForNull + public String getComponentKey() { + return componentKey; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String key; + private String value; + private String componentId; + private String componentKey; + + private Builder() { + // enforce factory method use + } + + public Builder setKey(String key) { + this.key = key; + return this; + } + + public Builder setValue(String value) { + this.value = value; + return this; + } + + public Builder setComponentId(@Nullable String componentId) { + this.componentId = componentId; + return this; + } + + public Builder setComponentKey(@Nullable String componentKey) { + this.componentKey = componentKey; + return this; + } + + public SetRequest build() { + checkArgument(key != null && !key.isEmpty(), "Setting key is mandatory and must not be empty."); + checkArgument(value != null && !value.isEmpty(), "Setting value is mandatory and must not be empty."); + return new SetRequest(this); + } + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/setting/SetRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/setting/SetRequestTest.java new file mode 100644 index 00000000000..472044e874e --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/setting/SetRequestTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.sonarqube.ws.client.setting; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SetRequestTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + SetRequest.Builder underTest = SetRequest.builder(); + + @Test + public void create_set_request() { + SetRequest result = underTest.setKey("my.key").setValue("my value").build(); + + assertThat(result.getKey()).isEqualTo("my.key"); + assertThat(result.getValue()).isEqualTo("my value"); + } + + @Test + public void fail_when_empty_key() { + expectedException.expect(IllegalArgumentException.class); + + underTest + .setKey("") + .setValue("value") + .build(); + } + + @Test + public void fail_when_empty_value() { + expectedException.expect(IllegalArgumentException.class); + + underTest + .setKey("key") + .setValue(null) + .build(); + } +}