Skip to content

Commit 85b86b4

Browse files
authored
Add java.util.Date support (#9111)
Add special support of Date type to manipulate it as internal representation of epoch in milliseconds. Allow to see the value without deep reflection (support for jdk17+) and be able to use it as condition. Chosen to represent it using epoch to avoid time zone issue: Date::toString will use the current TimeZone which maybe different from the user one.
1 parent 2ee59a9 commit 85b86b4

File tree

5 files changed

+51
-16
lines changed

5 files changed

+51
-16
lines changed

dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/util/WellKnownClasses.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.lang.reflect.Method;
66
import java.util.Arrays;
77
import java.util.Collection;
8+
import java.util.Date;
89
import java.util.HashMap;
910
import java.util.HashSet;
1011
import java.util.Map;
@@ -14,6 +15,7 @@
1415
import java.util.OptionalLong;
1516
import java.util.Set;
1617
import java.util.function.Function;
18+
import java.util.function.ToLongFunction;
1719
import org.slf4j.Logger;
1820
import org.slf4j.LoggerFactory;
1921

@@ -58,6 +60,13 @@ public class WellKnownClasses {
5860
// implementations of java.io.file.Path interfaces
5961
SAFE_TO_STRING_FUNCTIONS.put("sun.nio.fs.UnixPath", String::valueOf);
6062
SAFE_TO_STRING_FUNCTIONS.put("sun.nio.fs.WindowsPath", String::valueOf);
63+
SAFE_TO_STRING_FUNCTIONS.put("java.util.Date", WellKnownClasses::dateToString);
64+
}
65+
66+
private static final Map<String, ToLongFunction<Object>> LONG_FUNCTIONS = new HashMap<>();
67+
68+
static {
69+
LONG_FUNCTIONS.put("java.util.Date", WellKnownClasses::dateToLongValue);
6170
}
6271

6372
private static final Set<String> EQUALS_SAFE_CLASSES = new HashSet<>();
@@ -102,6 +111,8 @@ public class WellKnownClasses {
102111
"sun.nio.fs.UnixPath",
103112
"sun.nio.fs.WindowsPath"));
104113

114+
private static final Set<String> LONG_PRIMITIVES = new HashSet<>(Arrays.asList("java.util.Date"));
115+
105116
private static final Map<Class<?>, Map<String, Function<Object, CapturedContext.CapturedValue>>>
106117
SPECIAL_TYPE_ACCESS = new HashMap<>();
107118

@@ -211,6 +222,14 @@ public static boolean isStringPrimitive(String type) {
211222
return STRING_PRIMITIVES.contains(type);
212223
}
213224

225+
/**
226+
* indicates if type is considered as a int/long primitive and can be compared to another long
227+
* value or literal with Expression Language
228+
*/
229+
public static boolean isLongPrimitive(String type) {
230+
return LONG_PRIMITIVES.contains(type);
231+
}
232+
214233
/**
215234
* @return a map of fields with function to access special field of a type, or null if type is not
216235
* supported. This is used to avoid using reflection to access fields on well known types
@@ -232,6 +251,8 @@ public static Map<String, Function<Object, CapturedContext.CapturedValue>> getSp
232251
}
233252

234253
/**
254+
* @param type the type name of the object to generate a string representation for. Must be a
255+
* concrete type, not a declared type. see {@link #isToStringSafe(String)}
235256
* @return a function to generate a string representation of a type where the default toString
236257
* method is not suitable
237258
*/
@@ -243,12 +264,24 @@ private static String classToString(Object o) {
243264
return ((Class<?>) o).getTypeName();
244265
}
245266

267+
private static String dateToString(Object o) {
268+
return Long.toString(((Date) o).getTime());
269+
}
270+
271+
private static long dateToLongValue(Object o) {
272+
return ((Date) o).getTime();
273+
}
274+
246275
public static boolean isEqualsSafe(Class<?> clazz) {
247276
return clazz.isPrimitive()
248277
|| clazz.isEnum()
249278
|| EQUALS_SAFE_CLASSES.contains(clazz.getTypeName());
250279
}
251280

