-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDeclarativeConfigPropertiesBridge.java
More file actions
256 lines (231 loc) · 7.98 KB
/
Copy pathDeclarativeConfigPropertiesBridge.java
File metadata and controls
256 lines (231 loc) · 7.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.config.bridge;
import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import javax.annotation.Nullable;
/**
* A {@link ConfigProperties} which resolves properties based on {@link
* DeclarativeConfigProperties}.
*
* <p>Only properties starting with "otel.instrumentation." are resolved. Others return null (or
* default value if provided).
*
* <p>To resolve:
*
* <ul>
* <li>"otel.instrumentation" refers to the ".instrumentation.java" node
* <li>The portion of the property after "otel.instrumentation." is split into segments based on
* ".".
* <li>For each N-1 segment, we walk down the tree to find the relevant leaf {@link
* DeclarativeConfigProperties}.
* <li>We extract the property from the resolved {@link DeclarativeConfigProperties} using the
* last segment as the property key.
* </ul>
*
* <p>For example, given the following YAML, asking for {@code
* ConfigProperties#getString("otel.instrumentation.common.string_key")} yields "value":
*
* <pre>
* instrumentation:
* java:
* common:
* string_key: value
* </pre>
*/
final class DeclarativeConfigPropertiesBridge implements ConfigProperties {
private static final String OTEL_INSTRUMENTATION_PREFIX = "otel.instrumentation.";
private final DeclarativeConfigProperties baseNode;
// lookup order matters - we choose the first match
private final Map<String, String> mappings;
private final Map<String, Object> overrideValues;
DeclarativeConfigPropertiesBridge(
DeclarativeConfigProperties baseNode,
Map<String, String> mappings,
Map<String, Object> overrideValues) {
this.baseNode = Objects.requireNonNull(baseNode);
this.mappings = mappings;
this.overrideValues = overrideValues;
}
@Nullable
@Override
public String getString(String propertyName) {
return getPropertyValue(propertyName, String.class, DeclarativeConfigProperties::getString);
}
@Nullable
@Override
public Boolean getBoolean(String propertyName) {
return getPropertyValue(propertyName, Boolean.class, DeclarativeConfigProperties::getBoolean);
}
@Nullable
@Override
public Integer getInt(String propertyName) {
return getPropertyValue(propertyName, Integer.class, DeclarativeConfigProperties::getInt);
}
@Nullable
@Override
public Long getLong(String propertyName) {
return getPropertyValue(propertyName, Long.class, DeclarativeConfigProperties::getLong);
}
@Nullable
@Override
public Double getDouble(String propertyName) {
return getPropertyValue(propertyName, Double.class, DeclarativeConfigProperties::getDouble);
}
@Nullable
@Override
public Duration getDuration(String propertyName) {
// If this is a raw integer number then assume it is the number of milliseconds
Long millis = getLong(propertyName);
if (millis != null) {
return Duration.ofMillis(millis);
}
// If this is a string than it consists of value and time unit
String value = getString(propertyName);
if (value == null) {
return null;
}
String unitString = getUnitString(value);
String numberString = value.substring(0, value.length() - unitString.length());
try {
long rawNumber = Long.parseLong(numberString.trim());
TimeUnit unit = getDurationUnit(unitString.trim());
return Duration.ofNanos(TimeUnit.NANOSECONDS.convert(rawNumber, unit));
} catch (NumberFormatException ex) {
throw new ConfigurationException(
"Invalid duration property "
+ propertyName
+ "="
+ value
+ ". Expected number, found: "
+ numberString,
ex);
} catch (ConfigurationException ex) {
throw new ConfigurationException(
"Invalid duration property " + propertyName + "=" + value + ". " + ex.getMessage());
}
}
/** Returns the TimeUnit associated with a unit string. Defaults to milliseconds. */
private static TimeUnit getDurationUnit(String unitString) {
switch (unitString) {
case "us":
return TimeUnit.MICROSECONDS;
case "ns":
return TimeUnit.NANOSECONDS;
case "": // Fallthrough expected
case "ms":
return TimeUnit.MILLISECONDS;
case "s":
return TimeUnit.SECONDS;
case "m":
return TimeUnit.MINUTES;
case "h":
return TimeUnit.HOURS;
case "d":
return TimeUnit.DAYS;
default:
throw new ConfigurationException("Invalid duration string, found: " + unitString);
}
}
/**
* Fragments the 'units' portion of a config value from the 'value' portion.
*
* <p>E.g. "1ms" would return the string "ms".
*/
private static String getUnitString(String rawValue) {
int lastDigitIndex = rawValue.length() - 1;
while (lastDigitIndex >= 0) {
char c = rawValue.charAt(lastDigitIndex);
if (Character.isDigit(c)) {
break;
}
lastDigitIndex--;
}
// Pull everything after the last digit.
return rawValue.substring(lastDigitIndex + 1);
}
@SuppressWarnings("unchecked")
@Override
public List<String> getList(String propertyName) {
List<String> propertyValue =
getPropertyValue(
propertyName,
List.class,
(properties, lastPart) -> properties.getScalarList(lastPart, String.class));
return propertyValue == null ? Collections.emptyList() : propertyValue;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, String> getMap(String propertyName) {
DeclarativeConfigProperties propertyValue =
getPropertyValue(
propertyName,
DeclarativeConfigProperties.class,
DeclarativeConfigProperties::getStructured);
if (propertyValue == null) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
propertyValue
.getPropertyKeys()
.forEach(
key -> {
String value = propertyValue.getString(key);
if (value == null) {
return;
}
result.put(key, value);
});
return Collections.unmodifiableMap(result);
}
@Nullable
private <T> T getPropertyValue(
String property,
Class<T> clazz,
BiFunction<DeclarativeConfigProperties, String, T> extractor) {
T override = clazz.cast(overrideValues.get(property));
if (override != null) {
return override;
}
String[] segments = getSegments(translateProperty(property));
if (segments.length == 0) {
return null;
}
// Extract the value by walking to the N-1 entry
DeclarativeConfigProperties target = baseNode;
if (segments.length > 1) {
for (int i = 0; i < segments.length - 1; i++) {
target = target.getStructured(segments[i], empty());
}
}
String lastPart = segments[segments.length - 1];
return extractor.apply(target, lastPart);
}
static String[] getSegments(String property) {
if (property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
property = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
}
// Split the remainder of the property on "."
return property.replace('-', '_').split("\\.");
}
private String translateProperty(String property) {
for (Map.Entry<String, String> entry : mappings.entrySet()) {
if (property.startsWith(entry.getKey())) {
return entry.getValue() + property.substring(entry.getKey().length());
}
}
return property;
}
}