55
66package io .opentelemetry .contrib .disk .buffering .internal .serialization .mapping .common ;
77
8+ import static java .util .Collections .emptyList ;
9+
810import io .opentelemetry .api .common .AttributeKey ;
911import io .opentelemetry .api .common .AttributeType ;
1012import io .opentelemetry .api .common .Attributes ;
1113import io .opentelemetry .api .common .AttributesBuilder ;
14+ import io .opentelemetry .api .common .Value ;
1215import io .opentelemetry .proto .common .v1 .AnyValue ;
1316import io .opentelemetry .proto .common .v1 .ArrayValue ;
1417import io .opentelemetry .proto .common .v1 .KeyValue ;
18+ import io .opentelemetry .proto .common .v1 .KeyValueList ;
19+ import java .nio .ByteBuffer ;
1520import java .util .ArrayList ;
1621import java .util .List ;
22+ import okio .ByteString ;
1723
1824public final class AttributesMapper {
1925
2026 private static final AttributesMapper INSTANCE = new AttributesMapper ();
2127
28+ /** Represents the type of value stored in a proto AnyValue. */
29+ private enum ProtoValueType {
30+ STRING ,
31+ BOOL ,
32+ INT ,
33+ DOUBLE ,
34+ BYTES ,
35+ ARRAY ,
36+ KVLIST ,
37+ EMPTY
38+ }
39+
2240 public static AttributesMapper getInstance () {
2341 return INSTANCE ;
2442 }
@@ -44,7 +62,8 @@ private static KeyValue attributeEntryToProto(AttributeKey<?> key, Object value)
4462 return builder .build ();
4563 }
4664
47- @ SuppressWarnings ("unchecked" ) // data type is checked before casting
65+ // Type is checked via AttributeType before casting
66+ @ SuppressWarnings ("unchecked" )
4867 private static AnyValue attributeValueToProto (AttributeType type , Object value ) {
4968 switch (type ) {
5069 case STRING :
@@ -63,10 +82,72 @@ private static AnyValue attributeValueToProto(AttributeType type, Object value)
6382 return arrayToAnyValue (longListToAnyValue ((List <Long >) value ));
6483 case DOUBLE_ARRAY :
6584 return arrayToAnyValue (doubleListToAnyValue ((List <Double >) value ));
85+ case VALUE :
86+ return valueToAnyValue ((Value <?>) value );
6687 }
6788 throw new UnsupportedOperationException ();
6889 }
6990
91+ // Type is checked via ValueType before casting
92+ @ SuppressWarnings ("unchecked" )
93+ private static AnyValue valueToAnyValue (Value <?> value ) {
94+ switch (value .getType ()) {
95+ case STRING :
96+ return stringToAnyValue ((String ) value .getValue ());
97+ case BOOLEAN :
98+ return booleanToAnyValue ((Boolean ) value .getValue ());
99+ case LONG :
100+ return longToAnyValue ((Long ) value .getValue ());
101+ case DOUBLE :
102+ return doubleToAnyValue ((Double ) value .getValue ());
103+ case BYTES :
104+ ByteBuffer byteBuffer = (ByteBuffer ) value .getValue ();
105+ byte [] bytes = new byte [byteBuffer .remaining ()];
106+ byteBuffer .get (bytes );
107+ return bytesToAnyValue (bytes );
108+ case ARRAY :
109+ List <Value <?>> arrayValues = (List <Value <?>>) value .getValue ();
110+ return arrayValueToAnyValue (arrayValues );
111+ case KEY_VALUE_LIST :
112+ List <io .opentelemetry .api .common .KeyValue > kvList =
113+ (List <io .opentelemetry .api .common .KeyValue >) value .getValue ();
114+ return keyValueListToAnyValue (kvList );
115+ case EMPTY :
116+ return new AnyValue .Builder ().build ();
117+ }
118+ throw new UnsupportedOperationException ("Unsupported ValueType: " + value .getType ());
119+ }
120+
121+ private static AnyValue bytesToAnyValue (byte [] bytes ) {
122+ AnyValue .Builder anyValue = new AnyValue .Builder ();
123+ anyValue .bytes_value (ByteString .of (bytes ));
124+ return anyValue .build ();
125+ }
126+
127+ private static AnyValue arrayValueToAnyValue (List <Value <?>> values ) {
128+ List <AnyValue > anyValues = new ArrayList <>(values .size ());
129+ for (Value <?> v : values ) {
130+ anyValues .add (valueToAnyValue (v ));
131+ }
132+ return new AnyValue .Builder ()
133+ .array_value (new ArrayValue .Builder ().values (anyValues ).build ())
134+ .build ();
135+ }
136+
137+ private static AnyValue keyValueListToAnyValue (
138+ List <io .opentelemetry .api .common .KeyValue > kvList ) {
139+ List <KeyValue > protoKeyValues = new ArrayList <>(kvList .size ());
140+ for (io .opentelemetry .api .common .KeyValue kv : kvList ) {
141+ KeyValue .Builder kvBuilder = new KeyValue .Builder ();
142+ kvBuilder .key (kv .getKey ());
143+ kvBuilder .value (valueToAnyValue (kv .getValue ()));
144+ protoKeyValues .add (kvBuilder .build ());
145+ }
146+ return new AnyValue .Builder ()
147+ .kvlist_value (new KeyValueList .Builder ().values (protoKeyValues ).build ())
148+ .build ();
149+ }
150+
70151 private static AnyValue arrayToAnyValue (List <AnyValue > value ) {
71152 return new AnyValue .Builder ()
72153 .array_value (new ArrayValue .Builder ().values (value ).build ())
@@ -84,30 +165,106 @@ private static void addValue(AttributesBuilder builder, String key, AnyValue val
84165 builder .put (AttributeKey .doubleKey (key ), value .double_value );
85166 } else if (value .array_value != null ) {
86167 addArray (builder , key , value .array_value );
168+ } else if (value .bytes_value != null ) {
169+ builder .put (AttributeKey .valueKey (key ), Value .of (value .bytes_value .toByteArray ()));
170+ } else if (value .kvlist_value != null ) {
171+ builder .put (AttributeKey .valueKey (key ), anyValueToValue (value ));
87172 } else {
88- // Until we have complex attribute types that could potentially yield
89- // empty objects, we MUST assume here that the writer put an empty string
90- // into the value of the attribute. This will need to change later, when complex
91- // types arrive and the spec issue is resolved.
92- //
93- // See spec issue: https://github.com/open-telemetry/opentelemetry-specification/issues/4660
173+ // Update after SDK v1.60.0 is released which includes:
174+ // https://github.com/open-telemetry/opentelemetry-java/pull/8014
94175 builder .put (AttributeKey .stringKey (key ), "" );
95176 }
96177 }
97178
179+ private static Value <?> anyValueToValue (AnyValue value ) {
180+ if (value .string_value != null ) {
181+ return Value .of (value .string_value );
182+ } else if (value .bool_value != null ) {
183+ return Value .of (value .bool_value );
184+ } else if (value .int_value != null ) {
185+ return Value .of (value .int_value );
186+ } else if (value .double_value != null ) {
187+ return Value .of (value .double_value );
188+ } else if (value .bytes_value != null ) {
189+ return Value .of (value .bytes_value .toByteArray ());
190+ } else if (value .array_value != null ) {
191+ List <Value <?>> values = new ArrayList <>();
192+ for (AnyValue anyValue : value .array_value .values ) {
193+ values .add (anyValueToValue (anyValue ));
194+ }
195+ return Value .of (values );
196+ } else if (value .kvlist_value != null ) {
197+ List <io .opentelemetry .api .common .KeyValue > kvList = new ArrayList <>();
198+ for (KeyValue kv : value .kvlist_value .values ) {
199+ kvList .add (io .opentelemetry .api .common .KeyValue .of (kv .key , anyValueToValue (kv .value )));
200+ }
201+ return Value .of (kvList .toArray (new io .opentelemetry .api .common .KeyValue [0 ]));
202+ } else {
203+ return Value .empty ();
204+ }
205+ }
206+
98207 private static void addArray (AttributesBuilder builder , String key , ArrayValue arrayValue ) {
99208 List <AnyValue > values = arrayValue .values ;
100- AnyValue anyValue = values .get (0 );
101- if (anyValue .string_value != null ) {
102- builder .put (AttributeKey .stringArrayKey (key ), anyValuesToStrings (values ));
103- } else if (anyValue .bool_value != null ) {
104- builder .put (AttributeKey .booleanArrayKey (key ), anyValuesToBooleans (values ));
105- } else if (anyValue .int_value != null ) {
106- builder .put (AttributeKey .longArrayKey (key ), anyValuesToLongs (values ));
107- } else if (anyValue .double_value != null ) {
108- builder .put (AttributeKey .doubleArrayKey (key ), anyValuesToDoubles (values ));
209+
210+ // Per SDK behavior (ArrayBackedAttributesBuilder#attributeType):
211+ // "VALUE if the array is empty, non-homogeneous, or contains unsupported element types"
212+ if (values .isEmpty ()) {
213+ builder .put (AttributeKey .valueKey (key ), Value .of (emptyList ()));
214+ return ;
215+ }
216+
217+ // Check if array is homogeneous and of a primitive type
218+ AnyValue firstValue = values .get (0 );
219+ boolean isHomogeneous = true ;
220+ ProtoValueType arrayType = getProtoValueType (firstValue );
221+
222+ for (int i = 1 ; i < values .size (); i ++) {
223+ if (getProtoValueType (values .get (i )) != arrayType ) {
224+ isHomogeneous = false ;
225+ break ;
226+ }
227+ }
228+
229+ // If homogeneous and primitive, use typed array keys
230+ if (isHomogeneous ) {
231+ if (firstValue .string_value != null ) {
232+ builder .put (AttributeKey .stringArrayKey (key ), anyValuesToStrings (values ));
233+ return ;
234+ } else if (firstValue .bool_value != null ) {
235+ builder .put (AttributeKey .booleanArrayKey (key ), anyValuesToBooleans (values ));
236+ return ;
237+ } else if (firstValue .int_value != null ) {
238+ builder .put (AttributeKey .longArrayKey (key ), anyValuesToLongs (values ));
239+ return ;
240+ } else if (firstValue .double_value != null ) {
241+ builder .put (AttributeKey .doubleArrayKey (key ), anyValuesToDoubles (values ));
242+ return ;
243+ }
244+ }
245+
246+ // Heterogeneous or complex array - use VALUE type
247+ AnyValue anyValue = new AnyValue .Builder ().array_value (arrayValue ).build ();
248+ builder .put (AttributeKey .valueKey (key ), anyValueToValue (anyValue ));
249+ }
250+
251+ private static ProtoValueType getProtoValueType (AnyValue value ) {
252+ if (value .string_value != null ) {
253+ return ProtoValueType .STRING ;
254+ } else if (value .bool_value != null ) {
255+ return ProtoValueType .BOOL ;
256+ } else if (value .int_value != null ) {
257+ return ProtoValueType .INT ;
258+ } else if (value .double_value != null ) {
259+ return ProtoValueType .DOUBLE ;
260+ } else if (value .bytes_value != null ) {
261+ return ProtoValueType .BYTES ;
262+ } else if (value .array_value != null ) {
263+ return ProtoValueType .ARRAY ;
264+ } else if (value .kvlist_value != null ) {
265+ return ProtoValueType .KVLIST ;
109266 } else {
110- throw new UnsupportedOperationException () ;
267+ return ProtoValueType . EMPTY ;
111268 }
112269 }
113270
0 commit comments