Skip to content

[DE-762] configure jackson stream constraints #537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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