package org.sonar.server.settings.ws;
-import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.gson.JsonParseException;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collector;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import javax.annotation.Nullable;
+import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.PropertyFieldDefinition;
import org.sonar.api.i18n.I18n;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.property.PropertyDto;
+import org.sonar.scanner.protocol.GsonHelper;
import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.platform.SettingsChangeNotifier;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.client.setting.SetRequest;
import static org.sonarqube.ws.client.setting.SettingsWsParameters.ACTION_SET;
import static org.sonarqube.ws.client.setting.SettingsWsParameters.PARAM_COMPONENT_ID;
import static org.sonarqube.ws.client.setting.SettingsWsParameters.PARAM_COMPONENT_KEY;
+import static org.sonarqube.ws.client.setting.SettingsWsParameters.PARAM_FIELD_VALUES;
import static org.sonarqube.ws.client.setting.SettingsWsParameters.PARAM_KEY;
import static org.sonarqube.ws.client.setting.SettingsWsParameters.PARAM_VALUE;
import static org.sonarqube.ws.client.setting.SettingsWsParameters.PARAM_VALUES;
public class SetAction implements SettingsWsAction {
- private static final Joiner COMMA_JOINER = Joiner.on(",");
+ private static final Collector<CharSequence, ?, String> COMMA_JOINER = Collectors.joining(",");
+
private final PropertyDefinitions propertyDefinitions;
private final I18n i18n;
private final DbClient dbClient;
private final ComponentFinder componentFinder;
private final UserSession userSession;
+ private final SettingsUpdater settingsUpdater;
+ private final SettingsChangeNotifier settingsChangeNotifier;
- public SetAction(PropertyDefinitions propertyDefinitions, I18n i18n, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
+ public SetAction(PropertyDefinitions propertyDefinitions, I18n i18n, DbClient dbClient, ComponentFinder componentFinder, UserSession userSession,
+ SettingsUpdater settingsUpdater, SettingsChangeNotifier settingsChangeNotifier) {
this.propertyDefinitions = propertyDefinitions;
this.i18n = i18n;
this.dbClient = dbClient;
this.componentFinder = componentFinder;
this.userSession = userSession;
+ this.settingsUpdater = settingsUpdater;
+ this.settingsChangeNotifier = settingsChangeNotifier;
}
@Override
.setDescription("Setting multi value. To set several values, the parameter must be called once for each value.")
.setExampleValue("values=firstValue&values=secondValue&values=thirdValue");
+ action.createParam(PARAM_FIELD_VALUES)
+ .setDescription("Setting field values. To set several values, the parameter must be called once for each value.")
+ .setExampleValue(PARAM_FIELD_VALUES + "={\"firstField\":\"first value\", \"secondField\":\"second value\", \"thirdField\":\"third value\"}");
+
action.createParam(PARAM_COMPONENT_ID)
.setDescription("Component id")
.setExampleValue(Uuids.UUID_EXAMPLE_01);
public void handle(Request request, Response response) throws Exception {
DbSession dbSession = dbClient.openSession(false);
try {
- SetRequest setRequest = toWsRequest(request);
- Optional<ComponentDto> component = searchComponent(dbSession, setRequest);
- checkPermissions(component);
-
- validate(setRequest, component);
- dbClient.propertiesDao().insertProperty(dbSession, toProperty(setRequest, component));
+ doHandle(dbSession, toWsRequest(request));
dbSession.commit();
} finally {
dbClient.closeSession(dbSession);
response.noContent();
}
- private void validate(SetRequest request, Optional<ComponentDto> component) {
+ private void doHandle(DbSession dbSession, SetRequest request) {
+ Optional<ComponentDto> component = searchComponent(dbSession, request);
+ checkPermissions(component);
+
+ PropertyDefinition definition = propertyDefinitions.get(request.getKey());
+
+ String value;
+
+ commonChecks(request, definition, component);
+
+ if (!request.getFieldValues().isEmpty()) {
+ value = doHandlePropertySet(dbSession, request, definition, component);
+ } else {
+ validate(request);
+ PropertyDto property = toProperty(request, component);
+ value = property.getValue();
+ dbClient.propertiesDao().insertProperty(dbSession, property);
+ }
+
+ if (!component.isPresent()) {
+ settingsChangeNotifier.onGlobalPropertyChange(request.getKey(), value);
+ }
+ }
+
+ private void commonChecks(SetRequest request, @Nullable PropertyDefinition definition, Optional<ComponentDto> component) {
+ checkValueIsSet(request);
+ checkGlobalOrProject(request, definition, component);
+ checkComponentQualifier(request, definition, component);
+ }
+
+ private String doHandlePropertySet(DbSession dbSession, SetRequest request, @Nullable PropertyDefinition definition, Optional<ComponentDto> component) {
+ validatePropertySet(request, definition, component);
+
+ int[] fieldIds = IntStream.rangeClosed(1, request.getFieldValues().size()).toArray();
+ String inlinedFieldKeys = IntStream.of(fieldIds).mapToObj(String::valueOf).collect(COMMA_JOINER);
+ String key = persistedKey(request);
+ Long componentId = component.isPresent() ? component.get().getId() : null;
+
+ deleteSettings(dbSession, component, key);
+ dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto().setKey(key).setValue(inlinedFieldKeys).setResourceId(componentId));
+
+ List<Map<String, String>> fieldValues = request.getFieldValues();
+ IntStream.of(fieldIds).boxed()
+ .flatMap(i -> fieldValues.get(i - 1).entrySet().stream().map(entry -> new KeyValue(key + "." + i + "." + entry.getKey(), entry.getValue())))
+ .forEach(keyValue -> dbClient.propertiesDao().insertProperty(dbSession, toFieldProperty(keyValue, componentId)));
+
+ return inlinedFieldKeys;
+ }
+
+ private void deleteSettings(DbSession dbSession, Optional<ComponentDto> component, String key) {
+ if (component.isPresent()) {
+ settingsUpdater.deleteComponentSetting(dbSession, key, component.get());
+ } else {
+ settingsUpdater.deleteGlobalSetting(dbSession, key);
+ }
+ }
+
+ private void validatePropertySet(SetRequest request, @Nullable PropertyDefinition definition, Optional<ComponentDto> component) {
+ checkRequest(definition != null, "Setting '%s' is undefined", request.getKey());
+ checkRequest(PropertyType.PROPERTY_SET.equals(definition.type()), "Parameter '%s' is used for setting of property set type only", PARAM_FIELD_VALUES);
+
+ Set<String> fieldKeys = definition.fields().stream().map(PropertyFieldDefinition::key).collect(Collectors.toSet());
+ ListMultimap<String, String> valuesByFieldKeys = ArrayListMultimap.create(fieldKeys.size(), request.getFieldValues().size() * fieldKeys.size());
+
+ request.getFieldValues().stream()
+ .flatMap(map -> map.entrySet().stream())
+ .peek(entry -> valuesByFieldKeys.put(entry.getKey(), entry.getValue()))
+ .forEach(entry -> checkRequest(fieldKeys.contains(entry.getKey()), "Unknown field key '%s' for setting '%s'", entry.getKey(), request.getKey()));
+
+ checkFieldType(request, definition, valuesByFieldKeys);
+ }
+
+ private void validate(SetRequest request) {
PropertyDefinition definition = propertyDefinitions.get(request.getKey());
if (definition == null) {
return;
checkType(request, definition);
checkSingleOrMultiValue(request, definition);
- checkGlobalOrProject(request, definition, component);
- checkComponentQualifier(request, definition, component);
+ }
+
+ private static void checkFieldType(SetRequest request, PropertyDefinition definition, ListMultimap<String, String> valuesByFieldKeys) {
+ for (PropertyFieldDefinition fieldDefinition : definition.fields()) {
+ for (String value : valuesByFieldKeys.get(fieldDefinition.key())) {
+ PropertyDefinition.Result result = fieldDefinition.validate(value);
+ checkRequest(result.isValid(),
+ "Error when validating setting with key '%s'. Field '%s' has incorrect value '%s'.",
+ request.getKey(), fieldDefinition.key(), value);
+ }
+ }
}
private static void checkSingleOrMultiValue(SetRequest request, PropertyDefinition definition) {
"Parameter '%s' must be used for single value setting. Parameter '%s' must be used for multi value setting.", PARAM_VALUE, PARAM_VALUES);
}
- private static void checkGlobalOrProject(SetRequest request, PropertyDefinition definition, Optional<ComponentDto> component) {
- checkRequest(component.isPresent() || definition.global(), "Setting '%s' cannot be global", request.getKey());
+ private static void checkGlobalOrProject(SetRequest request, @Nullable PropertyDefinition definition, Optional<ComponentDto> component) {
+ checkRequest(component.isPresent() || definition == null || definition.global(), "Setting '%s' cannot be global", request.getKey());
}
- private void checkComponentQualifier(SetRequest request, PropertyDefinition definition, Optional<ComponentDto> component) {
+ private void checkComponentQualifier(SetRequest request, @Nullable PropertyDefinition definition, Optional<ComponentDto> component) {
String qualifier = component.isPresent() ? component.get().qualifier() : "";
checkRequest(!component.isPresent()
- || definition.qualifiers().contains(component.get().qualifier()),
+ || definition == null
+ || definition.qualifiers().contains(component.get().qualifier()),
"Setting '%s' cannot be set on a %s", request.getKey(), i18n.message(Locale.ENGLISH, "qualifier." + qualifier, null));
}
}
private static void checkValueIsSet(SetRequest request) {
- checkRequest(isNullOrEmpty(request.getValue()) ^ request.getValues().isEmpty(),
- "Either '%s' or '%s' must be provided, not both", PARAM_VALUE, PARAM_VALUES);
+ checkRequest(!isNullOrEmpty(request.getValue())
+ ^ !request.getValues().isEmpty()
+ ^ !request.getFieldValues().isEmpty(),
+ "One and only one of '%s', '%s', '%s' must be provided", PARAM_VALUE, PARAM_VALUES, PARAM_FIELD_VALUES);
}
private static List<String> valuesFromRequest(SetRequest request) {
return request.getValue() == null ? request.getValues() : Collections.singletonList(request.getValue());
}
+ private String persistedKey(SetRequest request) {
+ PropertyDefinition definition = propertyDefinitions.get(request.getKey());
+ // handles deprecated key but persist the new key
+ return definition == null ? request.getKey() : definition.key();
+ }
+
private static String persistedValue(SetRequest request) {
return request.getValue() == null
- ? COMMA_JOINER.join(request.getValues().stream().map(value -> value.replace(",", "%2C")).collect(Collectors.toList()))
+ ? request.getValues().stream().map(value -> value.replace(",", "%2C")).collect(COMMA_JOINER)
: request.getValue();
}
}
private static SetRequest toWsRequest(Request request) {
- SetRequest setRequest = SetRequest.builder()
+ return SetRequest.builder()
.setKey(request.mandatoryParam(PARAM_KEY))
.setValue(request.param(PARAM_VALUE))
.setValues(request.multiParam(PARAM_VALUES))
+ .setFieldValues(readFieldValues(request))
.setComponentId(request.param(PARAM_COMPONENT_ID))
.setComponentKey(request.param(PARAM_COMPONENT_KEY))
.build();
+ }
- checkValueIsSet(setRequest);
+ private static List<Map<String, String>> readFieldValues(Request request) {
+ String key = request.mandatoryParam(PARAM_KEY);
- return setRequest;
+ return request.multiParam(PARAM_FIELD_VALUES).stream()
+ .map(json -> readOneFieldValues(json, key))
+ .collect(Collectors.toList());
+ }
+
+ private static Map<String, String> readOneFieldValues(String json, String key) {
+ Type type = new TypeToken<Map<String, String>>() {
+ }.getType();
+ try {
+ return (Map<String, String>) GsonHelper.create().fromJson(json, type);
+ } catch (JsonParseException e) {
+ throw new BadRequestException(String.format("Invalid JSON '%s' for setting '%s'", json, key));
+ }
}
private Optional<ComponentDto> searchComponent(DbSession dbSession, SetRequest request) {
}
private PropertyDto toProperty(SetRequest request, Optional<ComponentDto> component) {
- PropertyDefinition definition = propertyDefinitions.get(request.getKey());
- // handles deprecated key but persist the new key
- String key = definition == null ? request.getKey() : definition.key();
+ String key = persistedKey(request);
String value = persistedValue(request);
PropertyDto property = new PropertyDto()
return property;
}
+
+ private static PropertyDto toFieldProperty(KeyValue keyValue, @Nullable Long componentId) {
+ return new PropertyDto().setKey(keyValue.key).setValue(keyValue.value).setResourceId(componentId);
+ }
+
+ private static class KeyValue {
+ private final String key;
+ private final String value;
+
+ private KeyValue(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
}
package org.sonar.server.settings.ws;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
import java.net.HttpURLConnection;
import java.util.List;
import javax.annotation.Nullable;
import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.PropertyFieldDefinition;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.db.property.PropertyDbTester;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;
+import org.sonar.scanner.protocol.GsonHelper;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.i18n.I18nRule;
+import org.sonar.server.platform.SettingsChangeNotifier;
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 com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
public class SetActionTest {
+ private static final Gson GSON = GsonHelper.create();
+
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
I18nRule i18n = new I18nRule();
PropertyDefinitions propertyDefinitions = new PropertyDefinitions();
+ SettingsChangeNotifier settingsChangeNotifier = mock(SettingsChangeNotifier.class);
+ SettingsUpdater settingsUpdater = new SettingsUpdater(dbClient, propertyDefinitions);
- SetAction underTest = new SetAction(propertyDefinitions, i18n, dbClient, componentFinder, userSession);
+ SetAction underTest = new SetAction(propertyDefinitions, i18n, dbClient, componentFinder, userSession, settingsUpdater, settingsChangeNotifier);
WsActionTester ws = new WsActionTester(underTest);
}
@Test
- public void persist_new_global_property() {
- callForGlobalProperty("my.key", "my,value");
+ public void persist_new_global_setting() {
+ callForGlobalSetting("my.key", "my,value");
- assertGlobalProperty("my.key", "my,value");
+ assertGlobalSetting("my.key", "my,value");
+ verify(settingsChangeNotifier).onGlobalPropertyChange("my.key", "my,value");
}
@Test
- public void update_existing_global_property() {
+ public void update_existing_global_setting() {
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my value"));
- assertGlobalProperty("my.key", "my value");
+ assertGlobalSetting("my.key", "my value");
- callForGlobalProperty("my.key", "my new value");
+ callForGlobalSetting("my.key", "my new value");
- assertGlobalProperty("my.key", "my new value");
+ assertGlobalSetting("my.key", "my new value");
+ verify(settingsChangeNotifier).onGlobalPropertyChange("my.key", "my new value");
}
@Test
- public void persist_new_project_property() {
+ public void persist_new_project_setting() {
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"));
ComponentDto project = componentDb.insertProject();
- callForProjectPropertyByUuid("my.key", "my project value", project.uuid());
+ callForProjectSettingByUuid("my.key", "my project value", project.uuid());
- assertGlobalProperty("my.key", "my global value");
- assertProjectProperty("my.key", "my project value", project.getId());
+ assertGlobalSetting("my.key", "my global value");
+ assertComponentSetting("my.key", "my project value", project.getId());
+ verifyZeroInteractions(settingsChangeNotifier);
}
@Test
ComponentDto project = componentDb.insertProject();
userSession.anonymous().addProjectUuidPermissions(UserRole.ADMIN, project.uuid());
- callForProjectPropertyByUuid("my.key", "my value", project.uuid());
+ callForProjectSettingByUuid("my.key", "my value", project.uuid());
- assertProjectProperty("my.key", "my value", project.getId());
+ assertComponentSetting("my.key", "my value", project.getId());
}
@Test
- public void update_existing_project_property() {
+ public void update_existing_project_setting() {
propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"));
ComponentDto project = componentDb.insertProject();
propertyDb.insertProperty(newComponentPropertyDto("my.key", "my project value", project));
- assertProjectProperty("my.key", "my project value", project.getId());
+ assertComponentSetting("my.key", "my project value", project.getId());
- callForProjectPropertyByKey("my.key", "my new project value", project.key());
+ callForProjectSettingByKey("my.key", "my new project value", project.key());
- assertProjectProperty("my.key", "my new project value", project.getId());
+ assertComponentSetting("my.key", "my new project value", project.getId());
}
@Test
- public void persist_several_multi_value_property() {
- callForMultiValueGlobalProperty("my.key", newArrayList("first,Value", "second,Value", "third,Value"));
+ public void persist_several_multi_value_setting() {
+ callForMultiValueGlobalSetting("my.key", newArrayList("first,Value", "second,Value", "third,Value"));
- assertGlobalProperty("my.key", "first%2CValue,second%2CValue,third%2CValue");
+ String expectedValue = "first%2CValue,second%2CValue,third%2CValue";
+ assertGlobalSetting("my.key", expectedValue);
+ verify(settingsChangeNotifier).onGlobalPropertyChange("my.key", expectedValue);
}
@Test
- public void persist_one_multi_value_property() {
- callForMultiValueGlobalProperty("my.key", newArrayList("first,Value"));
+ public void persist_one_multi_value_setting() {
+ callForMultiValueGlobalSetting("my.key", newArrayList("first,Value"));
+
+ assertGlobalSetting("my.key", "first%2CValue");
+ }
+
+ @Test
+ public void persist_property_set_setting() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .fields(newArrayList(
+ PropertyFieldDefinition.build("firstField")
+ .name("First Field")
+ .type(PropertyType.STRING)
+ .build(),
+ PropertyFieldDefinition.build("secondField")
+ .name("Second Field")
+ .type(PropertyType.STRING)
+ .build()))
+ .build());
- assertGlobalProperty("my.key", "first%2CValue");
+ callForGlobalPropertySet("my.key", newArrayList(
+ GSON.toJson(ImmutableMap.of("firstField", "firstValue", "secondField", "secondValue")),
+ GSON.toJson(ImmutableMap.of("firstField", "anotherFirstValue", "secondField", "anotherSecondValue")),
+ GSON.toJson(ImmutableMap.of("firstField", "yetFirstValue", "secondField", "yetSecondValue"))));
+
+ assertThat(dbClient.propertiesDao().selectGlobalProperties(dbSession)).hasSize(7);
+ assertGlobalSetting("my.key", "1,2,3");
+ assertGlobalSetting("my.key.1.firstField", "firstValue");
+ assertGlobalSetting("my.key.1.secondField", "secondValue");
+ assertGlobalSetting("my.key.2.firstField", "anotherFirstValue");
+ assertGlobalSetting("my.key.2.secondField", "anotherSecondValue");
+ assertGlobalSetting("my.key.3.firstField", "yetFirstValue");
+ assertGlobalSetting("my.key.3.secondField", "yetSecondValue");
+ verify(settingsChangeNotifier).onGlobalPropertyChange("my.key", "1,2,3");
}
@Test
- public void user_property_is_not_updated() {
+ public void update_property_set_setting() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .fields(newArrayList(
+ PropertyFieldDefinition.build("firstField")
+ .name("First Field")
+ .type(PropertyType.STRING)
+ .build(),
+ PropertyFieldDefinition.build("secondField")
+ .name("Second Field")
+ .type(PropertyType.STRING)
+ .build()))
+ .build());
+ propertyDb.insertProperties(
+ newGlobalPropertyDto("my.key", "1,2,3,4"),
+ newGlobalPropertyDto("my.key.1.firstField", "oldFirstValue"),
+ newGlobalPropertyDto("my.key.1.secondField", "oldSecondValue"),
+ newGlobalPropertyDto("my.key.2.firstField", "anotherOldFirstValue"),
+ newGlobalPropertyDto("my.key.2.secondField", "anotherOldSecondValue"),
+ newGlobalPropertyDto("my.key.3.firstField", "oldFirstValue"),
+ newGlobalPropertyDto("my.key.3.secondField", "oldSecondValue"),
+ newGlobalPropertyDto("my.key.4.firstField", "anotherOldFirstValue"),
+ newGlobalPropertyDto("my.key.4.secondField", "anotherOldSecondValue"));
+
+ callForGlobalPropertySet("my.key", newArrayList(
+ GSON.toJson(ImmutableMap.of("firstField", "firstValue", "secondField", "secondValue")),
+ GSON.toJson(ImmutableMap.of("firstField", "anotherFirstValue", "secondField", "anotherSecondValue")),
+ GSON.toJson(ImmutableMap.of("firstField", "yetFirstValue", "secondField", "yetSecondValue"))));
+
+ assertThat(dbClient.propertiesDao().selectGlobalProperties(dbSession)).hasSize(7);
+ assertGlobalSetting("my.key", "1,2,3");
+ assertGlobalSetting("my.key.1.firstField", "firstValue");
+ assertGlobalSetting("my.key.1.secondField", "secondValue");
+ assertGlobalSetting("my.key.2.firstField", "anotherFirstValue");
+ assertGlobalSetting("my.key.2.secondField", "anotherSecondValue");
+ assertGlobalSetting("my.key.3.firstField", "yetFirstValue");
+ assertGlobalSetting("my.key.3.secondField", "yetSecondValue");
+ verify(settingsChangeNotifier).onGlobalPropertyChange("my.key", "1,2,3");
+ }
+
+ @Test
+ public void update_property_set_on_component_setting() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .onQualifiers(Qualifiers.PROJECT)
+ .fields(newArrayList(
+ PropertyFieldDefinition.build("firstField")
+ .name("First Field")
+ .type(PropertyType.STRING)
+ .build(),
+ PropertyFieldDefinition.build("secondField")
+ .name("Second Field")
+ .type(PropertyType.STRING)
+ .build()))
+ .build());
+ ComponentDto project = componentDb.insertProject();
+ propertyDb.insertProperties(
+ newGlobalPropertyDto("my.key", "1"),
+ newGlobalPropertyDto("my.key.1.firstField", "oldFirstValue"),
+ newGlobalPropertyDto("my.key.1.secondField", "oldSecondValue"),
+ newComponentPropertyDto("my.key", "1", project),
+ newComponentPropertyDto("my.key.1.firstField", "componentFirstValue", project),
+ newComponentPropertyDto("my.key.1.firstField", "componentSecondValue", project));
+
+ callForComponentPropertySetByUuid("my.key", newArrayList(
+ GSON.toJson(ImmutableMap.of("firstField", "firstValue", "secondField", "secondValue")),
+ GSON.toJson(ImmutableMap.of("firstField", "anotherFirstValue", "secondField", "anotherSecondValue"))),
+ project.uuid());
+
+ assertThat(dbClient.propertiesDao().selectGlobalProperties(dbSession)).hasSize(3);
+ assertThat(dbClient.propertiesDao().selectProjectProperties(dbSession, project.key())).hasSize(5);
+ assertGlobalSetting("my.key", "1");
+ assertGlobalSetting("my.key.1.firstField", "oldFirstValue");
+ assertGlobalSetting("my.key.1.secondField", "oldSecondValue");
+ Long projectId = project.getId();
+ assertComponentSetting("my.key", "1,2", projectId);
+ assertComponentSetting("my.key.1.firstField", "firstValue", projectId);
+ assertComponentSetting("my.key.1.secondField", "secondValue", projectId);
+ assertComponentSetting("my.key.2.firstField", "anotherFirstValue", projectId);
+ assertComponentSetting("my.key.2.secondField", "anotherSecondValue", projectId);
+ verifyZeroInteractions(settingsChangeNotifier);
+ }
+
+ @Test
+ public void user_setting_is_not_updated() {
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");
+ callForGlobalSetting("my.key", "my new global value");
- assertGlobalProperty("my.key", "my new global value");
- assertUserProperty("my.key", "my user value", 42L);
+ assertGlobalSetting("my.key", "my new global value");
+ assertUserSetting("my.key", "my user value", 42L);
}
@Test
.defaultValue("default")
.build());
- callForGlobalProperty("my.old.key", "My Value");
+ callForGlobalSetting("my.old.key", "My Value");
- assertGlobalProperty("my.key", "My Value");
+ assertGlobalSetting("my.key", "My Value");
}
@Test
public void fail_when_no_key() {
expectedException.expect(IllegalArgumentException.class);
- callForGlobalProperty(null, "my value");
+ callForGlobalSetting(null, "my value");
}
@Test
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Setting key is mandatory and must not be empty");
- callForGlobalProperty(" ", "my value");
+ callForGlobalSetting(" ", "my value");
}
@Test
public void fail_when_no_value() {
expectedException.expect(BadRequestException.class);
- expectedException.expectMessage("Either 'value' or 'values' must be provided, not both");
+ expectedException.expectMessage("One and only one of 'value', 'values', 'fieldValues' must be provided");
- callForGlobalProperty("my.key", null);
+ callForGlobalSetting("my.key", null);
}
@Test
public void fail_when_empty_value() {
expectedException.expect(BadRequestException.class);
- expectedException.expectMessage("Either 'value' or 'values' must be provided, not both");
+ expectedException.expectMessage("One and only one of 'value', 'values', 'fieldValues' must be provided");
- callForGlobalProperty("my.key", "");
+ callForGlobalSetting("my.key", "");
}
@Test
userSession.anonymous().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);
expectedException.expect(ForbiddenException.class);
- callForGlobalProperty("my.key", "my value");
+ callForGlobalSetting("my.key", "my value");
}
@Test
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Not an integer error message");
- callForGlobalProperty("my.key", "My Value");
+ callForGlobalSetting("my.key", "My Value");
}
@Test
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Error when validating setting with key 'my.key' and value 'My Value'");
- callForGlobalProperty("my.key", "My Value");
+ callForGlobalSetting("my.key", "My Value");
}
@Test
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Setting 'my.key' cannot be global");
- callForGlobalProperty("my.key", "42");
+ callForGlobalSetting("my.key", "42");
}
@Test
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Setting 'my.key' cannot be set on a View");
- callForProjectPropertyByUuid("my.key", "My Value", view.uuid());
+ callForProjectSettingByUuid("my.key", "My Value", view.uuid());
}
@Test
public void fail_when_single_and_multi_value_provided() {
expectedException.expect(BadRequestException.class);
- expectedException.expectMessage("Either 'value' or 'values' must be provided, not both");
+ expectedException.expectMessage("One and only one of 'value', 'values', 'fieldValues' must be provided");
- call("my.key", "My Value", newArrayList("Another Value"), null, null);
+ call("my.key", "My Value", newArrayList("Another Value"), null, null, null);
}
@Test
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Parameter 'value' must be used for single value setting. Parameter 'values' must be used for multi value setting.");
- callForGlobalProperty("my.key", "My Value");
+ callForGlobalSetting("my.key", "My Value");
}
@Test
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Parameter 'value' must be used for single value setting. Parameter 'values' must be used for multi value setting.");
- callForMultiValueGlobalProperty("my.key", newArrayList("My Value"));
+ callForMultiValueGlobalSetting("my.key", newArrayList("My Value"));
+ }
+
+ @Test
+ public void fail_when_property_set_setting_is_not_defined() {
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage("Setting 'my.key' is undefined");
+
+ callForGlobalPropertySet("my.key", singletonList("{\"field\":\"value\"}"));
+ }
+
+ @Test
+ public void fail_when_property_set_with_unknown_field() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .fields(newArrayList(
+ PropertyFieldDefinition.build("field")
+ .name("Field")
+ .type(PropertyType.STRING)
+ .build()))
+ .build());
+
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage("Unknown field key 'unknownField' for setting 'my.key'");
+
+ callForGlobalPropertySet("my.key", newArrayList(GSON.toJson(ImmutableMap.of("field", "value", "unknownField", "anotherValue"))));
+ }
+
+ @Test
+ public void fail_when_property_set_has_field_with_incorrect_type() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .fields(newArrayList(
+ PropertyFieldDefinition.build("field")
+ .name("Field")
+ .type(PropertyType.INTEGER)
+ .build()))
+ .build());
+
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage("Error when validating setting with key 'my.key'. Field 'field' has incorrect value 'notAnInt'.");
+
+ callForGlobalPropertySet("my.key", newArrayList(GSON.toJson(ImmutableMap.of("field", "notAnInt"))));
+ }
+
+ @Test
+ public void fail_when_property_set_with_invalid_json() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .fields(newArrayList(
+ PropertyFieldDefinition.build("field")
+ .name("Field")
+ .type(PropertyType.STRING)
+ .build()))
+ .build());
+
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage("Invalid JSON 'incorrectJson:incorrectJson' for setting 'my.key'");
+
+ callForGlobalPropertySet("my.key", newArrayList("incorrectJson:incorrectJson"));
+ }
+
+ @Test
+ public void fail_when_property_set_on_component_of_global_setting() {
+ propertyDefinitions.addComponent(PropertyDefinition
+ .builder("my.key")
+ .name("foo")
+ .description("desc")
+ .category("cat")
+ .subCategory("subCat")
+ .type(PropertyType.PROPERTY_SET)
+ .defaultValue("default")
+ .fields(newArrayList(PropertyFieldDefinition.build("firstField").name("First Field").type(PropertyType.STRING).build()))
+ .build());
+ i18n.put("qualifier." + Qualifiers.PROJECT, "Project");
+ ComponentDto project = componentDb.insertProject();
+
+ expectedException.expect(BadRequestException.class);
+ expectedException.expectMessage("Setting 'my.key' cannot be set on a Project");
+
+ callForComponentPropertySetByUuid("my.key", newArrayList(
+ GSON.toJson(ImmutableMap.of("firstField", "firstValue"))), project.uuid());
}
@Test
assertThat(definition.isPost()).isTrue();
assertThat(definition.since()).isEqualTo("6.1");
assertThat(definition.params()).extracting(Param::key)
- .containsOnly("key", "value", "values", "componentId", "componentKey");
+ .containsOnly("key", "value", "values", "fieldValues", "componentId", "componentKey");
}
- private void assertGlobalProperty(String key, String value) {
+ private void assertGlobalSetting(String key, String value) {
PropertyDto result = dbClient.propertiesDao().selectGlobalProperty(key);
assertThat(result)
.containsExactly(key, value, null);
}
- private void assertUserProperty(String key, String value, long userId) {
+ private void assertUserSetting(String key, String value, long userId) {
List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setKey(key).setUserId((int) userId).build(), dbSession);
assertThat(result).hasSize(1)
.containsExactly(tuple(key, value, userId));
}
- private void assertProjectProperty(String key, String value, long componentId) {
+ private void assertComponentSetting(String key, String value, long componentId) {
PropertyDto result = dbClient.propertiesDao().selectProjectProperty(componentId, key);
assertThat(result)
.containsExactly(key, value, componentId);
}
- private void callForGlobalProperty(@Nullable String key, @Nullable String value) {
- call(key, value, null, null, null);
+ private void callForGlobalSetting(@Nullable String key, @Nullable String value) {
+ call(key, value, null, null, null, null);
}
- private void callForMultiValueGlobalProperty(@Nullable String key, @Nullable List<String> values) {
- call(key, null, values, null, null);
+ private void callForMultiValueGlobalSetting(@Nullable String key, @Nullable List<String> values) {
+ call(key, null, values, null, null, null);
}
- private void callForProjectPropertyByUuid(@Nullable String key, @Nullable String value, @Nullable String componentUuid) {
- call(key, value, null, componentUuid, null);
+ private void callForGlobalPropertySet(@Nullable String key, @Nullable List<String> fieldValues) {
+ call(key, null, null, fieldValues, null, null);
}
- private void callForProjectPropertyByKey(@Nullable String key, @Nullable String value, @Nullable String componentKey) {
- call(key, value, null, null, componentKey);
+ private void callForComponentPropertySetByUuid(@Nullable String key, @Nullable List<String> fieldValues, @Nullable String componentUuid) {
+ call(key, null, null, fieldValues, componentUuid, null);
}
- private void call(@Nullable String key, @Nullable String value, @Nullable List<String> values, @Nullable String componentUuid, @Nullable String componentKey) {
+ private void callForProjectSettingByUuid(@Nullable String key, @Nullable String value, @Nullable String componentUuid) {
+ call(key, value, null, null, componentUuid, null);
+ }
+
+ private void callForProjectSettingByKey(@Nullable String key, @Nullable String value, @Nullable String componentKey) {
+ call(key, value, null, null, null, componentKey);
+ }
+
+ private void call(@Nullable String key, @Nullable String value, @Nullable List<String> values, @Nullable List<String> fieldValues, @Nullable String componentUuid,
+ @Nullable String componentKey) {
TestRequest request = ws.newRequest();
if (key != null) {
request.setMultiParam("values", values);
}
+ if (fieldValues != null) {
+ request.setMultiParam("fieldValues", fieldValues);
+ }
+
if (componentUuid != null) {
request.setParam("componentId", componentUuid);
}