From 1b413005a67fac23a682176e837e75a57073088f Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Wed, 28 Oct 2026 17:26:09 +0530 Subject: [PATCH 01/12] feat: Make declarative config parsing and mapping pluggable Add parser and mapper SPIs, and move the Jackson 2 mapper into an optional adapter module. Signed-off-by: Ashish Thakur --- .../declarative-config-jackson/README.md | 8 + .../build.gradle.kts | 17 ++ .../gradle.properties | 1 + ...JacksonDeclarativeConfigurationMapper.java | 35 +++ .../jackson/internal/JacksonModelMapper.java} | 19 +- ...ativeconfig.DeclarativeConfigurationMapper | 1 + sdk-extensions/declarative-config/README.md | 1 + .../declarative-config/build.gradle.kts | 12 +- .../DeclarativeConfiguration.java | 241 ++---------------- .../DeclarativeConfigurationMapper.java | 41 +++ .../DeclarativeConfigurationYamlParser.java | 38 +++ .../YamlDeclarativeConfigProperties.java | 5 +- .../DeclarativeConfigProviderLoader.java | 42 +++ .../DeclarativeConfigurationMapperUtil.java | 68 +++++ ...akeYamlDeclarativeConfigurationParser.java | 219 ++++++++++++++++ .../model/internal/ExtensionPropertyUtil.java | 11 +- ...econfig.DeclarativeConfigurationYamlParser | 1 + .../DeclarativeConfigurationSpiTest.java | 103 ++++++++ .../ExtensionPropertyUtilTest.java | 12 +- .../DeclarativeConfigProviderLoaderTest.java | 95 +++++++ .../model/ModelPackageBoundaryTest.java | 1 - settings.gradle.kts | 1 + 22 files changed, 721 insertions(+), 251 deletions(-) create mode 100644 sdk-extensions/declarative-config-jackson/README.md create mode 100644 sdk-extensions/declarative-config-jackson/build.gradle.kts create mode 100644 sdk-extensions/declarative-config-jackson/gradle.properties create mode 100644 sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java rename sdk-extensions/{declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ModelMapper.java => declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java} (61%) create mode 100644 sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper create mode 100644 sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java create mode 100644 sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java create mode 100644 sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java create mode 100644 sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java create mode 100644 sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java create mode 100644 sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser create mode 100644 sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java create mode 100644 sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java diff --git a/sdk-extensions/declarative-config-jackson/README.md b/sdk-extensions/declarative-config-jackson/README.md new file mode 100644 index 00000000000..6d4a22f4371 --- /dev/null +++ b/sdk-extensions/declarative-config-jackson/README.md @@ -0,0 +1,8 @@ +# Declarative Configuration Jackson Mapper + +This optional extension provides a Jackson 2 implementation of the +`DeclarativeConfigurationMapper` SPI for `opentelemetry-sdk-extension-declarative-config`. + +Add this artifact when using the default Jackson 2 mapper. Projects using another mapper can +exclude this artifact and register their own `DeclarativeConfigurationMapper` provider. For +example, a project using Jackson 3 can implement the SPI with its Jackson 3 `ObjectMapper`. diff --git a/sdk-extensions/declarative-config-jackson/build.gradle.kts b/sdk-extensions/declarative-config-jackson/build.gradle.kts new file mode 100644 index 00000000000..ee4fe87ae81 --- /dev/null +++ b/sdk-extensions/declarative-config-jackson/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") +} + +description = "OpenTelemetry SDK Declarative Config Jackson Mapper" +otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson") +otelJava.requireSuppressWarningsExplanation.set(false) +otelJava.osgiServiceLoaderProvides.set(listOf( + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper", +)) + +dependencies { + api(project(":sdk-extensions:declarative-config")) + compileOnly(project(":sdk-extensions:autoconfigure-spi")) + implementation("com.fasterxml.jackson.core:jackson-databind") +} diff --git a/sdk-extensions/declarative-config-jackson/gradle.properties b/sdk-extensions/declarative-config-jackson/gradle.properties new file mode 100644 index 00000000000..4476ae57e31 --- /dev/null +++ b/sdk-extensions/declarative-config-jackson/gradle.properties @@ -0,0 +1 @@ +otel.release=alpha diff --git a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java new file mode 100644 index 00000000000..af51505275f --- /dev/null +++ b/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class JacksonDeclarativeConfigurationMapper implements DeclarativeConfigurationMapper { + + @Override + public int order() { + return Integer.MIN_VALUE; + } + + @Override + public T convert(Object value, Class targetType) { + return JacksonModelMapper.MAPPER.convertValue(value, targetType); + } + + @Nullable + @Override + public Map toMap(Object value) { + return JacksonModelMapper.MAPPER.convertValue( + value, new TypeReference>() {}); + } +} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ModelMapper.java b/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java similarity index 61% rename from sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ModelMapper.java rename to sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java index 722c2e69f82..a5803b7d15a 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ModelMapper.java +++ b/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java @@ -3,37 +3,32 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.ObjectMapper; /** - * Canonical {@link ObjectMapper} configuration for parsing declarative configuration YAML. {@link - * io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfiguration#MAPPER} delegates - * to this instance. - * - *

