Skip to content

Commit

Permalink
[DE-762] configure jackson stream constraints (#537)
Browse files Browse the repository at this point in the history
* configure Jackson StreamReadConstraints and StreamWriteConstraints with permissive values

* test JSON serde with big strings

* fix native tests

* fix shaded native tests
  • Loading branch information
rashtao authored Jan 18, 2024
1 parent 2ae5af9 commit 5b1b958
Show file tree
Hide file tree
Showing 11 changed files with 500 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
import org.slf4j.LoggerFactory;

import java.util.ServiceLoader;
import java.util.function.Supplier;

public interface InternalMapperProvider extends Supplier<ObjectMapper> {
Logger LOG = LoggerFactory.getLogger(InternalMapperProvider.class);
class InternalMapperProvider {
private static final Logger LOG = LoggerFactory.getLogger(InternalMapperProvider.class);

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

ServiceLoader<JsonFactory> sl = ServiceLoader.load(JsonFactory.class);
for (JsonFactory jf : sl) {
if(formatName.equals(jf.getFormatName())){
if (formatName.equals(jf.getFormatName())) {
if (contentType == ContentType.JSON) {
JacksonUtils.tryConfigureJsonFactory(jf);
}
return new ObjectMapper(jf);
}
LOG.debug("Required format ({}) not supported by JsonFactory: {}", formatName, jf.getClass().getName());
}

throw new ArangoDBException("No JsonFactory found for content type: " + contentType);
}
}
102 changes: 102 additions & 0 deletions core/src/main/java/com/arangodb/internal/serde/JacksonUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.arangodb.internal.serde;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public final class JacksonUtils {
private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class);

private JacksonUtils() {
}

/**
* Configure JsonFactory with permissive StreamReadConstraints and StreamWriteConstraints.
* It uses reflection to avoid compilation errors with older Jackson versions.
* It uses dynamic package names to be compatible with shaded Jackson.
*
* @param jf JsonFactory to configure
*/
public static void tryConfigureJsonFactory(Object jf) {
try {
configureJsonFactory(jf);
} catch (Throwable t) {
LOG.warn("Got exception while configuring JsonFactory, skipping...", t);
}
}

private static void configureJsonFactory(Object jf) throws Exception {
// using reflection because these configuration are not supported in older Jackson versions
if (isAtLeastVersion(jf, 2, 15)) {
LOG.debug("Configuring StreamReadConstraints ...");
List<Invocation> readConf = new ArrayList<>();
readConf.add(new Invocation("maxNumberLength", int.class, Integer.MAX_VALUE));
readConf.add(new Invocation("maxStringLength", int.class, Integer.MAX_VALUE));
readConf.add(new Invocation("maxNestingDepth", int.class, Integer.MAX_VALUE));
if (isAtLeastVersion(jf, 2, 16)) {
readConf.add(new Invocation("maxNameLength", int.class, Integer.MAX_VALUE));
readConf.add(new Invocation("maxDocumentLength", long.class, Long.MAX_VALUE));
} else {
LOG.debug("Skipping configuring StreamReadConstraints maxNameLength");
LOG.debug("Skipping configuring StreamReadConstraints maxDocumentLength");
}
configureStreamConstraints(jf, "StreamReadConstraints", readConf);
} else {
LOG.debug("Skipping configuring StreamReadConstraints");
}

if (isAtLeastVersion(jf, 2, 16)) {
LOG.debug("Configuring StreamWriteConstraints ...");
List<Invocation> writeConf = new ArrayList<>();
writeConf.add(new Invocation("maxNestingDepth", int.class, Integer.MAX_VALUE));
configureStreamConstraints(jf, "StreamWriteConstraints", writeConf);
} else {
LOG.debug("Skipping configuring StreamWriteConstraints");
}
}

private static boolean isAtLeastVersion(Object jf, int major, int minor) throws Exception {
Class<?> packageVersionClass = Class.forName(jf.getClass().getPackage().getName() + ".json.PackageVersion");
Object version = packageVersionClass.getDeclaredField("VERSION").get(null);

Class<?> versionClass = Class.forName(jf.getClass().getPackage().getName() + ".Version");
int currentMajor = (int) versionClass.getDeclaredMethod("getMajorVersion").invoke(version);
int currentMinor = (int) versionClass.getDeclaredMethod("getMinorVersion").invoke(version);

LOG.debug("Detected Jackson version: {}.{}", currentMajor, currentMinor);

return currentMajor > major || (currentMajor == major && currentMinor >= minor);
}

private static void configureStreamConstraints(Object jf, String className, List<Invocation> conf) throws Exception {
// get pkg name dynamically, to support shaded Jackson
String basePkg = jf.getClass().getPackage().getName();
Class<?> streamConstraintsClass = Class.forName(basePkg + "." + className);
Class<?> builderClass = Class.forName(basePkg + "." + className + "$Builder");
Method buildMethod = builderClass.getDeclaredMethod("build");
Method builderMethod = streamConstraintsClass.getDeclaredMethod("builder");
Object builder = builderMethod.invoke(null);
for (Invocation i : conf) {
Method method = builderClass.getDeclaredMethod(i.method, i.argType);
method.invoke(builder, i.arg);
}
Object streamReadConstraints = buildMethod.invoke(builder);
Method setStreamReadConstraintsMethod = jf.getClass().getDeclaredMethod("set" + className, streamConstraintsClass);
setStreamReadConstraintsMethod.invoke(jf, streamReadConstraints);
}

private static class Invocation {
final String method;
final Class<?> argType;
final Object arg;

Invocation(String method, Class<?> argType, Object arg) {
this.method = method;
this.argType = argType;
this.arg = arg;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Args=\
-H:ResourceConfigurationResources=${.}/resource-config.json,${.}/resource-config-spi.json \
-H:ReflectionConfigurationResources=${.}/reflect-config.json,${.}/reflect-config-spi.json,${.}/reflect-config-mp-config.json \
-H:ReflectionConfigurationResources=${.}/reflect-config.json,${.}/reflect-config-serde.json,${.}/reflect-config-spi.json,${.}/reflect-config-mp-config.json \
-H:SerializationConfigurationResources=${.}/serialization-config.json \
--initialize-at-build-time=\
org.slf4j \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
[
{
"name": "com.fasterxml.jackson.core.JsonFactory",
"methods": [
{
"name": "setStreamReadConstraints",
"parameterTypes": [
"com.fasterxml.jackson.core.StreamReadConstraints"
]
},
{
"name": "setStreamWriteConstraints",
"parameterTypes": [
"com.fasterxml.jackson.core.StreamWriteConstraints"
]
}
]
},
{
"name": "com.fasterxml.jackson.core.StreamReadConstraints",
"methods": [
{
"name": "builder",
"parameterTypes": []
}
]
},
{
"name": "com.fasterxml.jackson.core.StreamReadConstraints$Builder",
"methods": [
{
"name": "build",
"parameterTypes": []
},
{
"name": "maxDocumentLength",
"parameterTypes": [
"long"
]
},
{
"name": "maxNameLength",
"parameterTypes": [
"int"
]
},
{
"name": "maxNestingDepth",
"parameterTypes": [
"int"
]
},
{
"name": "maxNumberLength",
"parameterTypes": [
"int"
]
},
{
"name": "maxStringLength",
"parameterTypes": [
"int"
]
}
]
},
{
"name": "com.fasterxml.jackson.core.StreamWriteConstraints",
"methods": [
{
"name": "builder",
"parameterTypes": []
}
]
},
{
"name": "com.fasterxml.jackson.core.StreamWriteConstraints$Builder",
"methods": [
{
"name": "build",
"parameterTypes": []
},
{
"name": "maxNestingDepth",
"parameterTypes": [
"int"
]
}
]
},
{
"name": "com.fasterxml.jackson.core.Version",
"methods": [
{
"name": "getMajorVersion",
"parameterTypes": []
},
{
"name": "getMinorVersion",
"parameterTypes": []
}
]
},
{
"name": "com.fasterxml.jackson.core.json.PackageVersion",
"fields": [
{
"name": "VERSION"
}
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.arangodb.serde;

import com.arangodb.ContentType;
import com.arangodb.internal.serde.InternalSerdeProvider;
import com.arangodb.serde.jackson.JacksonSerde;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

public class JacksonConfigurationTest {

@ParameterizedTest
@EnumSource(ContentType.class)
void bigStringInternalSerde(ContentType type) {
ArangoSerde s = new InternalSerdeProvider(type).create();

StringBuilder sb = new StringBuilder();
while (sb.length() < 40_000_000) {
sb.append(UUID.randomUUID());
}
String in = sb.toString();
byte[] bytes = s.serialize(in);
String out = s.deserialize(bytes, String.class);
assertThat(out).isEqualTo(in);
}

@ParameterizedTest
@EnumSource(ContentType.class)
void bigStringUserSerde(ContentType type) {
ArangoSerde s = JacksonSerde.of(type);

StringBuilder sb = new StringBuilder();
while (sb.length() < 40_000_000) {
sb.append(UUID.randomUUID());
}
String in = sb.toString();
byte[] bytes = s.serialize(in);
String out = s.deserialize(bytes, String.class);
assertThat(out).isEqualTo(in);
}



}
1 change: 1 addition & 0 deletions driver/src/test/resources/simplelogger.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ org.slf4j.simpleLogger.showShortLogName=false


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

0 comments on commit 5b1b958

Please sign in to comment.