]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7969 Create /api/settings/values WS
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 17 Aug 2016 16:00:26 +0000 (18:00 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 25 Aug 2016 08:03:31 +0000 (10:03 +0200)
16 files changed:
server/sonar-server/src/main/java/org/sonar/server/settings/ws/ListDefinitionsAction.java
server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsComponentParameters.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsModule.java
server/sonar-server/src/main/java/org/sonar/server/settings/ws/ValuesAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/settings/ws/values-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/settings/ws/ListDefinitionsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/settings/ws/SetActionTest.java
server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/settings/ws/ValuesActionTest.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/property/PropertiesDao.java
sonar-db/src/main/java/org/sonar/db/property/PropertiesMapper.java
sonar-db/src/main/java/org/sonar/db/property/PropertyTesting.java [new file with mode: 0644]
sonar-db/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
sonar-db/src/test/java/org/sonar/db/property/PropertyTesting.java [deleted file]
sonar-ws/src/main/protobuf/ws-settings.proto

index 3baef74a38052d945a64c66ae962f56826d70d86..b098a1c9c598ab28d044047690e95314f92ce87c 100644 (file)
@@ -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);
     }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsComponentParameters.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsWsComponentParameters.java
new file mode 100644 (file)
index 0000000..40a3e5f
--- /dev/null
@@ -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());
+    }
+  }
+
+}
index 006dc678b466a63e9bb75f8a70d0a84091cdaec2..c5eb79e52e4337297524dfc17600824136eca894 100644 (file)
@@ -27,6 +27,8 @@ public class SettingsWsModule extends Module {
     add(
       SettingsWs.class,
       SetAction.class,
-      ListDefinitionsAction.class);
+      SettingsWsComponentParameters.class,
+      ListDefinitionsAction.class,
+      ValuesAction.class);
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/ValuesAction.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/ValuesAction.java
new file mode 100644 (file)
index 0000000..83dec42
--- /dev/null
@@ -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());
+  }
+
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/settings/ws/values-example.json b/server/sonar-server/src/main/resources/org/sonar/server/settings/ws/values-example.json
new file mode 100644 (file)
index 0000000..11d7e50
--- /dev/null
@@ -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
+    }
+  ]
+}
index 47ebd860da5fe7a39253f0e5de644d4f6cc2797a..c670f788ad5a1b3b90fb3e9bd6724c7511b7f0b9 100644 (file)
@@ -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);
   }
index 5a5b8eae291d0897e8d9c3d3d14ce98bb2cf9d62..aa8cb63e548f34c20bfee092e10c9f73601ed69d 100644 (file)
@@ -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");
 
index d2f1b858c0bcc0bec7016a5b16ec87bd7ff3f5d1..3b9185d8b5570c7df1c96b82d25e2a127c098794 100644 (file)
@@ -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);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/settings/ws/ValuesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/settings/ws/ValuesActionTest.java
new file mode 100644 (file)
index 0000000..7920053
--- /dev/null
@@ -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();
+  }
+
+}
index b7325cee8a3c9558de5039942f5ee6048fb25bb3..140d7edf142f5e2f8964598a87340ab2f46f8ec9 100644 (file)
@@ -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);
index a90fed439af6a1b3e4a1cdff6bc4814a7f13a387..e8ef34a84da10be36ec4bdea0330c6c482dc71af 100644 (file)
@@ -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,
diff --git a/sonar-db/src/main/java/org/sonar/db/property/PropertyTesting.java b/sonar-db/src/main/java/org/sonar/db/property/PropertyTesting.java
new file mode 100644 (file)
index 0000000..88ab57a
--- /dev/null
@@ -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;
+  }
+
+}
index afb6f9d5c1dd9351b8773c4072e2a54e7a580984..a64c3756236ca89a68c76b160e98490fcb4a59f4 100644 (file)
     </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
index a31013ac096b66831fb2203bcf9e09b54935b624..60fcf5edcb191046a92bf9cfed763c76884b0065 100644 (file)
 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();
+  }
 }
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
deleted file mode 100644 (file)
index cb7a089..0000000
+++ /dev/null
@@ -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);
-  }
-}
index c4f09b10fe1e0c247af5817df1a19da25b722b3f..345aef7c675bcd421fde9e16983412247ab466a8 100644 (file)
@@ -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;
+}
+