Skip to content

Commit b07dab3

Browse files
Extends TextMapGetter with GetAll() method, implement usage in W3CBaggagePropagator (#6852)
Co-authored-by: Jack Berg <[email protected]>
1 parent ee8d735 commit b07dab3

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed

api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java

+37
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import io.opentelemetry.context.propagation.TextMapGetter;
1717
import io.opentelemetry.context.propagation.TextMapPropagator;
1818
import io.opentelemetry.context.propagation.TextMapSetter;
19+
import io.opentelemetry.context.propagation.internal.ExtendedTextMapGetter;
1920
import java.util.Collection;
21+
import java.util.Iterator;
2022
import java.util.List;
2123
import javax.annotation.Nullable;
2224

@@ -95,6 +97,14 @@ public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C
9597
return context;
9698
}
9799

100+
if (getter instanceof ExtendedTextMapGetter) {
101+
return extractMulti(context, carrier, (ExtendedTextMapGetter<C>) getter);
102+
}
103+
return extractSingle(context, carrier, getter);
104+
}
105+
106+
private static <C> Context extractSingle(
107+
Context context, @Nullable C carrier, TextMapGetter<C> getter) {
98108
String baggageHeader = getter.get(carrier, FIELD);
99109
if (baggageHeader == null) {
100110
return context;
@@ -112,6 +122,33 @@ public <C> Context extract(Context context, @Nullable C carrier, TextMapGetter<C
112122
return context.with(baggageBuilder.build());
113123
}
114124

125+
private static <C> Context extractMulti(
126+
Context context, @Nullable C carrier, ExtendedTextMapGetter<C> getter) {
127+
Iterator<String> baggageHeaders = getter.getAll(carrier, FIELD);
128+
if (baggageHeaders == null) {
129+
return context;
130+
}
131+
132+
boolean extracted = false;
133+
BaggageBuilder baggageBuilder = Baggage.builder();
134+
135+
while (baggageHeaders.hasNext()) {
136+
String header = baggageHeaders.next();
137+
if (header.isEmpty()) {
138+
continue;
139+
}
140+
141+
try {
142+
extractEntries(header, baggageBuilder);
143+
extracted = true;
144+
} catch (RuntimeException expected) {
145+
// invalid baggage header, continue
146+
}
147+
}
148+
149+
return extracted ? context.with(baggageBuilder.build()) : context;
150+
}
151+
115152
private static void extractEntries(String baggageHeader, BaggageBuilder baggageBuilder) {
116153
new Parser(baggageHeader).parseInto(baggageBuilder);
117154
}

api/all/src/test/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagatorTest.java

+121
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
import static java.util.Collections.singletonMap;
99
import static org.assertj.core.api.Assertions.assertThat;
1010

11+
import com.google.common.collect.ImmutableList;
1112
import com.google.common.collect.ImmutableMap;
1213
import io.opentelemetry.api.baggage.Baggage;
1314
import io.opentelemetry.api.baggage.BaggageEntryMetadata;
1415
import io.opentelemetry.context.Context;
1516
import io.opentelemetry.context.propagation.TextMapGetter;
17+
import io.opentelemetry.context.propagation.internal.ExtendedTextMapGetter;
1618
import java.util.Collections;
1719
import java.util.HashMap;
20+
import java.util.Iterator;
1821
import java.util.LinkedHashMap;
22+
import java.util.List;
1923
import java.util.Map;
2024
import javax.annotation.Nullable;
2125
import org.junit.jupiter.api.Test;
@@ -36,6 +40,28 @@ public String get(Map<String, String> carrier, String key) {
3640
}
3741
};
3842

43+
private static final ExtendedTextMapGetter<Map<String, List<String>>> multiGetter =
44+
new ExtendedTextMapGetter<Map<String, List<String>>>() {
45+
@Override
46+
public Iterable<String> keys(Map<String, List<String>> carrier) {
47+
return carrier.keySet();
48+
}
49+
50+
@Nullable
51+
@Override
52+
public String get(Map<String, List<String>> carrier, String key) {
53+
return carrier.getOrDefault(key, Collections.emptyList()).stream()
54+
.findFirst()
55+
.orElse(null);
56+
}
57+
58+
@Override
59+
public Iterator<String> getAll(Map<String, List<String>> carrier, String key) {
60+
List<String> values = carrier.get(key);
61+
return values == null ? Collections.emptyIterator() : values.iterator();
62+
}
63+
};
64+
3965
@Test
4066
void fields() {
4167
assertThat(W3CBaggagePropagator.getInstance().fields()).containsExactly("baggage");
@@ -421,6 +447,101 @@ void extract_nullGetter() {
421447
.isSameAs(context);
422448
}
423449

450+
@Test
451+
void extract_multiple_headers() {
452+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
453+
454+
Context result =
455+
propagator.extract(
456+
Context.root(),
457+
ImmutableMap.of("baggage", ImmutableList.of("k1=v1", "k2=v2")),
458+
multiGetter);
459+
460+
Baggage expectedBaggage = Baggage.builder().put("k1", "v1").put("k2", "v2").build();
461+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
462+
}
463+
464+
@Test
465+
void extract_multiple_headers_duplicate_key() {
466+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
467+
468+
Context result =
469+
propagator.extract(
470+
Context.root(),
471+
ImmutableMap.of("baggage", ImmutableList.of("k1=v1", "k1=v2")),
472+
multiGetter);
473+
474+
Baggage expectedBaggage = Baggage.builder().put("k1", "v2").build();
475+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
476+
}
477+
478+
@Test
479+
void extract_multiple_headers_mixed_duplicates_non_duplicates() {
480+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
481+
482+
Context result =
483+
propagator.extract(
484+
Context.root(),
485+
ImmutableMap.of("baggage", ImmutableList.of("k1=v1,k2=v0", "k2=v2,k3=v3")),
486+
multiGetter);
487+
488+
Baggage expectedBaggage =
489+
Baggage.builder().put("k1", "v1").put("k2", "v2").put("k3", "v3").build();
490+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
491+
}
492+
493+
@Test
494+
void extract_multiple_headers_all_empty() {
495+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
496+
497+
Context result =
498+
propagator.extract(
499+
Context.root(), ImmutableMap.of("baggage", ImmutableList.of("", "")), multiGetter);
500+
501+
Baggage expectedBaggage = Baggage.builder().build();
502+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
503+
}
504+
505+
@Test
506+
void extract_multiple_headers_some_empty() {
507+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
508+
509+
Context result =
510+
propagator.extract(
511+
Context.root(), ImmutableMap.of("baggage", ImmutableList.of("", "k=v")), multiGetter);
512+
513+
Baggage expectedBaggage = Baggage.builder().put("k", "v").build();
514+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
515+
}
516+
517+
@Test
518+
void extract_multiple_headers_all_invalid() {
519+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
520+
521+
Context result =
522+
propagator.extract(
523+
Context.root(),
524+
ImmutableMap.of("baggage", ImmutableList.of("!@#$%^", "key=va%lue")),
525+
multiGetter);
526+
527+
Baggage expectedBaggage = Baggage.builder().build();
528+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
529+
}
530+
531+
@Test
532+
void extract_multiple_headers_some_invalid() {
533+
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
534+
535+
Context result =
536+
propagator.extract(
537+
Context.root(),
538+
ImmutableMap.of("baggage", ImmutableList.of("k1=v1", "key=va%lue", "k2=v2")),
539+
multiGetter);
540+
541+
Baggage expectedBaggage = Baggage.builder().put("k1", "v1").put("k2", "v2").build();
542+
assertThat(Baggage.fromContext(result)).isEqualTo(expectedBaggage);
543+
}
544+
424545
@Test
425546
void inject_noBaggage() {
426547
W3CBaggagePropagator propagator = W3CBaggagePropagator.getInstance();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.context.propagation.internal;
7+
8+
import io.opentelemetry.context.propagation.TextMapGetter;
9+
import java.util.Collections;
10+
import java.util.Iterator;
11+
import javax.annotation.Nullable;
12+
13+
/**
14+
* Extends {@link TextMapGetter} to return possibly multiple values for a given key.
15+
*
16+
* <p>This class is internal and experimental. Its APIs are unstable and can change at any time. Its
17+
* APIs (or a version of them) may be promoted to the public stable API in the future, but no
18+
* guarantees are made.
19+
*
20+
* @param <C> carrier of propagation fields, such as an http request.
21+
*/
22+
public interface ExtendedTextMapGetter<C> extends TextMapGetter<C> {
23+
/**
24+
* If implemented, returns all values for a given {@code key} in order, or returns an empty list.
25+
*
26+
* <p>The default method returns the first value of the given propagation {@code key} as a
27+
* singleton list, or returns an empty list.
28+
*
29+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
30+
* at any time.
31+
*
32+
* @param carrier carrier of propagation fields, such as an http request.
33+
* @param key the key of the field.
34+
* @return all values for a given {@code key} in order, or returns an empty list. Default method
35+
* wraps {@code get()} as an {@link Iterator}.
36+
*/
37+
default Iterator<String> getAll(@Nullable C carrier, String key) {
38+
String first = get(carrier, key);
39+
if (first == null) {
40+
return Collections.emptyIterator();
41+
}
42+
return Collections.singleton(first).iterator();
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.context.propagation.internal;
7+
8+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
9+
10+
import com.google.common.collect.ImmutableList;
11+
import java.util.ArrayList;
12+
import java.util.Collections;
13+
import java.util.Iterator;
14+
import java.util.List;
15+
import javax.annotation.Nullable;
16+
import org.junit.jupiter.api.Test;
17+
18+
class ExtendedTextMapGetterTest {
19+
20+
final ExtendedTextMapGetter<Void> nullGet =
21+
new ExtendedTextMapGetter<Void>() {
22+
@Override
23+
public Iterable<String> keys(Void carrier) {
24+
return ImmutableList.of("key");
25+
}
26+
27+
@Nullable
28+
@Override
29+
public String get(@Nullable Void carrier, String key) {
30+
return null;
31+
}
32+
};
33+
34+
final ExtendedTextMapGetter<Void> nonNullGet =
35+
new ExtendedTextMapGetter<Void>() {
36+
@Override
37+
public Iterable<String> keys(Void carrier) {
38+
return ImmutableList.of("key");
39+
}
40+
41+
@Override
42+
public String get(@Nullable Void carrier, String key) {
43+
return "123";
44+
}
45+
};
46+
47+
@Test
48+
void extendedTextMapGetterdefaultMethod_returnsEmpty() {
49+
Iterator<String> result = nullGet.getAll(null, "key");
50+
assertThat(result).isNotNull();
51+
List<String> values = iterToList(result);
52+
assertThat(values).isEqualTo(Collections.emptyList());
53+
}
54+
55+
@Test
56+
void extendedTextMapGetterdefaultMethod_returnsSingleVal() {
57+
Iterator<String> result = nonNullGet.getAll(null, "key");
58+
assertThat(result).isNotNull();
59+
List<String> values = iterToList(result);
60+
assertThat(values).isEqualTo(Collections.singletonList("123"));
61+
}
62+
63+
private static <T> List<T> iterToList(Iterator<T> iter) {
64+
List<T> list = new ArrayList<>();
65+
while (iter.hasNext()) {
66+
list.add(iter.next());
67+
}
68+
return list;
69+
}
70+
}

0 commit comments

Comments
 (0)