From 4f278861d17d5057ce11bf1082e6c90015c555e7 Mon Sep 17 00:00:00 2001 From: Jonathan Fuerth Date: Thu, 14 May 2026 16:41:46 -0400 Subject: [PATCH 1/6] [CU-86b9ybg6d] Bump parent POM to dnastack-spring-boot-parent 4.0.0-rc2 Adopt the java-libraries-monorepo parent (4.0.0-rc2-9-gd79f5a8). The parent now manages versions for: spring-boot 3.5.14, spring-cloud 2024.0.3, dnastack-token-validator, spring-boot-audit-event-logger, dnastack-oauth-client-factory-spring-starter, logback-extensions, jdbi 3.52.1, resilience4j 2.4.0, zonky embedded-postgres, postgresql 42.7.11 (CVE-2026-42198), bouncycastle 1.84 (CVE-2026-5598). Drop the local Jackson 2.18.x pin (was needed when oauth-client-factory 1.0.5 used the removed-in-2.19 PropertyNamingStrategy.SNAKE_CASE). Switch micrometer-tracing-bridge-brave -> bridge-otel. The bridge-otel artifact comes from OTel; Brave is going away in the monorepo libs. Remove duplicate dependencyManagement entries that the new parent already provides (logback, jdbi BOM, spring-cloud BOM). Remove the audit-event-logger zipkin-reporter exclusions; those were needed for the 1.0.x lib and don't apply to the 4.x version. This commit only updates the POM and intentionally does not touch the source code yet; subsequent commits migrate the affected APIs (brave -> micrometer, OAuthClientFactory -> FeignClients, etc.) to keep the diff reviewable. Co-Authored-By: Claude Opus 4.7 (1M context) --- pom.xml | 62 ++++----------------------------------------------------- 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/pom.xml b/pom.xml index 9184f97..7f75491 100644 --- a/pom.xml +++ b/pom.xml @@ -5,9 +5,9 @@ 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 3.5.13 + com.dnastack.starter + spring-boot-parent + 4.0.0-rc2-9-gd79f5a8 @@ -18,35 +18,11 @@ - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - joda-time joda-time ${version.joda} - - org.jdbi - jdbi3-bom - pom - ${jdbi.version} - import - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - @@ -57,16 +33,7 @@ 3.2.0 3.0.0-M7 UTF-8 - - 1.0.18 - 1.0.20 - 1.0.5 - - 2.18.6 - 2021.0.9 - 3.49.5 4.12.0 359 32.1.3-jre @@ -76,10 +43,7 @@ 3.27.7 13.6 3.8.0 - 1.0.1 - 1.5.25 1.18.42 - 2.3.0 5.20.0 @@ -169,7 +133,7 @@ io.micrometer - micrometer-tracing-bridge-brave + micrometer-tracing-bridge-otel joda-time @@ -261,62 +225,44 @@ io.github.resilience4j resilience4j-retry - ${resilience4j.version} io.github.resilience4j resilience4j-core - ${resilience4j.version} com.dnastack dnastack-token-validator - ${dnastack-token-validator.version} com.dnastack spring-boot-audit-event-logger - ${audit-event-logger.version} - - - io.zipkin.reporter2 - zipkin-reporter-metrics-micrometer - - - io.zipkin.reporter2 - zipkin-reporter - - com.dnastack dnastack-oauth-client-factory-spring-starter - ${oauth-client-factory.version} com.dnastack logback-extensions - ${logback-extensions.version} io.zonky.test embedded-database-spring-test - 2.6.0 test io.zonky.test embedded-postgres - 2.1.1 test From 47d864a8540feabc3cc4fb36f985589923564f64 Mon Sep 17 00:00:00 2001 From: Jonathan Fuerth Date: Thu, 14 May 2026 16:46:35 -0400 Subject: [PATCH 2/6] [CU-86b9ybg6d] Use PropertyNamingStrategies for SNAKE_CASE / KebabCaseStrategy Jackson 2.19 removed the deprecated PropertyNamingStrategy.SNAKE_CASE constant and KebabCaseStrategy inner class. The replacements have lived in PropertyNamingStrategies since 2.12, so this is a drop-in swap: PropertyNamingStrategy.SNAKE_CASE -> PropertyNamingStrategies.SNAKE_CASE PropertyNamingStrategy.KebabCaseStrategy -> PropertyNamingStrategies.KebabCaseStrategy Touches DataConnectAuthRequest (JSON serialization of the auth challenge returned to clients), and the two tablesregistry config classes used by the deprecated tables-registry feature. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dataconnect/adapter/shared/DataConnectAuthRequest.java | 4 ++-- .../dataconnect/client/tablesregistry/OAuthClientConfig.java | 4 ++-- .../client/tablesregistry/TablesRegistryClientConfig.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/DataConnectAuthRequest.java b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/DataConnectAuthRequest.java index a009565..bea4f7d 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/DataConnectAuthRequest.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/DataConnectAuthRequest.java @@ -1,13 +1,13 @@ package com.dnastack.ga4gh.dataconnect.adapter.shared; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Value; import java.util.Map; @Value -@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) public class DataConnectAuthRequest { String key; String resourceType; diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/OAuthClientConfig.java b/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/OAuthClientConfig.java index 11e7b21..0220d78 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/OAuthClientConfig.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/OAuthClientConfig.java @@ -3,7 +3,7 @@ import com.dnastack.ga4gh.dataconnect.client.common.SimpleLogger; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import feign.Feign; import feign.Logger; import feign.codec.Encoder; @@ -53,7 +53,7 @@ public class OAuthClientConfig { Encoder encoder; private ObjectMapper mapper = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @Bean diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/TablesRegistryClientConfig.java b/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/TablesRegistryClientConfig.java index f443275..b041adc 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/TablesRegistryClientConfig.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/client/tablesregistry/TablesRegistryClientConfig.java @@ -5,7 +5,7 @@ import com.dnastack.ga4gh.dataconnect.client.tablesregistry.model.OAuthRequest; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import feign.Feign; import feign.Logger; import feign.RequestInterceptor; @@ -42,7 +42,7 @@ public class TablesRegistryClientConfig { private OAuthClientConfig oAuthClientConfig; private ObjectMapper mapper = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); private OAuthRequest getOAuthRequest() { From 4ff6760517784c7b2edc6e91b3c6ee87e6bd02bc Mon Sep 17 00:00:00 2001 From: Jonathan Fuerth Date: Thu, 14 May 2026 16:47:00 -0400 Subject: [PATCH 3/6] [CU-86b9ybg6d] Migrate Brave + OAuthClientFactory to Micrometer + FeignClients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Threads Micrometer Observation / OpenTelemetry through every previous Brave integration point, in line with the rest of the platform's 4.0 library migration: - brave.Tracing / brave.Tracer injection sites replaced with io.micrometer.tracing.Tracer and io.micrometer.observation.ObservationRegistry. Span creation rewritten with tracer.nextSpan().name(...).start(), tracer.withSpan(span), span.end(). The 128-bit traceId() string replaces brave's 16-char traceIdString() everywhere (X-Trino-Trace-Token, QueryJob.originalTraceId, GlobalControllerExceptionHandler trace_id response field). - JwtTokenParserFactory / PermissionCheckerFactory / TokenActionsHttpClientFactory migrated to their new (ObservationRegistry, ConnectionPool) signatures. Added a tokenValidatorConnectionPool bean and an OidcHttpClient bean; CachingIssuerPubKeyJwksResolver.create(issuerUri, oidcHttpClient) replaces the old constructor. - OAuthClientFactory.builderWithCommonSetup(...) callers (the collection-service and indexing-service Feign clients) migrated to FeignClients.newBuilder(config.withOverrides(...), feignClient, feignClient), with a per-resource OkHttp client built via OkHttpClients.buildOkHttpClient(name, observationRegistry, type) so metrics get a meaningful resource dimension. The same client is passed for the main and token slots — neither client has different network requirements from the other. - TrinoHttpClient.execute(): drop B3 single-format propagation through the X-Trino-Extra-Credential header. B3 isn't available without Brave; substituted W3C traceparent built from the current TraceContext so the SAC plugin / tables connector can still correlate downstream activity. X-Trino-Trace-Token continues to carry the raw traceId. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ga4gh/dataconnect/ApplicationConfig.java | 32 ++++++++++----- .../GlobalControllerExceptionHandler.java | 6 +-- .../trino/TrinoDataConnectAdapter.java | 8 ++-- .../adapter/trino/TrinoHttpClient.java | 39 ++++++++++--------- .../CollectionServiceClientConfiguration.java | 20 ++++++++-- .../IndexingServiceAutoConfiguration.java | 20 ++++++++-- 6 files changed, 84 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/ApplicationConfig.java b/src/main/java/com/dnastack/ga4gh/dataconnect/ApplicationConfig.java index 33ddd50..40891a7 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/ApplicationConfig.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/ApplicationConfig.java @@ -1,10 +1,10 @@ package com.dnastack.ga4gh.dataconnect; -import brave.Tracing; import com.dnastack.auth.JwtTokenParser; import com.dnastack.auth.JwtTokenParserFactory; import com.dnastack.auth.PermissionChecker; import com.dnastack.auth.PermissionCheckerFactory; +import com.dnastack.auth.client.OidcHttpClient; import com.dnastack.auth.client.TokenActionsHttpClientFactory; import com.dnastack.auth.keyresolver.CachingIssuerPubKeyJwksResolver; import com.dnastack.auth.keyresolver.IssuerPubKeyStaticResolver; @@ -21,8 +21,10 @@ import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtException; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.ObservationRegistry; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -96,9 +98,14 @@ public OkHttpClient httpClient() { } @Bean - public TrinoClient getTrinoClient(OkHttpClient httpClient, Tracing tracing, ServiceAccountAuthenticator accountAuthenticator, MeterRegistry registry) { + public TrinoClient getTrinoClient(OkHttpClient httpClient, io.micrometer.tracing.Tracer tracer, ServiceAccountAuthenticator accountAuthenticator, MeterRegistry registry) { return new TrinoTelemetryClient( - new TrinoHttpClient(tracing, httpClient, trinoDatasourceUrl, accountAuthenticator), registry); + new TrinoHttpClient(tracer, httpClient, trinoDatasourceUrl, accountAuthenticator), registry); + } + + @Bean + public ConnectionPool tokenValidatorConnectionPool() { + return new ConnectionPool(); } @Bean @@ -210,7 +217,12 @@ public SecurityFilterChain configure(HttpSecurity http) throws Exception { } @Bean - public List allowedIssuers(AuthConfig authConfig) { + public OidcHttpClient oidcHttpClient(ObservationRegistry observationRegistry, ConnectionPool tokenValidatorConnectionPool) { + return new OidcHttpClient(observationRegistry, tokenValidatorConnectionPool); + } + + @Bean + public List allowedIssuers(AuthConfig authConfig, OidcHttpClient oidcHttpClient) { List issuers = authConfig.getTokenIssuers(); if (issuers == null || issuers.isEmpty()) { throw new IllegalArgumentException("At least one token issuer must be defined"); @@ -224,7 +236,7 @@ public List allowedIssuers(AuthConfig authConfig) { .allowedAudiences(issuerConfig.getAudiences()) .publicKeyResolver(issuerConfig.getRsaPublicKey() != null ? new IssuerPubKeyStaticResolver(issuerUri, issuerConfig.getRsaPublicKey()) - : new CachingIssuerPubKeyJwksResolver(issuerUri)) + : CachingIssuerPubKeyJwksResolver.create(issuerUri, oidcHttpClient)) .build(); }) .toList(); @@ -236,10 +248,11 @@ public PermissionChecker permissionChecker( List allowedIssuers, @Value("${app.url}") String policyEvaluationRequester, @Value("${app.auth.token-issuers[0].issuer-uri}") String walletUrl, - Tracing tracing + ObservationRegistry observationRegistry, + ConnectionPool tokenValidatorConnectionPool ) { String policyEvaluationUrl = stripTrailingSlashes(walletUrl) + "/policies/evaluations"; - return PermissionCheckerFactory.create(allowedIssuers, policyEvaluationRequester, policyEvaluationUrl, tracing); + return PermissionCheckerFactory.create(allowedIssuers, policyEvaluationRequester, policyEvaluationUrl, observationRegistry, tokenValidatorConnectionPool); } private String stripTrailingSlashes(String url) { @@ -251,8 +264,9 @@ private String stripTrailingSlashes(String url) { } @Bean - public JwtDecoder jwtDecoder(List allowedIssuers, PermissionChecker permissionChecker, Tracing tracing) { - final JwtTokenParser jwtTokenParser = JwtTokenParserFactory.create(allowedIssuers, TokenActionsHttpClientFactory.create(tracing)); + public JwtDecoder jwtDecoder(List allowedIssuers, PermissionChecker permissionChecker, ObservationRegistry observationRegistry, ConnectionPool tokenValidatorConnectionPool) { + final JwtTokenParser jwtTokenParser = JwtTokenParserFactory.create(allowedIssuers, + TokenActionsHttpClientFactory.create(observationRegistry, tokenValidatorConnectionPool)); return (jwtToken) -> { try { permissionChecker.checkPermissions(jwtToken); diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/GlobalControllerExceptionHandler.java b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/GlobalControllerExceptionHandler.java index 5821b60..2087dcf 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/GlobalControllerExceptionHandler.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/shared/GlobalControllerExceptionHandler.java @@ -1,6 +1,6 @@ package com.dnastack.ga4gh.dataconnect.adapter.shared; -import brave.Tracer; +import io.micrometer.tracing.Tracer; import com.dnastack.ga4gh.dataconnect.adapter.trino.exception.TableApiErrorException; import com.dnastack.ga4gh.dataconnect.model.TableError; import lombok.extern.slf4j.Slf4j; @@ -24,12 +24,12 @@ public ResponseEntity handleAuthRequiredException(AuthRequiredException e) { return ResponseEntity.status(401) .contentType(MediaType.APPLICATION_JSON_UTF8) .header("WWW-Authenticate", "GA4GH-Search realm=\"" + escapeQuotes(cr.getKey()) + "\"") - .body(Map.of("authorization-request", cr, "trace_id", tracer.currentSpan().context().traceIdString())); + .body(Map.of("authorization-request", cr, "trace_id", tracer.currentSpan().context().traceId())); } @ExceptionHandler({TableApiErrorException.class}) public ResponseEntity handleTableApiErrorException(TableApiErrorException throwable) { - String traceId = tracer.currentSpan().context().traceIdString(); + String traceId = tracer.currentSpan().context().traceId(); TableError error = TableError.fromThrowable(throwable.getCause(), null); log.error("Generating response with error that escaped controller: {}", error, throwable); diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapter.java b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapter.java index b01c2d7..ffa1e3f 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapter.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapter.java @@ -1,6 +1,5 @@ package com.dnastack.ga4gh.dataconnect.adapter.trino; -import brave.Tracing; import com.dnastack.auth.cache.CachingConcurrentHashMap; import com.dnastack.ga4gh.dataconnect.ApplicationConfig; import com.dnastack.ga4gh.dataconnect.DataModelSupplier; @@ -15,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.google.common.collect.Streams; +import io.micrometer.tracing.Tracer; import jakarta.servlet.http.HttpServletRequest; import lombok.Getter; import lombok.SneakyThrows; @@ -86,14 +86,14 @@ public String toString() { private final ObjectMapper objectMapper; - private final Tracing tracer; + private final Tracer tracer; public TrinoDataConnectAdapter( TrinoClient client, Jdbi jdbi, ApplicationConfig applicationConfig, List dataModelSuppliers, - Tracing tracer, + Tracer tracer, // We use CachingConcurrentHashMap to cache the schema and catalog names to increase performance // When paginating through the tables @Value("${app.caching.expire-after:PT5M}") Duration expireAfter, @@ -309,7 +309,7 @@ private QueryJob createQueryJob(String queryId, String query, DataModel dataMode QueryJob queryJob = QueryJob.builder() .query(query) .id(queryId) - .originalTraceId(tracer.currentTraceContext().get().traceIdString()) + .originalTraceId(tracer.currentTraceContext().context().traceId()) .startedAt(currentTime) .lastActivityAt(currentTime) .schema(tableSchema) diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java index e8c0ccd..2f31889 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java @@ -1,9 +1,5 @@ package com.dnastack.ga4gh.dataconnect.adapter.trino; -import brave.Span; -import brave.Tracer; -import brave.Tracing; -import brave.propagation.B3SingleFormat; import com.dnastack.ga4gh.dataconnect.adapter.security.ServiceAccountAuthenticator; import com.dnastack.ga4gh.dataconnect.adapter.shared.AuthRequiredException; import com.dnastack.ga4gh.dataconnect.adapter.shared.DataConnectAuthRequest; @@ -14,6 +10,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.TraceContext; import lombok.Data; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; @@ -46,22 +45,20 @@ public class TrinoHttpClient implements TrinoClient { private final ServiceAccountAuthenticator authenticator; private final OkHttpClient httpClient; private final Tracer tracer; - private Tracing tracing; private final ObjectMapper objectMapper = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - public TrinoHttpClient(Tracing tracing, OkHttpClient httpClient, String trinoServerUrl, ServiceAccountAuthenticator accountAuthenticator) { + public TrinoHttpClient(Tracer tracer, OkHttpClient httpClient, String trinoServerUrl, ServiceAccountAuthenticator accountAuthenticator) { this.trinoServer = trinoServerUrl; this.trinoSearchEndpoint = trinoServerUrl + "/v1/statement"; this.authenticator = accountAuthenticator; - this.tracing = tracing; - this.tracer = tracing.tracer(); + this.tracer = tracer; this.httpClient = httpClient; } public TrinoDataPage query(String statement, Map extraCredentials) { - Span span = tracer.nextSpan().name("trinoQuery"); - try (Tracer.SpanInScope ws = tracer.withSpanInScope(span.start())) { + Span span = tracer.nextSpan().name("trinoQuery").start(); + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { log.debug("Posting to " + trinoSearchEndpoint); try (Response response = post(trinoSearchEndpoint, statement, extraCredentials)) { log.debug("Got response, now polling for query results"); @@ -73,14 +70,14 @@ public TrinoDataPage query(String statement, Map extraCredential throw new TrinoIOException("Unable to initiate search (I/O error).", e); } } finally { - span.finish(); + span.end(); } } public TrinoDataPage next(String page, Map extraCredentials) { - Span span = tracer.nextSpan().name("trinoNext"); - try (Tracer.SpanInScope ws = tracer.withSpanInScope(span.start())) { + Span span = tracer.nextSpan().name("trinoNext").start(); + try (Tracer.SpanInScope ws = tracer.withSpan(span)) { //TODO: better url construction String url = page.startsWith("/") ? this.trinoServer + page : this.trinoServer + "/" + page; @@ -90,7 +87,7 @@ public TrinoDataPage next(String page, Map extraCredentials) { throw new TrinoIOException("Unable to fetch more search or listing results (I/O error).", ie); } } finally { - span.finish(); + span.end(); } } @@ -212,11 +209,15 @@ private Response post(String url, String body, Map extraCredenti private Response execute(final Request.Builder request, Map extraCredentials) throws IOException { request.header("X-Forwarded-Proto", "https"); - request.header("X-Trino-Trace-Token",tracing.currentTraceContext().get().traceIdString()); - if (tracing.currentTraceContext().get() != null) { - request.header("X-Trino-Trace-Token",tracing.currentTraceContext().get().traceIdString()); - // We're adding the b3 contents to the Extra-Credential header, so we can extract it inside the SAC plugin & ga4gh-tables-connector - request.header("X-Trino-Extra-Credential", "b3=" + B3SingleFormat.writeB3SingleFormat(tracing.currentTraceContext().get())); + TraceContext traceContext = tracer.currentTraceContext().context(); + if (traceContext != null) { + request.header("X-Trino-Trace-Token", traceContext.traceId()); + // Pass W3C traceparent through to Trino as an extra credential so the SAC plugin & + // ga4gh-tables-connector can correlate downstream activity. Format follows the W3C Trace + // Context spec: version-traceId-spanId-flags. + String traceFlags = Boolean.TRUE.equals(traceContext.sampled()) ? "01" : "00"; + String traceparent = "00-" + traceContext.traceId() + "-" + traceContext.spanId() + "-" + traceFlags; + request.header("X-Trino-Extra-Credential", "traceparent=" + traceparent); } extraCredentials.forEach((k, v) -> request.addHeader("X-Trino-Extra-Credential", k + "=" + v)); diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/client/collectionservice/CollectionServiceClientConfiguration.java b/src/main/java/com/dnastack/ga4gh/dataconnect/client/collectionservice/CollectionServiceClientConfiguration.java index 2d41622..a9840c1 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/client/collectionservice/CollectionServiceClientConfiguration.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/client/collectionservice/CollectionServiceClientConfiguration.java @@ -1,8 +1,12 @@ package com.dnastack.ga4gh.dataconnect.client.collectionservice; import com.dnastack.ga4gh.dataconnect.DataModelSupplier; -import com.dnastack.oauth.client.OAuthClientFactory; +import com.dnastack.oauth.client.starter.config.OAuthClientFactoryConfiguration; +import com.dnastack.oauth.feign.FeignClients; +import com.dnastack.oauth.okhttp.OkHttpClients; +import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -13,9 +17,19 @@ public class CollectionServiceClientConfiguration { @Bean("collectionServiceClient") @ConditionalOnProperty(name = {"app.collection-service.enabled"}, havingValue = "true") - public CollectionServiceClient collectionServiceClient(OAuthClientFactory oAuthClientFactory, CollectionServiceConfiguration configuration) { + public CollectionServiceClient collectionServiceClient( + OAuthClientFactoryConfiguration oAuthClientFactoryConfiguration, + CollectionServiceConfiguration configuration, + ObservationRegistry observationRegistry + ) { log.info("Initializing the collection-service API client..."); - return oAuthClientFactory.builderWithCommonSetup(configuration.getOauthClient()) + OkHttpClient httpClient = OkHttpClients.buildOkHttpClient( + "collection-service", observationRegistry, CollectionServiceClient.class); + feign.okhttp.OkHttpClient feignClient = new feign.okhttp.OkHttpClient(httpClient); + return FeignClients.newBuilder( + oAuthClientFactoryConfiguration.getDefaultConfig().withOverrides(configuration.getOauthClient()), + feignClient, + feignClient) .target(CollectionServiceClient.class, configuration.getBaseUri()); } diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/client/indexingservice/IndexingServiceAutoConfiguration.java b/src/main/java/com/dnastack/ga4gh/dataconnect/client/indexingservice/IndexingServiceAutoConfiguration.java index 1013dea..478bd3c 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/client/indexingservice/IndexingServiceAutoConfiguration.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/client/indexingservice/IndexingServiceAutoConfiguration.java @@ -1,8 +1,12 @@ package com.dnastack.ga4gh.dataconnect.client.indexingservice; import com.dnastack.ga4gh.dataconnect.DataModelSupplier; -import com.dnastack.oauth.client.OAuthClientFactory; +import com.dnastack.oauth.client.starter.config.OAuthClientFactoryConfiguration; +import com.dnastack.oauth.feign.FeignClients; +import com.dnastack.oauth.okhttp.OkHttpClients; +import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -13,9 +17,19 @@ public class IndexingServiceAutoConfiguration { @Bean("indexingServiceClient") @ConditionalOnProperty(name = {"app.indexing-service.enabled"}, havingValue = "true") - public IndexingServiceClient indexingServiceClient(OAuthClientFactory factory, IndexingServiceConfiguration configuration) { + public IndexingServiceClient indexingServiceClient( + OAuthClientFactoryConfiguration oAuthClientFactoryConfiguration, + IndexingServiceConfiguration configuration, + ObservationRegistry observationRegistry + ) { log.info("Initializing the indexing service API client..."); - return factory.builderWithCommonSetup(configuration.getOauthClient()) + OkHttpClient httpClient = OkHttpClients.buildOkHttpClient( + "indexing-service", observationRegistry, IndexingServiceClient.class); + feign.okhttp.OkHttpClient feignClient = new feign.okhttp.OkHttpClient(httpClient); + return FeignClients.newBuilder( + oAuthClientFactoryConfiguration.getDefaultConfig().withOverrides(configuration.getOauthClient()), + feignClient, + feignClient) .target(IndexingServiceClient.class, configuration.getBaseUri()); } From f7a90c442d16d793605939789a1115be1b9d57df Mon Sep 17 00:00:00 2001 From: Jonathan Fuerth Date: Thu, 14 May 2026 16:47:19 -0400 Subject: [PATCH 4/6] [CU-86b9ybg6d] Drop dormant spring.zipkin block from application.yml Was only setting enabled: false. With the Brave -> Micrometer/OTel migration there is no Zipkin sender on the classpath at all, so the block has nothing to disable. Not adding management.tracing.sampling.probability either; it's a no-op without an exporter and we'd just have to remove it later. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/resources/application.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 88de673..5c47a5c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -109,8 +109,6 @@ spring: driver-class-name: org.postgresql.Driver hikari: maximum-pool-size: 5 - zipkin: - enabled: false management: metrics: From e0fb235d9ec254394454cfff48c1874d74726836 Mon Sep 17 00:00:00 2001 From: Jonathan Fuerth Date: Thu, 14 May 2026 16:49:13 -0400 Subject: [PATCH 5/6] [CU-86b9ybg6d] Replace brave Tracing test fixture with Tracer mock TrinoDataConnectAdapterTest used a live brave.Tracing instance plus a span-in-scope to satisfy the adapter's tracer dependency. Brave is no longer on the classpath, and the adapter now takes io.micrometer.tracing.Tracer. The test only needs tracer.currentTraceContext().context().traceId() (used when persisting QueryJob.originalTraceId), so a thin Mockito stub returning a fixed 128-bit traceId is enough. No tearDown needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../trino/TrinoDataConnectAdapterTest.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapterTest.java b/src/test/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapterTest.java index 0deccac..64f9167 100644 --- a/src/test/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapterTest.java +++ b/src/test/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoDataConnectAdapterTest.java @@ -1,7 +1,5 @@ package com.dnastack.ga4gh.dataconnect.adapter.trino; -import brave.Tracer; -import brave.Tracing; import com.dnastack.ga4gh.dataconnect.ApplicationConfig; import com.dnastack.ga4gh.dataconnect.DataModelSupplier; import com.dnastack.ga4gh.dataconnect.adapter.trino.exception.TrinoNoSuchCatalogException; @@ -15,6 +13,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; import lombok.extern.slf4j.Slf4j; import org.hamcrest.Matchers; import org.jdbi.v3.core.Jdbi; @@ -47,8 +48,7 @@ public class TrinoDataConnectAdapterTest { .registerModule(new JavaTimeModule()) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - private Tracing tracing; - private Tracer.SpanInScope spanInScope; + private Tracer tracer; /** * The object under test @@ -108,8 +108,14 @@ public void killQuery(String nextPageUrl) { @Before public void setUp() throws Exception { - tracing = Tracing.newBuilder().build(); - spanInScope = tracing.tracer().withSpanInScope(tracing.tracer().nextSpan()); + // Minimal Tracer stub: only currentTraceContext().context().traceId() is exercised + // by the code under test (when persisting QueryJob.originalTraceId). + TraceContext traceContext = mock(TraceContext.class); + when(traceContext.traceId()).thenReturn("00000000000000000000000000000000"); + CurrentTraceContext currentTraceContext = mock(CurrentTraceContext.class); + when(currentTraceContext.context()).thenReturn(traceContext); + tracer = mock(Tracer.class); + when(tracer.currentTraceContext()).thenReturn(currentTraceContext); // mock QueryJobDao and JDBI QueryJobDao queryJobDao = mock(QueryJobDao.class); @@ -142,14 +148,12 @@ public void setUp() throws Exception { when(mockApplicationConfig.getHiddenCatalogs()).thenReturn(Collections.emptySet()); // Default: no hidden catalogs dataConnectAdapter = new TrinoDataConnectAdapter( - mockTrinoClient, jdbi, mockApplicationConfig, List.of(dataModelSupplier), tracing, Duration.ofMinutes(5),100 + mockTrinoClient, jdbi, mockApplicationConfig, List.of(dataModelSupplier), tracer, Duration.ofMinutes(5),100 ); } @After public void tearDown() throws Exception { - spanInScope.close(); - tracing.close(); } private MockHttpServletRequest createRequestWithForwardedHeaders() { From 44208532384c119d945745ce3ce647a8877bbc7a Mon Sep 17 00:00:00 2001 From: Jonathan Fuerth Date: Thu, 14 May 2026 17:55:36 -0400 Subject: [PATCH 6/6] [CU-86b9ybg6d] Keep b3 extra-credential alongside traceparent for plugin compatibility The publisher SAC plugin (CU-86b9ybr65) and ga4gh-tables-connector (CU-86b9ybr0e) still read `b3` from Trino's extra-credentials map and parse it via brave's B3SingleFormat. Brave's parser handles 128-bit (32-hex) trace IDs natively, so the new OTel trace context can be hand-formatted into the same B3 single-format header without bringing brave back as a dependency. Sending both b3 and traceparent keeps existing plugins working unchanged while pre-positioning new plugins to consume the W3C traceparent once they migrate to the OpenTelemetry SPI. The b3 line can be dropped in a follow-up to CU-86b9ybg6d after both plugins ship. --- .../dataconnect/adapter/trino/TrinoHttpClient.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java index 2f31889..546a670 100644 --- a/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java +++ b/src/main/java/com/dnastack/ga4gh/dataconnect/adapter/trino/TrinoHttpClient.java @@ -212,11 +212,15 @@ private Response execute(final Request.Builder request, Map extr TraceContext traceContext = tracer.currentTraceContext().context(); if (traceContext != null) { request.header("X-Trino-Trace-Token", traceContext.traceId()); - // Pass W3C traceparent through to Trino as an extra credential so the SAC plugin & - // ga4gh-tables-connector can correlate downstream activity. Format follows the W3C Trace - // Context spec: version-traceId-spanId-flags. + // Pass both the W3C traceparent and a legacy single-format B3 header through to Trino + // as extra credentials. The publisher SAC plugin and ga4gh-tables-connector currently + // read `b3`; once they migrate to the OpenTelemetry SPI (CU-86b9ybr65, CU-86b9ybr0e), + // the `b3` line can be removed in a follow-up to CU-86b9ybg6d. String traceFlags = Boolean.TRUE.equals(traceContext.sampled()) ? "01" : "00"; + String b3SampledFlag = Boolean.TRUE.equals(traceContext.sampled()) ? "1" : "0"; String traceparent = "00-" + traceContext.traceId() + "-" + traceContext.spanId() + "-" + traceFlags; + String b3 = traceContext.traceId() + "-" + traceContext.spanId() + "-" + b3SampledFlag; + request.header("X-Trino-Extra-Credential", "b3=" + b3); request.header("X-Trino-Extra-Credential", "traceparent=" + traceparent); } extraCredentials.forEach((k, v) -> request.addHeader("X-Trino-Extra-Credential", k + "=" + v));