Repository: helidon-io/helidon (java)

Issue:
`JaegerTracerBuilder` `propagatorFormats` property retains initial default value even when explicitly set
## Environment Details
* Helidon Version: 4.x
* Helidon SE or Helidon MP
* JDK version:
* OS:
* Docker version (if applicable):

----------

## Problem Description
The `propagatorFormats` property of `JaegerTracerBuilder` is a `List` property with a default value which incorrectly remains even when `addPropagatorFormat` explicitly adds a value to the property.

This is not an issue in 3.x because in 3.x there is no assigned default value for `propagatorFormats`.

## Steps to reproduce
1. Use `helidon init` to create a custom SE app with Jaeger tracing.
2. Customize the generated `TracedResource` class inner record `TracedHandler#handle` method, adding these lines after the `Span` is instantiated:
   
   ```java
   SpanContext spanContext = span.context();
   HeaderConsumer headerConsumer = HeaderConsumer.create(new HashMap<>());
   tracer.inject(spanContext, null, headerConsumer);
   ```
3. Customize the `application.yaml` file:
   ```yaml
      tracing:
        service: helidon-tracing-service
        propagation: "b3"
   ```
4. Build the project and run it with the debugger.
5. Set a breakpoint just after the `tracer.inject` line.
6. Submit `curl http://localhost:8080/traced`.
7. At the breakpoint examine the headers in the `HeaderConsumer`. You will find the unexpected header `uber-trace-id` which comes from the Jaeger propagator. You also find the expected `b3` headers from the explicitly-selected `B3` propagator.
   
   If you open the variable `tracer -> propagator -> textMapPropagators` you see the `JaegerPropagator` unexpectedly included which explains the unexpected `uber` header.



Pull request description:
Clear default propagator when one is explicitly added
### Description
Resolves #8651 

The `propagationFormats` attribute of the `JaegerTracerBuilder` is an `EnumSet` and is assigned a single default value using a field initializer.

But the `addPropagationFormat` method simply adds its parameter to the `EnumSet` without regard to the default value.

This PR adds logic to clear the default value upon the first explicit addition of a propagation format.

The PR includes tests to make sure the default value is cleared properly and retained properly.

### Documentation
Bug fix; no doc impact.

Patch:
diff --git a/health/health/src/main/java/io/helidon/health/HealthCheckResponse.java b/health/health/src/main/java/io/helidon/health/HealthCheckResponse.java
index 67b6d8d471..1389855d3a 100644
--- a/health/health/src/main/java/io/helidon/health/HealthCheckResponse.java
+++ b/health/health/src/main/java/io/helidon/health/HealthCheckResponse.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
 
 package io.helidon.health;
 
-import java.util.HashMap;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * Health check response.
@@ -69,12 +69,15 @@ public interface HealthCheckResponse {
      * Fluent API builder for {@link HealthCheckResponse}.
      */
     class Builder implements io.helidon.common.Builder<Builder, HealthCheckResponse> {
-        private final Map<String, Object> details = new HashMap<>();
+
+        // Use a TreeMap to preserve stability of the details in JSON output.
+        private final Map<String, Object> details = new TreeMap<>();
         private Status status = Status.UP;
 
         @Override
         public HealthCheckResponse build() {
-            return new HealthResponseImpl(this.status, this.details);
+            // Use a new map in case the builder is reused and mutated after this  invocation of build().
+            return new HealthResponseImpl(this.status, new TreeMap<>(this.details));
         }
 
         /**
diff --git a/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java b/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java
index b2806cbc14..4f13506fd1 100644
--- a/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java
+++ b/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -161,6 +161,7 @@ public class JaegerTracerBuilder implements TracerBuilder<JaegerTracerBuilder> {
     private final Map<String, String> tags = new HashMap<>();
     // this is a backward incompatible change, but the correct choice is Jaeger, not B3
     private final Set<PropagationFormat> propagationFormats = EnumSet.of(PropagationFormat.JAEGER);
+    private boolean isPropagationFormatsDefaulted = true;
     private String serviceName;
     private String protocol = "http";
     private String host = DEFAULT_HTTP_HOST;
@@ -467,6 +468,10 @@ public class JaegerTracerBuilder implements TracerBuilder<JaegerTracerBuilder> {
     @ConfiguredOption(key = "propagation", kind = ConfiguredOption.Kind.LIST, type = PropagationFormat.class, value = "JAEGER")
     public JaegerTracerBuilder addPropagation(PropagationFormat propagationFormat) {
         Objects.requireNonNull(propagationFormat);
+        if (isPropagationFormatsDefaulted) {
+            isPropagationFormatsDefaulted = false;
+            this.propagationFormats.clear();
+        }
         this.propagationFormats.add(propagationFormat);
         return this;
     }


Test patch:
diff --git a/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/BuilderPropagatorDefaultTest.java b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/BuilderPropagatorDefaultTest.java
new file mode 100644
index 0000000000..3edaff8ffb
--- /dev/null
+++ b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/BuilderPropagatorDefaultTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.tracing.providers.jaeger;
+
+import io.opentelemetry.extension.trace.propagation.JaegerPropagator;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.not;
+
+class BuilderPropagatorDefaultTest {
+
+    @Test
+    void checkThatPropagatorFormatAssignmentOverridesJaegerDefault() {
+        JaegerTracerBuilder builder = JaegerTracerBuilder.create();
+        builder.addPropagation(JaegerTracerBuilder.PropagationFormat.B3_SINGLE);
+        var props = builder.createPropagators();
+        assertThat("Propagators when default overridden", props, not(hasItem(instanceOf(JaegerPropagator.class))));
+    }
+
+    @Test
+    void checkDefaultedPropagationFormatIsJaeger() {
+        JaegerTracerBuilder builder = JaegerTracerBuilder.create();
+        var props = builder.createPropagators();
+        assertThat("Propagators with only default", props, contains(instanceOf(JaegerPropagator.class)));
+    }
+}