diff --git a/core/src/main/java/com/arangodb/internal/serde/InternalMapperProvider.java b/core/src/main/java/com/arangodb/internal/serde/InternalMapperProvider.java index df088418e..51c381611 100644 --- a/core/src/main/java/com/arangodb/internal/serde/InternalMapperProvider.java +++ b/core/src/main/java/com/arangodb/internal/serde/InternalMapperProvider.java @@ -8,10 +8,9 @@ import org.slf4j.LoggerFactory; import java.util.ServiceLoader; -import java.util.function.Supplier; -public interface InternalMapperProvider extends Supplier { - 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; @@ -25,12 +24,14 @@ static ObjectMapper of(final ContentType contentType) { ServiceLoader 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); } } diff --git a/core/src/main/java/com/arangodb/internal/serde/JacksonUtils.java b/core/src/main/java/com/arangodb/internal/serde/JacksonUtils.java new file mode 100644 index 000000000..4a48f80bc --- /dev/null +++ b/core/src/main/java/com/arangodb/internal/serde/JacksonUtils.java @@ -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 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 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 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; + } + } +} diff --git a/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/native-image.properties b/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/native-image.properties index 5f368dc7b..c00509fef 100644 --- a/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/native-image.properties +++ b/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/native-image.properties @@ -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 \ diff --git a/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config-serde.json b/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config-serde.json new file mode 100644 index 000000000..b516f7c8c --- /dev/null +++ b/driver/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver/reflect-config-serde.json @@ -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" + } + ] + } +] \ No newline at end of file diff --git a/driver/src/test/java/com/arangodb/serde/JacksonConfigurationTest.java b/driver/src/test/java/com/arangodb/serde/JacksonConfigurationTest.java new file mode 100644 index 000000000..e483b4fb8 --- /dev/null +++ b/driver/src/test/java/com/arangodb/serde/JacksonConfigurationTest.java @@ -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); + } + + + +} diff --git a/driver/src/test/resources/simplelogger.properties b/driver/src/test/resources/simplelogger.properties index 3d0860b2c..7649bd6c7 100644 --- a/driver/src/test/resources/simplelogger.properties +++ b/driver/src/test/resources/simplelogger.properties @@ -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 diff --git a/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/native-image.properties b/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/native-image.properties index f6d4bf39a..6323e7ae3 100644 --- a/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/native-image.properties +++ b/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/native-image.properties @@ -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 diff --git a/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/reflect-config-serde.json b/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/reflect-config-serde.json new file mode 100644 index 000000000..b516f7c8c --- /dev/null +++ b/http/src/main/resources/META-INF/native-image/com.arangodb/http-protocol/reflect-config-serde.json @@ -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" + } + ] + } +] \ No newline at end of file diff --git a/jackson-serde-json/src/main/java/com/arangodb/serde/jackson/internal/JacksonMapperProvider.java b/jackson-serde-json/src/main/java/com/arangodb/serde/jackson/internal/JacksonMapperProvider.java index e4cca8d11..679e93222 100644 --- a/jackson-serde-json/src/main/java/com/arangodb/serde/jackson/internal/JacksonMapperProvider.java +++ b/jackson-serde-json/src/main/java/com/arangodb/serde/jackson/internal/JacksonMapperProvider.java @@ -2,6 +2,7 @@ import com.arangodb.ArangoDBException; import com.arangodb.ContentType; +import com.arangodb.internal.serde.JacksonUtils; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; @@ -28,7 +29,10 @@ static ObjectMapper of(final ContentType contentType) { ServiceLoader 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()); diff --git a/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/native-image.properties b/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/native-image.properties index 453d78f42..5c451f35e 100644 --- a/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/native-image.properties +++ b/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/native-image.properties @@ -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,${.}/reflect-config-netty.json \ +-H:ReflectionConfigurationResources=${.}/reflect-config.json,${.}/reflect-config-serde.json,${.}/reflect-config-spi.json,${.}/reflect-config-mp-config.json,${.}/reflect-config-netty.json \ -H:SerializationConfigurationResources=${.}/serialization-config.json \ -Dcom.arangodb.shaded.netty.noUnsafe=true \ -Dcom.arangodb.shaded.netty.leakDetection.level=DISABLED \ diff --git a/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config-serde.json b/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config-serde.json new file mode 100644 index 000000000..f0b0e436c --- /dev/null +++ b/shaded/src/main/resources/META-INF/native-image/com.arangodb/arangodb-java-driver-shaded/reflect-config-serde.json @@ -0,0 +1,112 @@ +[ + { + "name": "com.arangodb.shaded.fasterxml.jackson.core.JsonFactory", + "methods": [ + { + "name": "setStreamReadConstraints", + "parameterTypes": [ + "com.arangodb.shaded.fasterxml.jackson.core.StreamReadConstraints" + ] + }, + { + "name": "setStreamWriteConstraints", + "parameterTypes": [ + "com.arangodb.shaded.fasterxml.jackson.core.StreamWriteConstraints" + ] + } + ] + }, + { + "name": "com.arangodb.shaded.fasterxml.jackson.core.StreamReadConstraints", + "methods": [ + { + "name": "builder", + "parameterTypes": [] + } + ] + }, + { + "name": "com.arangodb.shaded.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.arangodb.shaded.fasterxml.jackson.core.StreamWriteConstraints", + "methods": [ + { + "name": "builder", + "parameterTypes": [] + } + ] + }, + { + "name": "com.arangodb.shaded.fasterxml.jackson.core.StreamWriteConstraints$Builder", + "methods": [ + { + "name": "build", + "parameterTypes": [] + }, + { + "name": "maxNestingDepth", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "name": "com.arangodb.shaded.fasterxml.jackson.core.Version", + "methods": [ + { + "name": "getMajorVersion", + "parameterTypes": [] + }, + { + "name": "getMinorVersion", + "parameterTypes": [] + } + ] + }, + { + "name": "com.arangodb.shaded.fasterxml.jackson.core.json.PackageVersion", + "fields": [ + { + "name": "VERSION" + } + ] + } +]