]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9283 Settings, Configuration and Props trim values at insert 2427/head
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 24 Aug 2017 14:56:40 +0000 (16:56 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 19 Sep 2017 08:34:53 +0000 (10:34 +0200)
16 files changed:
server/sonar-process/src/main/java/org/sonar/process/Props.java
server/sonar-process/src/test/java/org/sonar/process/PropsTest.java
server/sonar-process/src/test/java/org/sonar/process/jmvoptions/JvmOptionsTest.java
server/sonar-server/src/main/java/org/sonar/ce/settings/ProjectSettings.java
server/sonar-server/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java
sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinitions.java
sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java
sonar-plugin-api/src/main/java/org/sonar/api/config/internal/MapSettings.java
sonar-plugin-api/src/test/java/org/sonar/api/config/ConfigurationTest.java
sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionsTest.java
sonar-plugin-api/src/test/java/org/sonar/api/config/internal/MapSettingsTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalConfiguration.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/MutableGlobalSettings.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/config/DefaultConfiguration.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableModuleSettings.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableProjectSettings.java

index 53cb323b365eec7113e3715cd587ed11c6f588e6..5869f9877e1012aae41bad0cc1aea67e2ac76a78 100644 (file)
@@ -31,7 +31,8 @@ public class Props {
   private final Encryption encryption;
 
   public Props(Properties props) {
-    this.properties = props;
+    this.properties = new Properties();
+    props.forEach((k, v) -> this.properties.put(k.toString().trim(), v == null ? null : v.toString().trim()));
     this.encryption = new Encryption(props.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH));
   }
 
@@ -41,7 +42,7 @@ public class Props {
 
   @CheckForNull
   public String value(String key) {
-    String value = valueImpl(key);
+    String value = properties.getProperty(key);
     if (value != null && encryption.isEncrypted(value)) {
       value = encryption.decrypt(value);
     }
@@ -110,18 +111,10 @@ public class Props {
   }
 
   public void setDefault(String key, String value) {
-    String s = valueImpl(key);
+    String s = properties.getProperty(key);
     if (StringUtils.isBlank(s)) {
       properties.setProperty(key, value);
     }
   }
 
-  @CheckForNull
-  private String valueImpl(String key) {
-    String value = properties.getProperty(key);
-    if (value == null) {
-      return null;
-    }
-    return value.trim();
-  }
 }
index 0a6d4311dba33440fc7ea495925261b6655a2c26..1f6d2412b6114100056e856345f97b40a8944178 100644 (file)
@@ -25,6 +25,7 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.io.File;
 import java.io.IOException;
 import java.util.Properties;
+import org.apache.commons.lang.RandomStringUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -42,11 +43,27 @@ public class PropsTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
+  @Test
+  @UseDataProvider("beforeAndAfterBlanks")
+  public void constructor_trims_key_and_values_from_Properties_argument(String blankBefore, String blankAfter) {
+    Properties properties = new Properties();
+    String key = RandomStringUtils.randomAlphanumeric(3);
+    String value = RandomStringUtils.randomAlphanumeric(3);
+    properties.put(blankBefore + key + blankAfter, blankBefore + value + blankAfter);
+
+    Props underTest = new Props(properties);
+
+    if (!blankBefore.isEmpty() || !blankAfter.isEmpty()) {
+      assertThat(underTest.contains(blankBefore + key + blankAfter)).isFalse();
+    }
+    assertThat(underTest.value(key)).isEqualTo(value);
+  }
+
   @Test
   @UseDataProvider("beforeAndAfterBlanks")
   public void value(String blankBefore, String blankAfter) {
     Properties p = new Properties();
-    p.setProperty("foo", blankBefore + "bar" + blankAfter);
+    p.setProperty(blankBefore + "foo" + blankAfter, blankBefore + "bar" + blankAfter);
     p.setProperty("blank", blankBefore + blankAfter);
     Props props = new Props(p);
 
index 84d54ed339e0954de9deb8bbf3db36c84f22bd42..35de8ef0b8cb34fb591b360b800d6cebe9d2b880 100644 (file)
@@ -56,7 +56,6 @@ public class JvmOptionsTest {
   private final String randomPrefix = "-" + randomAlphabetic(5).toLowerCase(Locale.ENGLISH);
   private final String randomValue = randomAlphanumeric(4).toLowerCase(Locale.ENGLISH);
   private final Properties properties = new Properties();
-  private final Props props = new Props(properties);
   private final JvmOptions underTest = new JvmOptions();
 
   @Test
@@ -230,14 +229,14 @@ public class JvmOptionsTest {
   public void addFromMandatoryProperty_fails_with_IAE_if_property_does_not_exist() {
     expectMissingPropertyIAE(this.randomPropertyName);
 
-    underTest.addFromMandatoryProperty(props, this.randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), this.randomPropertyName);
   }
 
   @Test
   public void addFromMandatoryProperty_fails_with_IAE_if_property_contains_an_empty_value() {
     expectMissingPropertyIAE(this.randomPropertyName);
 
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
   }
 
   @Test
@@ -245,7 +244,7 @@ public class JvmOptionsTest {
   public void addFromMandatoryProperty_adds_single_option_of_property_with_trimming(String emptyString) {
     properties.put(randomPropertyName, emptyString + "-foo" + emptyString);
 
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
 
     assertThat(underTest.getAll()).containsOnly("-foo");
   }
@@ -257,7 +256,7 @@ public class JvmOptionsTest {
 
     expectJvmOptionNotEmptyAndStartByDashMessageException(randomPropertyName, "foo");
 
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
   }
 
   @Test
@@ -265,7 +264,7 @@ public class JvmOptionsTest {
   public void addFromMandatoryProperty_adds_options_of_property_with_trimming(String emptyString) {
     properties.put(randomPropertyName, emptyString + "-foo" + emptyString + " -bar" + emptyString + " -duck" + emptyString);
 
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
 
     assertThat(underTest.getAll()).containsOnly("-foo", "-bar", "-duck");
   }
@@ -274,7 +273,7 @@ public class JvmOptionsTest {
   public void addFromMandatoryProperty_supports_spaces_inside_options() {
     properties.put(randomPropertyName, "-foo bar -duck");
 
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
 
     assertThat(underTest.getAll()).containsOnly("-foo bar", "-duck");
   }
@@ -298,7 +297,7 @@ public class JvmOptionsTest {
     for (String optionOverride : optionOverrides) {
       try {
         properties.put(randomPropertyName, optionOverride);
-        underTest.addFromMandatoryProperty(props, randomPropertyName);
+        underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
         fail("an MessageException should have been thrown");
       } catch (MessageException e) {
         assertThat(e.getMessage())
@@ -326,7 +325,7 @@ public class JvmOptionsTest {
 
     for (String optionOverride : optionOverrides) {
       properties.setProperty(randomPropertyName, optionOverride.toUpperCase(Locale.ENGLISH));
-      underTest.addFromMandatoryProperty(props, randomPropertyName);
+      underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
     }
   }
 
@@ -343,7 +342,7 @@ public class JvmOptionsTest {
       "The following JVM options defined by property '" + randomPropertyName + "' are invalid: " +
       overriding1 + " overwrites " + randomPrefix + randomValue + ", " + overriding2 + " overwrites " + randomPrefix + randomValue);
 
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
   }
 
   @Test
@@ -351,7 +350,7 @@ public class JvmOptionsTest {
     JvmOptions underTest = new JvmOptions(ImmutableMap.of(randomPrefix, randomValue));
 
     properties.put(randomPropertyName, randomPrefix + randomValue);
-    underTest.addFromMandatoryProperty(props, randomPropertyName);
+    underTest.addFromMandatoryProperty(new Props(properties), randomPropertyName);
 
     assertThat(underTest.getAll()).containsOnly(randomPrefix + randomValue);
   }
index 38748c6f3eaaf34a23f8d5e0331985ce75c4e985..560de13d496f95d855893cf4ba349de6e90385d4 100644 (file)
@@ -25,6 +25,8 @@ import java.util.Optional;
 import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.config.Settings;
 
+import static java.util.Objects.requireNonNull;
+
 @ComputeEngineSide
 public class ProjectSettings extends Settings {
 
@@ -47,7 +49,9 @@ public class ProjectSettings extends Settings {
 
   @Override
   protected void set(String key, String value) {
-    projectProps.put(key, value);
+    projectProps.put(
+      requireNonNull(key, "key can't be null"),
+      requireNonNull(value, "value can't be null").trim());
   }
 
   @Override
index de0a702bd95cb129ad725e49f1214a9894130be9..c2992be406e68eb67f874e5ab447bcaeeeedc3d6 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.api.config.Settings;
 import org.sonar.api.server.ServerSide;
 
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
 
 /**
  * Merge of {@link SystemSettings} and the global properties stored in the db table "properties". These
@@ -65,7 +66,8 @@ public class ThreadLocalSettings extends Settings {
   ThreadLocalSettings(PropertyDefinitions definitions, Properties props, SettingLoader settingLoader) {
     super(definitions, new Encryption(null));
     this.settingLoader = settingLoader;
-    this.systemProps = props;
+    this.systemProps = new Properties();
+    props.forEach((k, v) -> systemProps.put(k, v == null ? null : v.toString().trim()));
 
     // TODO something wrong about lifecycle here. It could be improved
     getEncryption().setPathToSecretKey(props.getProperty(CoreProperties.ENCRYPTION_SECRET_KEY_PATH));
@@ -114,9 +116,11 @@ public class ThreadLocalSettings extends Settings {
 
   @Override
   protected void set(String key, String value) {
+    requireNonNull(key, "key can't be null");
+    requireNonNull(value, "value can't be null");
     Map<String, String> dbProps = CACHE.get();
     if (dbProps != null) {
-      dbProps.put(key, value);
+      dbProps.put(key, value.trim());
     }
   }
 
index b0e5275674fd8c1cc4c084f84995278e7c61b6cf..4ba15a7f239d3a90a5ac2ff706c94a1faf85ae28 100644 (file)
@@ -35,6 +35,8 @@ import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.AnnotationUtils;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Metadata of all the properties declared by plugins
  *
@@ -127,7 +129,9 @@ public final class PropertyDefinitions {
   }
 
   public String validKey(String key) {
-    return StringUtils.defaultString(deprecatedKeys.get(key), key);
+    requireNonNull(key, "key can't be null");
+    String trimmedKey = key.trim();
+    return StringUtils.defaultString(deprecatedKeys.get(trimmedKey), trimmedKey);
   }
 
   /**
index 20fb15bff452bd1a323896697360c906527d3085..05cbf19a7269c3a7f2d5591f221e05c40bccbbe9 100644 (file)
@@ -60,6 +60,11 @@ public abstract class Settings {
 
   protected abstract Optional<String> get(String key);
 
+  /**
+   * Add the settings with the specified key and value, both are trimmed and neither can be null.
+   *
+   * @throws NullPointerException if {@code key} and/or {@code value} is {@code null}.
+   */
   protected abstract void set(String key, String value);
 
   protected abstract void remove(String key);
@@ -265,7 +270,8 @@ public abstract class Settings {
    * </ul>
    */
   public String[] getStringArray(String key) {
-    Optional<PropertyDefinition> def = getDefinition(key);
+    String effectiveKey = definitions.validKey(key);
+    Optional<PropertyDefinition> def = getDefinition(effectiveKey);
     if ((def.isPresent()) && (def.get().multiValues())) {
       String value = getString(key);
       if (value == null) {
@@ -324,7 +330,9 @@ public abstract class Settings {
   }
 
   public Settings setProperty(String key, @Nullable String[] values) {
-    Optional<PropertyDefinition> def = getDefinition(key);
+    requireNonNull(key, "key can't be null");
+    String effectiveKey = key.trim();
+    Optional<PropertyDefinition> def = getDefinition(effectiveKey);
     if (!def.isPresent() || (!def.get().multiValues())) {
       throw new IllegalStateException("Fail to set multiple values on a single value property " + key);
     }
@@ -361,7 +369,6 @@ public abstract class Settings {
     String validKey = definitions.validKey(key);
     if (value == null) {
       removeProperty(validKey);
-
     } else {
       set(validKey, trim(value));
     }
index 9a58e9eb6a06850fb1092ceb138a2678756f7181..6990165fc3cbfba56c78ab629982d73b3c501c35 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.config.Settings;
 
 import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
 
 /**
  * In-memory map-based implementation of {@link Settings}. It must be used
@@ -58,7 +59,9 @@ public class MapSettings extends Settings {
 
   @Override
   protected void set(String key, String value) {
-    props.put(key, value);
+    props.put(
+      requireNonNull(key, "key can't be null"),
+      requireNonNull(value, "value can't be null").trim());
   }
 
   @Override
index 1f712844412e2cac45c03516d9edfb000bf5f4c6..6950f8dd5fcccab1185ee261e206bd3d1f4db834 100644 (file)
@@ -82,7 +82,7 @@ public class ConfigurationTest {
     private final Map<String, String> keyValues = new HashMap<>();
 
     public Configuration put(String key, String value) {
-      keyValues.put(key, value);
+      keyValues.put(key, value.trim());
       return this;
     }
 
index 649ace7b7221b34d3cf857190647c2d2e47f9e7a..b0d01e89132c95446e3834f411bc7cd693a5cd24 100644 (file)
@@ -21,22 +21,29 @@ package org.sonar.api.config;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
 import org.sonar.api.resources.Qualifiers;
 
+import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class PropertyDefinitionsTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
 
   @Test
   public void should_build_with_predefined_list_of_definitions() {
     List<PropertyDefinition> list = Arrays.asList(
       PropertyDefinition.builder("foo").name("Foo").build(),
       PropertyDefinition.builder("one").name("One").build(),
-      PropertyDefinition.builder("two").name("Two").defaultValue("2").build()
-    );
+      PropertyDefinition.builder("two").name("Two").defaultValue("2").build());
     PropertyDefinitions def = new PropertyDefinitions(list);
 
     assertProperties(def);
@@ -47,8 +54,7 @@ public class PropertyDefinitionsTest {
     PropertyDefinitions def = new PropertyDefinitions(
       PropertyDefinition.builder("foo").name("Foo").build(),
       PropertyDefinition.builder("one").name("One").build(),
-      PropertyDefinition.builder("two").name("Two").defaultValue("2").build()
-    );
+      PropertyDefinition.builder("two").name("Two").defaultValue("2").build());
 
     assertProperties(def);
   }
@@ -71,8 +77,7 @@ public class PropertyDefinitionsTest {
   public void test_categories() {
     PropertyDefinitions def = new PropertyDefinitions(
       PropertyDefinition.builder("inCateg").name("In Categ").category("categ").build(),
-      PropertyDefinition.builder("noCateg").name("No categ").build()
-    );
+      PropertyDefinition.builder("noCateg").name("No categ").build());
 
     assertThat(def.getCategory("inCateg")).isEqualTo("categ");
     assertThat(def.getCategory("noCateg")).isEmpty();
@@ -125,8 +130,7 @@ public class PropertyDefinitionsTest {
       PropertyDefinition.builder("project").name("Project").category("catProject").onlyOnQualifiers(Qualifiers.PROJECT).build(),
       PropertyDefinition.builder("module").name("Module").category("catModule").onlyOnQualifiers(Qualifiers.MODULE).build(),
       PropertyDefinition.builder("view").name("View").category("catView").onlyOnQualifiers(Qualifiers.VIEW).build(),
-      PropertyDefinition.builder("app").name("Application").category("catApp").onlyOnQualifiers(Qualifiers.APP).build()
-    );
+      PropertyDefinition.builder("app").name("Application").category("catApp").onlyOnQualifiers(Qualifiers.APP).build());
 
     assertThat(def.propertiesByCategory(null).keySet()).contains(new Category("catGlobal1"), new Category("catGlobal2"));
     assertThat(def.propertiesByCategory(Qualifiers.PROJECT).keySet()).containsOnly(new Category("catProject"));
@@ -142,8 +146,7 @@ public class PropertyDefinitionsTest {
       PropertyDefinition.builder("global1").name("Global1").category("catGlobal1").subCategory("sub1").build(),
       PropertyDefinition.builder("global2").name("Global2").category("catGlobal1").subCategory("sub2").build(),
       PropertyDefinition.builder("global3").name("Global3").category("catGlobal1").build(),
-      PropertyDefinition.builder("global4").name("Global4").category("catGlobal2").build()
-    );
+      PropertyDefinition.builder("global4").name("Global4").category("catGlobal2").build());
 
     assertThat(def.propertiesByCategory(null).get(new Category("catGlobal1")).keySet()).containsOnly(new SubCategory("catGlobal1"), new SubCategory("sub1"),
       new SubCategory("sub2"));
@@ -171,6 +174,48 @@ public class PropertyDefinitionsTest {
     assertThat(definitions.getAll().size()).isEqualTo(3);
   }
 
+  @Test
+  public void validKey_throws_NPE_if_key_is_null() {
+    PropertyDefinitions underTest = new PropertyDefinitions();
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("key can't be null");
+
+    underTest.validKey(null);
+  }
+
+  @Test
+  public void get_throws_NPE_if_key_is_null() {
+    PropertyDefinitions underTest = new PropertyDefinitions();
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("key can't be null");
+
+    underTest.get(null);
+  }
+
+  @Test
+  public void get_trims_key_before_looking_for_replacement() {
+    Random random = new Random();
+    String key = RandomStringUtils.randomAlphanumeric(4);
+    String deprecatedKey = RandomStringUtils.randomAlphanumeric(4);
+    PropertyDefinitions underTest = new PropertyDefinitions(singletonList(
+      PropertyDefinition.builder(key)
+        .deprecatedKey(deprecatedKey)
+        .build()));
+
+    String untrimmedKey = blank(random) + deprecatedKey + blank(random);
+    assertThat(underTest.get(untrimmedKey).key())
+        .describedAs("expecting key %s being returned for get(%s)", key, untrimmedKey)
+        .isEqualTo(key);
+  }
+
+  private static String blank(Random random) {
+    StringBuilder b = new StringBuilder();
+    IntStream.range(0, random.nextInt(3)).mapToObj(s -> " ").forEach(b::append);
+    return b.toString();
+  }
+
   @Property(key = "foo", name = "Foo")
   static final class PluginWithProperty {
   }
index e28a73cdb9c38790f8fb80d6554c6b9a4cd57d70..48e6a67aece4cc133a9571ee96ede85afab06212 100644 (file)
  */
 package org.sonar.api.config.internal;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
 import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.function.BiConsumer;
+import java.util.stream.IntStream;
 import org.assertj.core.data.Offset;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
 import org.sonar.api.PropertyType;
+import org.sonar.api.config.PropertyDefinition;
 import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.config.Settings;
-import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.utils.DateUtils;
 
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 
+@RunWith(DataProviderRunner.class)
 public class MapSettingsTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
 
   private PropertyDefinitions definitions;
 
@@ -65,12 +79,133 @@ public class MapSettingsTest {
     definitions.addComponent(Init.class);
   }
 
+  @Test
+  public void set_throws_NPE_if_key_is_null() {
+    MapSettings underTest = new MapSettings();
+
+    expectKeyNullNPE();
+
+    underTest.set(null, randomAlphanumeric(3));
+  }
+
+  @Test
+  public void set_throws_NPE_if_value_is_null() {
+    MapSettings underTest = new MapSettings();
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("value can't be null");
+
+    underTest.set(randomAlphanumeric(3), null);
+  }
+
+  @Test
+  public void set_accepts_empty_value_and_trims_it() {
+    MapSettings underTest = new MapSettings();
+    Random random = new Random();
+    String key = randomAlphanumeric(3);
+
+    underTest.set(key, blank(random));
+
+    assertThat(underTest.getString(key)).isEmpty();
+  }
+
   @Test
   public void default_values_should_be_loaded_from_definitions() {
     Settings settings = new MapSettings(definitions);
     assertThat(settings.getDefaultValue("hello")).isEqualTo("world");
   }
 
+  @Test
+  @UseDataProvider("setPropertyCalls")
+  public void all_setProperty_methods_throws_NPE_if_key_is_null(BiConsumer<Settings, String> setPropertyCaller) {
+    Settings settings = new MapSettings();
+
+    expectKeyNullNPE();
+
+    setPropertyCaller.accept(settings, null);
+  }
+
+  @Test
+  public void set_property_string_throws_NPE_if_key_is_null() {
+    String key = randomAlphanumeric(3);
+
+    Settings underTest = new MapSettings(new PropertyDefinitions(singletonList(PropertyDefinition.builder(key).multiValues(true).build())));
+
+    expectKeyNullNPE();
+
+    underTest.setProperty(null, new String[] {"1", "2"});
+  }
+
+  private void expectKeyNullNPE() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("key can't be null");
+  }
+
+  @Test
+  @UseDataProvider("setPropertyCalls")
+  public void all_set_property_methods_trims_key(BiConsumer<Settings, String> setPropertyCaller) {
+    Settings underTest = new MapSettings();
+
+    Random random = new Random();
+    String blankBefore = blank(random);
+    String blankAfter = blank(random);
+    String key = randomAlphanumeric(3);
+
+    setPropertyCaller.accept(underTest, blankBefore + key + blankAfter);
+
+    assertThat(underTest.hasKey(key)).isTrue();
+  }
+
+  @Test
+  public void set_property_string_array_trims_key() {
+    String key = randomAlphanumeric(3);
+
+    Settings underTest = new MapSettings(new PropertyDefinitions(singletonList(PropertyDefinition.builder(key).multiValues(true).build())));
+
+    Random random = new Random();
+    String blankBefore = blank(random);
+    String blankAfter = blank(random);
+
+    underTest.setProperty(blankBefore + key + blankAfter, new String[] {"1", "2"});
+
+    assertThat(underTest.hasKey(key)).isTrue();
+  }
+
+  private static String blank(Random random) {
+    StringBuilder b = new StringBuilder();
+    IntStream.range(0, random.nextInt(3)).mapToObj(s -> " ").forEach(b::append);
+    return b.toString();
+  }
+
+  @DataProvider
+  public static Object[][] setPropertyCalls() {
+    List<BiConsumer<Settings, String>> callers = Arrays.asList(
+      (settings, key) -> settings.setProperty(key, 123),
+      (settings, key) -> settings.setProperty(key, 123L),
+      (settings, key) -> settings.setProperty(key, 123.2F),
+      (settings, key) -> settings.setProperty(key, 123.2D),
+      (settings, key) -> settings.setProperty(key, false),
+      (settings, key) -> settings.setProperty(key, new Date()),
+      (settings, key) -> settings.setProperty(key, new Date(), true));
+
+    return callers.stream().map(t -> new Object[] {t}).toArray(Object[][]::new);
+  }
+
+  @Test
+  public void setProperty_methods_trims_value() {
+    Settings underTest = new MapSettings();
+
+    Random random = new Random();
+    String blankBefore = blank(random);
+    String blankAfter = blank(random);
+    String key = randomAlphanumeric(3);
+    String value = randomAlphanumeric(3);
+
+    underTest.setProperty(key, blankBefore + value + blankAfter);
+
+    assertThat(underTest.getString(key)).isEqualTo(value);
+  }
+
   @Test
   public void set_property_int() {
     Settings settings = new MapSettings();
index b41cf7d4f0b7bc99477497d6f917c86135bdb37f..f22c14f4f99a1c35e00b6311de200cb516f8d74e 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.scanner.bootstrap;
 
 import com.google.common.collect.ImmutableMap;
-import java.util.Collections;
 import java.util.Map;
 import javax.annotation.concurrent.Immutable;
 import org.slf4j.Logger;
@@ -50,14 +49,14 @@ public class GlobalConfiguration extends DefaultConfiguration {
   public GlobalConfiguration(PropertyDefinitions propertyDefinitions, Encryption encryption, GlobalAnalysisMode mode,
     Map<String, String> settings, Map<String, String> serverSideSettings) {
     super(propertyDefinitions, encryption, mode, settings);
-    this.serverSideSettings = serverSideSettings;
+    this.serverSideSettings = unmodifiableMapWithTrimmedValues(propertyDefinitions, serverSideSettings);
 
     get(CoreProperties.PERMANENT_SERVER_ID).ifPresent(v -> LOG.info("Server id: {}", v));
     new DroppedPropertyChecker(getProperties(), DROPPED_PROPERTIES).checkDroppedProperties();
   }
 
   public Map<String, String> getServerSideSettings() {
-    return Collections.unmodifiableMap(serverSideSettings);
+    return serverSideSettings;
   }
 
 }
index b3355a89228cadb5273de6602900d9549c3328f0..6378bd3319eff9a6f9bdd9ca3441da7aa5bf2f33 100644 (file)
@@ -25,6 +25,8 @@ import java.util.Optional;
 import org.sonar.api.config.Settings;
 import org.sonar.api.utils.MessageException;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * @deprecated since 6.5 {@link GlobalConfiguration} used to be mutable, so keep a mutable copy for backward compatibility.
  */
@@ -56,7 +58,9 @@ public class MutableGlobalSettings extends Settings {
 
   @Override
   protected void set(String key, String value) {
-    mutableProperties.put(key, value);
+    mutableProperties.put(
+      requireNonNull(key, "key can't be null"),
+      requireNonNull(value, "value can't be null").trim());
   }
 
   @Override
index 3a5e18f2e3fb049506d6d708daeb6c6b4763c3a9..710b02bbee179a86a37e1be01d57c408c5186b5b 100644 (file)
@@ -53,16 +53,22 @@ public abstract class DefaultConfiguration implements Configuration {
   private final PropertyDefinitions definitions;
   private final Encryption encryption;
   private final GlobalAnalysisMode mode;
-  private final Map<String, String> properties = new HashMap<>();
+  private final Map<String, String> properties;
 
   public DefaultConfiguration(PropertyDefinitions propertyDefinitions, Encryption encryption, GlobalAnalysisMode mode, Map<String, String> props) {
     this.definitions = requireNonNull(propertyDefinitions);
     this.encryption = encryption;
     this.mode = mode;
+    this.properties = unmodifiableMapWithTrimmedValues(definitions, props);
+  }
+
+  protected static Map<String, String> unmodifiableMapWithTrimmedValues(PropertyDefinitions definitions, Map<String, String> props) {
+    Map<String, String> map = new HashMap<>(props.size());
     props.forEach((k, v) -> {
       String validKey = definitions.validKey(k);
-      properties.put(validKey, trim(v));
+      map.put(validKey, trim(v));
     });
+    return Collections.unmodifiableMap(map);
   }
 
   public GlobalAnalysisMode getMode() {
@@ -78,7 +84,7 @@ public abstract class DefaultConfiguration implements Configuration {
   }
 
   public Map<String, String> getProperties() {
-    return Collections.unmodifiableMap(properties);
+    return properties;
   }
 
   @Override
index f906f59f53250768be358000fa11fcba7a77d173..1a6768979bc6f3e4693652608cb8764a6c6deff3 100644 (file)
@@ -30,6 +30,8 @@ import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.bootstrap.MutableGlobalSettings;
 import org.sonar.scanner.repository.ProjectRepositories;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * @deprecated since 6.5 {@link ModuleSettings} used to be mutable, so keep a mutable copy for backward compatibility.
  */
@@ -84,7 +86,9 @@ public class MutableModuleSettings extends Settings {
 
   @Override
   protected void set(String key, String value) {
-    properties.put(key, value);
+    properties.put(
+      requireNonNull(key, "key can't be null"),
+      requireNonNull(value, "value can't be null").trim());
   }
 
   @Override
index 8c234a9ed33001f694e1344c0fc24de2b8053358..63344b97736f9a643cd1fcb8e122dcf6bd6d8599 100644 (file)
@@ -29,6 +29,8 @@ import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
 import org.sonar.scanner.bootstrap.MutableGlobalSettings;
 import org.sonar.scanner.repository.ProjectRepositories;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * @deprecated since 6.5 {@link ProjectSettings} used to be mutable, so keep a mutable copy for backward compatibility.
  */
@@ -57,7 +59,9 @@ public class MutableProjectSettings extends Settings {
 
   @Override
   protected void set(String key, String value) {
-    properties.put(key, value);
+    properties.put(
+      requireNonNull(key, "key can't be null"),
+      requireNonNull(value, "value can't be null").trim());
   }
 
   @Override