|
| 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 | +} |
0 commit comments