]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7975 Return inherited values in /api/settings/values for modules
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 22 Aug 2016 16:45:37 +0000 (18:45 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 25 Aug 2016 09:29:40 +0000 (11:29 +0200)
server/sonar-server/src/main/java/org/sonar/server/settings/ws/Setting.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsFinder.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
server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsFinderTest.java [new file with mode: 0644]
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
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/resources/org/sonar/db/property/PropertiesMapper.xml
sonar-db/src/test/java/org/sonar/db/property/PropertiesDaoTest.java

diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/Setting.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/Setting.java
new file mode 100644 (file)
index 0000000..668ed30
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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 com.google.common.collect.ImmutableTable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.db.property.PropertyDto;
+
+public class Setting {
+
+  private static final Splitter DOT_SPLITTER = Splitter.on(".").omitEmptyStrings();
+
+  private final String key;
+  private final Long componentId;
+  private final String value;
+  private final PropertyDefinition definition;
+  private final List<Map<String, String>> propertySets;
+  private final boolean isDefault;
+
+  /**
+   * Use this constructor to create setting from a property dto, and that can have a definition or not
+   */
+  Setting(PropertyDto propertyDto, List<PropertyDto> propertyDtoSetValues, @Nullable PropertyDefinition definition) {
+    this.key = propertyDto.getKey();
+    this.value = propertyDto.getValue();
+    this.componentId = propertyDto.getResourceId();
+    this.definition = definition;
+    this.propertySets = buildPropertySetValuesAsMap(key, propertyDtoSetValues);
+    this.isDefault = false;
+  }
+
+  /**
+   * Use this constructor to create setting for default value
+   */
+  Setting(PropertyDefinition definition) {
+    this.key = definition.key();
+    this.value = definition.defaultValue();
+    this.componentId = null;
+    this.definition = definition;
+    this.propertySets = Collections.emptyList();
+    this.isDefault = true;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  @CheckForNull
+  public Long getComponentId() {
+    return componentId;
+  }
+
+  @CheckForNull
+  public PropertyDefinition getDefinition() {
+    return definition;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public boolean isDefault() {
+    return isDefault;
+  }
+
+  public List<Map<String, String>> getPropertySets() {
+    return propertySets;
+  }
+
+  private static List<Map<String, String>> buildPropertySetValuesAsMap(String propertyKey, List<PropertyDto> propertySets) {
+    if (propertySets.isEmpty()) {
+      return Collections.emptyList();
+    }
+    ImmutableTable.Builder<String, String, String> tableBuilder = new ImmutableTable.Builder<>();
+    propertySets.forEach(property -> {
+      List<String> setIdWithFieldKey = DOT_SPLITTER.splitToList(property.getKey().replace(propertyKey + ".", ""));
+      String setId = setIdWithFieldKey.get(0);
+      String fieldKey = setIdWithFieldKey.get(1);
+      tableBuilder.put(setId, fieldKey, property.getValue());
+    });
+    ImmutableTable<String, String, String> table = tableBuilder.build();
+    return table.rowKeySet().stream()
+      .map(table::row)
+      .collect(Collectors.toList());
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsFinder.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ws/SettingsFinder.java
new file mode 100644 (file)
index 0000000..e1501d8
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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 com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.TreeMultimap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyDto;
+
+import static org.sonar.api.PropertyType.PROPERTY_SET;
+
+public class SettingsFinder {
+
+  private static final Splitter DOT_SPLITTER = Splitter.on(".").omitEmptyStrings();
+  private static final Splitter COMMA_SPLITTER = Splitter.on(",");
+
+  private final DbClient dbClient;
+  private final PropertyDefinitions definitions;
+
+  public SettingsFinder(DbClient dbClient, PropertyDefinitions definitions) {
+    this.dbClient = dbClient;
+    this.definitions = definitions;
+  }
+
+  public List<Setting> loadGlobalSettings(DbSession dbSession, Set<String> keys) {
+    List<PropertyDto> properties = dbClient.propertiesDao().selectGlobalPropertiesByKeys(dbSession, keys);
+    List<PropertyDto> propertySets = dbClient.propertiesDao().selectGlobalPropertiesByKeys(dbSession, getPropertySetKeys(properties));
+    return properties.stream()
+      .map(property -> new Setting(property, getPropertySets(property.getKey(), propertySets, null), definitions.get(property.getKey())))
+      .collect(Collectors.toList());
+  }
+
+  /**
+   * Return list of settings by component uuid, sorted from project to lowest module
+   */
+  public Multimap<String, Setting> loadComponentSettings(DbSession dbSession, Set<String> keys, ComponentDto component) {
+    List<String> componentUuids = DOT_SPLITTER.splitToList(component.moduleUuidPath());
+    List<ComponentDto> componentDtos = dbClient.componentDao().selectByUuids(dbSession, componentUuids);
+    Set<Long> componentIds = componentDtos.stream().map(ComponentDto::getId).collect(Collectors.toSet());
+    Map<Long, String> uuidsById = componentDtos.stream().collect(Collectors.toMap(ComponentDto::getId, ComponentDto::uuid));
+    List<PropertyDto> properties = dbClient.propertiesDao().selectPropertiesByKeysAndComponentIds(dbSession, keys, componentIds);
+    List<PropertyDto> propertySets = dbClient.propertiesDao().selectPropertiesByKeysAndComponentIds(dbSession, getPropertySetKeys(properties), componentIds);
+
+    Multimap<String, Setting> settingsByUuid = TreeMultimap.create(Ordering.explicit(componentUuids), Ordering.arbitrary());
+    for (PropertyDto propertyDto : properties) {
+      Long componentId = propertyDto.getResourceId();
+      String componentUuid = uuidsById.get(componentId);
+      String propertyKey = propertyDto.getKey();
+      settingsByUuid.put(componentUuid,
+        new Setting(propertyDto, getPropertySets(propertyKey, propertySets, componentId), definitions.get(propertyKey)));
+    }
+    return settingsByUuid;
+  }
+
+  private Set<String> getPropertySetKeys(List<PropertyDto> properties) {
+    Set<String> propertySetKeys = new HashSet<>();
+    properties.stream()
+      .filter(propertyDto -> definitions.get(propertyDto.getKey()) != null)
+      .filter(propertyDto -> definitions.get(propertyDto.getKey()).type().equals(PROPERTY_SET))
+      .forEach(propertyDto -> definitions.get(propertyDto.getKey()).fields()
+        .forEach(field -> COMMA_SPLITTER.splitToList(propertyDto.getValue())
+          .forEach(setId -> propertySetKeys.add(generatePropertySetKey(propertyDto.getKey(), setId, field.key())))));
+    return propertySetKeys;
+  }
+
+  private static String generatePropertySetKey(String propertyBaseKey, String id, String fieldKey) {
+    return propertyBaseKey + "." + id + "." + fieldKey;
+  }
+
+  private static List<PropertyDto> getPropertySets(String propertyKey, List<PropertyDto> propertySets, @Nullable Long componentId) {
+    return propertySets.stream()
+      .filter(propertyDto -> Objects.equals(propertyDto.getResourceId(), componentId))
+      .filter(propertyDto -> propertyDto.getKey().startsWith(propertyKey + "."))
+      .collect(Collectors.toList());
+  }
+
+}
index c5eb79e52e4337297524dfc17600824136eca894..2d067ebcc7f032c89d894a9d9d3fc07288a1c1f5 100644 (file)
@@ -29,6 +29,7 @@ public class SettingsWsModule extends Module {
       SetAction.class,
       SettingsWsComponentParameters.class,
       ListDefinitionsAction.class,
-      ValuesAction.class);
+      ValuesAction.class,
+      SettingsFinder.class);
   }
 }
index 98f982c1fd99c4ffe7d19d286eee282d14b41dc1..3ca00245a23b5411ec46313ed4142e8eb15f3926 100644 (file)
 package org.sonar.server.settings.ws;
 
 import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableTable;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 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.core.util.stream.Collectors;
 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;
 
@@ -52,16 +52,18 @@ public class ValuesAction implements SettingsWsAction {
 
   private static final Splitter COMMA_SPLITTER = Splitter.on(",");
 
-  static final String PARAM_KEYS = "keys";
+  private static final String PARAM_KEYS = "keys";
 
   private final DbClient dbClient;
   private final SettingsWsComponentParameters settingsWsComponentParameters;
   private final PropertyDefinitions propertyDefinitions;
+  private final SettingsFinder settingsFinder;
 
-  public ValuesAction(DbClient dbClient, SettingsWsComponentParameters settingsWsComponentParameters, PropertyDefinitions propertyDefinitions) {
+  public ValuesAction(DbClient dbClient, SettingsWsComponentParameters settingsWsComponentParameters, PropertyDefinitions propertyDefinitions, SettingsFinder settingsFinder) {
     this.dbClient = dbClient;
     this.settingsWsComponentParameters = settingsWsComponentParameters;
     this.propertyDefinitions = propertyDefinitions;
+    this.settingsFinder = settingsFinder;
   }
 
   @Override
@@ -93,89 +95,63 @@ public class ValuesAction implements SettingsWsAction {
   private ValuesWsResponse doHandle(Request request) {
     DbSession dbSession = dbClient.openSession(true);
     try {
-      ComponentDto component = settingsWsComponentParameters.getComponent(dbSession, request);
-      settingsWsComponentParameters.checkAdminPermission(component);
+      ComponentDto componentDto = settingsWsComponentParameters.getComponent(dbSession, request);
+      settingsWsComponentParameters.checkAdminPermission(componentDto);
       Set<String> keys = new HashSet<>(request.mandatoryParamAsStrings(PARAM_KEYS));
-
-      ValuesWsResponse.Builder valuesBuilder = ValuesWsResponse.newBuilder();
-      new SettingsBuilder(dbSession, valuesBuilder, getDefinitions(keys), keys, component).build();
-      ValuesWsResponse response = valuesBuilder.build();
-      return response;
+      Optional<ComponentDto> component = Optional.ofNullable(componentDto);
+      return new ValuesResponseBuilder(loadSettings(dbSession, component, keys), component).build();
     } finally {
       dbClient.closeSession(dbSession);
     }
   }
 
-  private class SettingsBuilder {
-    private final DbSession dbSession;
-    private final ComponentDto component;
+  private List<Setting> loadSettings(DbSession dbSession, Optional<ComponentDto> component, Set<String> keys) {
+    if (keys.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    // List of settings must be kept in the following orders : default -> global -> component
+    List<Setting> settings = new ArrayList<>();
+    settings.addAll(loadDefaultSettings(keys));
+    settings.addAll(settingsFinder.loadGlobalSettings(dbSession, keys));
+    if (component.isPresent()) {
+      settings.addAll(settingsFinder.loadComponentSettings(dbSession, keys, component.get()).values());
+    }
+    return settings;
+  }
+
+  private List<Setting> loadDefaultSettings(Set<String> keys) {
+    return propertyDefinitions.getAll().stream()
+      .filter(definition -> keys.contains(definition.key()))
+      .filter(defaultProperty -> !isNullOrEmpty(defaultProperty.defaultValue()))
+      .map(Setting::new)
+      .collect(Collectors.toList());
+  }
 
-    private final ValuesWsResponse.Builder valuesWsBuilder;
-    private final Map<String, PropertyDefinition> definitionsByKey;
-    private final Set<String> keys;
-    private final Set<String> propertySetKeys;
+  private class ValuesResponseBuilder {
+    private final List<Setting> settings;
+    private final Optional<ComponentDto> component;
 
+    private final ValuesWsResponse.Builder valuesWsBuilder = ValuesWsResponse.newBuilder();
     private final Map<String, Settings.Setting.Builder> settingsBuilderByKey = new HashMap<>();
 
-    SettingsBuilder(DbSession dbSession, ValuesWsResponse.Builder valuesWsBuilder, List<PropertyDefinition> definitions,
-      Set<String> keys, @Nullable ComponentDto component) {
-      this.dbSession = dbSession;
-      this.valuesWsBuilder = valuesWsBuilder;
-      this.definitionsByKey = definitions.stream()
-        .collect(Collectors.toMap(PropertyDefinition::key, Function.identity()));
-      this.keys = keys;
+    ValuesResponseBuilder(List<Setting> settings, Optional<ComponentDto> component) {
+      this.settings = settings;
       this.component = component;
-
-      this.propertySetKeys = definitions.stream()
-        .filter(definition -> definition.type().equals(PROPERTY_SET))
-        .map(PropertyDefinition::key)
-        .collect(Collectors.toSet());
     }
 
-    void build() {
-      processDefinitions();
-      processPropertyDtos(true);
-      if (component != null) {
-        processPropertyDtos(false);
-      }
+    ValuesWsResponse build() {
+      processSettings();
       settingsBuilderByKey.values().forEach(Settings.Setting.Builder::build);
+      return valuesWsBuilder.build();
     }
 
-    private void processDefinitions() {
-      definitionsByKey.values().stream()
-        .filter(defaultProperty -> !isNullOrEmpty(defaultProperty.defaultValue()))
-        .forEach(this::processDefaultValue);
-    }
-
-    private void processDefaultValue(PropertyDefinition definition) {
-      Settings.Setting.Builder settingBuilder = valuesWsBuilder.addSettingsBuilder()
-        .setKey(definition.key())
-        .setInherited(false)
-        .setDefault(true);
-      setValue(settingBuilder, definition.defaultValue());
-      settingsBuilderByKey.put(definition.key(), settingBuilder);
-    }
-
-    private void processPropertyDtos(boolean loadGlobal) {
-      List<PropertyDto> properties = loadProperties(dbSession, loadGlobal, keys);
-      PropertySetValues propertySetValues = new PropertySetValues(properties, loadGlobal);
-
-      properties.forEach(property -> {
-        String key = property.getKey();
-        Settings.Setting.Builder valueBuilder = getOrCreateValueBuilder(key);
-        valueBuilder.setInherited(component != null && property.getResourceId() == null);
-        valueBuilder.setDefault(false);
-
-        PropertyDefinition definition = definitionsByKey.get(key);
-        if (definition != null && definition.type().equals(PROPERTY_SET)) {
-          Settings.FieldsValues.Builder builder = Settings.FieldsValues.newBuilder();
-          for (Map<String, String> propertySetMap : propertySetValues.get(key)) {
-            builder.addFieldsValuesBuilder().putAllValue(propertySetMap);
-          }
-          valueBuilder.setFieldsValues(builder);
-        } else {
-          setValue(valueBuilder, property.getValue());
-        }
+    private void processSettings() {
+      settings.forEach(setting -> {
+        Settings.Setting.Builder valueBuilder = getOrCreateValueBuilder(setting.getKey());
+        valueBuilder.setDefault(setting.isDefault());
+        setInherited(setting, valueBuilder);
+        setValue(setting, valueBuilder);
       });
     }
 
@@ -188,108 +164,33 @@ public class ValuesAction implements SettingsWsAction {
       return valueBuilder;
     }
 
-    private void setValue(Settings.Setting.Builder valueBuilder, String value) {
-      PropertyDefinition definition = definitionsByKey.get(valueBuilder.getKey());
-      if (definition != null && definition.multiValues()) {
+    private void setValue(Setting setting, Settings.Setting.Builder valueBuilder) {
+      PropertyDefinition definition = setting.getDefinition();
+      String value = setting.getValue();
+      if (definition == null) {
+        valueBuilder.setValue(value);
+        return;
+      }
+      if (definition.type().equals(PROPERTY_SET)) {
+        Settings.FieldsValues.Builder builder = Settings.FieldsValues.newBuilder();
+        for (Map<String, String> propertySetMap : setting.getPropertySets()) {
+          builder.addFieldsValuesBuilder().putAllValue(propertySetMap);
+        }
+        valueBuilder.setFieldsValues(builder);
+      } else if (definition.multiValues()) {
         valueBuilder.setValues(Settings.Values.newBuilder().addAllValues(COMMA_SPLITTER.split(value)));
       } else {
         valueBuilder.setValue(value);
       }
     }
 
-    private List<PropertyDto> loadProperties(DbSession dbSession, boolean loadGlobal, Set<String> keys) {
-      if (loadGlobal) {
-        return dbClient.propertiesDao().selectGlobalPropertiesByKeys(dbSession, keys);
-      }
-      return dbClient.propertiesDao().selectComponentPropertiesByKeys(dbSession, keys, component.getId());
-    }
-
-    private class PropertySetValues {
-      private final Map<String, PropertyKeyWithFieldAndSetId> propertyKeyWithFieldAndSetIds = new HashMap<>();
-      private final Map<String, PropertySetValue> propertySetValuesByPropertyKey = new HashMap<>();
-
-      PropertySetValues(List<PropertyDto> properties, boolean loadGlobal) {
-        properties.stream()
-          .filter(propertyDto -> propertySetKeys.contains(propertyDto.getKey()))
-          .forEach(propertyDto -> definitionsByKey.get(propertyDto.getKey()).fields()
-            .forEach(field -> COMMA_SPLITTER.splitToList(propertyDto.getValue())
-              .forEach(value -> add(propertyDto.getKey(), field.key(), value))));
-
-        loadProperties(dbSession, loadGlobal, propertyKeyWithFieldAndSetIds.keySet())
-          .forEach(propertySetDto -> {
-            PropertyKeyWithFieldAndSetId propertyKeyWithFieldAndSetIdKey = propertyKeyWithFieldAndSetIds.get(propertySetDto.getKey());
-            PropertySetValue propertySetValue = getOrCreatePropertySetValue(propertyKeyWithFieldAndSetIdKey.getPropertyKey());
-            propertySetValue.add(propertyKeyWithFieldAndSetIdKey.getSetId(), propertyKeyWithFieldAndSetIdKey.getFieldKey(), propertySetDto.getValue());
-          });
-      }
-
-      private void add(String propertyKey, String fieldKey, String value) {
-        String propertySetKey = generatePropertySetKey(propertyKey, value, fieldKey);
-        propertyKeyWithFieldAndSetIds.put(propertySetKey, new PropertyKeyWithFieldAndSetId(propertyKey, fieldKey, value));
-      }
-
-      private PropertySetValue getOrCreatePropertySetValue(String propertyKey) {
-        PropertySetValue propertySetValue = propertySetValuesByPropertyKey.get(propertyKey);
-        if (propertySetValue == null) {
-          propertySetValue = new PropertySetValue();
-          propertySetValuesByPropertyKey.put(propertyKey, propertySetValue);
-        }
-        return propertySetValue;
-      }
-
-      List<Map<String, String>> get(String propertyKey) {
-        return propertySetValuesByPropertyKey.get(propertyKey).get();
-      }
-
-      private String generatePropertySetKey(String propertyKey, String id, String fieldKey) {
-        return propertyKey + "." + id + "." + fieldKey;
+    private void setInherited(Setting setting, Settings.Setting.Builder valueBuilder) {
+      if (setting.isDefault()) {
+        valueBuilder.setInherited(false);
+      } else {
+        valueBuilder.setInherited(component.isPresent() && !Objects.equals(setting.getComponentId(), component.get().getId()));
       }
     }
   }
 
-  private List<PropertyDefinition> getDefinitions(Set<String> keys) {
-    return propertyDefinitions.getAll().stream()
-      .filter(def -> keys.contains(def.key()))
-      .collect(Collectors.toList());
-  }
-
-  private static class PropertyKeyWithFieldAndSetId {
-    private final String propertyKey;
-    private final String fieldKey;
-    private final String setId;
-
-    PropertyKeyWithFieldAndSetId(String propertyKey, String fieldKey, String setId) {
-      this.propertyKey = propertyKey;
-      this.fieldKey = fieldKey;
-      this.setId = setId;
-    }
-
-    public String getPropertyKey() {
-      return propertyKey;
-    }
-
-    public String getFieldKey() {
-      return fieldKey;
-    }
-
-    public String getSetId() {
-      return setId;
-    }
-  }
-
-  private static class PropertySetValue {
-    ImmutableTable.Builder<String, String, String> tableBuilder = new ImmutableTable.Builder<>();
-
-    public void add(String setId, String fieldKey, String value) {
-      tableBuilder.put(setId, fieldKey, value);
-    }
-
-    public List<Map<String, String>> get() {
-      ImmutableTable<String, String, String> table = tableBuilder.build();
-      return table.rowKeySet().stream()
-        .map(table::row)
-        .collect(Collectors.toList());
-    }
-  }
-
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/settings/ws/SettingsFinderTest.java
new file mode 100644 (file)
index 0000000..b11d4a7
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * 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.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.PropertyFieldDefinition;
+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 static com.google.common.collect.Sets.newHashSet;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.guava.api.Assertions.assertThat;
+import static org.sonar.api.PropertyType.PROPERTY_SET;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
+import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
+
+public class SettingsFinderTest {
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  DbClient dbClient = db.getDbClient();
+  DbSession dbSession = db.getSession();
+  ComponentDbTester componentDb = new ComponentDbTester(db);
+  PropertyDefinitions propertyDefinitions = new PropertyDefinitions();
+
+  SettingsFinder underTest = new SettingsFinder(dbClient, propertyDefinitions);
+
+  @Test
+  public void return_global_settings() throws Exception {
+    PropertyDefinition definition = PropertyDefinition.builder("foo").build();
+    addDefinitions(definition);
+    insertProperties(newGlobalPropertyDto().setKey("foo").setValue("one"));
+
+    List<Setting> settings = underTest.loadGlobalSettings(dbSession, newHashSet("foo"));
+    assertThat(settings).hasSize(1);
+    assertSetting(settings.get(0), "foo", "one", null, true);
+
+    assertThat(underTest.loadGlobalSettings(dbSession, newHashSet("unknown"))).isEmpty();
+  }
+
+  @Test
+  public void return_global_setting_even_if_no_definition() throws Exception {
+    insertProperties(newGlobalPropertyDto().setKey("foo").setValue("one"));
+
+    List<Setting> settings = underTest.loadGlobalSettings(dbSession, newHashSet("foo"));
+    assertThat(settings).hasSize(1);
+    assertSetting(settings.get(0), "foo", "one", null, false);
+  }
+
+  @Test
+  public void return_global_settings_with_property_set() throws Exception {
+    addDefinitions(PropertyDefinition.builder("set1")
+      .type(PROPERTY_SET)
+      .fields(asList(
+        PropertyFieldDefinition.build("key").name("Key").build(),
+        PropertyFieldDefinition.build("size").name("Size").build()))
+      .build(),
+      PropertyDefinition.builder("another")
+        .type(PROPERTY_SET)
+        .fields(singletonList(PropertyFieldDefinition.build("key").name("Key").build()))
+        .build());
+    insertProperties(
+      newGlobalPropertyDto().setKey("set1").setValue("1,2"),
+      newGlobalPropertyDto().setKey("set1.1.key").setValue("key1"),
+      newGlobalPropertyDto().setKey("set1.1.size").setValue("size1"),
+      newGlobalPropertyDto().setKey("set1.2.key").setValue("key2"),
+      newGlobalPropertyDto().setKey("set2").setValue("1"),
+      newGlobalPropertyDto().setKey("another.1.key").setValue("key1"));
+
+    List<Setting> settings = underTest.loadGlobalSettings(dbSession, newHashSet("set1"));
+    assertThat(settings).hasSize(1);
+    assertSetting(settings.get(0), "set1", "1,2", null, true, ImmutableMap.of("key", "key1", "size", "size1"), ImmutableMap.of("key", "key2"));
+  }
+
+  @Test
+  public void return_component_settings() throws Exception {
+    ComponentDto project = componentDb.insertComponent(newProjectDto());
+    addDefinitions(PropertyDefinition.builder("property").defaultValue("default").build());
+    insertProperties(newComponentPropertyDto(project).setKey("property").setValue("one"));
+
+    Multimap<String, Setting> result = underTest.loadComponentSettings(dbSession, newHashSet("property"), project);
+    assertThat(result.values()).hasSize(1);
+    List<Setting> settings = new ArrayList<>(result.get(project.uuid()));
+    assertThat(settings).hasSize(1);
+    assertSetting(settings.get(0), "property", "one", project.getId(), true);
+
+    assertThat(underTest.loadComponentSettings(dbSession, newHashSet("unknown"), project)).isEmpty();
+  }
+
+  @Test
+  public void return_component_setting_even_if_no_definition() throws Exception {
+    ComponentDto project = componentDb.insertComponent(newProjectDto());
+    insertProperties(newComponentPropertyDto(project).setKey("property").setValue("one"));
+
+    Multimap<String, Setting> settings = underTest.loadComponentSettings(dbSession, newHashSet("property"), project);
+    assertThat(settings.values()).hasSize(1);
+    assertSetting(settings.get(project.uuid()).iterator().next(), "property", "one", project.getId(), false);
+  }
+
+  @Test
+  public void return_component_settings_with_property_set() throws Exception {
+    ComponentDto project = componentDb.insertComponent(newProjectDto());
+    addDefinitions(PropertyDefinition.builder("set1")
+      .type(PROPERTY_SET)
+      .fields(asList(
+        PropertyFieldDefinition.build("key").name("Key").build(),
+        PropertyFieldDefinition.build("size").name("Size").build()))
+      .build(),
+      PropertyDefinition.builder("another")
+        .type(PROPERTY_SET)
+        .fields(singletonList(PropertyFieldDefinition.build("key").name("Key").build()))
+        .build());
+    insertProperties(
+      newComponentPropertyDto(project).setKey("set1").setValue("1,2"),
+      newComponentPropertyDto(project).setKey("set1.1.key").setValue("key1"),
+      newComponentPropertyDto(project).setKey("set1.1.size").setValue("size1"),
+      newComponentPropertyDto(project).setKey("set1.2.key").setValue("key2"),
+      newComponentPropertyDto(project).setKey("set2").setValue("1"),
+      newComponentPropertyDto(project).setKey("another.1.key").setValue("key1"));
+
+    Multimap<String, Setting> settings = underTest.loadComponentSettings(dbSession, newHashSet("set1"), project);
+    assertThat(settings).hasSize(1);
+    assertSetting(settings.get(project.uuid()).iterator().next(), "set1", "1,2", project.getId(), true, ImmutableMap.of("key", "key1", "size", "size1"), ImmutableMap.of("key", "key2"));
+  }
+
+  @Test
+  public void return_module_settings() throws Exception {
+    ComponentDto project = componentDb.insertComponent(newProjectDto());
+    ComponentDto module = componentDb.insertComponent(newModuleDto(project));
+    ComponentDto subModule = componentDb.insertComponent(newModuleDto(module));
+    ComponentDto anotherProject = componentDb.insertComponent(newProjectDto());
+
+    insertProperties(
+      newComponentPropertyDto(project).setKey("property").setValue("one"),
+      newComponentPropertyDto(module).setKey("property").setValue("two"),
+      newComponentPropertyDto(subModule).setKey("property2").setValue("three"),
+      newComponentPropertyDto(anotherProject).setKey("property").setValue("another one"));
+
+    Multimap<String, Setting> result = underTest.loadComponentSettings(dbSession, newHashSet("property", "property2"), subModule);
+    assertThat(result).hasSize(3);
+    assertThat(result.keySet()).containsExactly(project.uuid(), module.uuid(), subModule.uuid());
+
+    assertSetting(result.get(subModule.uuid()).iterator().next(), "property2", "three", subModule.getId(), false);
+    assertSetting(result.get(module.uuid()).iterator().next(), "property", "two", module.getId(), false);
+    assertSetting(result.get(project.uuid()).iterator().next(), "property", "one", project.getId(), false);
+  }
+
+  private void assertSetting(Setting setting, String expectedKey, String expectedValue, @Nullable Long expectedComponentId, boolean hasPropertyDefinition,
+    Map<String, String>... propertySets) {
+    assertThat(setting.getKey()).isEqualTo(expectedKey);
+    assertThat(setting.getValue()).isEqualTo(expectedValue);
+    assertThat(setting.getComponentId()).isEqualTo(expectedComponentId);
+    if (hasPropertyDefinition) {
+      assertThat(setting.getDefinition()).isEqualTo(propertyDefinitions.get(expectedKey));
+    } else {
+      assertThat(setting.getDefinition()).isNull();
+    }
+    assertThat(setting.getPropertySets()).containsOnly(propertySets);
+  }
+
+  private void insertProperties(PropertyDto... properties) {
+    for (PropertyDto propertyDto : properties) {
+      dbClient.propertiesDao().insertProperty(dbSession, propertyDto);
+    }
+    dbSession.commit();
+  }
+
+  private void addDefinitions(PropertyDefinition... definitions) {
+    propertyDefinitions.addComponents(asList(definitions));
+  }
+}
index 3b9185d8b5570c7df1c96b82d25e2a127c098794..ff8ee47d2f35a24b01a9ab552cf21ca49311a121 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(5 + 2);
+    assertThat(container.size()).isEqualTo(6 + 2);
   }
 }
index 65b220877374812717b4514586bae1f376250bbd..ee060b1d13870c142b9abcd4b7e69ccde86c866a 100644 (file)
@@ -56,6 +56,7 @@ 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.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
@@ -79,10 +80,11 @@ public class ValuesActionTest {
   ComponentDbTester componentDb = new ComponentDbTester(db);
   SettingsWsComponentParameters settingsWsComponentParameters = new SettingsWsComponentParameters(new ComponentFinder(dbClient), userSession);
   PropertyDefinitions propertyDefinitions = new PropertyDefinitions();
+  SettingsFinder settingsFinder = new SettingsFinder(dbClient, propertyDefinitions);
 
   ComponentDto project;
 
-  WsActionTester ws = new WsActionTester(new ValuesAction(dbClient, settingsWsComponentParameters, propertyDefinitions));
+  WsActionTester ws = new WsActionTester(new ValuesAction(dbClient, settingsWsComponentParameters, propertyDefinitions, settingsFinder));
 
   @Before
   public void setUp() throws Exception {
@@ -208,12 +210,7 @@ public class ValuesActionTest {
 
     ValuesWsResponse result = newRequestForGlobalProperties("foo");
     assertThat(result.getSettingsList()).hasSize(1);
-
-    Settings.Setting value = result.getSettings(0);
-    assertThat(value.getKey()).isEqualTo("foo");
-    assertThat(value.getValue()).isEqualTo("default");
-    assertThat(value.getDefault()).isTrue();
-    assertThat(value.getInherited()).isFalse();
+    assertSetting(result.getSettings(0), "foo", "default", true, false);
   }
 
   @Test
@@ -226,12 +223,7 @@ public class ValuesActionTest {
 
     ValuesWsResponse result = newRequestForGlobalProperties("property");
     assertThat(result.getSettingsList()).hasSize(1);
-
-    Settings.Setting globalPropertyValue = result.getSettings(0);
-    assertThat(globalPropertyValue.getKey()).isEqualTo("property");
-    assertThat(globalPropertyValue.getValue()).isEqualTo("one");
-    assertThat(globalPropertyValue.getDefault()).isFalse();
-    assertThat(globalPropertyValue.getInherited()).isFalse();
+    assertSetting(result.getSettings(0), "property", "one", false, false);
   }
 
   @Test
@@ -245,12 +237,7 @@ public class ValuesActionTest {
 
     ValuesWsResponse result = newRequestForProjectProperties("property");
     assertThat(result.getSettingsList()).hasSize(1);
-
-    Settings.Setting globalPropertyValue = result.getSettings(0);
-    assertThat(globalPropertyValue.getKey()).isEqualTo("property");
-    assertThat(globalPropertyValue.getValue()).isEqualTo("two");
-    assertThat(globalPropertyValue.getDefault()).isFalse();
-    assertThat(globalPropertyValue.getInherited()).isFalse();
+    assertSetting(result.getSettings(0), "property", "two", false, false);
   }
 
   @Test
@@ -262,12 +249,7 @@ public class ValuesActionTest {
 
     ValuesWsResponse result = newRequestForProjectProperties("property");
     assertThat(result.getSettingsList()).hasSize(1);
-
-    Settings.Setting globalPropertyValue = result.getSettings(0);
-    assertThat(globalPropertyValue.getKey()).isEqualTo("property");
-    assertThat(globalPropertyValue.getValue()).isEqualTo("one");
-    assertThat(globalPropertyValue.getDefault()).isFalse();
-    assertThat(globalPropertyValue.getInherited()).isTrue();
+    assertSetting(result.getSettings(0), "property", "one", false, true);
   }
 
   @Test
@@ -306,6 +288,46 @@ public class ValuesActionTest {
     assertThat(result.getSettingsList()).isEmpty();
   }
 
+  @Test
+  public void return_module_values() throws Exception {
+    setUserAsSystemAdmin();
+    ComponentDto module = componentDb.insertComponent(newModuleDto(project));
+
+    propertyDefinitions.addComponent(PropertyDefinition.builder("property").defaultValue("default").build());
+    insertProperties(
+      newGlobalPropertyDto().setKey("property").setValue("one"),
+      // The property is overriding global value
+      newComponentPropertyDto(module).setKey("property").setValue("two"));
+
+    ValuesWsResponse result = newRequestForComponentProperties(module, "property");
+    assertThat(result.getSettingsList()).hasSize(1);
+    assertSetting(result.getSettings(0), "property", "two", false, false);
+  }
+
+  @Test
+  public void return_inherited_values_on_sub_module() throws Exception {
+    setUserAsSystemAdmin();
+    ComponentDto module = componentDb.insertComponent(newModuleDto(project));
+    ComponentDto subModule = componentDb.insertComponent(newModuleDto(module));
+
+    propertyDefinitions.addComponents(asList(
+      PropertyDefinition.builder("defaultProperty").defaultValue("default").build(),
+      PropertyDefinition.builder("globalProperty").build(),
+      PropertyDefinition.builder("projectProperty").build(),
+      PropertyDefinition.builder("moduleProperty").build()));
+    insertProperties(
+      newGlobalPropertyDto().setKey("globalProperty").setValue("global"),
+      newComponentPropertyDto(project).setKey("projectProperty").setValue("project"),
+      newComponentPropertyDto(module).setKey("moduleProperty").setValue("module"));
+
+    ValuesWsResponse result = newRequestForComponentProperties(subModule, "defaultProperty", "globalProperty", "projectProperty", "moduleProperty");
+    assertThat(result.getSettingsList()).hasSize(4);
+    assertSetting(result.getSettings(0), "defaultProperty", "default", true, false);
+    assertSetting(result.getSettings(1), "globalProperty", "global", false, true);
+    assertSetting(result.getSettings(2), "projectProperty", "project", false, true);
+    assertSetting(result.getSettings(3), "moduleProperty", "module", false, true);
+  }
+
   @Test
   public void test_example_json_response() {
     setUserAsSystemAdmin();
@@ -376,6 +398,10 @@ public class ValuesActionTest {
     assertThat(action.params()).hasSize(3);
   }
 
+  private ValuesWsResponse newRequestForComponentProperties(ComponentDto componentDto, String... keys) {
+    return newRequest(componentDto.uuid(), null, keys);
+  }
+
   private ValuesWsResponse newRequestForProjectProperties(String... keys) {
     return newRequest(project.uuid(), null, keys);
   }
@@ -416,4 +442,11 @@ public class ValuesActionTest {
     dbSession.commit();
   }
 
+  private void assertSetting(Settings.Setting setting, String expectedKey, String expectedValue, boolean expectedDefault, boolean expectedInherited) {
+    assertThat(setting.getKey()).isEqualTo(expectedKey);
+    assertThat(setting.getValue()).isEqualTo(expectedValue);
+    assertThat(setting.getDefault()).isEqualTo(expectedDefault);
+    assertThat(setting.getInherited()).isEqualTo(expectedInherited);
+  }
+
 }
index 140d7edf142f5e2f8964598a87340ab2f46f8ec9..38683018c9107783c1aee6d9354c1d609da6f331 100644 (file)
@@ -168,12 +168,17 @@ public class PropertiesDao implements Dao {
     return selectByKeys(session, keys, null);
   }
 
-  public List<PropertyDto> selectComponentPropertiesByKeys(DbSession session, Set<String> keys, long componentId) {
+  public List<PropertyDto> selectPropertiesByKeysAndComponentId(DbSession session, Set<String> keys, long componentId) {
     return selectByKeys(session, keys, componentId);
   }
 
+  public List<PropertyDto> selectPropertiesByKeysAndComponentIds(DbSession session, Set<String> keys, Set<Long> componentIds) {
+    return executeLargeInputs(keys, partitionKeys -> executeLargeInputs(componentIds,
+      partitionComponentIds -> session.getMapper(PropertiesMapper.class).selectByKeysAndComponentIds(partitionKeys, partitionComponentIds)));
+  }
+
   private List<PropertyDto> selectByKeys(DbSession session, Set<String> keys, @Nullable Long componentId) {
-    return executeLargeInputs(keys, propertyKeys -> session.getMapper(PropertiesMapper.class).selectByKeys(propertyKeys, componentId));
+    return executeLargeInputs(keys, partitionKeys -> session.getMapper(PropertiesMapper.class).selectByKeys(partitionKeys, componentId));
   }
 
   public void insertProperty(DbSession session, PropertyDto property) {
index e8ef34a84da10be36ec4bdea0330c6c482dc71af..781c1ecb79e3dd55541367d5152268946a03b530 100644 (file)
@@ -41,6 +41,8 @@ public interface PropertiesMapper {
 
   List<PropertyDto> selectByKeys(@Param("keys") List<String> keys, @Nullable @Param("componentId") Long componentId);
 
+  List<PropertyDto> selectByKeysAndComponentIds(@Param("keys") List<String> keys, @Param("componentIds") List<Long> componentIds);
+
   List<PropertyDto> selectByQuery(@Param("query") PropertyQuery query);
 
   List<PropertyDto> selectDescendantModuleProperties(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,
index a64c3756236ca89a68c76b160e98490fcb4a59f4..6c8f3d3336a111e3449b98cf99b86661ed27c867 100644 (file)
     </where>
   </select>
 
+  <select id="selectByKeysAndComponentIds" 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>
+      AND p.resource_id in
+      <foreach collection="componentIds" open="(" close=")" item="componentId" separator=",">
+        #{componentId}
+      </foreach>
+      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 60fcf5edcb191046a92bf9cfed763c76884b0065..e4c3b695dfe0b538f1a321246374b31207e6214f 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.property;
 
 import com.google.common.collect.ImmutableMap;
 import java.util.List;
+import org.assertj.core.groups.Tuple;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -242,12 +243,48 @@ public class PropertiesDaoTest {
       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.selectPropertiesByKeysAndComponentId(session, newHashSet(key), project.getId())).extracting("key").containsOnly(key);
+    assertThat(dao.selectPropertiesByKeysAndComponentId(session, newHashSet(key, anotherKey), project.getId())).extracting("key").containsOnly(key, anotherKey);
+    assertThat(dao.selectPropertiesByKeysAndComponentId(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();
+    assertThat(dao.selectPropertiesByKeysAndComponentId(session, newHashSet("unknown"), project.getId())).isEmpty();
+    assertThat(dao.selectPropertiesByKeysAndComponentId(session, newHashSet(key), 123456789L)).isEmpty();
+  }
+
+  @Test
+  public void select_properties_by_keys_and_component_ids() throws Exception {
+    ComponentDto project = ComponentTesting.newProjectDto();
+    dbClient.componentDao().insert(session, project);
+    ComponentDto project2 = ComponentTesting.newProjectDto();
+    dbClient.componentDao().insert(session, project2);
+
+    UserDto user = UserTesting.newUserDto();
+    dbClient.userDao().insert(session, user);
+
+    String key = "key";
+    String anotherKey = "anotherKey";
+    insertProperties(
+      newGlobalPropertyDto().setKey(key),
+      newComponentPropertyDto(project).setKey(key),
+      newComponentPropertyDto(project2).setKey(key),
+      newComponentPropertyDto(project2).setKey(anotherKey),
+      newUserPropertyDto(user).setKey(key));
+
+    assertThat(dao.selectPropertiesByKeysAndComponentIds(session, newHashSet(key), newHashSet(project.getId())))
+      .extracting("key", "resourceId").containsOnly(Tuple.tuple(key, project.getId()));
+    assertThat(dao.selectPropertiesByKeysAndComponentIds(session, newHashSet(key), newHashSet(project.getId(), project2.getId())))
+      .extracting("key", "resourceId").containsOnly(
+        Tuple.tuple(key, project.getId()),
+        Tuple.tuple(key, project2.getId()));
+    assertThat(dao.selectPropertiesByKeysAndComponentIds(session, newHashSet(key, anotherKey), newHashSet(project.getId(), project2.getId())))
+      .extracting("key", "resourceId").containsOnly(
+        Tuple.tuple(key, project.getId()),
+        Tuple.tuple(key, project2.getId()),
+        Tuple.tuple(anotherKey, project2.getId()));
+
+    assertThat(dao.selectPropertiesByKeysAndComponentIds(session, newHashSet("unknown"), newHashSet(project.getId()))).isEmpty();
+    assertThat(dao.selectPropertiesByKeysAndComponentIds(session, newHashSet("key"), newHashSet(123456789L))).isEmpty();
+    assertThat(dao.selectPropertiesByKeysAndComponentIds(session, newHashSet("unknown"), newHashSet(123456789L))).isEmpty();
   }
 
   @Test