This class is internal and experimental. Its APIs are unstable and can change at any time. Its - * APIs (or a version of them) may be promoted to the public stable API in the future, but no - * guarantees are made. + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. */ -public final class ModelMapper { +public final class JacksonModelMapper { public static final ObjectMapper MAPPER; static { MAPPER = new ObjectMapper() - // Create empty object instances for keys which are present but have null values + // Create empty object instances for keys which are present but have null values. .setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); // Boxed primitives which are present but have null values should be set to null, rather than - // empty instances + // empty instances. MAPPER.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET)); MAPPER.configOverride(Integer.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET)); MAPPER.configOverride(Double.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET)); MAPPER.configOverride(Boolean.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET)); } - private ModelMapper() {} + private JacksonModelMapper() {} } diff --git a/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper b/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper new file mode 100644 index 00000000000..afde493d993 --- /dev/null +++ b/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper @@ -0,0 +1 @@ +io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal.JacksonDeclarativeConfigurationMapper diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index 99ed7553c00..335ec434f7d 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -16,6 +16,7 @@ try (FileInputStream yamlConfigFileInputStream = new FileInputStream("/path/to/c Notes: * Environment variable substitution is supported as [defined in the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/data-model.md#environment-variable-substitution) * Customization is limited to customizing the in-memory config model, or exporters. See `DeclarativeConfigurationCustomizer` SPI for details. +* YAML parsing and mapping parsed values to configuration models can be replaced with `DeclarativeConfigurationYamlParser` and `DeclarativeConfigurationMapper` SPI implementations. Providers are discovered with `ServiceLoader`; the provider with the highest `order()` is selected. The built-in SnakeYAML parser is included. The Jackson 2 mapper is provided by the optional `opentelemetry-sdk-extension-declarative-config-jackson` artifact. Add that artifact to use the default mapper, or register a mapper provider for another implementation such as Jackson 3. With no mapper provider, operations that need model mapping fail with a `DeclarativeConfigException`. * Custom SDK extension components which reference the [ComponentProvider](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java) SPI can be referenced in declarative configuration. Supported types include: * `Resource` * `SpanExporter` diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index 2390d633d1b..5a680f2b431 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -16,10 +16,13 @@ otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser", )) -// declarative-config discovers customizer providers at runtime via ServiceLoader +// declarative-config discovers these providers at runtime via ServiceLoader otelJava.osgiServiceLoaderRequires.set(listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper", )) dependencies { @@ -33,9 +36,9 @@ dependencies { api("com.fasterxml.jackson.core:jackson-annotations") implementation("org.snakeyaml:snakeyaml-engine") - implementation("com.fasterxml.jackson.core:jackson-databind") testImplementation(project(":sdk:testing")) + testImplementation(project(":sdk-extensions:declarative-config-jackson")) testImplementation(project(":sdk-extensions:autoconfigure-spi")) testImplementation(project(":exporters:logging")) testImplementation(project(":exporters:logging-otlp")) @@ -46,6 +49,7 @@ dependencies { testImplementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") testImplementation("io.opentelemetry.instrumentation:opentelemetry-resources") testImplementation("com.linecorp.armeria:armeria-junit5") + testImplementation("com.fasterxml.jackson.core:jackson-databind") // testImplementation("com.google.guava:guava-testlib") testImplementation("nl.jqno.equalsverifier:equalsverifier") @@ -108,8 +112,8 @@ val syncPojoModelsToSrc = tasks.register("syncPojoModelsToSrc") { val modelSrcDir = File(projectDir, "src/main/java") doLast { val modelDir = File(modelSrcDir, modelPackage.replace('.', '/')) - // Delete only @Generated files so hand-written files (ModelMapper, ExtensionPropertyUtil) - // in model.internal survive the regeneration cycle. + // Delete only @Generated files so hand-written files (ExtensionPropertyUtil) in model.internal + // survive the regeneration cycle. modelDir.walkTopDown() .filter { it.isFile && it.extension == "java" } .filter { it.readText().contains("@Generated(") } diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index 245936f5efe..821136750e4 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -5,15 +5,15 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigurationMapperUtil; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal.ModelMapper; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @@ -26,23 +26,6 @@ import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nullable; -import org.snakeyaml.engine.v2.api.Load; -import org.snakeyaml.engine.v2.api.LoadSettings; -import org.snakeyaml.engine.v2.api.YamlUnicodeReader; -import org.snakeyaml.engine.v2.common.ScalarStyle; -import org.snakeyaml.engine.v2.composer.Composer; -import org.snakeyaml.engine.v2.nodes.MappingNode; -import org.snakeyaml.engine.v2.nodes.Node; -import org.snakeyaml.engine.v2.nodes.ScalarNode; -import org.snakeyaml.engine.v2.nodes.Tag; -import org.snakeyaml.engine.v2.parser.ParserImpl; -import org.snakeyaml.engine.v2.resolver.ScalarResolver; -import org.snakeyaml.engine.v2.scanner.StreamReader; -import org.snakeyaml.engine.v2.schema.CoreSchema; /** * Configure {@link OpenTelemetrySdk} using For Implementers * - *

External consumers needing to parse OpenTelemetry YAML configuration files should use the same - * Jackson {@code ObjectMapper} configuration for compatibility. See {@link - * io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal.ModelMapper#MAPPER}. + *

YAML parsing and model mapping can be replaced by implementations of {@link + * DeclarativeConfigurationYamlParser} and {@link DeclarativeConfigurationMapper}. */ public final class DeclarativeConfiguration { private static final Logger logger = Logger.getLogger(DeclarativeConfiguration.class.getName()); - // Matches ${VAR_NAME}, ${env:VAR_NAME}, or ${sys:property.name} with optional :-default - private static final Pattern ENV_VARIABLE_REFERENCE = - Pattern.compile("\\$\\{(?:(env|sys):)?([a-zA-Z_][a-zA-Z0-9_.]*)(?::-([^\\n}]*))?}"); private static final ComponentLoader DEFAULT_COMPONENT_LOADER = ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); - - // Visible for testing - static final ObjectMapper MAPPER = ModelMapper.MAPPER; + private static final DeclarativeConfigurationYamlParser YAML_PARSER = + DeclarativeConfigProviderLoader.load( + DeclarativeConfigurationYamlParser.class, SnakeYamlDeclarativeConfigurationParser::new); private DeclarativeConfiguration() {} @@ -152,7 +131,9 @@ static OpenTelemetryConfigurationModel parse( Map environmentVariables, Map systemProperties) { Object yamlObj = loadYaml(configuration, environmentVariables, systemProperties); - return MAPPER.convertValue(yamlObj, OpenTelemetryConfigurationModel.class); + OpenTelemetryConfigurationModel model = + DeclarativeConfigurationMapperUtil.convert(yamlObj, OpenTelemetryConfigurationModel.class); + return Objects.requireNonNull(model, "Unable to map configuration to model"); } // Visible for testing @@ -160,9 +141,7 @@ static Object loadYaml( InputStream inputStream, Map environmentVariables, Map systemProperties) { - LoadSettings settings = LoadSettings.builder().setSchema(new CoreSchema()).build(); - Load yaml = new EnvLoad(settings, environmentVariables, systemProperties); - return yaml.loadFromInputStream(inputStream); + return YAML_PARSER.parse(inputStream, environmentVariables, systemProperties); } /** @@ -188,8 +167,7 @@ public static DeclarativeConfigProperties toConfigProperties(InputStream configu static DeclarativeConfigProperties toConfigProperties( Object model, ComponentLoader componentLoader) { - Map configurationMap = - MAPPER.convertValue(model, new TypeReference>() {}); + Map configurationMap = DeclarativeConfigurationMapperUtil.toMap(model); if (configurationMap == null) { configurationMap = Collections.emptyMap(); } @@ -210,8 +188,11 @@ public static Sampler createSampler(DeclarativeConfigProperties genericSamplerMo YamlDeclarativeConfigProperties yamlDeclarativeConfigProperties = requireYamlDeclarativeConfigProperties(genericSamplerModel); SamplerModel samplerModel = - MAPPER.convertValue( - DeclarativeConfigProperties.toMap(yamlDeclarativeConfigProperties), SamplerModel.class); + Objects.requireNonNull( + DeclarativeConfigurationMapperUtil.convert( + DeclarativeConfigProperties.toMap(yamlDeclarativeConfigProperties), + SamplerModel.class), + "Unable to map sampler configuration to model"); return createAndMaybeCleanup( SamplerFactory.getInstance(), new DeclarativeConfigContext(yamlDeclarativeConfigProperties.getComponentLoader()), @@ -248,192 +229,6 @@ static R createAndMaybeCleanup( } } - private static final class EnvLoad extends Load { - - private final LoadSettings settings; - private final Map environmentVariables; - private final Map systemProperties; - - private EnvLoad( - LoadSettings settings, - Map environmentVariables, - Map systemProperties) { - super(settings); - this.settings = settings; - this.environmentVariables = environmentVariables; - this.systemProperties = systemProperties; - } - - @Override - public Object loadFromInputStream(InputStream yamlStream) { - Objects.requireNonNull(yamlStream, "InputStream cannot be null"); - return loadOne( - new EnvComposer( - settings, - new ParserImpl( - settings, new StreamReader(settings, new YamlUnicodeReader(yamlStream))), - environmentVariables, - systemProperties)); - } - } - - /** - * A YAML Composer that performs environment variable and system property substitution according - * to the - * OpenTelemetry Configuration Data Model specification. - * - *

This composer supports: - * - *

    - *
  • Environment variable references: {@code ${ENV_VAR}} or {@code ${env:ENV_VAR}} - *
  • System property references: {@code ${sys:property.name}} - *
  • Default values: {@code ${ENV_VAR:-default_value}} - *
  • Escape sequences: {@code $$} is replaced with a single {@code $} - *
- * - *

Substitution only applies to scalar values. Mapping keys are not candidates for - * substitution. Referenced variables that are undefined, null, or empty are replaced with empty - * values unless a default value is provided. - * - *

The {@code $} character serves as an escape sequence where {@code $$} in the input is - * translated to a single {@code $} in the output. This prevents substitution for the escaped - * content. - */ - private static final class EnvComposer extends Composer { - - private final Load load; - private final Map environmentVariables; - private final Map systemProperties; - private final ScalarResolver scalarResolver; - - private static final String ESCAPE_SEQUENCE = "$$"; - private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length(); - private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$'; - - private EnvComposer( - LoadSettings settings, - ParserImpl parser, - Map environmentVariables, - Map systemProperties) { - super(settings, parser); - this.load = new Load(settings); - this.environmentVariables = environmentVariables; - this.systemProperties = systemProperties; - this.scalarResolver = settings.getSchema().getScalarResolver(); - } - - @Override - protected Node composeValueNode(MappingNode node) { - Node itemValue = super.composeValueNode(node); - if (!(itemValue instanceof ScalarNode)) { - // Only apply environment variable substitution to ScalarNodes - return itemValue; - } - ScalarNode scalarNode = (ScalarNode) itemValue; - String envSubstitution = envSubstitution(scalarNode.getValue()); - - // If the environment variable substitution does not change the value, do not modify the node - if (envSubstitution.equals(scalarNode.getValue())) { - return itemValue; - } - - Object envSubstitutionObj = load.loadFromString(envSubstitution); - Tag tag = itemValue.getTag(); - ScalarStyle scalarStyle = scalarNode.getScalarStyle(); - - Tag resolvedTag = - envSubstitutionObj == null - ? Tag.NULL - : scalarResolver.resolve(envSubstitutionObj.toString(), true); - - // Only non-quoted substituted scalars can have their tag changed - if (!itemValue.getTag().equals(resolvedTag) - && scalarStyle != ScalarStyle.SINGLE_QUOTED - && scalarStyle != ScalarStyle.DOUBLE_QUOTED) { - tag = resolvedTag; - } - - boolean resolved = true; - return new ScalarNode( - tag, - resolved, - envSubstitution, - scalarStyle, - itemValue.getStartMark(), - itemValue.getEndMark()); - } - - private String envSubstitution(String val) { - // Iterate through val left to right, search for escape sequence "$$" - // For the substring of val between the last escape sequence and the next found, perform - // environment variable substitution - // Add the escape replacement character '$' in place of each escape sequence found - - int lastEscapeIndexEnd = 0; - StringBuilder newVal = null; - while (true) { - int escapeIndex = val.indexOf(ESCAPE_SEQUENCE, lastEscapeIndexEnd); - int substitutionEndIndex = escapeIndex == -1 ? val.length() : escapeIndex; - newVal = envVarSubstitution(newVal, val, lastEscapeIndexEnd, substitutionEndIndex); - if (escapeIndex == -1) { - break; - } else { - newVal.append(ESCAPE_SEQUENCE_REPLACEMENT); - } - lastEscapeIndexEnd = escapeIndex + ESCAPE_SEQUENCE_LENGTH; - if (lastEscapeIndexEnd >= val.length()) { - break; - } - } - - return newVal.toString(); - } - - private StringBuilder envVarSubstitution( - @Nullable StringBuilder newVal, String source, int startIndex, int endIndex) { - String val = source.substring(startIndex, endIndex); - Matcher matcher = ENV_VARIABLE_REFERENCE.matcher(val); - - if (!matcher.find()) { - return newVal == null ? new StringBuilder(val) : newVal.append(val); - } - - if (newVal == null) { - newVal = new StringBuilder(); - } - - int offset = 0; - do { - MatchResult matchResult = matcher.toMatchResult(); - String prefix = matcher.group(1); // "env", "sys", or null - String key = matcher.group(2); // variable/property name - String defaultValue = matcher.group(3); - if (defaultValue == null) { - defaultValue = ""; - } - - String replacement; - if ("sys".equals(prefix)) { - // System property substitution - Object sysProp = systemProperties.get(key); - replacement = sysProp != null ? sysProp.toString() : defaultValue; - } else { - // Environment variable substitution (default or explicit "env:" prefix) - replacement = environmentVariables.getOrDefault(key, defaultValue); - } - - newVal.append(val, offset, matchResult.start()).append(replacement); - offset = matchResult.end(); - } while (matcher.find()); - if (offset != val.length()) { - newVal.append(val, offset, val.length()); - } - - return newVal; - } - } - // Visible for testing static void callAutoConfigureListeners( DeclarativeConfigContext context, OpenTelemetrySdk openTelemetrySdk) { diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java new file mode 100644 index 00000000000..3273af04b0d --- /dev/null +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig; + +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Converts parsed configuration values to declarative configuration models and generic maps. + * + *

Implementations are discovered with {@link java.util.ServiceLoader}. The implementation with + * the highest {@link #order()} is used. The optional {@code + * opentelemetry-sdk-extension-declarative-config-jackson} artifact supplies a Jackson 2 + * implementation. Implementations must preserve the mapping behavior of the declarative + * configuration models, including extension properties and null values. + */ +public interface DeclarativeConfigurationMapper extends Ordered { + + /** + * Convert a value to an instance of {@code targetType}. + * + * @param value the value to convert + * @param targetType the target type + * @param the target type + * @return the converted value + */ + T convert(Object value, Class targetType); + + /** + * Convert a model or parsed configuration value to a map with string keys. + * + * @param value the value to convert + * @return the converted map, or null if the input value is null + */ + @Nullable + Map toMap(Object value); +} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java new file mode 100644 index 00000000000..c6faeb47650 --- /dev/null +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig; + +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import java.io.InputStream; +import java.util.Map; + +/** + * Parses a declarative configuration YAML stream into generic Java objects. + * + *

Implementations are discovered with {@link java.util.ServiceLoader}. The implementation with + * the highest {@link #order()} is used. The built-in parser has the lowest possible order and is + * used when no other implementation is registered. + * + *

Implementations must apply environment variable and system property substitution according to + * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar + * values, not mapping keys. For unquoted scalar values, substituted text must be parsed using the + * same YAML scalar rules as the original text. + */ +public interface DeclarativeConfigurationYamlParser extends Ordered { + + /** + * Parse a YAML stream. + * + * @param configuration the YAML input stream + * @param environmentVariables environment variables used for substitution + * @param systemProperties system properties used for substitution + * @return the parsed configuration + */ + Object parse( + InputStream configuration, + Map environmentVariables, + Map systemProperties); +} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/YamlDeclarativeConfigProperties.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/YamlDeclarativeConfigProperties.java index 43a4bb16ef1..9dce82ac27f 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/YamlDeclarativeConfigProperties.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/YamlDeclarativeConfigProperties.java @@ -67,9 +67,8 @@ private YamlDeclarativeConfigProperties( /** * Create a {@link YamlDeclarativeConfigProperties} from the {@code properties} map. * - *

{@code properties} is expected to be the output of YAML parsing (i.e. with Jackson {@code - * com.fasterxml.jackson.databind.ObjectMapper}), and have values which are scalars, lists of - * scalars, lists of maps, and maps. + *

{@code properties} is expected to have values which are scalars, lists of scalars, lists of + * maps, and maps. * * @see DeclarativeConfiguration#toConfigProperties(Object) */ diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java new file mode 100644 index 00000000000..8c110fb46af --- /dev/null +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import java.util.List; +import java.util.function.Supplier; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class DeclarativeConfigProviderLoader { + + private static final ComponentLoader COMPONENT_LOADER = + ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); + + private DeclarativeConfigProviderLoader() {} + + /** + * Load the highest-ordered implementation of {@code spiClass}, or create its default. + * + * @param spiClass the SPI type + * @param defaultProvider supplies the default provider when none are registered + * @param the SPI type + * @return the selected provider + */ + public static T load(Class spiClass, Supplier defaultProvider) { + return load(spiClass, defaultProvider, COMPONENT_LOADER); + } + + static T load( + Class spiClass, Supplier defaultProvider, ComponentLoader componentLoader) { + List providers = Ordered.loadOrderedList(componentLoader, spiClass); + return providers.isEmpty() ? defaultProvider.get() : providers.get(providers.size() - 1); + } +} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java new file mode 100644 index 00000000000..0dfd1129e88 --- /dev/null +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class DeclarativeConfigurationMapperUtil { + + private static final DeclarativeConfigurationMapper MAPPER = + DeclarativeConfigProviderLoader.load( + DeclarativeConfigurationMapper.class, MissingMapper::new); + + private DeclarativeConfigurationMapperUtil() {} + + /** + * Convert a value to an instance of {@code targetType} using the selected mapper. + * + * @param value the value to convert + * @param targetType the target type + * @param the target type + * @return the converted value + */ + public static T convert(Object value, Class targetType) { + return MAPPER.convert(value, targetType); + } + + /** + * Convert a model or parsed configuration value to a generic map using the selected mapper. + * + * @param value the value to convert + * @return the converted map, or null if the mapper returns null + */ + @Nullable + public static Map toMap(Object value) { + return MAPPER.toMap(value); + } + + private static final class MissingMapper implements DeclarativeConfigurationMapper { + + @Override + public T convert(Object value, Class targetType) { + throw missingMapperException(); + } + + @Nullable + @Override + public Map toMap(Object value) { + throw missingMapperException(); + } + + private static DeclarativeConfigException missingMapperException() { + return new DeclarativeConfigException( + "No DeclarativeConfigurationMapper provider is available. Add the optional " + + "opentelemetry-sdk-extension-declarative-config-jackson artifact or register a " + + "custom mapper provider."); + } + } +} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java new file mode 100644 index 00000000000..0a947c707af --- /dev/null +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -0,0 +1,219 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; + +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser; +import java.io.InputStream; +import java.util.Map; +import java.util.Objects; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import org.snakeyaml.engine.v2.api.Load; +import org.snakeyaml.engine.v2.api.LoadSettings; +import org.snakeyaml.engine.v2.api.YamlUnicodeReader; +import org.snakeyaml.engine.v2.common.ScalarStyle; +import org.snakeyaml.engine.v2.composer.Composer; +import org.snakeyaml.engine.v2.nodes.MappingNode; +import org.snakeyaml.engine.v2.nodes.Node; +import org.snakeyaml.engine.v2.nodes.ScalarNode; +import org.snakeyaml.engine.v2.nodes.Tag; +import org.snakeyaml.engine.v2.parser.ParserImpl; +import org.snakeyaml.engine.v2.resolver.ScalarResolver; +import org.snakeyaml.engine.v2.scanner.StreamReader; +import org.snakeyaml.engine.v2.schema.CoreSchema; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class SnakeYamlDeclarativeConfigurationParser + implements DeclarativeConfigurationYamlParser { + + // Matches ${VAR_NAME}, ${env:VAR_NAME}, or ${sys:property.name} with optional :-default + private static final Pattern ENV_VARIABLE_REFERENCE = + Pattern.compile("\\$\\{(?:(env|sys):)?([a-zA-Z_][a-zA-Z0-9_.]*)(?::-([^\\n}]*))?}"); + + private static final String ESCAPE_SEQUENCE = "$$"; + private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length(); + private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$'; + + @Override + public int order() { + return Integer.MIN_VALUE; + } + + @Override + public Object parse( + InputStream configuration, + Map environmentVariables, + Map systemProperties) { + LoadSettings settings = LoadSettings.builder().setSchema(new CoreSchema()).build(); + Load yaml = new EnvLoad(settings, environmentVariables, systemProperties); + return yaml.loadFromInputStream(configuration); + } + + private static final class EnvLoad extends Load { + + private final LoadSettings settings; + private final Map environmentVariables; + private final Map systemProperties; + + private EnvLoad( + LoadSettings settings, + Map environmentVariables, + Map systemProperties) { + super(settings); + this.settings = settings; + this.environmentVariables = environmentVariables; + this.systemProperties = systemProperties; + } + + @Override + public Object loadFromInputStream(InputStream yamlStream) { + Objects.requireNonNull(yamlStream, "InputStream cannot be null"); + return loadOne( + new EnvComposer( + settings, + new ParserImpl( + settings, new StreamReader(settings, new YamlUnicodeReader(yamlStream))), + environmentVariables, + systemProperties)); + } + } + + /** + * A YAML Composer that performs environment variable and system property substitution according + * to the OpenTelemetry Configuration Data Model specification. + */ + private static final class EnvComposer extends Composer { + + private final Load load; + private final Map environmentVariables; + private final Map systemProperties; + private final ScalarResolver scalarResolver; + + private EnvComposer( + LoadSettings settings, + ParserImpl parser, + Map environmentVariables, + Map systemProperties) { + super(settings, parser); + this.load = new Load(settings); + this.environmentVariables = environmentVariables; + this.systemProperties = systemProperties; + this.scalarResolver = settings.getSchema().getScalarResolver(); + } + + @Override + protected Node composeValueNode(MappingNode node) { + Node itemValue = super.composeValueNode(node); + if (!(itemValue instanceof ScalarNode)) { + // Only apply environment variable substitution to ScalarNodes + return itemValue; + } + ScalarNode scalarNode = (ScalarNode) itemValue; + String envSubstitution = envSubstitution(scalarNode.getValue()); + + // If the environment variable substitution does not change the value, do not modify the node + if (envSubstitution.equals(scalarNode.getValue())) { + return itemValue; + } + + Object envSubstitutionObj = load.loadFromString(envSubstitution); + Tag tag = itemValue.getTag(); + ScalarStyle scalarStyle = scalarNode.getScalarStyle(); + + Tag resolvedTag = + envSubstitutionObj == null + ? Tag.NULL + : scalarResolver.resolve(envSubstitutionObj.toString(), true); + + // Only non-quoted substituted scalars can have their tag changed + if (!itemValue.getTag().equals(resolvedTag) + && scalarStyle != ScalarStyle.SINGLE_QUOTED + && scalarStyle != ScalarStyle.DOUBLE_QUOTED) { + tag = resolvedTag; + } + + boolean resolved = true; + return new ScalarNode( + tag, + resolved, + envSubstitution, + scalarStyle, + itemValue.getStartMark(), + itemValue.getEndMark()); + } + + private String envSubstitution(String val) { + // Substitute each portion between escaped dollar signs independently. Each "$$" becomes + // one literal "$" and prevents substitution in the adjacent portion. + int lastEscapeIndexEnd = 0; + StringBuilder newVal = null; + while (true) { + int escapeIndex = val.indexOf(ESCAPE_SEQUENCE, lastEscapeIndexEnd); + int substitutionEndIndex = escapeIndex == -1 ? val.length() : escapeIndex; + newVal = envVarSubstitution(newVal, val, lastEscapeIndexEnd, substitutionEndIndex); + if (escapeIndex == -1) { + break; + } else { + newVal.append(ESCAPE_SEQUENCE_REPLACEMENT); + } + lastEscapeIndexEnd = escapeIndex + ESCAPE_SEQUENCE_LENGTH; + if (lastEscapeIndexEnd >= val.length()) { + break; + } + } + + return newVal.toString(); + } + + private StringBuilder envVarSubstitution( + @Nullable StringBuilder newVal, String source, int startIndex, int endIndex) { + String val = source.substring(startIndex, endIndex); + Matcher matcher = ENV_VARIABLE_REFERENCE.matcher(val); + + if (!matcher.find()) { + return newVal == null ? new StringBuilder(val) : newVal.append(val); + } + + if (newVal == null) { + newVal = new StringBuilder(); + } + + int offset = 0; + do { + MatchResult matchResult = matcher.toMatchResult(); + String prefix = matcher.group(1); // "env", "sys", or null + String key = matcher.group(2); // variable/property name + String defaultValue = matcher.group(3); + if (defaultValue == null) { + defaultValue = ""; + } + + String replacement; + if ("sys".equals(prefix)) { + // System property substitution + Object sysProp = systemProperties.get(key); + replacement = sysProp != null ? sysProp.toString() : defaultValue; + } else { + // Environment variable substitution (default or explicit "env:" prefix) + replacement = environmentVariables.getOrDefault(key, defaultValue); + } + + newVal.append(val, offset, matchResult.start()).append(replacement); + offset = matchResult.end(); + } while (matcher.find()); + if (offset != val.length()) { + newVal.append(val, offset, val.length()); + } + + return newVal; + } + } +} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ExtensionPropertyUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ExtensionPropertyUtil.java index 1f9f3ce0b44..80d972e2a9c 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ExtensionPropertyUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/internal/ExtensionPropertyUtil.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigurationMapperUtil; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -48,7 +49,8 @@ public static void handleAnySetter( LOGGER.warning( "Property '" + name + "' has been stabilized. Use '" + candidate + "' instead."); extensionProperties.put( - name, value == null ? null : ModelMapper.MAPPER.convertValue(value, stableType)); + name, + value == null ? null : DeclarativeConfigurationMapperUtil.convert(value, stableType)); return; } } @@ -56,7 +58,10 @@ public static void handleAnySetter( Class experimentalType = experimentalPropertyTypes.get(name); if (experimentalType != null) { extensionProperties.put( - name, value == null ? null : ModelMapper.MAPPER.convertValue(value, experimentalType)); + name, + value == null + ? null + : DeclarativeConfigurationMapperUtil.convert(value, experimentalType)); return; } @@ -72,7 +77,7 @@ public static void handleAnySetter( * Returns the extension property value for {@code key}, cast to {@code type}. * *

Values are guaranteed to be the correct type at store time: {@link #handleAnySetter} - * converts known experimental properties via {@code ModelMapper} before storing, and the + * converts known experimental properties via the configured mapper before storing, and the * generated accessor builders enforce the type at the call site. A {@link ClassCastException} * here indicates a contract violation (e.g. writing directly to the extension properties map with * the wrong type). diff --git a/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser new file mode 100644 index 00000000000..64e47bdf73d --- /dev/null +++ b/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser @@ -0,0 +1 @@ +io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java new file mode 100644 index 00000000000..32d0679399a --- /dev/null +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -0,0 +1,103 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class DeclarativeConfigurationSpiTest { + + @Test + void defaultProvidersAreRegisteredWithServiceLoader() { + assertThat( + DeclarativeConfigProviderLoader.load( + DeclarativeConfigurationYamlParser.class, + SnakeYamlDeclarativeConfigurationParser::new)) + .isInstanceOf(SnakeYamlDeclarativeConfigurationParser.class); + DeclarativeConfigurationMapper mapper = loadMapper(); + assertThat(mapper.getClass().getName()) + .isEqualTo( + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal." + + "JacksonDeclarativeConfigurationMapper"); + assertThat(mapper.order()).isEqualTo(Integer.MIN_VALUE); + } + + @Test + void yamlParserPerformsEnvironmentAndSystemPropertySubstitution() { + String yaml = "boolean: ${BOOL}\nquoted: '${BOOL}'\nproperty: ${sys:port}\n"; + Map environmentVariables = Collections.singletonMap("BOOL", "true"); + Map systemProperties = new HashMap<>(); + systemProperties.put("port", "4317"); + + Object parsed = + new SnakeYamlDeclarativeConfigurationParser() + .parse( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), + environmentVariables, + systemProperties); + + assertThat(parsed).isEqualTo(mapOf("boolean", true, "quoted", "true", "property", 4317)); + } + + @Test + void mapperConvertsModelsAndGenericMaps() { + Map input = new HashMap<>(); + input.put("file_format", "1.2"); + input.put("disabled", true); + + DeclarativeConfigurationMapper mapper = loadMapper(); + OpenTelemetryConfigurationModel model = + mapper.convert(input, OpenTelemetryConfigurationModel.class); + + assertThat(model.getFileFormat()).isEqualTo("1.2"); + assertThat(model.getDisabled()).isTrue(); + assertThat(mapper.toMap(model)) + .containsEntry("file_format", "1.2") + .containsEntry("disabled", true); + } + + @Test + void topLevelParseAndPropertiesUseRegisteredProviders() { + String yaml = "file_format: '1.2'\ndisabled: true\n"; + ByteArrayInputStream input = new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)); + + OpenTelemetryConfigurationModel model = DeclarativeConfiguration.parse(input); + DeclarativeConfigProperties properties = + DeclarativeConfiguration.toConfigProperties( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); + + assertThat(model.getFileFormat()).isEqualTo("1.2"); + assertThat(model.getDisabled()).isTrue(); + assertThat(properties.getString("file_format")).isEqualTo("1.2"); + assertThat(properties.getBoolean("disabled")).isTrue(); + } + + private static Map mapOf(Object... entries) { + Map result = new HashMap<>(); + for (int i = 0; i < entries.length; i += 2) { + result.put((String) entries[i], entries[i + 1]); + } + return result; + } + + private static DeclarativeConfigurationMapper loadMapper() { + return DeclarativeConfigProviderLoader.load( + DeclarativeConfigurationMapper.class, + () -> { + throw new AssertionError("Expected a mapper provider to be registered"); + }); + } +} diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ExtensionPropertyUtilTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ExtensionPropertyUtilTest.java index 6cad1c2a064..078b3fd08e7 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ExtensionPropertyUtilTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/ExtensionPropertyUtilTest.java @@ -7,13 +7,13 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.databind.ObjectMapper; import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.LogRecordLimitsModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.LoggerProviderModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal.ExperimentalComposableSamplerModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal.ExtensionPropertyUtil; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.internal.ModelMapper; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -24,6 +24,8 @@ class ExtensionPropertyUtilTest { + private final ObjectMapper mapper = new ObjectMapper(); + @RegisterExtension LogCapturer logs = LogCapturer.create().captureForLogger(ExtensionPropertyUtil.class.getName(), Level.TRACE); @@ -181,20 +183,20 @@ void filterSerializable_dropsGraduatedKeys_preservesOthers() { @Test void stableModel_roundTripsGraduatedKey_emitsStableKeyOnly() throws Exception { String json = "{\"limits/development\":{}}"; - LoggerProviderModel model = ModelMapper.MAPPER.readValue(json, LoggerProviderModel.class); + LoggerProviderModel model = mapper.readValue(json, LoggerProviderModel.class); assertThat(model.getLimits()).isNotNull().isInstanceOf(LogRecordLimitsModel.class); - String reserialized = ModelMapper.MAPPER.writeValueAsString(model); + String reserialized = mapper.writeValueAsString(model); assertThat(reserialized).contains("\"limits\":").doesNotContain("limits/development"); } @Test void stableModel_roundTripsNonGraduatedDevelopmentKey_verbatim() throws Exception { String json = "{\"composite/development\":{}}"; - SamplerModel model = ModelMapper.MAPPER.readValue(json, SamplerModel.class); + SamplerModel model = mapper.readValue(json, SamplerModel.class); - String reserialized = ModelMapper.MAPPER.writeValueAsString(model); + String reserialized = mapper.writeValueAsString(model); assertThat(reserialized).contains("composite/development"); } } diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java new file mode 100644 index 00000000000..79d37406000 --- /dev/null +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + +class DeclarativeConfigProviderLoaderTest { + + @Test + void loadSelectsHighestOrderedProvider() { + TestProvider lowerOrder = new TestProvider(1); + TestProvider higherOrder = new TestProvider(2); + ComponentLoader componentLoader = componentLoader(lowerOrder, higherOrder); + + TestSpi selected = + DeclarativeConfigProviderLoader.load( + TestSpi.class, () -> new TestProvider(Integer.MIN_VALUE), componentLoader); + + assertThat(selected).isSameAs(higherOrder); + } + + @Test + void loadUsesDefaultWhenNoProvidersAreRegistered() { + AtomicBoolean defaultProviderCalled = new AtomicBoolean(); + Supplier defaultProvider = + () -> { + defaultProviderCalled.set(true); + return new TestProvider(Integer.MIN_VALUE); + }; + + TestSpi selected = + DeclarativeConfigProviderLoader.load(TestSpi.class, defaultProvider, componentLoader()); + + assertThat(defaultProviderCalled).isTrue(); + assertThat(selected).isInstanceOf(TestProvider.class); + } + + @Test + void loadDoesNotCreateDefaultWhenProviderIsRegistered() { + AtomicBoolean defaultProviderCalled = new AtomicBoolean(); + TestProvider provider = new TestProvider(0); + + TestSpi selected = + DeclarativeConfigProviderLoader.load( + TestSpi.class, + () -> { + defaultProviderCalled.set(true); + return new TestProvider(Integer.MIN_VALUE); + }, + componentLoader(provider)); + + assertThat(selected).isSameAs(provider); + assertThat(defaultProviderCalled).isFalse(); + } + + private static ComponentLoader componentLoader(TestProvider... providers) { + return new ComponentLoader() { + @Override + public Iterable load(Class spiClass) { + List result = new ArrayList<>(); + for (TestProvider provider : providers) { + result.add(spiClass.cast(provider)); + } + return result; + } + }; + } + + private interface TestSpi extends Ordered {} + + private static final class TestProvider implements TestSpi { + + private final int order; + + private TestProvider(int order) { + this.order = order; + } + + @Override + public int order() { + return order; + } + } +} diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/ModelPackageBoundaryTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/ModelPackageBoundaryTest.java index dec71f24aef..ff1f4a37d6f 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/ModelPackageBoundaryTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/model/ModelPackageBoundaryTest.java @@ -56,7 +56,6 @@ void experimentalTypesAreExactlyTheInternalPackage() throws IOException { // Internal utility classes are exempt from the Experimental prefix requirement. boolean isInfrastructure = clazz.getSimpleName().endsWith("Accessor") - || clazz.getSimpleName().equals("ModelMapper") || clazz.getSimpleName().equals("ExtensionPropertyUtil"); assertThat(experimental || isInfrastructure) .as( diff --git a/settings.gradle.kts b/settings.gradle.kts index bf8362e1464..f55eeff5351 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -69,6 +69,7 @@ include(":sdk:trace-shaded-deps") include(":sdk-extensions:autoconfigure") include(":sdk-extensions:autoconfigure-spi") include(":sdk-extensions:declarative-config") +include(":sdk-extensions:declarative-config-jackson") include(":sdk-extensions:incubator") include(":sdk-extensions:jaeger-remote-sampler") include(":testing-internal") From a103e4d2b95aa51aa4fa480cbcd11412cff96acb Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Wed, 28 Oct 2026 19:14:06 +0530 Subject: [PATCH 02/12] feat: dependency fix based on tests run Signed-off-by: Ashish Thakur --- CHANGELOG.md | 9 +++++++++ api/incubator/build.gradle.kts | 3 +++ integration-tests/graal-incubating/build.gradle.kts | 2 ++ integration-tests/osgi/build.gradle.kts | 6 ++++-- sdk-extensions/autoconfigure/build.gradle.kts | 2 ++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebd56e3531..db3fc0e58b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +### SDK + +#### Extensions + +* Declarative config: Add parser and mapper SPIs for issue + [#8820](https://github.com/open-telemetry/opentelemetry-java/issues/8820), and move the Jackson 2 + mapper to an optional extension. Add the extension to use the built-in Jackson 2 mapper; register + a custom mapper to use another implementation such as Jackson 3. + ## Version 1.67.0 (2026-10-09) ### API diff --git a/api/incubator/build.gradle.kts b/api/incubator/build.gradle.kts index 56046af9eeb..e4d57df0456 100644 --- a/api/incubator/build.gradle.kts +++ b/api/incubator/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { // To use parsed config file as input for InstrumentationConfigUtilTest testImplementation(project(":sdk-extensions:declarative-config")) + testRuntimeOnly(project(":sdk-extensions:autoconfigure-spi")) + testRuntimeOnly(project(":sdk-extensions:declarative-config-jackson")) testImplementation(project(":sdk:testing")) testImplementation(project(":api:testing-internal")) @@ -36,6 +38,7 @@ testing { dependencies { implementation("com.fasterxml.jackson.core:jackson-databind") implementation(project(":sdk-extensions:declarative-config")) + implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":sdk-extensions:autoconfigure")) implementation("com.google.guava:guava") } diff --git a/integration-tests/graal-incubating/build.gradle.kts b/integration-tests/graal-incubating/build.gradle.kts index d669cb3b773..3b666c3cce9 100644 --- a/integration-tests/graal-incubating/build.gradle.kts +++ b/integration-tests/graal-incubating/build.gradle.kts @@ -25,7 +25,9 @@ dependencies { implementation(project(":sdk:testing")) implementation(project(":exporters:otlp:all")) implementation(project(":api:incubator")) + implementation(project(":sdk-extensions:autoconfigure-spi")) implementation(project(":sdk-extensions:declarative-config")) + implementation(project(":sdk-extensions:declarative-config-jackson")) } // org.graalvm.buildtools.native plugin requires java 17+ as of version 0.11.0 diff --git a/integration-tests/osgi/build.gradle.kts b/integration-tests/osgi/build.gradle.kts index 9a4dac688f0..ebdbf61e8eb 100644 --- a/integration-tests/osgi/build.gradle.kts +++ b/integration-tests/osgi/build.gradle.kts @@ -284,8 +284,8 @@ val autoconfigureSuiteTask = registerOsgiSuite( runtimeOnly(project(":exporters:sender:jdk")) } -// Suite: autoconfigure + declarative-config. Same as above but with declarative-config on the -// classpath, additionally exercising declarative-config bundle OSGi metadata. +// Suite: autoconfigure + declarative-config. Same as above but with declarative-config and its +// Jackson mapper adapter on the classpath, additionally exercising their OSGi metadata. val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "autoconfigureDeclarativeConfig", extraRunrequires = listOf( @@ -293,6 +293,7 @@ val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "opentelemetry-exporter-otlp", "opentelemetry-extension-trace-propagators", "opentelemetry-sdk-extension-declarative-config", + "opentelemetry-sdk-extension-declarative-config-jackson", ), serviceLoaderProvides = listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", @@ -302,6 +303,7 @@ val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( implementation(project(":sdk:all")) implementation(project(":sdk-extensions:autoconfigure")) implementation(project(":sdk-extensions:declarative-config")) + implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":exporters:otlp:all")) implementation(project(":extensions:trace-propagators")) runtimeOnly(project(":exporters:sender:jdk")) diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index 692bb0c7812..e600915941f 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -107,6 +107,7 @@ testing { register("testIncubating") { dependencies { implementation(project(":sdk-extensions:declarative-config")) + implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":exporters:logging")) implementation(project(":exporters:otlp:all")) implementation(project(":sdk:testing")) @@ -120,6 +121,7 @@ testing { register("testDeclarativeConfigSpi") { dependencies { implementation(project(":sdk-extensions:declarative-config")) + implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":exporters:logging")) implementation(project(":sdk:testing")) } From 93ffc5d92e0551aaa40cc3f3a79b035787bed2a0 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Wed, 28 Oct 2026 20:01:43 +0530 Subject: [PATCH 03/12] feat: few changes for better arch Signed-off-by: Ashish Thakur --- CHANGELOG.md | 8 ++-- api/incubator/build.gradle.kts | 3 -- .../graal-incubating/build.gradle.kts | 2 - integration-tests/osgi/build.gradle.kts | 5 +-- sdk-extensions/autoconfigure/build.gradle.kts | 2 - .../declarative-config-jackson/README.md | 10 ++--- .../build.gradle.kts | 5 +-- ...JacksonDeclarativeConfigurationMapper.java | 2 +- ...config.spi.DeclarativeConfigurationMapper} | 0 .../declarative-config-spi/build.gradle.kts | 12 ++++++ .../declarative-config-spi/gradle.properties | 1 + .../spi}/DeclarativeConfigurationMapper.java | 11 ++--- .../DeclarativeConfigurationYamlParser.java | 2 +- sdk-extensions/declarative-config/README.md | 43 ++++++++++++++++++- .../declarative-config/build.gradle.kts | 13 +++--- .../DeclarativeConfiguration.java | 2 + .../DeclarativeConfigurationMapperUtil.java | 2 +- ...akeYamlDeclarativeConfigurationParser.java | 2 +- ...ig.spi.DeclarativeConfigurationYamlParser} | 0 .../DeclarativeConfigurationSpiTest.java | 2 + settings.gradle.kts | 1 + 21 files changed, 89 insertions(+), 39 deletions(-) rename sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/{io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper => io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper} (100%) create mode 100644 sdk-extensions/declarative-config-spi/build.gradle.kts create mode 100644 sdk-extensions/declarative-config-spi/gradle.properties rename sdk-extensions/{declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig => declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi}/DeclarativeConfigurationMapper.java (67%) rename sdk-extensions/{declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig => declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi}/DeclarativeConfigurationYamlParser.java (95%) rename sdk-extensions/declarative-config/src/main/resources/META-INF/services/{io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser => io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index db3fc0e58b6..ef9f61140a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,10 @@ #### Extensions -* Declarative config: Add parser and mapper SPIs for issue - [#8820](https://github.com/open-telemetry/opentelemetry-java/issues/8820), and move the Jackson 2 - mapper to an optional extension. Add the extension to use the built-in Jackson 2 mapper; register - a custom mapper to use another implementation such as Jackson 3. +* Declarative config: Add parser and mapper SPIs in a dedicated artifact for issue + [#8820](https://github.com/open-telemetry/opentelemetry-java/issues/8820). Include the Jackson 2 + mapper by default through a runtime adapter that can be excluded to register a custom mapper such + as one based on Jackson 3. ## Version 1.67.0 (2026-10-09) diff --git a/api/incubator/build.gradle.kts b/api/incubator/build.gradle.kts index e4d57df0456..56046af9eeb 100644 --- a/api/incubator/build.gradle.kts +++ b/api/incubator/build.gradle.kts @@ -21,8 +21,6 @@ dependencies { // To use parsed config file as input for InstrumentationConfigUtilTest testImplementation(project(":sdk-extensions:declarative-config")) - testRuntimeOnly(project(":sdk-extensions:autoconfigure-spi")) - testRuntimeOnly(project(":sdk-extensions:declarative-config-jackson")) testImplementation(project(":sdk:testing")) testImplementation(project(":api:testing-internal")) @@ -38,7 +36,6 @@ testing { dependencies { implementation("com.fasterxml.jackson.core:jackson-databind") implementation(project(":sdk-extensions:declarative-config")) - implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":sdk-extensions:autoconfigure")) implementation("com.google.guava:guava") } diff --git a/integration-tests/graal-incubating/build.gradle.kts b/integration-tests/graal-incubating/build.gradle.kts index 3b666c3cce9..d669cb3b773 100644 --- a/integration-tests/graal-incubating/build.gradle.kts +++ b/integration-tests/graal-incubating/build.gradle.kts @@ -25,9 +25,7 @@ dependencies { implementation(project(":sdk:testing")) implementation(project(":exporters:otlp:all")) implementation(project(":api:incubator")) - implementation(project(":sdk-extensions:autoconfigure-spi")) implementation(project(":sdk-extensions:declarative-config")) - implementation(project(":sdk-extensions:declarative-config-jackson")) } // org.graalvm.buildtools.native plugin requires java 17+ as of version 0.11.0 diff --git a/integration-tests/osgi/build.gradle.kts b/integration-tests/osgi/build.gradle.kts index ebdbf61e8eb..92fccff98da 100644 --- a/integration-tests/osgi/build.gradle.kts +++ b/integration-tests/osgi/build.gradle.kts @@ -284,8 +284,8 @@ val autoconfigureSuiteTask = registerOsgiSuite( runtimeOnly(project(":exporters:sender:jdk")) } -// Suite: autoconfigure + declarative-config. Same as above but with declarative-config and its -// Jackson mapper adapter on the classpath, additionally exercising their OSGi metadata. +// Suite: autoconfigure + declarative-config. Same as above but with declarative-config on the +// classpath, additionally exercising declarative-config and its Jackson mapper OSGi metadata. val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "autoconfigureDeclarativeConfig", extraRunrequires = listOf( @@ -303,7 +303,6 @@ val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( implementation(project(":sdk:all")) implementation(project(":sdk-extensions:autoconfigure")) implementation(project(":sdk-extensions:declarative-config")) - implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":exporters:otlp:all")) implementation(project(":extensions:trace-propagators")) runtimeOnly(project(":exporters:sender:jdk")) diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index e600915941f..692bb0c7812 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -107,7 +107,6 @@ testing { register("testIncubating") { dependencies { implementation(project(":sdk-extensions:declarative-config")) - implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":exporters:logging")) implementation(project(":exporters:otlp:all")) implementation(project(":sdk:testing")) @@ -121,7 +120,6 @@ testing { register("testDeclarativeConfigSpi") { dependencies { implementation(project(":sdk-extensions:declarative-config")) - implementation(project(":sdk-extensions:declarative-config-jackson")) implementation(project(":exporters:logging")) implementation(project(":sdk:testing")) } diff --git a/sdk-extensions/declarative-config-jackson/README.md b/sdk-extensions/declarative-config-jackson/README.md index 6d4a22f4371..ffd9da02767 100644 --- a/sdk-extensions/declarative-config-jackson/README.md +++ b/sdk-extensions/declarative-config-jackson/README.md @@ -1,8 +1,8 @@ # Declarative Configuration Jackson Mapper -This optional extension provides a Jackson 2 implementation of the -`DeclarativeConfigurationMapper` SPI for `opentelemetry-sdk-extension-declarative-config`. +This extension provides a Jackson 2 implementation of the `DeclarativeConfigurationMapper` SPI. +It is included as a runtime dependency by `opentelemetry-sdk-extension-declarative-config`. -Add this artifact when using the default Jackson 2 mapper. Projects using another mapper can -exclude this artifact and register their own `DeclarativeConfigurationMapper` provider. For -example, a project using Jackson 3 can implement the SPI with its Jackson 3 `ObjectMapper`. +Projects using another mapper can exclude this artifact from the declarative-config dependency and +register their own `DeclarativeConfigurationMapper` provider. For example, a project using Jackson 3 +can implement the SPI with its Jackson 3 `ObjectMapper`. diff --git a/sdk-extensions/declarative-config-jackson/build.gradle.kts b/sdk-extensions/declarative-config-jackson/build.gradle.kts index ee4fe87ae81..1223b4edd08 100644 --- a/sdk-extensions/declarative-config-jackson/build.gradle.kts +++ b/sdk-extensions/declarative-config-jackson/build.gradle.kts @@ -7,11 +7,10 @@ description = "OpenTelemetry SDK Declarative Config Jackson Mapper" otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson") otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiServiceLoaderProvides.set(listOf( - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper", )) dependencies { - api(project(":sdk-extensions:declarative-config")) - compileOnly(project(":sdk-extensions:autoconfigure-spi")) + api(project(":sdk-extensions:declarative-config-spi")) implementation("com.fasterxml.jackson.core:jackson-databind") } diff --git a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java index af51505275f..5995eeb48df 100644 --- a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java +++ b/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal; import com.fasterxml.jackson.core.type.TypeReference; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; diff --git a/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper b/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper similarity index 100% rename from sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper rename to sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper diff --git a/sdk-extensions/declarative-config-spi/build.gradle.kts b/sdk-extensions/declarative-config-spi/build.gradle.kts new file mode 100644 index 00000000000..90e1027fefd --- /dev/null +++ b/sdk-extensions/declarative-config-spi/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") +} + +description = "OpenTelemetry SDK Declarative Config SPI" +otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi") +otelJava.requireSuppressWarningsExplanation.set(false) + +dependencies { + api(project(":sdk-extensions:autoconfigure-spi")) +} diff --git a/sdk-extensions/declarative-config-spi/gradle.properties b/sdk-extensions/declarative-config-spi/gradle.properties new file mode 100644 index 00000000000..4476ae57e31 --- /dev/null +++ b/sdk-extensions/declarative-config-spi/gradle.properties @@ -0,0 +1 @@ +otel.release=alpha diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java similarity index 67% rename from sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java rename to sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java index 3273af04b0d..14acb1f3119 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationMapper.java +++ b/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.util.Map; @@ -13,10 +13,11 @@ * Converts parsed configuration values to declarative configuration models and generic maps. * *

Implementations are discovered with {@link java.util.ServiceLoader}. The implementation with - * the highest {@link #order()} is used. The optional {@code - * opentelemetry-sdk-extension-declarative-config-jackson} artifact supplies a Jackson 2 - * implementation. Implementations must preserve the mapping behavior of the declarative - * configuration models, including extension properties and null values. + * the highest {@link #order()} is used. The Jackson 2 implementation is supplied by the {@code + * opentelemetry-sdk-extension-declarative-config-jackson} runtime adapter, which is included by + * default with declarative config and can be excluded when using a custom mapper. Implementations + * must preserve the mapping behavior of the declarative configuration models, including extension + * properties and null values. */ public interface DeclarativeConfigurationMapper extends Ordered { diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java b/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationYamlParser.java similarity index 95% rename from sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java rename to sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationYamlParser.java index c6faeb47650..957fe53ed73 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationYamlParser.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.io.InputStream; diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index 335ec434f7d..5c6f947485c 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -16,7 +16,8 @@ try (FileInputStream yamlConfigFileInputStream = new FileInputStream("/path/to/c Notes: * Environment variable substitution is supported as [defined in the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/data-model.md#environment-variable-substitution) * Customization is limited to customizing the in-memory config model, or exporters. See `DeclarativeConfigurationCustomizer` SPI for details. -* YAML parsing and mapping parsed values to configuration models can be replaced with `DeclarativeConfigurationYamlParser` and `DeclarativeConfigurationMapper` SPI implementations. Providers are discovered with `ServiceLoader`; the provider with the highest `order()` is selected. The built-in SnakeYAML parser is included. The Jackson 2 mapper is provided by the optional `opentelemetry-sdk-extension-declarative-config-jackson` artifact. Add that artifact to use the default mapper, or register a mapper provider for another implementation such as Jackson 3. With no mapper provider, operations that need model mapping fail with a `DeclarativeConfigException`. +* YAML parsing and model mapping can be replaced with `DeclarativeConfigurationYamlParser` and `DeclarativeConfigurationMapper` implementations. Both interfaces are in the `opentelemetry-sdk-extension-declarative-config-spi` artifact and are included transitively. Providers are discovered with `ServiceLoader`; the provider with the highest `order()` is selected. +* The built-in SnakeYAML parser and Jackson 2 mapper work by default. The Jackson 2 mapper is supplied by a runtime adapter. If you exclude that adapter, register a mapper provider of your own; otherwise, operations that need mapping fail with a `DeclarativeConfigException`. * Custom SDK extension components which reference the [ComponentProvider](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java) SPI can be referenced in declarative configuration. Supported types include: * `Resource` * `SpanExporter` @@ -27,3 +28,43 @@ Notes: * `TextMapPropagator` * `Sampler` * You can use declarative configuration with [autoconfigure](https://opentelemetry.io/docs/languages/java/configuration/#declarative-configuration) to specify a configuration file via environment variable, e.g. `OTEL_CONFIG_FILE=/path/to/config.yaml`. + +## Use a custom mapper + +To use a mapper such as Jackson 3, exclude the default Jackson 2 adapter from the declarative config +dependency and add Jackson 3 Databind. This Gradle example uses Jackson 3.2.3: + +```kotlin +dependencies { + implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config") { + exclude( + group = "io.opentelemetry", + module = "opentelemetry-sdk-extension-declarative-config-jackson", + ) + } + implementation("tools.jackson.core:jackson-databind:3.2.3") +} +``` + +Implement `DeclarativeConfigurationMapper` and override these methods: + +* `order()` returns the provider priority. The provider with the highest value is selected. +* `convert(Object value, Class targetType)` converts parsed configuration values to a model. +* `toMap(Object value)` converts a model or configuration value to a map with string keys. Return + `null` when the input is `null`. + +Configure your mapper to preserve the null and extension-property behavior expected by the models. + +Register the class with `ServiceLoader` by adding this file to your application or mapper module: + +```text +META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper +``` + +Put your provider's fully qualified class name in that file: + +```text +your.package.YourMapper +``` + +With the service file on the runtime classpath, declarative config discovers and uses your mapper. diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index 5a680f2b431..d4a803c0ebe 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -13,22 +13,23 @@ plugins { description = "OpenTelemetry SDK Declarative Config" otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig") otelJava.requireSuppressWarningsExplanation.set(false) -otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider", - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser", )) // declarative-config discovers these providers at runtime via ServiceLoader otelJava.osgiServiceLoaderRequires.set(listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser", - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper", )) dependencies { api(project(":sdk:all")) api(project(":api:incubator")) - compileOnly(project(":sdk-extensions:autoconfigure-spi")) + api(project(":sdk-extensions:autoconfigure-spi")) + api(project(":sdk-extensions:declarative-config-spi")) + runtimeOnly(project(":sdk-extensions:declarative-config-jackson")) // Needed for composable samplers implementation(project(":sdk-extensions:incubator")) @@ -38,8 +39,6 @@ dependencies { implementation("org.snakeyaml:snakeyaml-engine") testImplementation(project(":sdk:testing")) - testImplementation(project(":sdk-extensions:declarative-config-jackson")) - testImplementation(project(":sdk-extensions:autoconfigure-spi")) testImplementation(project(":exporters:logging")) testImplementation(project(":exporters:logging-otlp")) testImplementation(project(":exporters:otlp:all")) diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index 821136750e4..ce641409f69 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -14,6 +14,8 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java index 0dfd1129e88..375c593700b 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java index 0a947c707af..527d3eadff3 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser; import java.io.InputStream; import java.util.Map; import java.util.Objects; diff --git a/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser similarity index 100% rename from sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationYamlParser rename to sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java index 32d0679399a..a69ca0a4be9 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -11,6 +11,8 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; diff --git a/settings.gradle.kts b/settings.gradle.kts index f55eeff5351..fa9165b25f4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,6 +70,7 @@ include(":sdk-extensions:autoconfigure") include(":sdk-extensions:autoconfigure-spi") include(":sdk-extensions:declarative-config") include(":sdk-extensions:declarative-config-jackson") +include(":sdk-extensions:declarative-config-spi") include(":sdk-extensions:incubator") include(":sdk-extensions:jaeger-remote-sampler") include(":testing-internal") From bf33c6f396794d2a7faedf05481932d85f72443b Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 5 Nov 2026 14:29:33 +0530 Subject: [PATCH 04/12] feat: fixes as per suggestions Signed-off-by: Ashish Thakur --- CHANGELOG.md | 9 --- integration-tests/osgi/build.gradle.kts | 2 +- .../DeclarativeConfigurationMapper.java | 47 ++++++++++++++ .../DeclarativeConfigurationYamlParser.java | 5 +- ...econfig.spi.DeclarativeConfigurationMapper | 1 - .../README.md | 5 +- .../build.gradle.kts | 9 +-- .../gradle.properties | 0 ...JacksonDeclarativeConfigurationMapper.java | 9 +-- .../internal/JacksonModelMapper.java | 2 +- ...pi.internal.DeclarativeConfigurationMapper | 1 + .../declarative-config-spi/build.gradle.kts | 12 ---- .../declarative-config-spi/gradle.properties | 1 - .../spi/DeclarativeConfigurationMapper.java | 42 ------------ sdk-extensions/declarative-config/README.md | 36 ++++++++-- .../declarative-config/build.gradle.kts | 10 +-- .../DeclarativeConfiguration.java | 4 +- .../DeclarativeConfigProviderLoader.java | 65 +++++++++++++++++++ .../DeclarativeConfigurationMapperUtil.java | 10 +-- ...akeYamlDeclarativeConfigurationParser.java | 2 +- .../DeclarativeConfigurationSpiTest.java | 19 +++--- .../DeclarativeConfigProviderLoaderTest.java | 63 +++++++++++++++++- settings.gradle.kts | 3 +- 23 files changed, 244 insertions(+), 113 deletions(-) create mode 100644 sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java rename sdk-extensions/{declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi => autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal}/DeclarativeConfigurationYamlParser.java (88%) delete mode 100644 sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper rename sdk-extensions/{declarative-config-jackson => declarative-config-jackson2}/README.md (73%) rename sdk-extensions/{declarative-config-jackson => declarative-config-jackson2}/build.gradle.kts (54%) rename sdk-extensions/{declarative-config-jackson => declarative-config-jackson2}/gradle.properties (100%) rename sdk-extensions/{declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson => declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2}/internal/JacksonDeclarativeConfigurationMapper.java (81%) rename sdk-extensions/{declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson => declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2}/internal/JacksonModelMapper.java (98%) create mode 100644 sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper delete mode 100644 sdk-extensions/declarative-config-spi/build.gradle.kts delete mode 100644 sdk-extensions/declarative-config-spi/gradle.properties delete mode 100644 sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9f61140a6..1ebd56e3531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,6 @@ ## Unreleased -### SDK - -#### Extensions - -* Declarative config: Add parser and mapper SPIs in a dedicated artifact for issue - [#8820](https://github.com/open-telemetry/opentelemetry-java/issues/8820). Include the Jackson 2 - mapper by default through a runtime adapter that can be excluded to register a custom mapper such - as one based on Jackson 3. - ## Version 1.67.0 (2026-10-09) ### API diff --git a/integration-tests/osgi/build.gradle.kts b/integration-tests/osgi/build.gradle.kts index 92fccff98da..304fe32ee2b 100644 --- a/integration-tests/osgi/build.gradle.kts +++ b/integration-tests/osgi/build.gradle.kts @@ -293,7 +293,7 @@ val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "opentelemetry-exporter-otlp", "opentelemetry-extension-trace-propagators", "opentelemetry-sdk-extension-declarative-config", - "opentelemetry-sdk-extension-declarative-config-jackson", + "opentelemetry-sdk-extension-declarative-config-jackson2", ), serviceLoaderProvides = listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java new file mode 100644 index 00000000000..921c6888e5c --- /dev/null +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.spi.internal; + +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Converts parsed configuration values to declarative configuration models and generic maps. + * + *

Implementations are discovered with {@link java.util.ServiceLoader}. If exactly one + * implementation is available, it is used. If multiple implementations are available, set a system + * property or environment variable whose name is this interface's fully qualified class name and + * whose value is the fully qualified class name of the desired implementation. If no implementation + * is selected, the first discovered implementation is used and a warning is logged. The Jackson 2 + * implementation is supplied by the {@code opentelemetry-sdk-extension-declarative-config-jackson2} + * runtime adapter, which is included by default with declarative config and can be excluded when + * using a custom mapper. Implementations must preserve the mapping behavior of the declarative + * configuration models, including extension properties and null values. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface DeclarativeConfigurationMapper { + + /** + * Convert a value to an instance of {@code targetType}. + * + * @param value the value to convert + * @param targetType the target type + * @param the target type + * @return the converted value + */ + T convert(Object value, Class targetType); + + /** + * Convert a model or parsed configuration value to a map with string keys. + * + * @param value the value to convert + * @return the converted map, or null if the input value is null + */ + @Nullable + Map toMap(Object value); +} diff --git a/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationYamlParser.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java similarity index 88% rename from sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationYamlParser.java rename to sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java index 957fe53ed73..c58fc2cd288 100644 --- a/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi; +package io.opentelemetry.sdk.autoconfigure.spi.internal; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.io.InputStream; @@ -20,6 +20,9 @@ * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar * values, not mapping keys. For unquoted scalar values, substituted text must be parsed using the * same YAML scalar rules as the original text. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. */ public interface DeclarativeConfigurationYamlParser extends Ordered { diff --git a/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper b/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper deleted file mode 100644 index afde493d993..00000000000 --- a/sdk-extensions/declarative-config-jackson/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper +++ /dev/null @@ -1 +0,0 @@ -io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal.JacksonDeclarativeConfigurationMapper diff --git a/sdk-extensions/declarative-config-jackson/README.md b/sdk-extensions/declarative-config-jackson2/README.md similarity index 73% rename from sdk-extensions/declarative-config-jackson/README.md rename to sdk-extensions/declarative-config-jackson2/README.md index ffd9da02767..041479e874b 100644 --- a/sdk-extensions/declarative-config-jackson/README.md +++ b/sdk-extensions/declarative-config-jackson2/README.md @@ -1,7 +1,8 @@ -# Declarative Configuration Jackson Mapper +# Declarative Configuration Jackson 2 Mapper This extension provides a Jackson 2 implementation of the `DeclarativeConfigurationMapper` SPI. -It is included as a runtime dependency by `opentelemetry-sdk-extension-declarative-config`. +It is included as a runtime dependency by `opentelemetry-sdk-extension-declarative-config` from +the `opentelemetry-sdk-extension-declarative-config-jackson2` artifact. Projects using another mapper can exclude this artifact from the declarative-config dependency and register their own `DeclarativeConfigurationMapper` provider. For example, a project using Jackson 3 diff --git a/sdk-extensions/declarative-config-jackson/build.gradle.kts b/sdk-extensions/declarative-config-jackson2/build.gradle.kts similarity index 54% rename from sdk-extensions/declarative-config-jackson/build.gradle.kts rename to sdk-extensions/declarative-config-jackson2/build.gradle.kts index 1223b4edd08..abac59989a5 100644 --- a/sdk-extensions/declarative-config-jackson/build.gradle.kts +++ b/sdk-extensions/declarative-config-jackson2/build.gradle.kts @@ -1,16 +1,17 @@ plugins { id("otel.java-conventions") id("otel.publish-conventions") + id("otel.animalsniffer-conventions") } -description = "OpenTelemetry SDK Declarative Config Jackson Mapper" -otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson") +description = "OpenTelemetry SDK Declarative Config Jackson 2 Mapper" +otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2") otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiServiceLoaderProvides.set(listOf( - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper", )) dependencies { - api(project(":sdk-extensions:declarative-config-spi")) + api(project(":sdk-extensions:autoconfigure-spi")) implementation("com.fasterxml.jackson.core:jackson-databind") } diff --git a/sdk-extensions/declarative-config-jackson/gradle.properties b/sdk-extensions/declarative-config-jackson2/gradle.properties similarity index 100% rename from sdk-extensions/declarative-config-jackson/gradle.properties rename to sdk-extensions/declarative-config-jackson2/gradle.properties diff --git a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java similarity index 81% rename from sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java rename to sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java index 5995eeb48df..ad1a3ff5260 100644 --- a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonDeclarativeConfigurationMapper.java +++ b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal; import com.fasterxml.jackson.core.type.TypeReference; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; @@ -16,11 +16,6 @@ */ public final class JacksonDeclarativeConfigurationMapper implements DeclarativeConfigurationMapper { - @Override - public int order() { - return Integer.MIN_VALUE; - } - @Override public T convert(Object value, Class targetType) { return JacksonModelMapper.MAPPER.convertValue(value, targetType); diff --git a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonModelMapper.java similarity index 98% rename from sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java rename to sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonModelMapper.java index a5803b7d15a..11444cf9d98 100644 --- a/sdk-extensions/declarative-config-jackson/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson/internal/JacksonModelMapper.java +++ b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonModelMapper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; diff --git a/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper b/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper new file mode 100644 index 00000000000..08b2e458c0a --- /dev/null +++ b/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper @@ -0,0 +1 @@ +io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal.JacksonDeclarativeConfigurationMapper diff --git a/sdk-extensions/declarative-config-spi/build.gradle.kts b/sdk-extensions/declarative-config-spi/build.gradle.kts deleted file mode 100644 index 90e1027fefd..00000000000 --- a/sdk-extensions/declarative-config-spi/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id("otel.java-conventions") - id("otel.publish-conventions") -} - -description = "OpenTelemetry SDK Declarative Config SPI" -otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi") -otelJava.requireSuppressWarningsExplanation.set(false) - -dependencies { - api(project(":sdk-extensions:autoconfigure-spi")) -} diff --git a/sdk-extensions/declarative-config-spi/gradle.properties b/sdk-extensions/declarative-config-spi/gradle.properties deleted file mode 100644 index 4476ae57e31..00000000000 --- a/sdk-extensions/declarative-config-spi/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -otel.release=alpha diff --git a/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java deleted file mode 100644 index 14acb1f3119..00000000000 --- a/sdk-extensions/declarative-config-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/spi/DeclarativeConfigurationMapper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi; - -import io.opentelemetry.sdk.autoconfigure.spi.Ordered; -import java.util.Map; -import javax.annotation.Nullable; - -/** - * Converts parsed configuration values to declarative configuration models and generic maps. - * - *

Implementations are discovered with {@link java.util.ServiceLoader}. The implementation with - * the highest {@link #order()} is used. The Jackson 2 implementation is supplied by the {@code - * opentelemetry-sdk-extension-declarative-config-jackson} runtime adapter, which is included by - * default with declarative config and can be excluded when using a custom mapper. Implementations - * must preserve the mapping behavior of the declarative configuration models, including extension - * properties and null values. - */ -public interface DeclarativeConfigurationMapper extends Ordered { - - /** - * Convert a value to an instance of {@code targetType}. - * - * @param value the value to convert - * @param targetType the target type - * @param the target type - * @return the converted value - */ - T convert(Object value, Class targetType); - - /** - * Convert a model or parsed configuration value to a map with string keys. - * - * @param value the value to convert - * @return the converted map, or null if the input value is null - */ - @Nullable - Map toMap(Object value); -} diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index 5c6f947485c..cf73824654c 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -16,8 +16,15 @@ try (FileInputStream yamlConfigFileInputStream = new FileInputStream("/path/to/c Notes: * Environment variable substitution is supported as [defined in the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/data-model.md#environment-variable-substitution) * Customization is limited to customizing the in-memory config model, or exporters. See `DeclarativeConfigurationCustomizer` SPI for details. -* YAML parsing and model mapping can be replaced with `DeclarativeConfigurationYamlParser` and `DeclarativeConfigurationMapper` implementations. Both interfaces are in the `opentelemetry-sdk-extension-declarative-config-spi` artifact and are included transitively. Providers are discovered with `ServiceLoader`; the provider with the highest `order()` is selected. -* The built-in SnakeYAML parser and Jackson 2 mapper work by default. The Jackson 2 mapper is supplied by a runtime adapter. If you exclude that adapter, register a mapper provider of your own; otherwise, operations that need mapping fail with a `DeclarativeConfigException`. +* YAML parsing and model mapping can be replaced with `DeclarativeConfigurationYamlParser` and + `DeclarativeConfigurationMapper` implementations. Both interfaces are in the + `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. + Providers are discovered with `ServiceLoader`; the YAML parser with the highest `order()` is + selected. Mapper selection is described below. +* The built-in SnakeYAML parser and Jackson 2 mapper work by default. The mapper is supplied by + the `declarative-config-jackson2` module (`opentelemetry-sdk-extension-declarative-config-jackson2` + artifact). If you exclude that module, register a mapper provider of your own; otherwise, + operations that need mapping fail with a `DeclarativeConfigException`. * Custom SDK extension components which reference the [ComponentProvider](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java) SPI can be referenced in declarative configuration. Supported types include: * `Resource` * `SpanExporter` @@ -31,7 +38,8 @@ Notes: ## Use a custom mapper -To use a mapper such as Jackson 3, exclude the default Jackson 2 adapter from the declarative config +To use a mapper such as Jackson 3, exclude the default +`opentelemetry-sdk-extension-declarative-config-jackson2` adapter from the declarative config dependency and add Jackson 3 Databind. This Gradle example uses Jackson 3.2.3: ```kotlin @@ -39,7 +47,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config") { exclude( group = "io.opentelemetry", - module = "opentelemetry-sdk-extension-declarative-config-jackson", + module = "opentelemetry-sdk-extension-declarative-config-jackson2", ) } implementation("tools.jackson.core:jackson-databind:3.2.3") @@ -48,7 +56,6 @@ dependencies { Implement `DeclarativeConfigurationMapper` and override these methods: -* `order()` returns the provider priority. The provider with the highest value is selected. * `convert(Object value, Class targetType)` converts parsed configuration values to a model. * `toMap(Object value)` converts a model or configuration value to a map with string keys. Return `null` when the input is `null`. @@ -58,7 +65,7 @@ Configure your mapper to preserve the null and extension-property behavior expec Register the class with `ServiceLoader` by adding this file to your application or mapper module: ```text -META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper +META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper ``` Put your provider's fully qualified class name in that file: @@ -67,4 +74,19 @@ Put your provider's fully qualified class name in that file: your.package.YourMapper ``` -With the service file on the runtime classpath, declarative config discovers and uses your mapper. +With the service file on the runtime classpath, declarative config discovers your mapper. + +## Select a mapper + +If exactly one mapper provider is on the classpath, it is used. If multiple providers are present, +select one by setting the following system property or environment variable to the provider's fully +qualified class name: + +| Configuration source | Name | +| --- | --- | +| System property | `io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper` | +| Environment variable | `IO_OPENTELEMETRY_SDK_AUTOCONFIGURE_SPI_INTERNAL_DECLARATIVECONFIGURATIONMAPPER` | + +When multiple providers are present and neither setting is set, the first discovered provider is +used and a warning is logged. If the setting names a provider that is not present, declarative +configuration fails with an `IllegalStateException`. diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index d4a803c0ebe..cd50dd6bf5e 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -13,23 +13,23 @@ plugins { description = "OpenTelemetry SDK Declarative Config" otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig") otelJava.requireSuppressWarningsExplanation.set(false) +otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider", - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", )) // declarative-config discovers these providers at runtime via ServiceLoader otelJava.osgiServiceLoaderRequires.set(listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser", - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper", )) dependencies { api(project(":sdk:all")) api(project(":api:incubator")) api(project(":sdk-extensions:autoconfigure-spi")) - api(project(":sdk-extensions:declarative-config-spi")) - runtimeOnly(project(":sdk-extensions:declarative-config-jackson")) + runtimeOnly(project(":sdk-extensions:declarative-config-jackson2")) // Needed for composable samplers implementation(project(":sdk-extensions:incubator")) diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index ce641409f69..a28951f1759 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -14,11 +14,11 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.io.IOException; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java index 8c110fb46af..491060772dd 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -7,9 +7,12 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.common.impl.ConfigUtil; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.util.List; import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -17,6 +20,9 @@ */ public final class DeclarativeConfigProviderLoader { + private static final Logger LOGGER = + Logger.getLogger(DeclarativeConfigProviderLoader.class.getName()); + private static final ComponentLoader COMPONENT_LOADER = ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); @@ -39,4 +45,63 @@ static T load( List providers = Ordered.loadOrderedList(componentLoader, spiClass); return providers.isEmpty() ? defaultProvider.get() : providers.get(providers.size() - 1); } + + /** + * Load the first discovered implementation of {@code spiClass}, or the configured implementation + * if multiple are registered. Create the default provider when none are registered. + * + * @param spiClass the SPI type + * @param defaultProvider supplies the default provider when none are registered + * @param configProperty the system property or environment variable used to select a provider + * @param the SPI type + * @return the selected provider + */ + static T loadFirstOrConfigured( + Class spiClass, Supplier defaultProvider, String configProperty) { + return loadFirstOrConfigured( + spiClass, + defaultProvider, + configProperty, + COMPONENT_LOADER, + ConfigUtil.getString(configProperty, "")); + } + + static T loadFirstOrConfigured( + Class spiClass, + Supplier defaultProvider, + String configProperty, + ComponentLoader componentLoader, + String configuredProvider) { + List providers = ComponentLoader.loadList(componentLoader, spiClass); + if (providers.isEmpty()) { + return defaultProvider.get(); + } + if (providers.size() == 1) { + return providers.get(0); + } + + if (configuredProvider.isEmpty()) { + LOGGER.log( + Level.WARNING, + "Multiple " + + spiClass.getSimpleName() + + " providers found. Please include only one, or specify preference setting " + + configProperty + + " to the FQCN of the preferred provider."); + return providers.get(0); + } + + for (T provider : providers) { + if (provider.getClass().getName().equals(configuredProvider)) { + return provider; + } + } + throw new IllegalStateException( + "No " + + spiClass.getSimpleName() + + " matched configured " + + configProperty + + ": " + + configuredProvider); + } } diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java index 375c593700b..a562b0fbe41 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; @@ -17,8 +17,10 @@ public final class DeclarativeConfigurationMapperUtil { private static final DeclarativeConfigurationMapper MAPPER = - DeclarativeConfigProviderLoader.load( - DeclarativeConfigurationMapper.class, MissingMapper::new); + DeclarativeConfigProviderLoader.loadFirstOrConfigured( + DeclarativeConfigurationMapper.class, + MissingMapper::new, + DeclarativeConfigurationMapper.class.getName()); private DeclarativeConfigurationMapperUtil() {} @@ -61,7 +63,7 @@ public Map toMap(Object value) { private static DeclarativeConfigException missingMapperException() { return new DeclarativeConfigException( "No DeclarativeConfigurationMapper provider is available. Add the optional " - + "opentelemetry-sdk-extension-declarative-config-jackson artifact or register a " + + "opentelemetry-sdk-extension-declarative-config-jackson2 artifact or register a " + "custom mapper provider."); } } diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java index 527d3eadff3..b7c352137fe 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.io.InputStream; import java.util.Map; import java.util.Objects; diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java index a69ca0a4be9..14eb73068a6 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -8,11 +8,12 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -32,9 +33,8 @@ void defaultProvidersAreRegisteredWithServiceLoader() { DeclarativeConfigurationMapper mapper = loadMapper(); assertThat(mapper.getClass().getName()) .isEqualTo( - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson.internal." + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal." + "JacksonDeclarativeConfigurationMapper"); - assertThat(mapper.order()).isEqualTo(Integer.MIN_VALUE); } @Test @@ -96,10 +96,11 @@ private static Map mapOf(Object... entries) { } private static DeclarativeConfigurationMapper loadMapper() { - return DeclarativeConfigProviderLoader.load( - DeclarativeConfigurationMapper.class, - () -> { - throw new AssertionError("Expected a mapper provider to be registered"); - }); + return ComponentLoader.loadList( + ComponentLoader.forClassLoader(DeclarativeConfigurationSpiTest.class.getClassLoader()), + DeclarativeConfigurationMapper.class) + .stream() + .findFirst() + .orElseThrow(() -> new AssertionError("Expected a mapper provider to be registered")); } } diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java index 79d37406000..b78c104472e 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.util.ArrayList; @@ -14,9 +15,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; class DeclarativeConfigProviderLoaderTest { + @RegisterExtension + LogCapturer logs = LogCapturer.create().captureForType(DeclarativeConfigProviderLoader.class); + @Test void loadSelectsHighestOrderedProvider() { TestProvider lowerOrder = new TestProvider(1); @@ -64,12 +69,60 @@ void loadDoesNotCreateDefaultWhenProviderIsRegistered() { assertThat(defaultProviderCalled).isFalse(); } - private static ComponentLoader componentLoader(TestProvider... providers) { + @Test + void loadFirstOrConfiguredUsesSingleProvider() { + FirstSelectionTestProvider provider = new FirstSelectionTestProvider(); + + SelectionTestSpi selected = + DeclarativeConfigProviderLoader.loadFirstOrConfigured( + SelectionTestSpi.class, + FirstSelectionTestProvider::new, + SelectionTestSpi.class.getName(), + componentLoader(provider), + ""); + + assertThat(selected).isSameAs(provider); + } + + @Test + void loadFirstOrConfiguredUsesFirstProviderAndWarnsWhenMultipleAreUnconfigured() { + FirstSelectionTestProvider firstProvider = new FirstSelectionTestProvider(); + SecondSelectionTestProvider secondProvider = new SecondSelectionTestProvider(); + + SelectionTestSpi selected = + DeclarativeConfigProviderLoader.loadFirstOrConfigured( + SelectionTestSpi.class, + FirstSelectionTestProvider::new, + SelectionTestSpi.class.getName(), + componentLoader(firstProvider, secondProvider), + ""); + + assertThat(selected).isSameAs(firstProvider); + logs.assertContains("Multiple SelectionTestSpi providers found."); + } + + @Test + void loadFirstOrConfiguredUsesConfiguredProviderWhenMultipleArePresent() { + FirstSelectionTestProvider firstProvider = new FirstSelectionTestProvider(); + SecondSelectionTestProvider selectedProvider = new SecondSelectionTestProvider(); + + SelectionTestSpi selected = + DeclarativeConfigProviderLoader.loadFirstOrConfigured( + SelectionTestSpi.class, + FirstSelectionTestProvider::new, + SelectionTestSpi.class.getName(), + componentLoader(firstProvider, selectedProvider), + selectedProvider.getClass().getName()); + + assertThat(selected).isSameAs(selectedProvider); + } + + private static ComponentLoader componentLoader(Object... providers) { return new ComponentLoader() { @Override public Iterable load(Class spiClass) { List result = new ArrayList<>(); - for (TestProvider provider : providers) { + for (Object provider : providers) { result.add(spiClass.cast(provider)); } return result; @@ -79,6 +132,12 @@ public Iterable load(Class spiClass) { private interface TestSpi extends Ordered {} + private interface SelectionTestSpi {} + + private static final class FirstSelectionTestProvider implements SelectionTestSpi {} + + private static final class SecondSelectionTestProvider implements SelectionTestSpi {} + private static final class TestProvider implements TestSpi { private final int order; diff --git a/settings.gradle.kts b/settings.gradle.kts index fa9165b25f4..2e696f4aa08 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -69,8 +69,7 @@ include(":sdk:trace-shaded-deps") include(":sdk-extensions:autoconfigure") include(":sdk-extensions:autoconfigure-spi") include(":sdk-extensions:declarative-config") -include(":sdk-extensions:declarative-config-jackson") -include(":sdk-extensions:declarative-config-spi") +include(":sdk-extensions:declarative-config-jackson2") include(":sdk-extensions:incubator") include(":sdk-extensions:jaeger-remote-sampler") include(":testing-internal") From 8fcb020467837aedd5c360bba87a88b480d9a2fc Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 5 Nov 2026 15:11:00 +0530 Subject: [PATCH 05/12] feat: update to move mapper from internal package Signed-off-by: Ashish Thakur --- ...pentelemetry-sdk-extension-autoconfigure-spi.txt | 13 ++++++++++++- .../DeclarativeConfigurationMapper.java | 5 +---- .../DeclarativeConfigurationYamlParser.java | 6 +----- .../declarative-config-jackson2/build.gradle.kts | 2 +- .../JacksonDeclarativeConfigurationMapper.java | 2 +- ...utoconfigure.spi.DeclarativeConfigurationMapper} | 0 sdk-extensions/declarative-config/README.md | 11 +++++++---- sdk-extensions/declarative-config/build.gradle.kts | 6 +++--- .../declarativeconfig/DeclarativeConfiguration.java | 4 ++-- .../DeclarativeConfigurationMapperUtil.java | 2 +- .../SnakeYamlDeclarativeConfigurationParser.java | 2 +- ...onfigure.spi.DeclarativeConfigurationYamlParser} | 0 .../DeclarativeConfigurationSpiTest.java | 4 ++-- 13 files changed, 32 insertions(+), 25 deletions(-) rename sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/{internal => }/DeclarativeConfigurationMapper.java (90%) rename sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/{internal => }/DeclarativeConfigurationYamlParser.java (85%) rename sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/{io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper => io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper} (100%) rename sdk-extensions/declarative-config/src/main/resources/META-INF/services/{io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser => io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser} (100%) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt index fa166d14326..c9b27d08943 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt @@ -1,2 +1,13 @@ Comparing source compatibility of opentelemetry-sdk-extension-autoconfigure-spi-1.68.0-SNAPSHOT.jar against opentelemetry-sdk-extension-autoconfigure-spi-1.67.0.jar -No changes. \ No newline at end of file ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object convert(java.lang.Object, java.lang.Class) + GENERIC TEMPLATES: +++ T:java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Map toMap(java.lang.Object) + +++ NEW ANNOTATION: javax.annotation.Nullable ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW INTERFACE: io.opentelemetry.sdk.autoconfigure.spi.Ordered + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object parse(java.io.InputStream, java.util.Map, java.util.Map) diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationMapper.java similarity index 90% rename from sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java rename to sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationMapper.java index 921c6888e5c..0ce39406b37 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationMapper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.spi.internal; +package io.opentelemetry.sdk.autoconfigure.spi; import java.util.Map; import javax.annotation.Nullable; @@ -20,9 +20,6 @@ * runtime adapter, which is included by default with declarative config and can be excluded when * using a custom mapper. Implementations must preserve the mapping behavior of the declarative * configuration models, including extension properties and null values. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. */ public interface DeclarativeConfigurationMapper { diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationYamlParser.java similarity index 85% rename from sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java rename to sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationYamlParser.java index c58fc2cd288..09145d7f9f0 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationYamlParser.java @@ -3,9 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.spi.internal; +package io.opentelemetry.sdk.autoconfigure.spi; -import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.io.InputStream; import java.util.Map; @@ -20,9 +19,6 @@ * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar * values, not mapping keys. For unquoted scalar values, substituted text must be parsed using the * same YAML scalar rules as the original text. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. */ public interface DeclarativeConfigurationYamlParser extends Ordered { diff --git a/sdk-extensions/declarative-config-jackson2/build.gradle.kts b/sdk-extensions/declarative-config-jackson2/build.gradle.kts index abac59989a5..401d7473625 100644 --- a/sdk-extensions/declarative-config-jackson2/build.gradle.kts +++ b/sdk-extensions/declarative-config-jackson2/build.gradle.kts @@ -8,7 +8,7 @@ description = "OpenTelemetry SDK Declarative Config Jackson 2 Mapper" otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2") otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiServiceLoaderProvides.set(listOf( - "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper", )) dependencies { diff --git a/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java index ad1a3ff5260..3126210aeef 100644 --- a/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java +++ b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal; import com.fasterxml.jackson.core.type.TypeReference; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; diff --git a/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper b/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper similarity index 100% rename from sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper rename to sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index cf73824654c..b9e177e0a71 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -18,7 +18,10 @@ Notes: * Customization is limited to customizing the in-memory config model, or exporters. See `DeclarativeConfigurationCustomizer` SPI for details. * YAML parsing and model mapping can be replaced with `DeclarativeConfigurationYamlParser` and `DeclarativeConfigurationMapper` implementations. Both interfaces are in the - `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. + public `io.opentelemetry.sdk.autoconfigure.spi` package in the + `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. This + lets provider implementations in separate artifacts implement the SPIs without depending on + another artifact's internal code. Providers are discovered with `ServiceLoader`; the YAML parser with the highest `order()` is selected. Mapper selection is described below. * The built-in SnakeYAML parser and Jackson 2 mapper work by default. The mapper is supplied by @@ -65,7 +68,7 @@ Configure your mapper to preserve the null and extension-property behavior expec Register the class with `ServiceLoader` by adding this file to your application or mapper module: ```text -META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper +META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper ``` Put your provider's fully qualified class name in that file: @@ -84,8 +87,8 @@ qualified class name: | Configuration source | Name | | --- | --- | -| System property | `io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper` | -| Environment variable | `IO_OPENTELEMETRY_SDK_AUTOCONFIGURE_SPI_INTERNAL_DECLARATIVECONFIGURATIONMAPPER` | +| System property | `io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper` | +| Environment variable | `IO_OPENTELEMETRY_SDK_AUTOCONFIGURE_SPI_DECLARATIVECONFIGURATIONMAPPER` | When multiple providers are present and neither setting is set, the first discovered provider is used and a warning is logged. If the setting names a provider that is not present, declarative diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index cd50dd6bf5e..517dbe84eea 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -16,13 +16,13 @@ otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider", - "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser", )) // declarative-config discovers these providers at runtime via ServiceLoader otelJava.osgiServiceLoaderRequires.set(listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", - "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", - "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper", )) dependencies { diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index a28951f1759..2b8d8cf26ec 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -14,11 +14,11 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.io.IOException; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java index a562b0fbe41..4534e742eb7 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java index b7c352137fe..4f6847d915e 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser; import java.io.InputStream; import java.util.Map; import java.util.Objects; diff --git a/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser similarity index 100% rename from sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.declarativeconfig.spi.DeclarativeConfigurationYamlParser rename to sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java index 14eb73068a6..9a5c844ddf4 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -12,8 +12,8 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; From fd8e4acc52310b0e79edd8a53999cd224d5f79a1 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 5 Nov 2026 15:54:42 +0530 Subject: [PATCH 06/12] feat: update to move mapper from internal package Signed-off-by: Ashish Thakur --- .../all/NoSharedInternalCodeTest.java | 1 + ...telemetry-sdk-extension-autoconfigure-spi.txt | 4 ++-- .../DeclarativeConfigurationMapper.java | 5 ++++- .../DeclarativeConfigurationYamlParser.java | 6 +++++- .../declarative-config-jackson2/build.gradle.kts | 2 +- .../JacksonDeclarativeConfigurationMapper.java | 2 +- ....spi.internal.DeclarativeConfigurationMapper} | 0 sdk-extensions/declarative-config/README.md | 16 ++++++++-------- .../declarative-config/build.gradle.kts | 6 +++--- .../DeclarativeConfiguration.java | 4 ++-- .../DeclarativeConfigProviderLoader.java | 8 +++----- .../DeclarativeConfigurationMapperUtil.java | 2 +- .../SnakeYamlDeclarativeConfigurationParser.java | 2 +- ....internal.DeclarativeConfigurationYamlParser} | 0 .../DeclarativeConfigurationSpiTest.java | 4 ++-- 15 files changed, 34 insertions(+), 28 deletions(-) rename sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/{ => internal}/DeclarativeConfigurationMapper.java (90%) rename sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/{ => internal}/DeclarativeConfigurationYamlParser.java (85%) rename sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/{io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper => io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper} (100%) rename sdk-extensions/declarative-config/src/main/resources/META-INF/services/{io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser => io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser} (100%) diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java index 58f276b290e..ba86733b22a 100644 --- a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java +++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java @@ -46,6 +46,7 @@ class NoSharedInternalCodeTest { "opentelemetry-sdk-trace", "opentelemetry-sdk-extension-autoconfigure", "opentelemetry-sdk-extension-declarative-config", + "opentelemetry-sdk-extension-declarative-config-jackson2", "opentelemetry-sdk-extension-incubator", "opentelemetry-sdk-extension-jaeger-remote-sampler", "opentelemetry-exporter-otlp", diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt index c9b27d08943..6d54c6fd6fe 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt @@ -1,12 +1,12 @@ Comparing source compatibility of opentelemetry-sdk-extension-autoconfigure-spi-1.68.0-SNAPSHOT.jar against opentelemetry-sdk-extension-autoconfigure-spi-1.67.0.jar -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper (not serializable) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW SUPERCLASS: java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object convert(java.lang.Object, java.lang.Class) GENERIC TEMPLATES: +++ T:java.lang.Object +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Map toMap(java.lang.Object) +++ NEW ANNOTATION: javax.annotation.Nullable -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser (not serializable) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser (not serializable) +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. +++ NEW INTERFACE: io.opentelemetry.sdk.autoconfigure.spi.Ordered +++ NEW SUPERCLASS: java.lang.Object diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationMapper.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java similarity index 90% rename from sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationMapper.java rename to sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java index 0ce39406b37..921c6888e5c 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationMapper.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.spi; +package io.opentelemetry.sdk.autoconfigure.spi.internal; import java.util.Map; import javax.annotation.Nullable; @@ -20,6 +20,9 @@ * runtime adapter, which is included by default with declarative config and can be excluded when * using a custom mapper. Implementations must preserve the mapping behavior of the declarative * configuration models, including extension properties and null values. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. */ public interface DeclarativeConfigurationMapper { diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationYamlParser.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java similarity index 85% rename from sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationYamlParser.java rename to sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java index 09145d7f9f0..c58fc2cd288 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java @@ -3,8 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.spi; +package io.opentelemetry.sdk.autoconfigure.spi.internal; +import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.io.InputStream; import java.util.Map; @@ -19,6 +20,9 @@ * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar * values, not mapping keys. For unquoted scalar values, substituted text must be parsed using the * same YAML scalar rules as the original text. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. */ public interface DeclarativeConfigurationYamlParser extends Ordered { diff --git a/sdk-extensions/declarative-config-jackson2/build.gradle.kts b/sdk-extensions/declarative-config-jackson2/build.gradle.kts index 401d7473625..abac59989a5 100644 --- a/sdk-extensions/declarative-config-jackson2/build.gradle.kts +++ b/sdk-extensions/declarative-config-jackson2/build.gradle.kts @@ -8,7 +8,7 @@ description = "OpenTelemetry SDK Declarative Config Jackson 2 Mapper" otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2") otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiServiceLoaderProvides.set(listOf( - "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper", )) dependencies { diff --git a/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java index 3126210aeef..ad1a3ff5260 100644 --- a/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java +++ b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal; import com.fasterxml.jackson.core.type.TypeReference; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; diff --git a/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper b/sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper similarity index 100% rename from sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper rename to sdk-extensions/declarative-config-jackson2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index b9e177e0a71..8fa2efd4850 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -17,11 +17,11 @@ Notes: * Environment variable substitution is supported as [defined in the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/data-model.md#environment-variable-substitution) * Customization is limited to customizing the in-memory config model, or exporters. See `DeclarativeConfigurationCustomizer` SPI for details. * YAML parsing and model mapping can be replaced with `DeclarativeConfigurationYamlParser` and - `DeclarativeConfigurationMapper` implementations. Both interfaces are in the - public `io.opentelemetry.sdk.autoconfigure.spi` package in the - `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. This - lets provider implementations in separate artifacts implement the SPIs without depending on - another artifact's internal code. + `DeclarativeConfigurationMapper` implementations. Both interfaces are in the internal + `io.opentelemetry.sdk.autoconfigure.spi.internal` package in the + `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. These + interfaces are internal and unstable, so their APIs can change at any time. Custom providers can + implement them, but should account for compatibility changes. Providers are discovered with `ServiceLoader`; the YAML parser with the highest `order()` is selected. Mapper selection is described below. * The built-in SnakeYAML parser and Jackson 2 mapper work by default. The mapper is supplied by @@ -68,7 +68,7 @@ Configure your mapper to preserve the null and extension-property behavior expec Register the class with `ServiceLoader` by adding this file to your application or mapper module: ```text -META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper +META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper ``` Put your provider's fully qualified class name in that file: @@ -87,8 +87,8 @@ qualified class name: | Configuration source | Name | | --- | --- | -| System property | `io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper` | -| Environment variable | `IO_OPENTELEMETRY_SDK_AUTOCONFIGURE_SPI_DECLARATIVECONFIGURATIONMAPPER` | +| System property | `io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper` | +| Environment variable | `IO_OPENTELEMETRY_SDK_AUTOCONFIGURE_SPI_INTERNAL_DECLARATIVECONFIGURATIONMAPPER` | When multiple providers are present and neither setting is set, the first discovered provider is used and a warning is logged. If the setting names a provider that is not present, declarative diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index 517dbe84eea..cd50dd6bf5e 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -16,13 +16,13 @@ otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider", - "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", )) // declarative-config discovers these providers at runtime via ServiceLoader otelJava.osgiServiceLoaderRequires.set(listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", - "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser", - "io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper", )) dependencies { diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index 2b8d8cf26ec..a28951f1759 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -14,11 +14,11 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.io.IOException; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java index 491060772dd..fd581c39eb4 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -83,11 +83,9 @@ static T loadFirstOrConfigured( if (configuredProvider.isEmpty()) { LOGGER.log( Level.WARNING, - "Multiple " - + spiClass.getSimpleName() - + " providers found. Please include only one, or specify preference setting " - + configProperty - + " to the FQCN of the preferred provider."); + "Multiple {0} providers found. Please include only one, or specify preference setting " + + "{1} to the FQCN of the preferred provider.", + new Object[] {spiClass.getSimpleName(), configProperty}); return providers.get(0); } diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java index 4534e742eb7..a562b0fbe41 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -6,7 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java index 4f6847d915e..b7c352137fe 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.io.InputStream; import java.util.Map; import java.util.Objects; diff --git a/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser similarity index 100% rename from sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser rename to sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java index 9a5c844ddf4..14eb73068a6 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -12,8 +12,8 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.spi.DeclarativeConfigurationYamlParser; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; From 657ee4a570d9ce5339b57762600ae2af37bced09 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 5 Nov 2026 16:15:36 +0530 Subject: [PATCH 07/12] feat: minor fixes Signed-off-by: Ashish Thakur --- ...entelemetry-sdk-extension-autoconfigure-spi.txt | 13 +------------ .../internal/DeclarativeConfigProviderLoader.java | 14 +++++++++----- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt index 6d54c6fd6fe..fa166d14326 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-autoconfigure-spi.txt @@ -1,13 +1,2 @@ Comparing source compatibility of opentelemetry-sdk-extension-autoconfigure-spi-1.68.0-SNAPSHOT.jar against opentelemetry-sdk-extension-autoconfigure-spi-1.67.0.jar -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object convert(java.lang.Object, java.lang.Class) - GENERIC TEMPLATES: +++ T:java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Map toMap(java.lang.Object) - +++ NEW ANNOTATION: javax.annotation.Nullable -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW INTERFACE: io.opentelemetry.sdk.autoconfigure.spi.Ordered - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.lang.Object parse(java.io.InputStream, java.util.Map, java.util.Map) +No changes. \ No newline at end of file diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java index fd581c39eb4..aee8c4841e1 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -9,6 +9,7 @@ import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.common.impl.ConfigUtil; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; +import java.text.MessageFormat; import java.util.List; import java.util.function.Supplier; import java.util.logging.Level; @@ -81,11 +82,14 @@ static T loadFirstOrConfigured( } if (configuredProvider.isEmpty()) { - LOGGER.log( - Level.WARNING, - "Multiple {0} providers found. Please include only one, or specify preference setting " - + "{1} to the FQCN of the preferred provider.", - new Object[] {spiClass.getSimpleName(), configProperty}); + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.log( + Level.WARNING, + MessageFormat.format( + "Multiple {0} providers found. Please include only one, or specify preference " + + "setting {1} to the FQCN of the preferred provider.", + spiClass.getSimpleName(), configProperty)); + } return providers.get(0); } From cc4a52b346391a04c0721545efd34710fccc6fa9 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 5 Nov 2026 16:33:48 +0530 Subject: [PATCH 08/12] feat: apply same logic to parser autoconfigure Signed-off-by: Ashish Thakur --- .../DeclarativeConfigurationYamlParser.java | 12 ++-- sdk-extensions/declarative-config/README.md | 33 +++++++++- .../DeclarativeConfiguration.java | 6 +- .../DeclarativeConfigProviderLoader.java | 21 +----- ...akeYamlDeclarativeConfigurationParser.java | 5 -- .../DeclarativeConfigurationSpiTest.java | 5 +- .../DeclarativeConfigProviderLoaderTest.java | 65 ++++--------------- 7 files changed, 57 insertions(+), 90 deletions(-) diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java index c58fc2cd288..eb56eaa7cf9 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java @@ -5,16 +5,18 @@ package io.opentelemetry.sdk.autoconfigure.spi.internal; -import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.io.InputStream; import java.util.Map; /** * Parses a declarative configuration YAML stream into generic Java objects. * - *

Implementations are discovered with {@link java.util.ServiceLoader}. The implementation with - * the highest {@link #order()} is used. The built-in parser has the lowest possible order and is - * used when no other implementation is registered. + *

Implementations are discovered with {@link java.util.ServiceLoader}. If exactly one + * implementation is available, it is used. If multiple implementations are available, set a system + * property or environment variable whose name is this interface's fully qualified class name and + * whose value is the fully qualified class name of the desired implementation. If no implementation + * is selected, the first discovered implementation is used and a warning is logged. The built-in + * parser is used if no implementation is registered. * *

Implementations must apply environment variable and system property substitution according to * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar @@ -24,7 +26,7 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public interface DeclarativeConfigurationYamlParser extends Ordered { +public interface DeclarativeConfigurationYamlParser { /** * Parse a YAML stream. diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index 8fa2efd4850..c7632de1cf4 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -22,8 +22,10 @@ Notes: `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. These interfaces are internal and unstable, so their APIs can change at any time. Custom providers can implement them, but should account for compatibility changes. - Providers are discovered with `ServiceLoader`; the YAML parser with the highest `order()` is - selected. Mapper selection is described below. + Providers are discovered with `ServiceLoader`. If exactly one provider is present, it is used. + If multiple providers are present, the first discovered provider is used with a warning unless a + provider is selected through a system property or environment variable. See the selection + sections below. * The built-in SnakeYAML parser and Jackson 2 mapper work by default. The mapper is supplied by the `declarative-config-jackson2` module (`opentelemetry-sdk-extension-declarative-config-jackson2` artifact). If you exclude that module, register a mapper provider of your own; otherwise, @@ -93,3 +95,30 @@ qualified class name: When multiple providers are present and neither setting is set, the first discovered provider is used and a warning is logged. If the setting names a provider that is not present, declarative configuration fails with an `IllegalStateException`. + +## Use a custom YAML parser + +Implement `DeclarativeConfigurationYamlParser` and register it with `ServiceLoader` by adding this +file to your application or parser module: + +```text +META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser +``` + +Put your parser's fully qualified class name in the service file. The parser receives the YAML +stream and the environment variables and system properties to use for substitution. + +## Select a YAML parser + +If exactly one YAML parser provider is on the classpath, it is used. If multiple providers are +present, select one by setting the following system property or environment variable to the +provider's fully qualified class name: + +| Configuration source | Name | +| --- | --- | +| System property | `io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser` | +| Environment variable | `IO_OPENTELEMETRY_SDK_AUTOCONFIGURE_SPI_INTERNAL_DECLARATIVECONFIGURATIONYAMLPARSER` | + +When multiple providers are present and neither setting is set, the first discovered provider is +used and a warning is logged. If the setting names a provider that is not present, declarative +configuration fails with an `IllegalStateException`. diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index a28951f1759..c134143a259 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -48,8 +48,10 @@ public final class DeclarativeConfiguration { private static final ComponentLoader DEFAULT_COMPONENT_LOADER = ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); private static final DeclarativeConfigurationYamlParser YAML_PARSER = - DeclarativeConfigProviderLoader.load( - DeclarativeConfigurationYamlParser.class, SnakeYamlDeclarativeConfigurationParser::new); + DeclarativeConfigProviderLoader.loadFirstOrConfigured( + DeclarativeConfigurationYamlParser.class, + SnakeYamlDeclarativeConfigurationParser::new, + DeclarativeConfigurationYamlParser.class.getName()); private DeclarativeConfiguration() {} diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java index aee8c4841e1..cfb0dd5ae78 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -8,7 +8,6 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.common.impl.ConfigUtil; -import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.text.MessageFormat; import java.util.List; import java.util.function.Supplier; @@ -29,24 +28,6 @@ public final class DeclarativeConfigProviderLoader { private DeclarativeConfigProviderLoader() {} - /** - * Load the highest-ordered implementation of {@code spiClass}, or create its default. - * - * @param spiClass the SPI type - * @param defaultProvider supplies the default provider when none are registered - * @param the SPI type - * @return the selected provider - */ - public static T load(Class spiClass, Supplier defaultProvider) { - return load(spiClass, defaultProvider, COMPONENT_LOADER); - } - - static T load( - Class spiClass, Supplier defaultProvider, ComponentLoader componentLoader) { - List providers = Ordered.loadOrderedList(componentLoader, spiClass); - return providers.isEmpty() ? defaultProvider.get() : providers.get(providers.size() - 1); - } - /** * Load the first discovered implementation of {@code spiClass}, or the configured implementation * if multiple are registered. Create the default provider when none are registered. @@ -57,7 +38,7 @@ static T load( * @param the SPI type * @return the selected provider */ - static T loadFirstOrConfigured( + public static T loadFirstOrConfigured( Class spiClass, Supplier defaultProvider, String configProperty) { return loadFirstOrConfigured( spiClass, diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java index b7c352137fe..be3cfb686b8 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -42,11 +42,6 @@ public final class SnakeYamlDeclarativeConfigurationParser private static final int ESCAPE_SEQUENCE_LENGTH = ESCAPE_SEQUENCE.length(); private static final char ESCAPE_SEQUENCE_REPLACEMENT = '$'; - @Override - public int order() { - return Integer.MIN_VALUE; - } - @Override public Object parse( InputStream configuration, diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java index 14eb73068a6..75f5c01137f 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -26,9 +26,10 @@ class DeclarativeConfigurationSpiTest { @Test void defaultProvidersAreRegisteredWithServiceLoader() { assertThat( - DeclarativeConfigProviderLoader.load( + DeclarativeConfigProviderLoader.loadFirstOrConfigured( DeclarativeConfigurationYamlParser.class, - SnakeYamlDeclarativeConfigurationParser::new)) + SnakeYamlDeclarativeConfigurationParser::new, + DeclarativeConfigurationYamlParser.class.getName())) .isInstanceOf(SnakeYamlDeclarativeConfigurationParser.class); DeclarativeConfigurationMapper mapper = loadMapper(); assertThat(mapper.getClass().getName()) diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java index b78c104472e..2aacbfcf8f1 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java @@ -9,7 +9,6 @@ import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.common.ComponentLoader; -import io.opentelemetry.sdk.autoconfigure.spi.Ordered; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -23,50 +22,24 @@ class DeclarativeConfigProviderLoaderTest { LogCapturer logs = LogCapturer.create().captureForType(DeclarativeConfigProviderLoader.class); @Test - void loadSelectsHighestOrderedProvider() { - TestProvider lowerOrder = new TestProvider(1); - TestProvider higherOrder = new TestProvider(2); - ComponentLoader componentLoader = componentLoader(lowerOrder, higherOrder); - - TestSpi selected = - DeclarativeConfigProviderLoader.load( - TestSpi.class, () -> new TestProvider(Integer.MIN_VALUE), componentLoader); - - assertThat(selected).isSameAs(higherOrder); - } - - @Test - void loadUsesDefaultWhenNoProvidersAreRegistered() { + void loadFirstOrConfiguredUsesDefaultWhenNoProvidersAreRegistered() { AtomicBoolean defaultProviderCalled = new AtomicBoolean(); - Supplier defaultProvider = + Supplier defaultProvider = () -> { defaultProviderCalled.set(true); - return new TestProvider(Integer.MIN_VALUE); + return new FirstSelectionTestProvider(); }; - TestSpi selected = - DeclarativeConfigProviderLoader.load(TestSpi.class, defaultProvider, componentLoader()); + SelectionTestSpi selected = + DeclarativeConfigProviderLoader.loadFirstOrConfigured( + SelectionTestSpi.class, + defaultProvider, + SelectionTestSpi.class.getName(), + componentLoader(), + ""); assertThat(defaultProviderCalled).isTrue(); - assertThat(selected).isInstanceOf(TestProvider.class); - } - - @Test - void loadDoesNotCreateDefaultWhenProviderIsRegistered() { - AtomicBoolean defaultProviderCalled = new AtomicBoolean(); - TestProvider provider = new TestProvider(0); - - TestSpi selected = - DeclarativeConfigProviderLoader.load( - TestSpi.class, - () -> { - defaultProviderCalled.set(true); - return new TestProvider(Integer.MIN_VALUE); - }, - componentLoader(provider)); - - assertThat(selected).isSameAs(provider); - assertThat(defaultProviderCalled).isFalse(); + assertThat(selected).isInstanceOf(FirstSelectionTestProvider.class); } @Test @@ -130,25 +103,9 @@ public Iterable load(Class spiClass) { }; } - private interface TestSpi extends Ordered {} - private interface SelectionTestSpi {} private static final class FirstSelectionTestProvider implements SelectionTestSpi {} private static final class SecondSelectionTestProvider implements SelectionTestSpi {} - - private static final class TestProvider implements TestSpi { - - private final int order; - - private TestProvider(int order) { - this.order = order; - } - - @Override - public int order() { - return order; - } - } } From 957940c817ef9fd9dadf790cf022ea5fa86f9afc Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 12 Nov 2026 17:40:54 +0530 Subject: [PATCH 09/12] feat: updates as per code review Signed-off-by: Ashish Thakur --- .../DeclarativeConfigurationMapper.java | 6 ++- .../DeclarativeConfigurationYamlParser.java | 11 +++-- .../declarative-config-snakeyaml/README.md | 9 +++++ .../build.gradle.kts | 17 ++++++++ .../gradle.properties | 1 + ...akeYamlDeclarativeConfigurationParser.java | 2 +- ...nternal.DeclarativeConfigurationYamlParser | 1 + sdk-extensions/declarative-config/README.md | 40 ++++++++++++------- .../declarative-config/build.gradle.kts | 5 +-- .../DeclarativeConfiguration.java | 24 +++++++++-- .../DeclarativeConfigProviderLoader.java | 8 ++-- .../DeclarativeConfigurationMapperUtil.java | 16 +++++++- ...nternal.DeclarativeConfigurationYamlParser | 1 - .../DeclarativeConfigurationSpiTest.java | 22 ++++++---- settings.gradle.kts | 1 + 15 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 sdk-extensions/declarative-config-snakeyaml/README.md create mode 100644 sdk-extensions/declarative-config-snakeyaml/build.gradle.kts create mode 100644 sdk-extensions/declarative-config-snakeyaml/gradle.properties rename sdk-extensions/{declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig => declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml}/internal/SnakeYamlDeclarativeConfigurationParser.java (98%) create mode 100644 sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser delete mode 100644 sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java index 921c6888e5c..cb0b9451581 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.spi.internal; +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import java.util.Map; import javax.annotation.Nullable; @@ -19,7 +20,8 @@ * implementation is supplied by the {@code opentelemetry-sdk-extension-declarative-config-jackson2} * runtime adapter, which is included by default with declarative config and can be excluded when * using a custom mapper. Implementations must preserve the mapping behavior of the declarative - * configuration models, including extension properties and null values. + * configuration models, including extension properties and null values, and throw {@link + * DeclarativeConfigException} when conversion fails. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. @@ -33,6 +35,7 @@ public interface DeclarativeConfigurationMapper { * @param targetType the target type * @param the target type * @return the converted value + * @throws DeclarativeConfigException if the value cannot be converted to {@code targetType} */ T convert(Object value, Class targetType); @@ -41,6 +44,7 @@ public interface DeclarativeConfigurationMapper { * * @param value the value to convert * @return the converted map, or null if the input value is null + * @throws DeclarativeConfigException if the value cannot be converted to a map */ @Nullable Map toMap(Object value); diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java index eb56eaa7cf9..7b90687b763 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.spi.internal; +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import java.io.InputStream; import java.util.Map; @@ -15,13 +16,16 @@ * implementation is available, it is used. If multiple implementations are available, set a system * property or environment variable whose name is this interface's fully qualified class name and * whose value is the fully qualified class name of the desired implementation. If no implementation - * is selected, the first discovered implementation is used and a warning is logged. The built-in - * parser is used if no implementation is registered. + * is selected, the first discovered implementation is used and a warning is logged. The SnakeYAML + * implementation is supplied by the {@code + * opentelemetry-sdk-extension-declarative-config-snakeyaml} runtime adapter, which is included by + * default with declarative config and can be excluded when using a custom parser. * *

Implementations must apply environment variable and system property substitution according to * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar * values, not mapping keys. For unquoted scalar values, substituted text must be parsed using the - * same YAML scalar rules as the original text. + * same YAML scalar rules as the original text. Implementations throw {@link + * DeclarativeConfigException} when parsing fails. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. @@ -35,6 +39,7 @@ public interface DeclarativeConfigurationYamlParser { * @param environmentVariables environment variables used for substitution * @param systemProperties system properties used for substitution * @return the parsed configuration + * @throws DeclarativeConfigException if the YAML cannot be parsed */ Object parse( InputStream configuration, diff --git a/sdk-extensions/declarative-config-snakeyaml/README.md b/sdk-extensions/declarative-config-snakeyaml/README.md new file mode 100644 index 00000000000..062519380ca --- /dev/null +++ b/sdk-extensions/declarative-config-snakeyaml/README.md @@ -0,0 +1,9 @@ +# SnakeYAML Declarative Configuration Parser + +This module provides the default `DeclarativeConfigurationYamlParser` implementation using +SnakeYAML Engine. It is included as a runtime dependency by +`opentelemetry-sdk-extension-declarative-config`. + +Applications that provide a custom YAML parser can exclude this module from the declarative config +dependency and register their parser with `ServiceLoader`. If neither this adapter nor another +parser provider is present, YAML parsing fails with a `DeclarativeConfigException`. diff --git a/sdk-extensions/declarative-config-snakeyaml/build.gradle.kts b/sdk-extensions/declarative-config-snakeyaml/build.gradle.kts new file mode 100644 index 00000000000..4ba78636b70 --- /dev/null +++ b/sdk-extensions/declarative-config-snakeyaml/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("otel.java-conventions") + id("otel.publish-conventions") + id("otel.animalsniffer-conventions") +} + +description = "OpenTelemetry SDK Declarative Config SnakeYAML Parser" +otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml") +otelJava.requireSuppressWarningsExplanation.set(false) +otelJava.osgiServiceLoaderProvides.set(listOf( + "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", +)) + +dependencies { + api(project(":sdk-extensions:autoconfigure-spi")) + implementation("org.snakeyaml:snakeyaml-engine") +} diff --git a/sdk-extensions/declarative-config-snakeyaml/gradle.properties b/sdk-extensions/declarative-config-snakeyaml/gradle.properties new file mode 100644 index 00000000000..4476ae57e31 --- /dev/null +++ b/sdk-extensions/declarative-config-snakeyaml/gradle.properties @@ -0,0 +1 @@ +otel.release=alpha diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml/internal/SnakeYamlDeclarativeConfigurationParser.java similarity index 98% rename from sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java rename to sdk-extensions/declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml/internal/SnakeYamlDeclarativeConfigurationParser.java index be3cfb686b8..0fd5ef1e4c0 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml.internal; import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.io.InputStream; diff --git a/sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser new file mode 100644 index 00000000000..7936676ed7c --- /dev/null +++ b/sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser @@ -0,0 +1 @@ +io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml.internal.SnakeYamlDeclarativeConfigurationParser diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index c7632de1cf4..3312d810e1b 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -16,20 +16,13 @@ try (FileInputStream yamlConfigFileInputStream = new FileInputStream("/path/to/c Notes: * Environment variable substitution is supported as [defined in the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/data-model.md#environment-variable-substitution) * Customization is limited to customizing the in-memory config model, or exporters. See `DeclarativeConfigurationCustomizer` SPI for details. -* YAML parsing and model mapping can be replaced with `DeclarativeConfigurationYamlParser` and - `DeclarativeConfigurationMapper` implementations. Both interfaces are in the internal - `io.opentelemetry.sdk.autoconfigure.spi.internal` package in the - `opentelemetry-sdk-extension-autoconfigure-spi` artifact, which is included transitively. These - interfaces are internal and unstable, so their APIs can change at any time. Custom providers can - implement them, but should account for compatibility changes. - Providers are discovered with `ServiceLoader`. If exactly one provider is present, it is used. - If multiple providers are present, the first discovered provider is used with a warning unless a - provider is selected through a system property or environment variable. See the selection - sections below. -* The built-in SnakeYAML parser and Jackson 2 mapper work by default. The mapper is supplied by - the `declarative-config-jackson2` module (`opentelemetry-sdk-extension-declarative-config-jackson2` - artifact). If you exclude that module, register a mapper provider of your own; otherwise, - operations that need mapping fail with a `DeclarativeConfigException`. +* The parser and mapper are internal, unstable SPIs in + `opentelemetry-sdk-extension-autoconfigure-spi` (`io.opentelemetry.sdk.autoconfigure.spi.internal`). + `ServiceLoader` loads providers; when several are present, the first is used with a warning unless + selected through the corresponding system property or environment variable. +* The SnakeYAML parser and Jackson 2 mapper adapters are included by default. Exclude an adapter to + use a custom provider. If no provider is available, the operation fails with a + `DeclarativeConfigException`. * Custom SDK extension components which reference the [ComponentProvider](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ComponentProvider.java) SPI can be referenced in declarative configuration. Supported types include: * `Resource` * `SpanExporter` @@ -65,6 +58,10 @@ Implement `DeclarativeConfigurationMapper` and override these methods: * `toMap(Object value)` converts a model or configuration value to a map with string keys. Return `null` when the input is `null`. +Mapper methods and `DeclarativeConfigurationYamlParser.parse` report conversion and parsing +failures with `DeclarativeConfigException`. The declarative configuration implementation wraps +other runtime exceptions thrown by providers in this exception. + Configure your mapper to preserve the null and extension-property behavior expected by the models. Register the class with `ServiceLoader` by adding this file to your application or mapper module: @@ -98,6 +95,21 @@ configuration fails with an `IllegalStateException`. ## Use a custom YAML parser +To replace the default SnakeYAML parser, exclude the +`opentelemetry-sdk-extension-declarative-config-snakeyaml` adapter from the declarative config +dependency: + +```kotlin +dependencies { + implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config") { + exclude( + group = "io.opentelemetry", + module = "opentelemetry-sdk-extension-declarative-config-snakeyaml", + ) + } +} +``` + Implement `DeclarativeConfigurationYamlParser` and register it with `ServiceLoader` by adding this file to your application or parser module: diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index cd50dd6bf5e..8e1c6b55e8b 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -16,7 +16,6 @@ otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider", - "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", )) // declarative-config discovers these providers at runtime via ServiceLoader otelJava.osgiServiceLoaderRequires.set(listOf( @@ -28,15 +27,15 @@ otelJava.osgiServiceLoaderRequires.set(listOf( dependencies { api(project(":sdk:all")) api(project(":api:incubator")) - api(project(":sdk-extensions:autoconfigure-spi")) + compileOnly(project(":sdk-extensions:autoconfigure-spi")) runtimeOnly(project(":sdk-extensions:declarative-config-jackson2")) + runtimeOnly(project(":sdk-extensions:declarative-config-snakeyaml")) // Needed for composable samplers implementation(project(":sdk-extensions:incubator")) annotationProcessor("com.google.auto.value:auto-value") api("com.fasterxml.jackson.core:jackson-annotations") - implementation("org.snakeyaml:snakeyaml-engine") testImplementation(project(":sdk:testing")) testImplementation(project(":exporters:logging")) diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index c134143a259..69647a35c8d 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -11,7 +11,6 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigurationMapperUtil; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; import io.opentelemetry.sdk.autoconfigure.spi.Ordered; @@ -50,11 +49,20 @@ public final class DeclarativeConfiguration { private static final DeclarativeConfigurationYamlParser YAML_PARSER = DeclarativeConfigProviderLoader.loadFirstOrConfigured( DeclarativeConfigurationYamlParser.class, - SnakeYamlDeclarativeConfigurationParser::new, + DeclarativeConfiguration::missingYamlParser, DeclarativeConfigurationYamlParser.class.getName()); private DeclarativeConfiguration() {} + private static DeclarativeConfigurationYamlParser missingYamlParser() { + return (configuration, environmentVariables, systemProperties) -> { + throw new DeclarativeConfigException( + "No DeclarativeConfigurationYamlParser provider is available. Add the optional " + + "opentelemetry-sdk-extension-declarative-config-snakeyaml artifact or register a " + + "custom parser provider."); + }; + } + /** * Combines {@link #parse(InputStream)} and {@link #create(OpenTelemetryConfigurationModel)}. * @@ -124,6 +132,8 @@ private static DeclarativeConfigResult create( public static OpenTelemetryConfigurationModel parse(InputStream configuration) { try { return parse(configuration, System.getenv(), System.getProperties()); + } catch (DeclarativeConfigException e) { + throw e; } catch (RuntimeException e) { throw new DeclarativeConfigException("Unable to parse configuration input stream", e); } @@ -145,7 +155,13 @@ static Object loadYaml( InputStream inputStream, Map environmentVariables, Map systemProperties) { - return YAML_PARSER.parse(inputStream, environmentVariables, systemProperties); + try { + return YAML_PARSER.parse(inputStream, environmentVariables, systemProperties); + } catch (DeclarativeConfigException e) { + throw e; + } catch (RuntimeException e) { + throw new DeclarativeConfigException("Unable to parse configuration input stream", e); + } } /** @@ -153,6 +169,7 @@ static Object loadYaml( * * @param model the configuration model * @return a generic {@link DeclarativeConfigProperties} representation of the model + * @throws DeclarativeConfigException if unable to convert */ public static DeclarativeConfigProperties toConfigProperties(Object model) { return toConfigProperties(model, DEFAULT_COMPONENT_LOADER); @@ -163,6 +180,7 @@ public static DeclarativeConfigProperties toConfigProperties(Object model) { * * @param configuration configuration YAML * @return a generic {@link DeclarativeConfigProperties} representation of the model + * @throws DeclarativeConfigException if unable to parse or convert */ public static DeclarativeConfigProperties toConfigProperties(InputStream configuration) { Object yamlObj = loadYaml(configuration, System.getenv(), System.getProperties()); diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java index cfb0dd5ae78..e8bb264b92a 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -8,7 +8,6 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.common.impl.ConfigUtil; -import java.text.MessageFormat; import java.util.List; import java.util.function.Supplier; import java.util.logging.Level; @@ -66,10 +65,9 @@ static T loadFirstOrConfigured( if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log( Level.WARNING, - MessageFormat.format( - "Multiple {0} providers found. Please include only one, or specify preference " - + "setting {1} to the FQCN of the preferred provider.", - spiClass.getSimpleName(), configProperty)); + "Multiple {0} providers found. Please include only one, or specify preference " + + "setting {1} to the FQCN of the preferred provider.", + new Object[] {spiClass.getSimpleName(), configProperty}); } return providers.get(0); } diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java index a562b0fbe41..57dfaefcc6b 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -33,7 +33,13 @@ private DeclarativeConfigurationMapperUtil() {} * @return the converted value */ public static T convert(Object value, Class targetType) { - return MAPPER.convert(value, targetType); + try { + return MAPPER.convert(value, targetType); + } catch (DeclarativeConfigException e) { + throw e; + } catch (RuntimeException e) { + throw new DeclarativeConfigException("Unable to convert value to " + targetType, e); + } } /** @@ -44,7 +50,13 @@ public static T convert(Object value, Class targetType) { */ @Nullable public static Map toMap(Object value) { - return MAPPER.toMap(value); + try { + return MAPPER.toMap(value); + } catch (DeclarativeConfigException e) { + throw e; + } catch (RuntimeException e) { + throw new DeclarativeConfigException("Unable to convert value to a map", e); + } } private static final class MissingMapper implements DeclarativeConfigurationMapper { diff --git a/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser deleted file mode 100644 index 64e47bdf73d..00000000000 --- a/sdk-extensions/declarative-config/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser +++ /dev/null @@ -1 +0,0 @@ -io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java index 75f5c01137f..865e0ca3cf7 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java @@ -10,7 +10,6 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.SnakeYamlDeclarativeConfigurationParser; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; @@ -25,12 +24,10 @@ class DeclarativeConfigurationSpiTest { @Test void defaultProvidersAreRegisteredWithServiceLoader() { - assertThat( - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - DeclarativeConfigurationYamlParser.class, - SnakeYamlDeclarativeConfigurationParser::new, - DeclarativeConfigurationYamlParser.class.getName())) - .isInstanceOf(SnakeYamlDeclarativeConfigurationParser.class); + assertThat(loadParser().getClass().getName()) + .isEqualTo( + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml.internal." + + "SnakeYamlDeclarativeConfigurationParser"); DeclarativeConfigurationMapper mapper = loadMapper(); assertThat(mapper.getClass().getName()) .isEqualTo( @@ -46,7 +43,7 @@ void yamlParserPerformsEnvironmentAndSystemPropertySubstitution() { systemProperties.put("port", "4317"); Object parsed = - new SnakeYamlDeclarativeConfigurationParser() + loadParser() .parse( new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), environmentVariables, @@ -104,4 +101,13 @@ private static DeclarativeConfigurationMapper loadMapper() { .findFirst() .orElseThrow(() -> new AssertionError("Expected a mapper provider to be registered")); } + + private static DeclarativeConfigurationYamlParser loadParser() { + return DeclarativeConfigProviderLoader.loadFirstOrConfigured( + DeclarativeConfigurationYamlParser.class, + () -> { + throw new AssertionError("Expected a YAML parser provider to be registered"); + }, + DeclarativeConfigurationYamlParser.class.getName()); + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2ed816b493e..0bc15b70da6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,6 +70,7 @@ include(":sdk-extensions:autoconfigure") include(":sdk-extensions:autoconfigure-spi") include(":sdk-extensions:declarative-config") include(":sdk-extensions:declarative-config-jackson2") +include(":sdk-extensions:declarative-config-snakeyaml") include(":sdk-extensions:incubator") include(":sdk-extensions:jaeger-remote-sampler") include(":testing-internal") From 77de82fb336ec84660731868c3b11817c2975b75 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 12 Nov 2026 18:02:33 +0530 Subject: [PATCH 10/12] feat: updates for tests Signed-off-by: Ashish Thakur --- .../io/opentelemetry/all/NoSharedInternalCodeTest.java | 1 + integration-tests/osgi/build.gradle.kts | 3 ++- sdk-extensions/declarative-config/build.gradle.kts | 4 +++- .../declarativeconfig/DeclarativeConfiguration.java | 10 ++++++++-- .../internal/DeclarativeConfigProviderLoaderTest.java | 7 ++++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java index d7df3b223f8..a696cd021dd 100644 --- a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java +++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java @@ -47,6 +47,7 @@ class NoSharedInternalCodeTest { "opentelemetry-sdk-extension-autoconfigure", "opentelemetry-sdk-extension-declarative-config", "opentelemetry-sdk-extension-declarative-config-jackson2", + "opentelemetry-sdk-extension-declarative-config-snakeyaml", "opentelemetry-sdk-extension-incubator", "opentelemetry-sdk-extension-jaeger-remote-sampler", "opentelemetry-exporter-otlp", diff --git a/integration-tests/osgi/build.gradle.kts b/integration-tests/osgi/build.gradle.kts index 304fe32ee2b..547a81cba1d 100644 --- a/integration-tests/osgi/build.gradle.kts +++ b/integration-tests/osgi/build.gradle.kts @@ -285,7 +285,7 @@ val autoconfigureSuiteTask = registerOsgiSuite( } // Suite: autoconfigure + declarative-config. Same as above but with declarative-config on the -// classpath, additionally exercising declarative-config and its Jackson mapper OSGi metadata. +// classpath, additionally exercising its SnakeYAML parser and Jackson mapper OSGi metadata. val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "autoconfigureDeclarativeConfig", extraRunrequires = listOf( @@ -294,6 +294,7 @@ val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "opentelemetry-extension-trace-propagators", "opentelemetry-sdk-extension-declarative-config", "opentelemetry-sdk-extension-declarative-config-jackson2", + "opentelemetry-sdk-extension-declarative-config-snakeyaml", ), serviceLoaderProvides = listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index 8e1c6b55e8b..5ddbe8b5dd1 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { api("com.fasterxml.jackson.core:jackson-annotations") testImplementation(project(":sdk:testing")) + testImplementation(project(":sdk-extensions:autoconfigure-spi")) testImplementation(project(":exporters:logging")) testImplementation(project(":exporters:logging-otlp")) testImplementation(project(":exporters:otlp:all")) @@ -47,7 +48,8 @@ dependencies { testImplementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") testImplementation("io.opentelemetry.instrumentation:opentelemetry-resources") testImplementation("com.linecorp.armeria:armeria-junit5") - testImplementation("com.fasterxml.jackson.core:jackson-databind") + testRuntimeOnly(project(":sdk-extensions:declarative-config-jackson2")) + testRuntimeOnly(project(":sdk-extensions:declarative-config-snakeyaml")) // testImplementation("com.google.guava:guava-testlib") testImplementation("nl.jqno.equalsverifier:equalsverifier") diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index 69647a35c8d..18974ad90ee 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -145,8 +145,14 @@ static OpenTelemetryConfigurationModel parse( Map environmentVariables, Map systemProperties) { Object yamlObj = loadYaml(configuration, environmentVariables, systemProperties); - OpenTelemetryConfigurationModel model = - DeclarativeConfigurationMapperUtil.convert(yamlObj, OpenTelemetryConfigurationModel.class); + OpenTelemetryConfigurationModel model; + try { + model = + DeclarativeConfigurationMapperUtil.convert( + yamlObj, OpenTelemetryConfigurationModel.class); + } catch (DeclarativeConfigException e) { + throw new DeclarativeConfigException("Unable to parse configuration input stream", e); + } return Objects.requireNonNull(model, "Unable to map configuration to model"); } diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java index 2aacbfcf8f1..a4742b32ddf 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java @@ -71,7 +71,12 @@ void loadFirstOrConfiguredUsesFirstProviderAndWarnsWhenMultipleAreUnconfigured() ""); assertThat(selected).isSameAs(firstProvider); - logs.assertContains("Multiple SelectionTestSpi providers found."); + assertThat( + logs.assertContains( + event -> event.getMessage().startsWith("Multiple {0} providers found."), + "Expected parameterized multiple-provider warning") + .getArguments()) + .containsExactly("SelectionTestSpi", SelectionTestSpi.class.getName()); } @Test From c2ebc567e06efbb012136f0e2a974215d71b5532 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Thu, 12 Nov 2026 22:30:11 +0530 Subject: [PATCH 11/12] feat: update readme doc Signed-off-by: Ashish Thakur --- sdk-extensions/declarative-config/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index 3312d810e1b..cbe19d87166 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -42,12 +42,13 @@ dependency and add Jackson 3 Databind. This Gradle example uses Jackson 3.2.3: ```kotlin dependencies { - implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config") { + implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config:$version") { exclude( group = "io.opentelemetry", module = "opentelemetry-sdk-extension-declarative-config-jackson2", ) } + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:$version") implementation("tools.jackson.core:jackson-databind:3.2.3") } ``` @@ -101,12 +102,13 @@ dependency: ```kotlin dependencies { - implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config") { + implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config:$version") { exclude( group = "io.opentelemetry", module = "opentelemetry-sdk-extension-declarative-config-snakeyaml", ) } + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:$version") } ``` From a72d54f767623e7754d2102e6bae80b10b390acb Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 12 Nov 2026 16:42:49 -0600 Subject: [PATCH 12/12] Final tweaks --- README.md | 2 + .../all/NoSharedInternalCodeTest.java | 2 +- integration-tests/osgi/build.gradle.kts | 2 +- .../DeclarativeConfigurationMapper.java | 12 +- .../DeclarativeConfigurationYamlParser.java | 12 +- .../build.gradle.kts | 1 + ...JacksonDeclarativeConfigurationMapper.java | 15 +- ...sonDeclarativeConfigurationMapperTest.java | 31 ++++ .../README.md | 4 +- .../build.gradle.kts | 5 +- .../gradle.properties | 0 ...akeYamlDeclarativeConfigurationParser.java | 10 +- ...nternal.DeclarativeConfigurationYamlParser | 1 + ...amlDeclarativeConfigurationParserTest.java | 29 +++ ...nternal.DeclarativeConfigurationYamlParser | 1 - sdk-extensions/declarative-config/README.md | 8 +- .../declarative-config/build.gradle.kts | 4 +- .../DeclarativeConfiguration.java | 17 +- .../DeclarativeConfigProviderLoader.java | 78 +++++--- .../DeclarativeConfigurationMapperUtil.java | 32 +--- .../DeclarativeConfigurationSpiTest.java | 113 ------------ .../DeclarativeConfigProviderLoaderTest.java | 170 ++++++++++-------- settings.gradle.kts | 2 +- 23 files changed, 253 insertions(+), 298 deletions(-) create mode 100644 sdk-extensions/declarative-config-jackson2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapperTest.java rename sdk-extensions/{declarative-config-snakeyaml => declarative-config-snakeyaml-engine2}/README.md (78%) rename sdk-extensions/{declarative-config-snakeyaml => declarative-config-snakeyaml-engine2}/build.gradle.kts (75%) rename sdk-extensions/{declarative-config-snakeyaml => declarative-config-snakeyaml-engine2}/gradle.properties (100%) rename sdk-extensions/{declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml => declarative-config-snakeyaml-engine2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2}/internal/SnakeYamlDeclarativeConfigurationParser.java (95%) create mode 100644 sdk-extensions/declarative-config-snakeyaml-engine2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser create mode 100644 sdk-extensions/declarative-config-snakeyaml-engine2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParserTest.java delete mode 100644 sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser delete mode 100644 sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java diff --git a/README.md b/README.md index c7e749bc279..4d1b5c244f3 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ Extensions to the OpenTelemetry SDK. | [SDK Autoconfigure](./sdk-extensions/autoconfigure) | Autoconfigure OpenTelemetry SDK from env vars, system properties, and SPI | `opentelemetry-sdk-extension-autoconfigure` | 1.68.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-extension-autoconfigure.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-extension-autoconfigure) | | [SDK Autoconfigure SPI](./sdk-extensions/autoconfigure-spi) | Service Provider Interface (SPI) definitions for autoconfigure | `opentelemetry-sdk-extension-autoconfigure-spi` | 1.68.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-extension-autoconfigure-spi.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-extension-autoconfigure-spi) | | [SDK Declarative Config](./sdk-extensions/declarative-config) | Declarative config implementation for YAML-based SDK configuration | `opentelemetry-sdk-extension-declarative-config` | 1.68.0-alpha | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-extension-declarative-config.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-extension-declarative-config) | +| [SDK Declarative Config Jackson 2 Mapper](./sdk-extensions/declarative-config-jackson2) | Jackson 2 model mapper for declarative config | `opentelemetry-sdk-extension-declarative-config-jackson2` | TODO | TODO | +| [SDK Declarative Config SnakeYAML Engine 2 Parser](./sdk-extensions/declarative-config-snakeyaml-engine2) | SnakeYAML Engine 2 YAML parser for declarative config | `opentelemetry-sdk-extension-declarative-config-snakeyaml-engine2` | TODO | TODO | | [SDK Jaeger Remote Sampler Extension](./sdk-extensions/jaeger-remote-sampler) | Sampler which obtains sampling configuration from remote Jaeger server | `opentelemetry-sdk-extension-jaeger-remote-sampler` | 1.68.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-extension-jaeger-remote-sampler.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-extension-jaeger-remote-sampler) | | [SDK Incubator](./sdk-extensions/incubator) | SDK incubator, including YAML based view configuration, LeakDetectingSpanProcessor | `opentelemetry-sdk-extension-incubator` | 1.68.0-alpha | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-extension-incubator.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-extension-incubator) | diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java index a696cd021dd..84cd7f9ce08 100644 --- a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java +++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java @@ -47,7 +47,7 @@ class NoSharedInternalCodeTest { "opentelemetry-sdk-extension-autoconfigure", "opentelemetry-sdk-extension-declarative-config", "opentelemetry-sdk-extension-declarative-config-jackson2", - "opentelemetry-sdk-extension-declarative-config-snakeyaml", + "opentelemetry-sdk-extension-declarative-config-snakeyaml-engine2", "opentelemetry-sdk-extension-incubator", "opentelemetry-sdk-extension-jaeger-remote-sampler", "opentelemetry-exporter-otlp", diff --git a/integration-tests/osgi/build.gradle.kts b/integration-tests/osgi/build.gradle.kts index 547a81cba1d..3db229d7b2c 100644 --- a/integration-tests/osgi/build.gradle.kts +++ b/integration-tests/osgi/build.gradle.kts @@ -294,7 +294,7 @@ val autoconfigureDeclarativeConfigSuiteTask = registerOsgiSuite( "opentelemetry-extension-trace-propagators", "opentelemetry-sdk-extension-declarative-config", "opentelemetry-sdk-extension-declarative-config-jackson2", - "opentelemetry-sdk-extension-declarative-config-snakeyaml", + "opentelemetry-sdk-extension-declarative-config-snakeyaml-engine2", ), serviceLoaderProvides = listOf( "io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider", diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java index cb0b9451581..f22e66978a3 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationMapper.java @@ -12,16 +12,8 @@ /** * Converts parsed configuration values to declarative configuration models and generic maps. * - *

Implementations are discovered with {@link java.util.ServiceLoader}. If exactly one - * implementation is available, it is used. If multiple implementations are available, set a system - * property or environment variable whose name is this interface's fully qualified class name and - * whose value is the fully qualified class name of the desired implementation. If no implementation - * is selected, the first discovered implementation is used and a warning is logged. The Jackson 2 - * implementation is supplied by the {@code opentelemetry-sdk-extension-declarative-config-jackson2} - * runtime adapter, which is included by default with declarative config and can be excluded when - * using a custom mapper. Implementations must preserve the mapping behavior of the declarative - * configuration models, including extension properties and null values, and throw {@link - * DeclarativeConfigException} when conversion fails. + *

Implementations must preserve the models' mapping semantics, including extension properties + * and null values. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java index 7b90687b763..079bfb431fe 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DeclarativeConfigurationYamlParser.java @@ -12,20 +12,10 @@ /** * Parses a declarative configuration YAML stream into generic Java objects. * - *

Implementations are discovered with {@link java.util.ServiceLoader}. If exactly one - * implementation is available, it is used. If multiple implementations are available, set a system - * property or environment variable whose name is this interface's fully qualified class name and - * whose value is the fully qualified class name of the desired implementation. If no implementation - * is selected, the first discovered implementation is used and a warning is logged. The SnakeYAML - * implementation is supplied by the {@code - * opentelemetry-sdk-extension-declarative-config-snakeyaml} runtime adapter, which is included by - * default with declarative config and can be excluded when using a custom parser. - * *

Implementations must apply environment variable and system property substitution according to * the OpenTelemetry Configuration Data Model specification. Substitution is performed on scalar * values, not mapping keys. For unquoted scalar values, substituted text must be parsed using the - * same YAML scalar rules as the original text. Implementations throw {@link - * DeclarativeConfigException} when parsing fails. + * same YAML scalar rules as the original text. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. diff --git a/sdk-extensions/declarative-config-jackson2/build.gradle.kts b/sdk-extensions/declarative-config-jackson2/build.gradle.kts index abac59989a5..928b2fe34b5 100644 --- a/sdk-extensions/declarative-config-jackson2/build.gradle.kts +++ b/sdk-extensions/declarative-config-jackson2/build.gradle.kts @@ -13,5 +13,6 @@ otelJava.osgiServiceLoaderProvides.set(listOf( dependencies { api(project(":sdk-extensions:autoconfigure-spi")) + implementation(project(":api:incubator")) implementation("com.fasterxml.jackson.core:jackson-databind") } diff --git a/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java index ad1a3ff5260..a71354150e4 100644 --- a/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java +++ b/sdk-extensions/declarative-config-jackson2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapper.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal; import com.fasterxml.jackson.core.type.TypeReference; +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; @@ -18,13 +19,21 @@ public final class JacksonDeclarativeConfigurationMapper implements DeclarativeC @Override public T convert(Object value, Class targetType) { - return JacksonModelMapper.MAPPER.convertValue(value, targetType); + try { + return JacksonModelMapper.MAPPER.convertValue(value, targetType); + } catch (IllegalArgumentException e) { + throw new DeclarativeConfigException("Unable to convert value to " + targetType, e); + } } @Nullable @Override public Map toMap(Object value) { - return JacksonModelMapper.MAPPER.convertValue( - value, new TypeReference>() {}); + try { + return JacksonModelMapper.MAPPER.convertValue( + value, new TypeReference>() {}); + } catch (IllegalArgumentException e) { + throw new DeclarativeConfigException("Unable to convert value to a map", e); + } } } diff --git a/sdk-extensions/declarative-config-jackson2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapperTest.java b/sdk-extensions/declarative-config-jackson2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapperTest.java new file mode 100644 index 00000000000..e4e365bffcd --- /dev/null +++ b/sdk-extensions/declarative-config-jackson2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/jackson2/internal/JacksonDeclarativeConfigurationMapperTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; +import org.junit.jupiter.api.Test; + +class JacksonDeclarativeConfigurationMapperTest { + + private final JacksonDeclarativeConfigurationMapper mapper = + new JacksonDeclarativeConfigurationMapper(); + + @Test + void convertWrapsJacksonException() { + assertThatThrownBy(() -> mapper.convert("not-an-integer", Integer.class)) + .isInstanceOf(DeclarativeConfigException.class) + .hasCauseInstanceOf(IllegalArgumentException.class); + } + + @Test + void toMapWrapsJacksonException() { + assertThatThrownBy(() -> mapper.toMap(42)) + .isInstanceOf(DeclarativeConfigException.class) + .hasCauseInstanceOf(IllegalArgumentException.class); + } +} diff --git a/sdk-extensions/declarative-config-snakeyaml/README.md b/sdk-extensions/declarative-config-snakeyaml-engine2/README.md similarity index 78% rename from sdk-extensions/declarative-config-snakeyaml/README.md rename to sdk-extensions/declarative-config-snakeyaml-engine2/README.md index 062519380ca..2f31842550d 100644 --- a/sdk-extensions/declarative-config-snakeyaml/README.md +++ b/sdk-extensions/declarative-config-snakeyaml-engine2/README.md @@ -1,7 +1,7 @@ -# SnakeYAML Declarative Configuration Parser +# SnakeYAML Engine 2 Declarative Configuration Parser This module provides the default `DeclarativeConfigurationYamlParser` implementation using -SnakeYAML Engine. It is included as a runtime dependency by +SnakeYAML Engine 2. It is included as a runtime dependency by `opentelemetry-sdk-extension-declarative-config`. Applications that provide a custom YAML parser can exclude this module from the declarative config diff --git a/sdk-extensions/declarative-config-snakeyaml/build.gradle.kts b/sdk-extensions/declarative-config-snakeyaml-engine2/build.gradle.kts similarity index 75% rename from sdk-extensions/declarative-config-snakeyaml/build.gradle.kts rename to sdk-extensions/declarative-config-snakeyaml-engine2/build.gradle.kts index 4ba78636b70..6852ce0233f 100644 --- a/sdk-extensions/declarative-config-snakeyaml/build.gradle.kts +++ b/sdk-extensions/declarative-config-snakeyaml-engine2/build.gradle.kts @@ -4,8 +4,8 @@ plugins { id("otel.animalsniffer-conventions") } -description = "OpenTelemetry SDK Declarative Config SnakeYAML Parser" -otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml") +description = "OpenTelemetry SDK Declarative Config SnakeYAML Engine 2 Parser" +otelJava.moduleName.set("io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyamlengine2") otelJava.requireSuppressWarningsExplanation.set(false) otelJava.osgiServiceLoaderProvides.set(listOf( "io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser", @@ -13,5 +13,6 @@ otelJava.osgiServiceLoaderProvides.set(listOf( dependencies { api(project(":sdk-extensions:autoconfigure-spi")) + implementation(project(":api:incubator")) implementation("org.snakeyaml:snakeyaml-engine") } diff --git a/sdk-extensions/declarative-config-snakeyaml/gradle.properties b/sdk-extensions/declarative-config-snakeyaml-engine2/gradle.properties similarity index 100% rename from sdk-extensions/declarative-config-snakeyaml/gradle.properties rename to sdk-extensions/declarative-config-snakeyaml-engine2/gradle.properties diff --git a/sdk-extensions/declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml/internal/SnakeYamlDeclarativeConfigurationParser.java b/sdk-extensions/declarative-config-snakeyaml-engine2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParser.java similarity index 95% rename from sdk-extensions/declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml/internal/SnakeYamlDeclarativeConfigurationParser.java rename to sdk-extensions/declarative-config-snakeyaml-engine2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParser.java index 0fd5ef1e4c0..0289dd86cf0 100644 --- a/sdk-extensions/declarative-config-snakeyaml/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyaml/internal/SnakeYamlDeclarativeConfigurationParser.java +++ b/sdk-extensions/declarative-config-snakeyaml-engine2/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParser.java @@ -3,8 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml.internal; +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyamlengine2.internal; +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.io.InputStream; import java.util.Map; @@ -18,6 +19,7 @@ import org.snakeyaml.engine.v2.api.YamlUnicodeReader; import org.snakeyaml.engine.v2.common.ScalarStyle; import org.snakeyaml.engine.v2.composer.Composer; +import org.snakeyaml.engine.v2.exceptions.YamlEngineException; import org.snakeyaml.engine.v2.nodes.MappingNode; import org.snakeyaml.engine.v2.nodes.Node; import org.snakeyaml.engine.v2.nodes.ScalarNode; @@ -49,7 +51,11 @@ public Object parse( Map systemProperties) { LoadSettings settings = LoadSettings.builder().setSchema(new CoreSchema()).build(); Load yaml = new EnvLoad(settings, environmentVariables, systemProperties); - return yaml.loadFromInputStream(configuration); + try { + return yaml.loadFromInputStream(configuration); + } catch (YamlEngineException e) { + throw new DeclarativeConfigException("Unable to parse configuration input stream", e); + } } private static final class EnvLoad extends Load { diff --git a/sdk-extensions/declarative-config-snakeyaml-engine2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config-snakeyaml-engine2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser new file mode 100644 index 00000000000..0f77cf3d548 --- /dev/null +++ b/sdk-extensions/declarative-config-snakeyaml-engine2/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser @@ -0,0 +1 @@ +io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyamlengine2.internal.SnakeYamlDeclarativeConfigurationParser diff --git a/sdk-extensions/declarative-config-snakeyaml-engine2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParserTest.java b/sdk-extensions/declarative-config-snakeyaml-engine2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParserTest.java new file mode 100644 index 00000000000..04174e4ebb4 --- /dev/null +++ b/sdk-extensions/declarative-config-snakeyaml-engine2/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/snakeyamlengine2/internal/SnakeYamlDeclarativeConfigurationParserTest.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyamlengine2.internal; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.snakeyaml.engine.v2.exceptions.YamlEngineException; + +class SnakeYamlDeclarativeConfigurationParserTest { + + @Test + void wrapsSnakeYamlException() { + ByteArrayInputStream input = + new ByteArrayInputStream("key: [unclosed".getBytes(StandardCharsets.UTF_8)); + assertThatThrownBy(() -> + new SnakeYamlDeclarativeConfigurationParser() + .parse(input, Collections.emptyMap(), Collections.emptyMap())) + .isInstanceOf(DeclarativeConfigException.class) + .hasCauseInstanceOf(YamlEngineException.class); + } +} diff --git a/sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser b/sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser deleted file mode 100644 index 7936676ed7c..00000000000 --- a/sdk-extensions/declarative-config-snakeyaml/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser +++ /dev/null @@ -1 +0,0 @@ -io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml.internal.SnakeYamlDeclarativeConfigurationParser diff --git a/sdk-extensions/declarative-config/README.md b/sdk-extensions/declarative-config/README.md index cbe19d87166..4ef3f48ed95 100644 --- a/sdk-extensions/declarative-config/README.md +++ b/sdk-extensions/declarative-config/README.md @@ -92,12 +92,12 @@ qualified class name: When multiple providers are present and neither setting is set, the first discovered provider is used and a warning is logged. If the setting names a provider that is not present, declarative -configuration fails with an `IllegalStateException`. +configuration fails with a `DeclarativeConfigException`. ## Use a custom YAML parser To replace the default SnakeYAML parser, exclude the -`opentelemetry-sdk-extension-declarative-config-snakeyaml` adapter from the declarative config +`opentelemetry-sdk-extension-declarative-config-snakeyaml-engine2` adapter from the declarative config dependency: ```kotlin @@ -105,7 +105,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-sdk-extension-declarative-config:$version") { exclude( group = "io.opentelemetry", - module = "opentelemetry-sdk-extension-declarative-config-snakeyaml", + module = "opentelemetry-sdk-extension-declarative-config-snakeyaml-engine2", ) } implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:$version") @@ -135,4 +135,4 @@ provider's fully qualified class name: When multiple providers are present and neither setting is set, the first discovered provider is used and a warning is logged. If the setting names a provider that is not present, declarative -configuration fails with an `IllegalStateException`. +configuration fails with a `DeclarativeConfigException`. diff --git a/sdk-extensions/declarative-config/build.gradle.kts b/sdk-extensions/declarative-config/build.gradle.kts index 5ddbe8b5dd1..c671186304e 100644 --- a/sdk-extensions/declarative-config/build.gradle.kts +++ b/sdk-extensions/declarative-config/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { api(project(":api:incubator")) compileOnly(project(":sdk-extensions:autoconfigure-spi")) runtimeOnly(project(":sdk-extensions:declarative-config-jackson2")) - runtimeOnly(project(":sdk-extensions:declarative-config-snakeyaml")) + runtimeOnly(project(":sdk-extensions:declarative-config-snakeyaml-engine2")) // Needed for composable samplers implementation(project(":sdk-extensions:incubator")) @@ -49,7 +49,7 @@ dependencies { testImplementation("io.opentelemetry.instrumentation:opentelemetry-resources") testImplementation("com.linecorp.armeria:armeria-junit5") testRuntimeOnly(project(":sdk-extensions:declarative-config-jackson2")) - testRuntimeOnly(project(":sdk-extensions:declarative-config-snakeyaml")) + testRuntimeOnly(project(":sdk-extensions:declarative-config-snakeyaml-engine2")) // testImplementation("com.google.guava:guava-testlib") testImplementation("nl.jqno.equalsverifier:equalsverifier") diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java index 18974ad90ee..4833bfb159c 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfiguration.java @@ -46,23 +46,9 @@ public final class DeclarativeConfiguration { private static final Logger logger = Logger.getLogger(DeclarativeConfiguration.class.getName()); private static final ComponentLoader DEFAULT_COMPONENT_LOADER = ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); - private static final DeclarativeConfigurationYamlParser YAML_PARSER = - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - DeclarativeConfigurationYamlParser.class, - DeclarativeConfiguration::missingYamlParser, - DeclarativeConfigurationYamlParser.class.getName()); private DeclarativeConfiguration() {} - private static DeclarativeConfigurationYamlParser missingYamlParser() { - return (configuration, environmentVariables, systemProperties) -> { - throw new DeclarativeConfigException( - "No DeclarativeConfigurationYamlParser provider is available. Add the optional " - + "opentelemetry-sdk-extension-declarative-config-snakeyaml artifact or register a " - + "custom parser provider."); - }; - } - /** * Combines {@link #parse(InputStream)} and {@link #create(OpenTelemetryConfigurationModel)}. * @@ -162,7 +148,8 @@ static Object loadYaml( Map environmentVariables, Map systemProperties) { try { - return YAML_PARSER.parse(inputStream, environmentVariables, systemProperties); + return DeclarativeConfigProviderLoader.getYamlParser() + .parse(inputStream, environmentVariables, systemProperties); } catch (DeclarativeConfigException e) { throw e; } catch (RuntimeException e) { diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java index e8bb264b92a..e586a37e24d 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoader.java @@ -5,11 +5,16 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.common.impl.ConfigUtil; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; import java.util.List; -import java.util.function.Supplier; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -22,40 +27,71 @@ public final class DeclarativeConfigProviderLoader { private static final Logger LOGGER = Logger.getLogger(DeclarativeConfigProviderLoader.class.getName()); + // TODO(jack-berg): Allow callers to supply the ComponentLoader used to resolve parser and mapper + // providers. private static final ComponentLoader COMPONENT_LOADER = ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader()); + private static final AtomicReference MAPPER = + new AtomicReference<>(); + private static final AtomicReference YAML_PARSER = + new AtomicReference<>(); + private DeclarativeConfigProviderLoader() {} - /** - * Load the first discovered implementation of {@code spiClass}, or the configured implementation - * if multiple are registered. Create the default provider when none are registered. - * - * @param spiClass the SPI type - * @param defaultProvider supplies the default provider when none are registered - * @param configProperty the system property or environment variable used to select a provider - * @param the SPI type - * @return the selected provider - */ - public static T loadFirstOrConfigured( - Class spiClass, Supplier defaultProvider, String configProperty) { - return loadFirstOrConfigured( - spiClass, - defaultProvider, - configProperty, + /** Returns the cached model mapper, resolving it on first use. */ + public static DeclarativeConfigurationMapper getMapper() { + return resolveProvider( + MAPPER, + DeclarativeConfigurationMapper.class, + COMPONENT_LOADER, + "No DeclarativeConfigurationMapper provider is available. Add the optional " + + "opentelemetry-sdk-extension-declarative-config-jackson2 artifact or register a " + + "custom mapper provider."); + } + + /** Returns the cached YAML parser, resolving it on first use. */ + public static DeclarativeConfigurationYamlParser getYamlParser() { + return resolveProvider( + YAML_PARSER, + DeclarativeConfigurationYamlParser.class, COMPONENT_LOADER, - ConfigUtil.getString(configProperty, "")); + "No DeclarativeConfigurationYamlParser provider is available. Add the optional " + + "opentelemetry-sdk-extension-declarative-config-snakeyaml-engine2 artifact or register a " + + "custom parser provider."); + } + + static T resolveProvider( + AtomicReference providerRef, + Class spiClass, + ComponentLoader componentLoader, + String missingProviderMessage) { + T provider = providerRef.get(); + if (provider != null) { + return provider; + } + String configProperty = spiClass.getName(); + T candidate = + loadFirstOrConfigured( + spiClass, + missingProviderMessage, + configProperty, + componentLoader, + ConfigUtil.getString(configProperty, "")); + return providerRef.compareAndSet(null, candidate) + ? candidate + : requireNonNull(providerRef.get()); } - static T loadFirstOrConfigured( + private static T loadFirstOrConfigured( Class spiClass, - Supplier defaultProvider, + String missingProviderMessage, String configProperty, ComponentLoader componentLoader, String configuredProvider) { List providers = ComponentLoader.loadList(componentLoader, spiClass); if (providers.isEmpty()) { - return defaultProvider.get(); + throw new DeclarativeConfigException(missingProviderMessage); } if (providers.size() == 1) { return providers.get(0); diff --git a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java index 57dfaefcc6b..d278209ccf8 100644 --- a/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java +++ b/sdk-extensions/declarative-config/src/main/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigurationMapperUtil.java @@ -6,7 +6,6 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; import java.util.Map; import javax.annotation.Nullable; @@ -16,12 +15,6 @@ */ public final class DeclarativeConfigurationMapperUtil { - private static final DeclarativeConfigurationMapper MAPPER = - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - DeclarativeConfigurationMapper.class, - MissingMapper::new, - DeclarativeConfigurationMapper.class.getName()); - private DeclarativeConfigurationMapperUtil() {} /** @@ -34,7 +27,7 @@ private DeclarativeConfigurationMapperUtil() {} */ public static T convert(Object value, Class targetType) { try { - return MAPPER.convert(value, targetType); + return DeclarativeConfigProviderLoader.getMapper().convert(value, targetType); } catch (DeclarativeConfigException e) { throw e; } catch (RuntimeException e) { @@ -51,32 +44,11 @@ public static T convert(Object value, Class targetType) { @Nullable public static Map toMap(Object value) { try { - return MAPPER.toMap(value); + return DeclarativeConfigProviderLoader.getMapper().toMap(value); } catch (DeclarativeConfigException e) { throw e; } catch (RuntimeException e) { throw new DeclarativeConfigException("Unable to convert value to a map", e); } } - - private static final class MissingMapper implements DeclarativeConfigurationMapper { - - @Override - public T convert(Object value, Class targetType) { - throw missingMapperException(); - } - - @Nullable - @Override - public Map toMap(Object value) { - throw missingMapperException(); - } - - private static DeclarativeConfigException missingMapperException() { - return new DeclarativeConfigException( - "No DeclarativeConfigurationMapper provider is available. Add the optional " - + "opentelemetry-sdk-extension-declarative-config-jackson2 artifact or register a " - + "custom mapper provider."); - } - } } diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java deleted file mode 100644 index 865e0ca3cf7..00000000000 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/DeclarativeConfigurationSpiTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure.declarativeconfig; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.common.ComponentLoader; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoader; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationMapper; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DeclarativeConfigurationYamlParser; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class DeclarativeConfigurationSpiTest { - - @Test - void defaultProvidersAreRegisteredWithServiceLoader() { - assertThat(loadParser().getClass().getName()) - .isEqualTo( - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.snakeyaml.internal." - + "SnakeYamlDeclarativeConfigurationParser"); - DeclarativeConfigurationMapper mapper = loadMapper(); - assertThat(mapper.getClass().getName()) - .isEqualTo( - "io.opentelemetry.sdk.autoconfigure.declarativeconfig.jackson2.internal." - + "JacksonDeclarativeConfigurationMapper"); - } - - @Test - void yamlParserPerformsEnvironmentAndSystemPropertySubstitution() { - String yaml = "boolean: ${BOOL}\nquoted: '${BOOL}'\nproperty: ${sys:port}\n"; - Map environmentVariables = Collections.singletonMap("BOOL", "true"); - Map systemProperties = new HashMap<>(); - systemProperties.put("port", "4317"); - - Object parsed = - loadParser() - .parse( - new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), - environmentVariables, - systemProperties); - - assertThat(parsed).isEqualTo(mapOf("boolean", true, "quoted", "true", "property", 4317)); - } - - @Test - void mapperConvertsModelsAndGenericMaps() { - Map input = new HashMap<>(); - input.put("file_format", "1.2"); - input.put("disabled", true); - - DeclarativeConfigurationMapper mapper = loadMapper(); - OpenTelemetryConfigurationModel model = - mapper.convert(input, OpenTelemetryConfigurationModel.class); - - assertThat(model.getFileFormat()).isEqualTo("1.2"); - assertThat(model.getDisabled()).isTrue(); - assertThat(mapper.toMap(model)) - .containsEntry("file_format", "1.2") - .containsEntry("disabled", true); - } - - @Test - void topLevelParseAndPropertiesUseRegisteredProviders() { - String yaml = "file_format: '1.2'\ndisabled: true\n"; - ByteArrayInputStream input = new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)); - - OpenTelemetryConfigurationModel model = DeclarativeConfiguration.parse(input); - DeclarativeConfigProperties properties = - DeclarativeConfiguration.toConfigProperties( - new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); - - assertThat(model.getFileFormat()).isEqualTo("1.2"); - assertThat(model.getDisabled()).isTrue(); - assertThat(properties.getString("file_format")).isEqualTo("1.2"); - assertThat(properties.getBoolean("disabled")).isTrue(); - } - - private static Map mapOf(Object... entries) { - Map result = new HashMap<>(); - for (int i = 0; i < entries.length; i += 2) { - result.put((String) entries[i], entries[i + 1]); - } - return result; - } - - private static DeclarativeConfigurationMapper loadMapper() { - return ComponentLoader.loadList( - ComponentLoader.forClassLoader(DeclarativeConfigurationSpiTest.class.getClassLoader()), - DeclarativeConfigurationMapper.class) - .stream() - .findFirst() - .orElseThrow(() -> new AssertionError("Expected a mapper provider to be registered")); - } - - private static DeclarativeConfigurationYamlParser loadParser() { - return DeclarativeConfigProviderLoader.loadFirstOrConfigured( - DeclarativeConfigurationYamlParser.class, - () -> { - throw new AssertionError("Expected a YAML parser provider to be registered"); - }, - DeclarativeConfigurationYamlParser.class.getName()); - } -} diff --git a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java index a4742b32ddf..5cd4945aadd 100644 --- a/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java +++ b/sdk-extensions/declarative-config/src/test/java/io/opentelemetry/sdk/autoconfigure/declarativeconfig/internal/DeclarativeConfigProviderLoaderTest.java @@ -6,111 +6,123 @@ package io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.github.netmikey.logunit.api.LogCapturer; +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.common.ComponentLoader; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.ClearSystemProperty; +@ClearSystemProperty( + key = + "io.opentelemetry.sdk.autoconfigure.declarativeconfig.internal.DeclarativeConfigProviderLoaderTest$TestSpi") class DeclarativeConfigProviderLoaderTest { + private static final TestSpi FIRST = new FirstProvider(); + private static final TestSpi SECOND = new SecondProvider(); + @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(DeclarativeConfigProviderLoader.class); + private final ComponentLoader loader = mock(ComponentLoader.class); + + @Test + void cachesSelectedProviders() { + assertThat(DeclarativeConfigProviderLoader.getMapper()) + .isSameAs(DeclarativeConfigProviderLoader.getMapper()); + assertThat(DeclarativeConfigProviderLoader.getYamlParser()) + .isSameAs(DeclarativeConfigProviderLoader.getYamlParser()); + } + + @Test + void retriesFailedResolutionAndCachesSuccess() { + AtomicReference ref = new AtomicReference<>(); + when(loader.load(TestSpi.class)) + .thenThrow(new IllegalStateException("lookup failed")) + .thenReturn(Collections.singletonList(FIRST)); + + assertThatThrownBy(() -> + DeclarativeConfigProviderLoader.resolveProvider(ref, TestSpi.class, loader, "missing")) + .isInstanceOf(IllegalStateException.class); + assertThat(ref.get()).isNull(); + assertThat( + DeclarativeConfigProviderLoader.resolveProvider(ref, TestSpi.class, loader, "missing")) + .isSameAs(FIRST); + assertThat( + DeclarativeConfigProviderLoader.resolveProvider(ref, TestSpi.class, loader, "missing")) + .isSameAs(FIRST); + verify(loader, times(2)).load(TestSpi.class); + } + @Test - void loadFirstOrConfiguredUsesDefaultWhenNoProvidersAreRegistered() { - AtomicBoolean defaultProviderCalled = new AtomicBoolean(); - Supplier defaultProvider = - () -> { - defaultProviderCalled.set(true); - return new FirstSelectionTestProvider(); - }; - - SelectionTestSpi selected = - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - SelectionTestSpi.class, - defaultProvider, - SelectionTestSpi.class.getName(), - componentLoader(), - ""); - - assertThat(defaultProviderCalled).isTrue(); - assertThat(selected).isInstanceOf(FirstSelectionTestProvider.class); + void rejectsMissingProvider() { + when(loader.load(TestSpi.class)).thenReturn(Collections.emptyList()); + + assertThatThrownBy(() -> + DeclarativeConfigProviderLoader.resolveProvider( + new AtomicReference<>(), TestSpi.class, loader, "missing")) + .isInstanceOf(DeclarativeConfigException.class) + .hasMessage("missing"); } @Test - void loadFirstOrConfiguredUsesSingleProvider() { - FirstSelectionTestProvider provider = new FirstSelectionTestProvider(); - - SelectionTestSpi selected = - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - SelectionTestSpi.class, - FirstSelectionTestProvider::new, - SelectionTestSpi.class.getName(), - componentLoader(provider), - ""); - - assertThat(selected).isSameAs(provider); + void selectsSingleProvider() { + when(loader.load(TestSpi.class)).thenReturn(Collections.singletonList(FIRST)); + + assertThat( + DeclarativeConfigProviderLoader.resolveProvider( + new AtomicReference<>(), TestSpi.class, loader, "missing")) + .isSameAs(FIRST); } @Test - void loadFirstOrConfiguredUsesFirstProviderAndWarnsWhenMultipleAreUnconfigured() { - FirstSelectionTestProvider firstProvider = new FirstSelectionTestProvider(); - SecondSelectionTestProvider secondProvider = new SecondSelectionTestProvider(); - - SelectionTestSpi selected = - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - SelectionTestSpi.class, - FirstSelectionTestProvider::new, - SelectionTestSpi.class.getName(), - componentLoader(firstProvider, secondProvider), - ""); - - assertThat(selected).isSameAs(firstProvider); + void selectsFirstProviderAndWarns() { + when(loader.load(TestSpi.class)).thenReturn(Arrays.asList(FIRST, SECOND)); + assertThat( - logs.assertContains( - event -> event.getMessage().startsWith("Multiple {0} providers found."), - "Expected parameterized multiple-provider warning") - .getArguments()) - .containsExactly("SelectionTestSpi", SelectionTestSpi.class.getName()); + DeclarativeConfigProviderLoader.resolveProvider( + new AtomicReference<>(), TestSpi.class, loader, "missing")) + .isSameAs(FIRST); + logs.assertContains( + event -> event.getMessage().startsWith("Multiple {0} providers found."), + "Expected multiple-provider warning"); } @Test - void loadFirstOrConfiguredUsesConfiguredProviderWhenMultipleArePresent() { - FirstSelectionTestProvider firstProvider = new FirstSelectionTestProvider(); - SecondSelectionTestProvider selectedProvider = new SecondSelectionTestProvider(); - - SelectionTestSpi selected = - DeclarativeConfigProviderLoader.loadFirstOrConfigured( - SelectionTestSpi.class, - FirstSelectionTestProvider::new, - SelectionTestSpi.class.getName(), - componentLoader(firstProvider, selectedProvider), - selectedProvider.getClass().getName()); - - assertThat(selected).isSameAs(selectedProvider); + void selectsConfiguredProvider() { + System.setProperty(TestSpi.class.getName(), SECOND.getClass().getName()); + when(loader.load(TestSpi.class)).thenReturn(Arrays.asList(FIRST, SECOND)); + + assertThat( + DeclarativeConfigProviderLoader.resolveProvider( + new AtomicReference<>(), TestSpi.class, loader, "missing")) + .isSameAs(SECOND); } - private static ComponentLoader componentLoader(Object... providers) { - return new ComponentLoader() { - @Override - public Iterable load(Class spiClass) { - List result = new ArrayList<>(); - for (Object provider : providers) { - result.add(spiClass.cast(provider)); - } - return result; - } - }; + @Test + void rejectsUnknownConfiguredProvider() { + System.setProperty(TestSpi.class.getName(), "missing.Provider"); + when(loader.load(TestSpi.class)).thenReturn(Arrays.asList(FIRST, SECOND)); + + assertThatThrownBy(() -> + DeclarativeConfigProviderLoader.resolveProvider( + new AtomicReference<>(), TestSpi.class, loader, "missing")) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("missing.Provider"); } - private interface SelectionTestSpi {} + private interface TestSpi {} - private static final class FirstSelectionTestProvider implements SelectionTestSpi {} + private static final class FirstProvider implements TestSpi {} - private static final class SecondSelectionTestProvider implements SelectionTestSpi {} + private static final class SecondProvider implements TestSpi {} } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0bc15b70da6..69757129954 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,7 +70,7 @@ include(":sdk-extensions:autoconfigure") include(":sdk-extensions:autoconfigure-spi") include(":sdk-extensions:declarative-config") include(":sdk-extensions:declarative-config-jackson2") -include(":sdk-extensions:declarative-config-snakeyaml") +include(":sdk-extensions:declarative-config-snakeyaml-engine2") include(":sdk-extensions:incubator") include(":sdk-extensions:jaeger-remote-sampler") include(":testing-internal")