diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/EmptyStructuredConfigProperties.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/EmptyStructuredConfigProperties.java new file mode 100644 index 00000000000..2315dfa013f --- /dev/null +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/EmptyStructuredConfigProperties.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.spi.internal; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +/** Empty instance of {@link StructuredConfigProperties}. */ +final class EmptyStructuredConfigProperties implements StructuredConfigProperties { + + private static final EmptyStructuredConfigProperties INSTANCE = + new EmptyStructuredConfigProperties(); + + private EmptyStructuredConfigProperties() {} + + static EmptyStructuredConfigProperties getInstance() { + return INSTANCE; + } + + @Nullable + @Override + public String getString(String name) { + return null; + } + + @Nullable + @Override + public Boolean getBoolean(String name) { + return null; + } + + @Nullable + @Override + public Integer getInt(String name) { + return null; + } + + @Nullable + @Override + public Long getLong(String name) { + return null; + } + + @Nullable + @Override + public Double getDouble(String name) { + return null; + } + + @Nullable + @Override + public List getScalarList(String name, Class scalarType) { + return null; + } + + @Nullable + @Override + public StructuredConfigProperties getStructured(String name) { + return null; + } + + @Nullable + @Override + public List getStructuredList(String name) { + return null; + } + + @Override + public Set getPropertyKeys() { + return Collections.emptySet(); + } +} diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/StructuredConfigProperties.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/StructuredConfigProperties.java index ad1e3b5de3c..99eff27f00b 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/StructuredConfigProperties.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/StructuredConfigProperties.java @@ -24,6 +24,17 @@ */ public interface StructuredConfigProperties { + /** + * Return an empty {@link StructuredConfigProperties} instance. + * + *

Useful for walking the tree without checking for null. For example, to access a string key + * nested at .foo.bar.baz, call: {@code config.getStructured("foo", empty()).getStructured("bar", + * empty()).getString("baz")}. + */ + static StructuredConfigProperties empty() { + return EmptyStructuredConfigProperties.getInstance(); + } + /** * Returns a {@link String} configuration property. * @@ -168,6 +179,18 @@ default List getScalarList(String name, Class scalarType, List defa @Nullable StructuredConfigProperties getStructured(String name); + /** + * Returns a {@link StructuredConfigProperties} configuration property. + * + * @return a map-valued configuration property, or {@code defaultValue} if {@code name} has not + * been configured + * @throws ConfigurationException if the property is not a mapping + */ + default StructuredConfigProperties getStructured( + String name, StructuredConfigProperties defaultValue) { + return defaultIfNull(getStructured(name), defaultValue); + } + /** * Returns a list of {@link StructuredConfigProperties} configuration property. * @@ -178,6 +201,18 @@ default List getScalarList(String name, Class scalarType, List defa @Nullable List getStructuredList(String name); + /** + * Returns a list of {@link StructuredConfigProperties} configuration property. + * + * @return a list of map-valued configuration property, or {@code defaultValue} if {@code name} + * has not been configured + * @throws ConfigurationException if the property is not a sequence of mappings + */ + default List getStructuredList( + String name, List defaultValue) { + return defaultIfNull(getStructuredList(name), defaultValue); + } + /** * Returns a set of all configuration property keys. * diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java index 2e64e93fe94..757331934bf 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/YamlStructuredConfigPropertiesTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; +import static io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties.empty; import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableSet; @@ -170,6 +171,18 @@ void additionalProperties() { assertThat(listKeyProps2.getInt("int_key1")).isEqualTo(2); } + @Test + void treeWalking() { + // Validate common pattern of walking down tree path which is not defined + // Access string at .foo.bar.baz without null checking and without exception. + assertThat( + structuredConfigProps + .getStructured("foo", empty()) + .getStructured("bar", empty()) + .getString("baz")) + .isNull(); + } + @Test void defaults() { assertThat(structuredConfigProps.getString("foo", "bar")).isEqualTo("bar"); @@ -181,6 +194,9 @@ void defaults() { structuredConfigProps.getScalarList( "foo", String.class, Collections.singletonList("bar"))) .isEqualTo(Collections.singletonList("bar")); + assertThat(structuredConfigProps.getStructured("foo", empty())).isEqualTo(empty()); + assertThat(structuredConfigProps.getStructuredList("foo", Collections.emptyList())) + .isEqualTo(Collections.emptyList()); } @Test @@ -209,4 +225,26 @@ void wrongType() { assertThat(otherProps.getStructured("str_key")).isNull(); assertThat(otherProps.getStructuredList("str_key")).isNull(); } + + @Test + void emptyProperties() { + assertThat(empty().getString("foo")).isNull(); + assertThat(empty().getInt("foo")).isNull(); + assertThat(empty().getLong("foo")).isNull(); + assertThat(empty().getDouble("foo")).isNull(); + assertThat(empty().getBoolean("foo")).isNull(); + assertThat(empty().getScalarList("foo", String.class)).isNull(); + assertThat(empty().getStructured("foo")).isNull(); + assertThat(empty().getStructuredList("foo")).isNull(); + assertThat(empty().getString("foo", "bar")).isEqualTo("bar"); + assertThat(empty().getInt("foo", 1)).isEqualTo(1); + assertThat(empty().getLong("foo", 1)).isEqualTo(1); + assertThat(empty().getDouble("foo", 1.1)).isEqualTo(1.1); + assertThat(empty().getBoolean("foo", true)).isTrue(); + assertThat(empty().getScalarList("foo", String.class, Collections.singletonList("bar"))) + .isEqualTo(Collections.singletonList("bar")); + assertThat(empty().getStructured("foo", empty())).isEqualTo(empty()); + assertThat(empty().getStructuredList("foo", Collections.emptyList())) + .isEqualTo(Collections.emptyList()); + } }