diff --git a/implementation/src/main/java/io/smallrye/config/Converters.java b/implementation/src/main/java/io/smallrye/config/Converters.java index ba8031f18..0c1b8cbe1 100644 --- a/implementation/src/main/java/io/smallrye/config/Converters.java +++ b/implementation/src/main/java/io/smallrye/config/Converters.java @@ -735,7 +735,7 @@ public A convert(final String str) { for (String itemString : itemStrings) { if (!itemString.isEmpty()) { final T item = getDelegate().convert(itemString); - if (item != null && !item.equals(",")) { + if (item != null) { Array.set(array, size++, item); } } diff --git a/implementation/src/main/java/io/smallrye/config/EnvConfigSource.java b/implementation/src/main/java/io/smallrye/config/EnvConfigSource.java index 8539a5478..75b5d2ad4 100644 --- a/implementation/src/main/java/io/smallrye/config/EnvConfigSource.java +++ b/implementation/src/main/java/io/smallrye/config/EnvConfigSource.java @@ -20,6 +20,7 @@ import static java.security.AccessController.doPrivileged; import static java.util.Collections.unmodifiableMap; +import java.io.Serializable; import java.security.PrivilegedAction; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -31,9 +32,11 @@ */ public class EnvConfigSource extends AbstractConfigSource { private static final long serialVersionUID = -4525015934376795496L; + private static final int DEFAULT_ORDINAL = 300; + private static final Object NULL_VALUE = new Object(); - private final Map cache = new ConcurrentHashMap<>(); //the regex match is expensive + private final Map cache = new ConcurrentHashMap<>(); protected EnvConfigSource() { super("EnvConfigSource", getEnvOrdinal()); @@ -50,9 +53,12 @@ public String getValue(String name) { return null; } - String cachedValue = cache.get(name); + Object cachedValue = cache.get(name); if (cachedValue != null) { - return cachedValue; + if (cachedValue == NULL_VALUE) { + return null; + } + return (String) cachedValue; } final Map properties = getProperties(); @@ -80,6 +86,7 @@ public String getValue(String name) { return value; } + cache.put(name, NULL_VALUE); return null; } @@ -133,4 +140,16 @@ private static int getEnvOrdinal() { return DEFAULT_ORDINAL; } + + Object writeReplace() { + return new Ser(); + } + + static final class Ser implements Serializable { + private static final long serialVersionUID = 6812312718645271331L; + + Object readResolve() { + return new EnvConfigSource(); + } + } } diff --git a/implementation/src/test/java/io/smallrye/config/EmptyValuesTest.java b/implementation/src/test/java/io/smallrye/config/EmptyValuesTest.java index dfec3701c..79890bcfd 100644 --- a/implementation/src/test/java/io/smallrye/config/EmptyValuesTest.java +++ b/implementation/src/test/java/io/smallrye/config/EmptyValuesTest.java @@ -2,118 +2,125 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; import java.util.NoSuchElementException; import java.util.Optional; import org.junit.jupiter.api.Test; -// https://github.com/eclipse/microprofile-config/issues/446 +// https://github.com/eclipse/microprofile-config/blob/master/spec/src/main/asciidoc/configexamples.asciidoc#config-value-conversion-rules class EmptyValuesTest { - @Test // Rule 1 + @Test void missingForArray() { assertThrows(NoSuchElementException.class, () -> config().getValue("my.prop", String[].class)); } - @Test // Rule 2 + @Test void emptyForArray() { assertThrows(NoSuchElementException.class, () -> config("my.prop", "").getValue("my.prop", String[].class)); } - @Test // Rule 3 + @Test void commaForArray() { assertThrows(NoSuchElementException.class, () -> config("my.prop", ",").getValue("my.prop", String[].class)); - assertThrows(NoSuchElementException.class, () -> config("my.prop", "\\,").getValue("my.prop", String[].class)); + assertArrayEquals(new String[] { "," }, config("my.prop", "\\,").getValue("my.prop", String[].class)); } - @Test // Rule 4 + @Test void multipleCommasForArray() { assertThrows(NoSuchElementException.class, () -> config("my.prop", ",,").getValue("my.prop", String[].class)); } - @Test // Rule 5 + @Test void valuesForArray() { assertArrayEquals(new String[] { "foo", "bar" }, config("my.prop", "foo,bar").getValue("my.prop", String[].class)); } - @Test // Rule 6 + @Test void valuesCommaEndForArray() { assertArrayEquals(new String[] { "foo" }, config("my.prop", "foo,").getValue("my.prop", String[].class)); } - @Test // Rule 7 + @Test void valuesCommaStartForArray() { assertArrayEquals(new String[] { "bar" }, config("my.prop", ",bar").getValue("my.prop", String[].class)); } - @Test // Rule 8 + @Test void whitespaceForArray() { assertArrayEquals(new String[] { " " }, config("my.prop", " ").getValue("my.prop", String[].class)); } - @Test // Rule 9 + @Test void value() { assertEquals("foo", config("my.prop", "foo").getValue("my.prop", String.class)); } - @Test // Rule 10 + @Test void empty() { assertThrows(NoSuchElementException.class, () -> config("my.prop", "").getValue("my.prop", String.class)); } - @Test // Rule 11 + @Test void comma() { assertEquals(",", config("my.prop", ",").getValue("my.prop", String.class)); } - @Test // Rule 12 + @Test void missing() { assertThrows(NoSuchElementException.class, () -> config().getValue("my.prop", String.class)); } - @Test // Rule 13 + @Test void valueForOptional() { assertEquals(Optional.of("foo"), config("my.prop", "foo").getOptionalValue("my.prop", String.class)); } - @Test // Rule 14 + @Test void emptyForOptional() { assertEquals(Optional.empty(), config("my.prop", "").getOptionalValue("my.prop", String.class)); assertNotEquals(Optional.of(""), config("my.prop", "").getOptionalValue("my.prop", String.class)); } - @Test // Rule 15 + @Test void missingForOptional() { assertEquals(Optional.empty(), config().getOptionalValue("my.prop", String.class)); } - @Test // Rule 16 + @Test void emptyForOptionalArray() { assertEquals(Optional.empty(), config("my.prop", "").getOptionalValue("my.prop", String[].class)); assertNotEquals(Optional.of(new String[] {}), config("my.prop", "").getOptionalValue("my.prop", String[].class)); } - @Test // Rule 17 + @Test void commaForOptionalArray() { assertEquals(Optional.empty(), config("my.prop", ",").getOptionalValue("my.prop", String[].class)); - assertNotEquals(Optional.of(new String[] { "", "" }), - config("my.prop", ",").getOptionalValue("my.prop", String[].class)); + assertTrue(config("my.prop", "\\,").getOptionalValue("my.prop", String[].class).isPresent()); + assertArrayEquals(new String[] { "," }, config("my.prop", "\\,").getOptionalValue("my.prop", String[].class).get()); + assertFalse(config("my.prop", ",").getOptionalValue("my.prop", String[].class).isPresent()); } - @Test // Rule 18 + @Test void mutipleCommasForOptionalArray() { assertEquals(Optional.empty(), config("my.prop", ",,").getOptionalValue("my.prop", String[].class)); - assertNotEquals(Optional.of(new String[] { "", "", "" }), - config("my.prop", ",").getOptionalValue("my.prop", String[].class)); } - @Test // Rule 19 + @Test void missingForOptionalArray() { assertEquals(Optional.empty(), config().getOptionalValue("my.prop", String[].class)); } + @Test + void commaForOptionalList() { + assertTrue(config("my.prop", "\\,").getOptionalValues("my.prop", String.class, ArrayList::new).isPresent()); + } + private static SmallRyeConfig config(String... keyValues) { return new SmallRyeConfigBuilder() .addDefaultInterceptors()