281+
public static ToLongFunction<Object> getLongPrimitiveValueFunction(String typeName) {
282+
return LONG_FUNCTIONS.get(typeName);
283+
}
284+
252285
private static class ThrowableFields {
253286
public static final String BECAUSE_OVERRIDDEN =
254287
"Special access method not safe to be called because overridden";

dd-java-agent/agent-debugger/debugger-el/src/main/java/com/datadog/debugger/el/Value.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Map;
1616
import java.util.Set;
1717
import java.util.function.Function;
18+
import java.util.function.ToLongFunction;
1819

1920
/** Represents any value of the expression language */
2021
public interface Value<T> {
@@ -64,6 +65,11 @@ static Value<?> of(Object value) {
6465
}
6566
value = toString.apply(value);
6667
}
68+
if (WellKnownClasses.isLongPrimitive(typeName)) {
69+
ToLongFunction<Object> longPrimitiveValueFunction =
70+
WellKnownClasses.getLongPrimitiveValueFunction(typeName);
71+
value = longPrimitiveValueFunction.applyAsLong(value);
72+
}
6773
if (value instanceof Boolean) {
6874
return new BooleanValue((Boolean) value);
6975
} else if (value instanceof Character) {

dd-java-agent/agent-debugger/debugger-el/src/test/java/com/datadog/debugger/el/ProbeConditionTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.Collection;
22+
import java.util.Date;
2223
import java.util.HashMap;
2324
import java.util.HashSet;
2425
import java.util.List;
@@ -199,12 +200,13 @@ void redaction() throws IOException {
199200
}
200201

201202
@Test
202-
void stringPrimitives() throws IOException {
203+
void primitives() throws IOException {
203204
ProbeCondition probeCondition = loadFromResource("/test_conditional_10.json");
204205
Map<String, Object> args = new HashMap<>();
205206
args.put("uuid", UUID.fromString("a3cbe9e7-edd3-4bef-8e5b-59bfcb04cf91"));
206207
args.put("duration", Duration.ofSeconds(42));
207208
args.put("clazz", "foo".getClass());
209+
args.put("now", new Date(1700000000000L)); // 2023-11-14T00:00:00Z
208210
ValueReferenceResolver ctx = RefResolverHelper.createResolver(args, null);
209211
assertTrue(probeCondition.execute(ctx));
210212
}

dd-java-agent/agent-debugger/debugger-el/src/test/resources/test_conditional_10.json

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,12 @@
1515
"eq": [
1616
{"ref": "clazz"},
1717
"java.lang.String"]
18-
}
18+
}, {
19+
"eq": [
20+
{"ref": "now"},
21+
1700000000000
22+
]
23+
}
1924
]
2025
}
2126
}
22-
23-
"dsl": "isEmpty(emptyStr) && isEmpty(emptyList) && isEmpty(emptyMap) && isEmpty(emptyArray) && isUndefined(@exception)",
24-
"json": {
25-
"and": [
26-
{"isEmpty": {"ref": "emptyStr"}},
27-
{"isEmpty": {"ref": "emptyList"}},
28-
{"isEmpty": {"ref": "emptyMap"}},
29-
{"isEmpty": {"ref": "emptyArray"}},
30-
{"isUndefined": {"ref": "@exception"}}
31-

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ static class WellKnownClasses {
297297
UUID uuid = UUID.nameUUIDFromBytes("foobar".getBytes());
298298
AtomicLong atomicLong = new AtomicLong(123);
299299
URI uri = URI.create("https://www.datadoghq.com");
300-
Optional<Date> maybeDate = Optional.of(new Date());
300+
Optional<Date> maybeDate = Optional.of(new Date(1700000000000L));
301301
Optional<Object> empty = Optional.empty();
302302
OptionalInt maybeInt = OptionalInt.of(42);
303303
OptionalDouble maybeDouble = OptionalDouble.of(3.14);
@@ -344,13 +344,12 @@ public void wellKnownClasses() throws IOException {
344344
Map<String, Object> maybeDate = (Map<String, Object>) objLocalFields.get("maybeDate");
345345
assertComplexClass(maybeDate, Optional.class.getTypeName());
346346
Map<String, Object> maybeDateFields = (Map<String, Object>) maybeDate.get(FIELDS);
347-
Map<String, Object> value = (Map<String, Object>) maybeDateFields.get("value");
348-
assertComplexClass(value, Date.class.getTypeName());
347+
assertPrimitiveValue(maybeDateFields, "value", Date.class.getTypeName(), "1700000000000");
349348
// empty
350349
Map<String, Object> empty = (Map<String, Object>) objLocalFields.get("empty");
351350
assertComplexClass(empty, Optional.class.getTypeName());
352351
Map<String, Object> emptyFields = (Map<String, Object>) empty.get(FIELDS);
353-
value = (Map<String, Object>) emptyFields.get("value");
352+
Map<String, Object> value = (Map<String, Object>) emptyFields.get("value");
354353
assertEquals(Object.class.getTypeName(), value.get(TYPE));
355354
assertTrue((Boolean) value.get(IS_NULL));
356355
// maybeInt

0 commit comments

Comments
 (0)