Procházet zdrojové kódy

SONAR-7969 Create /api/settings/values WS

tags/6.1-RC1
Julien Lancelot před 7 roky
rodič
revize
1e9159e3b8

+ 10
- 29
server/sonar-server/src/main/java/org/sonar/server/settings/ws/ListDefinitionsAction.java Zobrazit soubor

@@ -28,34 +28,27 @@ import org.sonar.api.config.PropertyFieldDefinition;
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.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Settings;
import org.sonarqube.ws.Settings.ListDefinitionsWsResponse;

import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.server.settings.ws.SettingsWsComponentParameters.PARAM_COMPONENT_ID;
import static org.sonar.server.settings.ws.SettingsWsComponentParameters.PARAM_COMPONENT_KEY;
import static org.sonar.server.settings.ws.SettingsWsComponentParameters.addComponentParameters;
import static org.sonar.server.ws.WsUtils.writeProtobuf;

public class ListDefinitionsAction implements SettingsWsAction {

private static final String PARAM_COMPONENT_ID = "componentId";
private static final String PARAM_COMPONENT_KEY = "componentKey";

private final DbClient dbClient;
private final ComponentFinder componentFinder;
private final UserSession userSession;
private final SettingsWsComponentParameters settingsWsComponentParameters;
private final PropertyDefinitions propertyDefinitions;

public ListDefinitionsAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, PropertyDefinitions propertyDefinitions) {
public ListDefinitionsAction(DbClient dbClient, SettingsWsComponentParameters settingsWsComponentParameters, PropertyDefinitions propertyDefinitions) {
this.dbClient = dbClient;
this.componentFinder = componentFinder;
this.userSession = userSession;
this.settingsWsComponentParameters = settingsWsComponentParameters;
this.propertyDefinitions = propertyDefinitions;
}

@@ -73,13 +66,7 @@ public class ListDefinitionsAction implements SettingsWsAction {
.setSince("6.1")
.setHandler(this);

action.createParam(PARAM_COMPONENT_ID)
.setDescription("Component id")
.setExampleValue(UUID_EXAMPLE_01);

action.createParam(PARAM_COMPONENT_KEY)
.setDescription("Component key")
.setExampleValue("my_component_key");
addComponentParameters(action);
}

@Override
@@ -150,15 +137,9 @@ public class ListDefinitionsAction implements SettingsWsAction {
private String getQualifier(Request request) {
DbSession dbSession = dbClient.openSession(false);
try {
if (request.hasParam(PARAM_COMPONENT_ID) || request.hasParam(PARAM_COMPONENT_KEY)) {
ComponentDto component = componentFinder.getByUuidOrKey(dbSession, request.param(PARAM_COMPONENT_ID), request.param(PARAM_COMPONENT_KEY),
ComponentFinder.ParamNames.ID_AND_KEY);
userSession.checkComponentUuidPermission(UserRole.ADMIN, component.uuid());
return component.qualifier();
} else {
userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN);
return null;
}
ComponentDto component = settingsWsComponentParameters.getComponent(dbSession, request);
settingsWsComponentParameters.checkAdminPermission(component);
return component == null ? null : component.qualifier();
} finally {
dbClient.closeSession(dbSession);
}

+ 76
- 0
server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsComponentParameters.java Zobrazit soubor

@@ -0,0 +1,76 @@
/*
* 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 javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.UserRole;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.user.UserSession;

import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.server.component.ComponentFinder.ParamNames.ID_AND_KEY;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;

public class SettingsWsComponentParameters {

static final String PARAM_COMPONENT_ID = "componentId";
static final String PARAM_COMPONENT_KEY = "componentKey";

private final ComponentFinder componentFinder;
private final UserSession userSession;

public SettingsWsComponentParameters(ComponentFinder componentFinder, UserSession userSession) {
this.componentFinder = componentFinder;
this.userSession = userSession;
}

static void addComponentParameters(WebService.NewAction action) {
action.createParam(PARAM_COMPONENT_ID)
.setDescription("Component id")
.setExampleValue(UUID_EXAMPLE_01);

action.createParam(PARAM_COMPONENT_KEY)
.setDescription("Component key")
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
}

@CheckForNull
ComponentDto getComponent(DbSession dbSession, Request request) {
if (request.hasParam(PARAM_COMPONENT_ID) || request.hasParam(PARAM_COMPONENT_KEY)) {
return componentFinder.getByUuidOrKey(dbSession, request.param(PARAM_COMPONENT_ID), request.param(PARAM_COMPONENT_KEY), ID_AND_KEY);
}
return null;
}

void checkAdminPermission(@Nullable ComponentDto component) {
if (component == null) {
userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN);
} else {
userSession.checkComponentUuidPermission(UserRole.ADMIN, component.uuid());
}
}

}

+ 3
- 1
server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsModule.java Zobrazit soubor

@@ -27,6 +27,8 @@ public class SettingsWsModule extends Module {
add(
SettingsWs.class,
SetAction.class,
ListDefinitionsAction.class);
SettingsWsComponentParameters.class,
ListDefinitionsAction.class,
ValuesAction.class);
}
}

+ 184
- 0
server/sonar-server/src/main/java/org/sonar/server/settings/ws/ValuesAction.java Zobrazit soubor

@@ -0,0 +1,184 @@
/*
* 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 com.google.common.base.Splitter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.property.PropertyDto;
import org.sonarqube.ws.Settings;
import org.sonarqube.ws.Settings.ValuesWsResponse;

import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.server.settings.ws.SettingsWsComponentParameters.PARAM_COMPONENT_ID;
import static org.sonar.server.settings.ws.SettingsWsComponentParameters.PARAM_COMPONENT_KEY;
import static org.sonar.server.settings.ws.SettingsWsComponentParameters.addComponentParameters;
import static org.sonar.server.ws.WsUtils.writeProtobuf;

public class ValuesAction implements SettingsWsAction {

private static final Splitter MULTI_VALUE_SPLITTER = Splitter.on(",");

static final String PARAM_KEYS = "keys";

private final DbClient dbClient;
private final SettingsWsComponentParameters settingsWsComponentParameters;
private final PropertyDefinitions propertyDefinitions;

public ValuesAction(DbClient dbClient, SettingsWsComponentParameters settingsWsComponentParameters, PropertyDefinitions propertyDefinitions) {
this.dbClient = dbClient;
this.settingsWsComponentParameters = settingsWsComponentParameters;
this.propertyDefinitions = propertyDefinitions;
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction("values")
.setDescription(String.format("Returns values of given properties.<br>" +
"If no value have been set for a property, then the default value is returned.<br>" +
"Either '%s' or '%s' could be provided, not both.<br> " +
"Requires one of the following permissions: " +
"<ul>" +
"<li>'Administer System'</li>" +
"<li>'Administer' rights on the specified component</li>" +
"</ul>", PARAM_COMPONENT_ID, PARAM_COMPONENT_KEY))
.setResponseExample(getClass().getResource("values-example.json"))
.setSince("6.1")
.setHandler(this);
addComponentParameters(action);
action.createParam(PARAM_KEYS)
.setDescription("List of property keys")
.setRequired(true)
.setExampleValue("sonar.technicalDebt.hoursInDay,sonar.dbcleaner.cleanDirectory");
}

@Override
public void handle(Request request, Response response) throws Exception {
writeProtobuf(doHandle(request), request, response);
}

private ValuesWsResponse doHandle(Request request) {
DbSession dbSession = dbClient.openSession(true);
try {
ComponentDto component = settingsWsComponentParameters.getComponent(dbSession, request);
settingsWsComponentParameters.checkAdminPermission(component);
Set<String> keys = new HashSet<>(request.mandatoryParamAsStrings(PARAM_KEYS));

List<PropertyDefinition> definitions = getDefinitions(keys);
Map<String, PropertyDefinition> definitionsByKey = definitions.stream()
.collect(Collectors.toMap(PropertyDefinition::key, Function.identity()));

ValuesWsResponse.Builder valuesBuilder = ValuesWsResponse.newBuilder();
new ValuesBuilder(dbSession, valuesBuilder, definitionsByKey, keys, component).build();
return valuesBuilder.build();
} finally {
dbClient.closeSession(dbSession);
}
}

private class ValuesBuilder {
private final DbSession dbSession;
private final ValuesWsResponse.Builder valuesWsBuilder;
private final Map<String, PropertyDefinition> definitionsByKey;
private final Set<String> keys;
private final ComponentDto component;

private final Map<String, Settings.Value.Builder> valueBuilderByKey = new HashMap<>();

ValuesBuilder(DbSession dbSession, ValuesWsResponse.Builder valuesWsBuilder, Map<String, PropertyDefinition> definitionsByKey,
Set<String> keys, @Nullable ComponentDto component) {
this.dbSession = dbSession;
this.valuesWsBuilder = valuesWsBuilder;
this.definitionsByKey = definitionsByKey;
this.keys = keys;
this.component = component;
}

void build() {
processDefinitions();
processPropertyDtos(dbClient.propertiesDao().selectGlobalPropertiesByKeys(dbSession, keys));
if (component != null) {
processPropertyDtos(dbClient.propertiesDao().selectComponentPropertiesByKeys(dbSession, keys, component.getId()));
}
valueBuilderByKey.values().forEach(Settings.Value.Builder::build);
}

private void processDefinitions() {
definitionsByKey.values().stream()
.filter(defaultProperty -> !isNullOrEmpty(defaultProperty.defaultValue()))
.forEach(this::processDefaultValue);
}

private void processDefaultValue(PropertyDefinition definition) {
Settings.Value.Builder valueBuilder = valuesWsBuilder.addValuesBuilder()
.setKey(definition.key())
.setIsDefault(true);
setValue(valueBuilder, definition.defaultValue());
valueBuilderByKey.put(definition.key(), valueBuilder);
}

private void processPropertyDtos(List<PropertyDto> properties) {
properties.stream()
.filter(propertyDto -> !isNullOrEmpty(propertyDto.getValue()))
.forEach(this::processDtoValue);
}

private void processDtoValue(PropertyDto property) {
Settings.Value.Builder valueBuilder = valueBuilderByKey.get(property.getKey());
if (valueBuilder == null) {
valueBuilder = valuesWsBuilder.addValuesBuilder().setKey(property.getKey());
valueBuilderByKey.put(property.getKey(), valueBuilder);
}
valueBuilder.setIsInherited(component != null && property.getResourceId() == null);
valueBuilder.setIsDefault(false);
setValue(valueBuilder, property.getValue());
}

private void setValue(Settings.Value.Builder valueBuilder, String value) {
PropertyDefinition definition = definitionsByKey.get(valueBuilder.getKey());
if (definition != null && definition.multiValues()) {
valueBuilder.addAllValues(MULTI_VALUE_SPLITTER.split(value));
} else {
valueBuilder.setValue(value);
}
}
}

private List<PropertyDefinition> getDefinitions(Set<String> keys) {
return propertyDefinitions.getAll().stream()
.filter(def -> keys.contains(def.key()))
.collect(Collectors.toList());
}

}

+ 36
- 0
server/sonar-server/src/main/resources/org/sonar/server/settings/ws/values-example.json Zobrazit soubor

@@ -0,0 +1,36 @@
{
"values": [
{
"key": "sonar.test.jira",
"value": "abc",
"isDefault": true
},
{
"key": "sonar.autogenerated",
"values": [
"val1",
"val2",
"val3"
],
"isDefault": false
},
{
"key": "sonar.demo",
"setValues": {
"text": {
"values": [
"foo",
"bar"
]
},
"boolean": {
"values": [
"true",
"false"
]
}
},
"isDefault": false
}
]
}

+ 5
- 7
server/sonar-server/src/test/java/org/sonar/server/settings/ws/ListDefinitionsActionTest.java Zobrazit soubor

@@ -55,6 +55,7 @@ import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING;
import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonarqube.ws.MediaTypes.JSON;
import static org.sonarqube.ws.Settings.Type.BOOLEAN;
import static org.sonarqube.ws.Settings.Type.PROPERTY_SET;
import static org.sonarqube.ws.Settings.Type.SINGLE_SELECT_LIST;
@@ -78,12 +79,13 @@ public class ListDefinitionsActionTest {
ComponentDto project;

PropertyDefinitions propertyDefinitions = new PropertyDefinitions();
SettingsWsComponentParameters settingsWsComponentParameters = new SettingsWsComponentParameters(new ComponentFinder(dbClient), userSession);

WsActionTester ws = new WsActionTester(new ListDefinitionsAction(dbClient, new ComponentFinder(dbClient), userSession, propertyDefinitions));
WsActionTester ws = new WsActionTester(new ListDefinitionsAction(dbClient, settingsWsComponentParameters, propertyDefinitions));

@Before
public void setUp() throws Exception {
project = insertProject();
project = componentDb.insertComponent(newProjectDto());
}

@Test
@@ -369,14 +371,10 @@ public class ListDefinitionsActionTest {
.build())
.build()));

String result = ws.newRequest().setMediaType(MediaTypes.JSON).execute().getInput();
String result = ws.newRequest().setMediaType(JSON).execute().getInput();
JsonAssert.assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result);
}

private ComponentDto insertProject() {
return componentDb.insertComponent(newProjectDto());
}

private ListDefinitionsWsResponse newRequest() {
return newRequest(null, null);
}

+ 8
- 8
server/sonar-server/src/test/java/org/sonar/server/settings/ws/SetActionTest.java Zobrazit soubor

@@ -48,8 +48,8 @@ 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;
import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;

public class SetActionTest {

@@ -90,7 +90,7 @@ public class SetActionTest {

@Test
public void update_existing_global_property() {
propertyDb.insertProperty(newGlobalProperty("my.key", "my value"));
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my value"));
assertGlobalProperty("my.key", "my value");

callForGlobalProperty("my.key", "my new value");
@@ -100,7 +100,7 @@ public class SetActionTest {

@Test
public void persist_new_project_property() {
propertyDb.insertProperty(newGlobalProperty("my.key", "my global value"));
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"));
ComponentDto project = componentDb.insertProject();

callForProjectPropertyByUuid("my.key", "my project value", project.uuid());
@@ -121,9 +121,9 @@ public class SetActionTest {

@Test
public void update_existing_project_property() {
propertyDb.insertProperty(newGlobalProperty("my.key", "my global value"));
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"));
ComponentDto project = componentDb.insertProject();
propertyDb.insertProperty(newProjectProperty("my.key", "my project value", project.getId()));
propertyDb.insertProperty(newComponentPropertyDto("my.key", "my project value", project));
assertProjectProperty("my.key", "my project value", project.getId());

callForProjectPropertyByKey("my.key", "my new project value", project.key());
@@ -133,8 +133,8 @@ public class SetActionTest {

@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"));
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my user value").setUserId(42L));
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"));

callForGlobalProperty("my.key", "my new global value");


+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsWsModuleTest.java Zobrazit soubor

@@ -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(3 + 2);
assertThat(container.size()).isEqualTo(5 + 2);
}
}

+ 342
- 0
server/sonar-server/src/test/java/org/sonar/server/settings/ws/ValuesActionTest.java Zobrazit soubor

@@ -0,0 +1,342 @@
/*
* 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 com.google.common.base.Joiner;
import java.io.IOException;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
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.PropertyDto;
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.WsActionTester;
import org.sonar.test.JsonAssert;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Settings;
import org.sonarqube.ws.Settings.ValuesWsResponse;

import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.sonar.api.web.UserRole.ADMIN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.permission.GlobalPermissions.DASHBOARD_SHARING;
import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
import static org.sonarqube.ws.MediaTypes.JSON;

public class ValuesActionTest {

static Joiner COMMA_JOINER = Joiner.on(",");

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Rule
public UserSessionRule userSession = UserSessionRule.standalone();

@Rule
public DbTester db = DbTester.create(System2.INSTANCE);

DbClient dbClient = db.getDbClient();
DbSession dbSession = db.getSession();
ComponentDbTester componentDb = new ComponentDbTester(db);
SettingsWsComponentParameters settingsWsComponentParameters = new SettingsWsComponentParameters(new ComponentFinder(dbClient), userSession);
PropertyDefinitions propertyDefinitions = new PropertyDefinitions();

ComponentDto project;

WsActionTester ws = new WsActionTester(new ValuesAction(dbClient, settingsWsComponentParameters, propertyDefinitions));

@Before
public void setUp() throws Exception {
project = componentDb.insertComponent(newProjectDto());
}

@Test
public void return_simple_value() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition
.builder("foo")
.build());
insertProperties(newGlobalPropertyDto().setKey("foo").setValue("one"));

ValuesWsResponse result = newRequestForGlobalProperties("foo");
assertThat(result.getValuesList()).hasSize(1);

Settings.Value value = result.getValues(0);
assertThat(value.getKey()).isEqualTo("foo");
assertThat(value.getValue()).isEqualTo("one");
assertThat(value.getValuesCount()).isZero();
assertThat(value.getSetValues()).isEmpty();
assertThat(value.getIsDefault()).isFalse();
}

@Test
public void return_multi_values() throws Exception {
setUserAsSystemAdmin();

// Property never defined, default value is returned
propertyDefinitions.addComponent(PropertyDefinition.builder("default")
.multiValues(true)
.defaultValue("one,two")
.build());

// Property defined at global level
propertyDefinitions.addComponent(PropertyDefinition.builder("global")
.multiValues(true)
.build());
insertProperties(newGlobalPropertyDto().setKey("global").setValue("three,four"));

ValuesWsResponse result = newRequestForGlobalProperties("default", "global");
assertThat(result.getValuesList()).hasSize(2);

Settings.Value foo = result.getValues(0);
assertThat(foo.getKey()).isEqualTo("default");
assertThat(foo.hasValue()).isFalse();
assertThat(foo.getValuesList()).containsOnly("one", "two");
assertThat(foo.getSetValues()).isEmpty();

Settings.Value bar = result.getValues(1);
assertThat(bar.getKey()).isEqualTo("global");
assertThat(bar.hasValue()).isFalse();
assertThat(bar.getValuesList()).containsOnly("three", "four");
assertThat(bar.getSetValues()).isEmpty();
}

@Test
public void return_default_values() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition
.builder("foo")
.defaultValue("default")
.build());

ValuesWsResponse result = newRequestForGlobalProperties("foo");
assertThat(result.getValuesList()).hasSize(1);

Settings.Value value = result.getValues(0);
assertThat(value.getKey()).isEqualTo("foo");
assertThat(value.getValue()).isEqualTo("default");
assertThat(value.getIsDefault()).isTrue();
assertThat(value.getIsInherited()).isFalse();
}

@Test
public void return_global_values() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition.builder("property").defaultValue("default").build());
insertProperties(
// The property is overriding default value
newGlobalPropertyDto().setKey("property").setValue("one"));

ValuesWsResponse result = newRequestForGlobalProperties("property");
assertThat(result.getValuesList()).hasSize(1);

Settings.Value globalPropertyValue = result.getValues(0);
assertThat(globalPropertyValue.getKey()).isEqualTo("property");
assertThat(globalPropertyValue.getValue()).isEqualTo("one");
assertThat(globalPropertyValue.getIsDefault()).isFalse();
assertThat(globalPropertyValue.getIsInherited()).isFalse();
}

@Test
public void return_component_values() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition.builder("property").defaultValue("default").build());
insertProperties(
newGlobalPropertyDto().setKey("property").setValue("one"),
// The property is overriding global value
newComponentPropertyDto(project).setKey("property").setValue("two"));

ValuesWsResponse result = newRequestForProjectProperties("property");
assertThat(result.getValuesList()).hasSize(1);

Settings.Value globalPropertyValue = result.getValues(0);
assertThat(globalPropertyValue.getKey()).isEqualTo("property");
assertThat(globalPropertyValue.getValue()).isEqualTo("two");
assertThat(globalPropertyValue.getIsDefault()).isFalse();
assertThat(globalPropertyValue.getIsInherited()).isFalse();
}

@Test
public void return_is_inherited_to_true_when_property_is_defined_only_at_global_level() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition.builder("property").defaultValue("default").build());
// The property is not defined on project
insertProperties(newGlobalPropertyDto().setKey("property").setValue("one"));

ValuesWsResponse result = newRequestForProjectProperties("property");
assertThat(result.getValuesList()).hasSize(1);

Settings.Value globalPropertyValue = result.getValues(0);
assertThat(globalPropertyValue.getKey()).isEqualTo("property");
assertThat(globalPropertyValue.getValue()).isEqualTo("one");
assertThat(globalPropertyValue.getIsDefault()).isFalse();
assertThat(globalPropertyValue.getIsInherited()).isTrue();
}

@Test
public void return_values_even_if_no_property_definition() throws Exception {
setUserAsSystemAdmin();
insertProperties(newGlobalPropertyDto().setKey("globalPropertyWithoutDefinition").setValue("value"));

ValuesWsResponse result = newRequestForGlobalProperties("globalPropertyWithoutDefinition");
Settings.Value globalPropertyWithoutDefinitionValue = result.getValues(0);
assertThat(globalPropertyWithoutDefinitionValue.getKey()).isEqualTo("globalPropertyWithoutDefinition");
assertThat(globalPropertyWithoutDefinitionValue.getValue()).isEqualTo("value");
assertThat(globalPropertyWithoutDefinitionValue.getIsDefault()).isFalse();
}

@Test
public void return_empty_when_property_def_exists_but_no_value() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition
.builder("foo")
.build());
insertProperties(newGlobalPropertyDto().setKey("bar").setValue(""));

ValuesWsResponse result = newRequestForGlobalProperties("foo", "bar");
assertThat(result.getValuesList()).isEmpty();
}

@Test
public void does_return_nothing_when_unknown_keys() throws Exception {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition
.builder("foo")
.defaultValue("default")
.build());
insertProperties(newGlobalPropertyDto().setKey("bar").setValue(""));

ValuesWsResponse result = newRequestForGlobalProperties("unknown");
assertThat(result.getValuesList()).isEmpty();
}

@Test
@Ignore
public void test_example_json_response() {
setUserAsSystemAdmin();
propertyDefinitions.addComponent(PropertyDefinition
.builder("sonar.test.jira")
.defaultValue("abc")
.build());

String result = ws.newRequest()
.setParam("keys", "sonar.test.jira,sonar.autogenerated,sonar.demo")
.setMediaType(JSON)
.execute()
.getInput();
JsonAssert.assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result);
}

@Test
public void fail_when_id_and_key_are_set() throws Exception {
setUserAsProjectAdmin();

expectedException.expect(IllegalArgumentException.class);
newRequest(project.uuid(), project.key());
}

@Test
public void fail_when_not_system_admin() throws Exception {
userSession.login("not-admin").setGlobalPermissions(DASHBOARD_SHARING);
propertyDefinitions.addComponent(PropertyDefinition.builder("foo").build());

expectedException.expect(ForbiddenException.class);
newRequestForGlobalProperties();
}

@Test
public void fail_when_not_project_admin() throws Exception {
userSession.login("project-admin").addProjectUuidPermissions(USER, project.uuid());
propertyDefinitions.addComponent(PropertyDefinition.builder("foo").build());

expectedException.expect(ForbiddenException.class);
newRequest(project.uuid(), null);
}

@Test
public void test_ws_definition() {
WebService.Action action = ws.getDef();
assertThat(action).isNotNull();
assertThat(action.isInternal()).isFalse();
assertThat(action.isPost()).isFalse();
assertThat(action.responseExampleAsString()).isNotEmpty();
assertThat(action.params()).hasSize(3);
}

private ValuesWsResponse newRequestForProjectProperties(String... keys) {
return newRequest(project.uuid(), null, keys);
}

private ValuesWsResponse newRequestForGlobalProperties(String... keys) {
return newRequest(null, null, keys);
}

private ValuesWsResponse newRequest(@Nullable String id, @Nullable String key, String... keys) {
TestRequest request = ws.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setParam("keys", COMMA_JOINER.join(keys));
if (id != null) {
request.setParam("componentId", id);
}
if (key != null) {
request.setParam("componentKey", key);
}
try {
return ValuesWsResponse.parseFrom(request.execute().getInputStream());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

private void setUserAsSystemAdmin() {
userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN);
}

private void setUserAsProjectAdmin() {
userSession.login("project-admin").addProjectUuidPermissions(ADMIN, project.uuid());
}

private void insertProperties(PropertyDto... properties) {
for (PropertyDto propertyDto : properties) {
dbClient.propertiesDao().insertProperty(dbSession, propertyDto);
}
dbSession.commit();
}

}

+ 15
- 0
sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java Zobrazit soubor

@@ -28,6 +28,7 @@ import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.dbutils.DbUtils;
@@ -38,6 +39,8 @@ import org.sonar.db.DatabaseUtils;
import org.sonar.db.DbSession;
import org.sonar.db.MyBatis;

import static org.sonar.db.DatabaseUtils.executeLargeInputs;

public class PropertiesDao implements Dao {

private static final String NOTIFICATION_PREFIX = "notification.";
@@ -161,6 +164,18 @@ public class PropertiesDao implements Dao {
return session.getMapper(PropertiesMapper.class).selectByQuery(query);
}

public List<PropertyDto> selectGlobalPropertiesByKeys(DbSession session, Set<String> keys) {
return selectByKeys(session, keys, null);
}

public List<PropertyDto> selectComponentPropertiesByKeys(DbSession session, Set<String> keys, long componentId) {
return selectByKeys(session, keys, componentId);
}

private List<PropertyDto> selectByKeys(DbSession session, Set<String> keys, @Nullable Long componentId) {
return executeLargeInputs(keys, propertyKeys -> session.getMapper(PropertiesMapper.class).selectByKeys(propertyKeys, componentId));
}

public void insertProperty(DbSession session, PropertyDto property) {
PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
PropertyDto persistedProperty = mapper.selectByKey(property);

+ 2
- 0
sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java Zobrazit soubor

@@ -39,6 +39,8 @@ public interface PropertiesMapper {

PropertyDto selectByKey(PropertyDto key);

List<PropertyDto> selectByKeys(@Param("keys") List<String> keys, @Nullable @Param("componentId") Long componentId);

List<PropertyDto> selectByQuery(@Param("query") PropertyQuery query);

List<PropertyDto> selectDescendantModuleProperties(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,

+ 98
- 0
sonar-db/src/main/java/org/sonar/db/property/PropertyTesting.java Zobrazit soubor

@@ -0,0 +1,98 @@
/*
* 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 javax.annotation.Nullable;
import org.apache.commons.lang.math.RandomUtils;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.user.UserDto;

import static com.google.common.base.Preconditions.checkNotNull;

public class PropertyTesting {

private static int cursor = RandomUtils.nextInt(100);

private PropertyTesting() {
// static methods only
}

public static PropertyDto newGlobalPropertyDto(String key, String value) {
return newPropertyDto(key, value, (Long) null, null);
}

public static PropertyDto newGlobalPropertyDto() {
return newPropertyDto((Long) null, null);
}

public static PropertyDto newComponentPropertyDto(String key, String value, ComponentDto component) {
checkNotNull(component.getId());
return newPropertyDto(key, value, component.getId(), null);
}

public static PropertyDto newComponentPropertyDto(ComponentDto component) {
checkNotNull(component.getId());
return newPropertyDto(component.getId(), null);
}

public static PropertyDto newUserPropertyDto(String key, String value, UserDto user) {
checkNotNull(user.getId());
return newPropertyDto(key, value, null, user.getId());
}

public static PropertyDto newUserPropertyDto(UserDto user) {
checkNotNull(user.getId());
return newPropertyDto(null, user.getId());
}

public static PropertyDto newPropertyDto(String key, String value, ComponentDto component, UserDto user) {
checkNotNull(component.getId());
checkNotNull(user.getId());
return newPropertyDto(key, value, component.getId(), user.getId());
}

public static PropertyDto newPropertyDto(ComponentDto component, UserDto user) {
checkNotNull(component.getId());
checkNotNull(user.getId());
return newPropertyDto(component.getId(), user.getId());
}

private static PropertyDto newPropertyDto(@Nullable Long componentId, @Nullable Long userId) {
String key = String.valueOf(cursor);
cursor++;
String value = String.valueOf(cursor);
cursor++;
return newPropertyDto(key, value, componentId, userId);
}

private static PropertyDto newPropertyDto(String key, String value, @Nullable Long componentId, @Nullable Long userId) {
PropertyDto propertyDto = new PropertyDto()
.setKey(key)
.setValue(value);
if (componentId != null) {
propertyDto.setResourceId(componentId);
}
if (userId != null) {
propertyDto.setUserId(userId);
}
return propertyDto;
}

}

+ 18
- 0
sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml Zobrazit soubor

@@ -81,6 +81,24 @@
</if>
</select>

<select id="selectByKeys" parameterType="map" resultType="Property">
SELECT p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
FROM properties p
<where>
AND p.prop_key in
<foreach collection="keys" open="(" close=")" item="key" separator=",">
#{key}
</foreach>
<if test="componentId == null">
AND p.resource_id is null
</if>
<if test="componentId != null">
AND p.resource_id=#{componentId}
</if>
AND p.user_id is null
</where>
</select>

<select id="selectByQuery" parameterType="map" resultType="Property">
select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId
from properties p

+ 72
- 8
sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java Zobrazit soubor

@@ -20,18 +20,27 @@
package org.sonar.db.property;

import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.List;
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 org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTesting;

import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
import static org.sonar.db.property.PropertyTesting.newUserPropertyDto;

public class PropertiesDaoTest {

@@ -41,6 +50,9 @@ public class PropertiesDaoTest {
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);

DbClient dbClient = dbTester.getDbClient();
DbSession session = dbTester.getSession();

PropertiesDao dao = dbTester.getDbClient().propertiesDao();

@Test
@@ -98,18 +110,18 @@ public class PropertiesDaoTest {
dbTester.prepareDbUnit(getClass(), "findNotificationSubscribers.xml");

// Nobody is subscribed
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("NotSexyDispatcher"))).isFalse();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", asList("NotSexyDispatcher"))).isFalse();

// Global subscribers
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("DispatcherWithGlobalSubscribers"))).isTrue();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", asList("DispatcherWithGlobalSubscribers"))).isTrue();

// Project subscribers
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("DispatcherWithProjectSubscribers"))).isTrue();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", Arrays.asList("DispatcherWithProjectSubscribers"))).isFalse();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", asList("DispatcherWithProjectSubscribers"))).isTrue();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", asList("DispatcherWithProjectSubscribers"))).isFalse();

// Global + Project subscribers
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", Arrays.asList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", Arrays.asList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_A", asList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
assertThat(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", asList("DispatcherWithGlobalAndProjectSubscribers"))).isTrue();
}

@Test
@@ -193,6 +205,51 @@ public class PropertiesDaoTest {
assertThat(results.get(0).getValue()).isEqualTo("one");
}

@Test
public void select_global_properties_by_keys() throws Exception {
ComponentDto project = ComponentTesting.newProjectDto();
dbClient.componentDao().insert(session, project);
UserDto user = UserTesting.newUserDto();
dbClient.userDao().insert(session, user);

String key = "key";
String anotherKey = "anotherKey";
insertProperties(
newGlobalPropertyDto().setKey(key),
newComponentPropertyDto(project).setKey(key),
newUserPropertyDto(user).setKey(key),
newGlobalPropertyDto().setKey(anotherKey));

assertThat(dao.selectGlobalPropertiesByKeys(session, newHashSet(key))).extracting("key").containsOnly(key);
assertThat(dao.selectGlobalPropertiesByKeys(session, newHashSet(key, anotherKey))).extracting("key").containsOnly(key, anotherKey);
assertThat(dao.selectGlobalPropertiesByKeys(session, newHashSet(key, anotherKey, "unknown"))).extracting("key").containsOnly(key, anotherKey);

assertThat(dao.selectGlobalPropertiesByKeys(session, newHashSet("unknown"))).isEmpty();
}

@Test
public void select_component_properties_by_keys() throws Exception {
ComponentDto project = ComponentTesting.newProjectDto();
dbClient.componentDao().insert(session, project);
UserDto user = UserTesting.newUserDto();
dbClient.userDao().insert(session, user);

String key = "key";
String anotherKey = "anotherKey";
insertProperties(
newGlobalPropertyDto().setKey(key),
newComponentPropertyDto(project).setKey(key),
newUserPropertyDto(user).setKey(key),
newComponentPropertyDto(project).setKey(anotherKey));

assertThat(dao.selectComponentPropertiesByKeys(session, newHashSet(key), project.getId())).extracting("key").containsOnly(key);
assertThat(dao.selectComponentPropertiesByKeys(session, newHashSet(key, anotherKey), project.getId())).extracting("key").containsOnly(key, anotherKey);
assertThat(dao.selectComponentPropertiesByKeys(session, newHashSet(key, anotherKey, "unknown"), project.getId())).extracting("key").containsOnly(key, anotherKey);

assertThat(dao.selectComponentPropertiesByKeys(session, newHashSet("unknown"), project.getId())).isEmpty();
assertThat(dao.selectComponentPropertiesByKeys(session, newHashSet(key), 123456789L)).isEmpty();
}

@Test
public void setProperty_update() {
dbTester.prepareDbUnit(getClass(), "update.xml");
@@ -336,4 +393,11 @@ public class PropertiesDaoTest {
}
return null;
}

private void insertProperties(PropertyDto... properties) {
for (PropertyDto propertyDto : properties) {
dao.insertProperty(session, propertyDto);
}
session.commit();
}
}

+ 0
- 32
sonar-db/src/test/java/org/sonar/db/property/PropertyTesting.java Zobrazit soubor

@@ -1,32 +0,0 @@
/*
* 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);
}
}

+ 18
- 0
sonar-ws/src/main/protobuf/ws-settings.proto Zobrazit soubor

@@ -66,3 +66,21 @@ enum Type {
PROPERTY_SET = 12;
}

// Response of GET api/settings/values
message ValuesWsResponse {
repeated Value values = 1;
}

message Value {
optional string key = 1;
optional string value = 2;
repeated string values = 3;
map<string, SetValue> setValues = 4;
optional bool isDefault = 5;
optional bool isInherited = 6;
}

message SetValue {
repeated string values = 1;
}


Načítá se…
Zrušit
Uložit