Skip to content

Commit 5b1b958

Browse files
authored
[DE-762] configure jackson stream constraints (#537)
* configure Jackson StreamReadConstraints and StreamWriteConstraints with permissive values * test JSON serde with big strings * fix native tests * fix shaded native tests
1 parent 2ae5af9 commit 5b1b958

File tree

11 files changed

+500
-9
lines changed

11 files changed

+500
-9
lines changed

Diff for: core/src/main/java/com/arangodb/internal/serde/InternalMapperProvider.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88
import org.slf4j.LoggerFactory;
99

1010
import java.util.ServiceLoader;
11-
import java.util.function.Supplier;
1211

13-
public interface InternalMapperProvider extends Supplier<ObjectMapper> {
14-
Logger LOG = LoggerFactory.getLogger(InternalMapperProvider.class);
12+
class InternalMapperProvider {
13+
private static final Logger LOG = LoggerFactory.getLogger(InternalMapperProvider.class);
1514

1615
static ObjectMapper of(final ContentType contentType) {
1716
String formatName;
@@ -25,12 +24,14 @@ static ObjectMapper of(final ContentType contentType) {
2524

2625
ServiceLoader<JsonFactory> sl = ServiceLoader.load(JsonFactory.class);
2726
for (JsonFactory jf : sl) {
28-
if(formatName.equals(jf.getFormatName())){
27+
if (formatName.equals(jf.getFormatName())) {
28+
if (contentType == ContentType.JSON) {
29+
JacksonUtils.tryConfigureJsonFactory(jf);
30+
}
2931
return new ObjectMapper(jf);
3032
}
3133
LOG.debug("Required format ({}) not supported by JsonFactory: {}", formatName, jf.getClass().getName());
3234
}
33-
3435
throw new ArangoDBException("No JsonFactory found for content type: " + contentType);
3536
}
3637
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.arangodb.internal.serde;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.lang.reflect.Method;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
public final class JacksonUtils {
11+
private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class);
12+
13+
private JacksonUtils() {
14+
}
15+
16+
/**
17+
* Configure JsonFactory with permissive StreamReadConstraints and StreamWriteConstraints.
18+
* It uses reflection to avoid compilation errors with older Jackson versions.
19+
* It uses dynamic package names to be compatible with shaded Jackson.
20+
*
21+
* @param jf JsonFactory to configure
22+
*/
23+
public static void tryConfigureJsonFactory(Object jf) {
24+
try {
25+
configureJsonFactory(jf);
26+
} catch (Throwable t) {
27+
LOG.warn("Got exception while configuring JsonFactory, skipping...", t);
28+
}
29+
}
30+
31+
private static void configureJsonFactory(Object jf) throws Exception {
32+
// using reflection because these configuration are not supported in older Jackson versions
33+
if (isAtLeastVersion(jf, 2, 15)) {
34+
LOG.debug("Configuring StreamReadConstraints ...");
35+
List<Invocation> readConf = new ArrayList<>();
36+
readConf.add(new Invocation("maxNumberLength", int.class, Integer.MAX_VALUE));
37+
readConf.add(new Invocation("maxStringLength", int.class, Integer.MAX_VALUE));
38+
readConf.add(new Invocation("maxNestingDepth", int.class, Integer.MAX_VALUE));
39+
if (isAtLeastVersion(jf, 2, 16)) {
40+
readConf.add(new Invocation("maxNameLength", int.class, Integer.MAX_VALUE));
41+
readConf.add(new Invocation("maxDocumentLength", long.class, Long.MAX_VALUE));
42+
} else {
43+
LOG.debug("Skipping configuring StreamReadConstraints maxNameLength");
44+
LOG.debug("Skipping configuring StreamReadConstraints maxDocumentLength");
45+
}
46+
configureStreamConstraints(jf, "StreamReadConstraints", readConf);
47+
} else {
48+
LOG.debug("Skipping configuring StreamReadConstraints");
49+
}
50+
51+
if (isAtLeastVersion(jf, 2, 16)) {
52+
LOG.debug("Configuring StreamWriteConstraints ...");
53+
List<Invocation> writeConf = new ArrayList<>();
54+
writeConf.add(new Invocation("maxNestingDepth", int.class, Integer.MAX_VALUE));
55+
configureStreamConstraints(jf, "StreamWriteConstraints", writeConf);
56+
} else {
57+
LOG.debug("Skipping configuring StreamWriteConstraints");
58+
}
59+
}
60+
61+
private static boolean isAtLeastVersion(Object jf, int major, int minor) throws Exception {
62+
Class<?> packageVersionClass = Class.forName(jf.getClass().getPackage().getName() + ".json.PackageVersion");
63+
Object version = packageVersionClass.getDeclaredField("VERSION").get(null);
64+
65+
Class<?> versionClass = Class.forName(jf.getClass().getPackage().getName() + ".Version");
66+
int currentMajor = (int) versionClass.getDeclaredMethod("getMajorVersion").invoke(version);
67+
int currentMinor = (int) versionClass.getDeclaredMethod("getMinorVersion").invoke(version);
68+
69+
LOG.debug("Detected Jackson version: {}.{}", currentMajor, currentMinor);
70+
71+
return currentMajor > major || (currentMajor == major && currentMinor >= minor);
72+
}
73+
74+
private static void configureStreamConstraints(Object jf, String className, List<Invocation> conf) throws Exception {
75+
// get pkg name dynamically, to support shaded Jackson
76+
String basePkg = jf.getClass().getPackage().getName();
77+
Class<?> streamConstraintsClass = Class.forName(basePkg + "." + className);
78+
Class<?> builderClass = Class.forName(basePkg + "." + className + "$Builder");
79+
Method buildMethod = builderClass.getDeclaredMethod("build");
80+
Method builderMethod = streamConstraintsClass.getDeclaredMethod("builder");
81+
Object builder = builderMethod.invoke(null);
82+
for (Invocation i : conf) {
83+
Method method = builderClass.getDeclaredMethod(i.method, i.argType);
84+
method.invoke(builder, i.arg);
85+
}
86+
Object streamReadConstraints = buildMethod.invoke(builder);
87+
Method setStreamReadConstraintsMethod = jf.getClass().getDeclaredMethod("set" + className, streamConstraintsClass);
88+
setStreamReadConstraintsMethod.invoke(jf, streamReadConstraints);
89+
}
90+
91+
private static class Invocation {
92+
final String method;
93+
final Class<?> argType;
94+
final Object arg;
95+
96+
Invocation(String method, Class<?> argType, Object arg) {
97+
this.method = method;
98+
this.argType = argType;
99+
this.arg = arg;
100+
}
101+
}
102+
}

Diff for: driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/native-image.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Args=\
22
-H:ResourceConfigurationResources=${.}/resource-config.json,${.}/resource-config-spi.json \
3-
-H:ReflectionConfigurationResources=${.}/reflect-config.json,${.}/reflect-config-spi.json,${.}/reflect-config-mp-config.json \
3+
-H:ReflectionConfigurationResources=${.}/reflect-config.json,${.}/reflect-config-serde.json,${.}/reflect-config-spi.json,${.}/reflect-config-mp-config.json \
44
-H:SerializationConfigurationResources=${.}/serialization-config.json \
55
--initialize-at-build-time=\
66
org.slf4j \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
[
2+
{
3+
"name": "com.fasterxml.jackson.core.JsonFactory",
4+
"methods": [
5+
{
6+
"name": "setStreamReadConstraints",
7+
"parameterTypes": [
8+
"com.fasterxml.jackson.core.StreamReadConstraints"
9+
]
10+
},
11+
{
12+
"name": "setStreamWriteConstraints",
13+
"parameterTypes": [
14+
"com.fasterxml.jackson.core.StreamWriteConstraints"
15+
]
16+
}
17+
]
18+
},
19+
{
20+
"name": "com.fasterxml.jackson.core.StreamReadConstraints",
21+
"methods": [
22+
{
23+
"name": "builder",
24+
"parameterTypes": []
25+
}
26+
]
27+
},
28+
{
29+
"name": "com.fasterxml.jackson.core.StreamReadConstraints$Builder",
30+
"methods": [
31+
{
32+
"name": "build",
33+
"parameterTypes": []
34+
},
35+
{
36+
"name": "maxDocumentLength",
37+
"parameterTypes": [
38+
"long"
39+
]
40+
},
41+
{
42+
"name": "maxNameLength",
43+
"parameterTypes": [
44+
"int"
45+
]
46+
},
47+
{
48+
"name": "maxNestingDepth",
49+
"parameterTypes": [
50+
"int"
51+
]
52+
},
53+
{
54+
"name": "maxNumberLength",
55+
"parameterTypes": [
56+
"int"
57+
]
58+
},
59+
{
60+
"name": "maxStringLength",
61+
"parameterTypes": [
62+
"int"
63+
]
64+
}
65+
]
66+
},
67+
{
68+
"name": "com.fasterxml.jackson.core.StreamWriteConstraints",
69+
"methods": [
70+
{
71+
"name": "builder",
72+
"parameterTypes": []
73+
}
74+
]
75+
},
76+
{
77+
"name": "com.fasterxml.jackson.core.StreamWriteConstraints$Builder",
78+
"methods": [
79+
{
80+
"name": "build",
81+
"parameterTypes": []
82+
},
83+
{
84+
"name": "maxNestingDepth",
85+
"parameterTypes": [
86+
"int"
87+
]
88+
}
89+
]
90+
},
91+
{
92+
"name": "com.fasterxml.jackson.core.Version",
93+
"methods": [
94+
{
95+
"name": "getMajorVersion",
96+
"parameterTypes": []
97+
},
98+
{
99+
"name": "getMinorVersion",
100+
"parameterTypes": []
101+
}
102+
]
103+
},
104+
{
105+
"name": "com.fasterxml.jackson.core.json.PackageVersion",
106+
"fields": [
107+
{
108+
"name": "VERSION"
109+
}
110+
]
111+
}
112+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.arangodb.serde;
2+
3+
import com.arangodb.ContentType;
4+
import com.arangodb.internal.serde.InternalSerdeProvider;
5+
import com.arangodb.serde.jackson.JacksonSerde;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.EnumSource;
8+
9+
import java.util.UUID;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
public class JacksonConfigurationTest {
14+
15+
@ParameterizedTest
16+
@EnumSource(ContentType.class)
17+
void bigStringInternalSerde(ContentType type) {
18+
ArangoSerde s = new InternalSerdeProvider(type).create();
19+
20+
StringBuilder sb = new StringBuilder();
21+
while (sb.length() < 40_000_000) {
22+
sb.append(UUID.randomUUID());
23+
}
24+
String in = sb.toString();
25+
byte[] bytes = s.serialize(in);
26+
String out = s.deserialize(bytes, String.class);
27+
assertThat(out).isEqualTo(in);
28+
}
29+
30+
@ParameterizedTest
31+
@EnumSource(ContentType.class)
32+
void bigStringUserSerde(ContentType type) {
33+
ArangoSerde s = JacksonSerde.of(type);
34+
35+
StringBuilder sb = new StringBuilder();
36+
while (sb.length() < 40_000_000) {
37+
sb.append(UUID.randomUUID());
38+
}
39+
String in = sb.toString();
40+
byte[] bytes = s.serialize(in);
41+
String out = s.deserialize(bytes, String.class);
42+
assertThat(out).isEqualTo(in);
43+
}
44+
45+
46+
47+
}

Diff for: driver/src/test/resources/simplelogger.properties

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ org.slf4j.simpleLogger.showShortLogName=false
77

88

99
org.slf4j.simpleLogger.defaultLogLevel=info
10+
#org.slf4j.simpleLogger.log.com.arangodb.internal.serde.JacksonUtils=debug
1011
#org.slf4j.simpleLogger.log.com.arangodb.internal.net.Communication=debug
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
Args=\
22
-H:ResourceConfigurationResources=${.}/resource-config-spi.json \
3-
-H:ReflectionConfigurationResources=${.}/reflect-config-spi.json
3+
-H:ReflectionConfigurationResources=${.}/reflect-config-spi.json,${.}/reflect-config-serde.json

0 commit comments

Comments
 (0)