Skip to content

Commit 8a45a62

Browse files
authored
[DE-770] refactoring JacksonUtils with dynamic proxy (#539)
* refactored JacksonUtils using dynamic proxy * fix native tests
1 parent 4b96892 commit 8a45a62

File tree

9 files changed

+386
-81
lines changed

9 files changed

+386
-81
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.arangodb.internal;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.lang.reflect.InvocationHandler;
7+
import java.lang.reflect.Method;
8+
import java.lang.reflect.Proxy;
9+
import java.util.*;
10+
11+
public class ShadedProxy {
12+
private static final Logger LOG = LoggerFactory.getLogger(ShadedProxy.class);
13+
private static final ClassLoader classLoader = ShadedProxy.class.getClassLoader();
14+
15+
@SuppressWarnings("unchecked")
16+
public static <T> T of(Class<T> i, Object target) {
17+
return (T) Proxy.newProxyInstance(
18+
classLoader,
19+
new Class[]{i},
20+
new ShadedInvocationHandler(i, target));
21+
}
22+
23+
public static Optional<Object> getTarget(Object o) {
24+
if (Proxy.isProxyClass(o.getClass())) {
25+
InvocationHandler h = Proxy.getInvocationHandler(o);
26+
if (h instanceof ShadedInvocationHandler) {
27+
return Optional.of(((ShadedInvocationHandler) h).target);
28+
}
29+
}
30+
return Optional.empty();
31+
}
32+
33+
private static class ShadedInvocationHandler implements InvocationHandler {
34+
private final Map<ProxyMethod, Method> targetMethods = new HashMap<>();
35+
private final Map<ProxyMethod, Class<?>> proxiedReturnTypes = new HashMap<>();
36+
private final Object target;
37+
38+
ShadedInvocationHandler(Class<?> i, Object target) {
39+
this.target = target;
40+
Map<ProxyMethod, Method> iMethods = new HashMap<>();
41+
for (Method method : i.getDeclaredMethods()) {
42+
iMethods.put(new ProxyMethod(method), method);
43+
}
44+
45+
Method[] methods;
46+
if (target instanceof Class<?>) {
47+
// proxy for static methods
48+
methods = ((Class<?>) target).getMethods();
49+
} else {
50+
methods = target.getClass().getMethods();
51+
}
52+
53+
for (Method method : methods) {
54+
ProxyMethod pm = new ProxyMethod(method);
55+
Method iMethod = iMethods.get(pm);
56+
if (iMethod != null) {
57+
LOG.trace("adding {}", iMethod);
58+
targetMethods.put(pm, method);
59+
Class<?> mRet = method.getReturnType();
60+
Class<?> iRet = iMethod.getReturnType();
61+
if (!mRet.equals(iRet)) {
62+
LOG.trace("adding proxied return type {}", iRet);
63+
proxiedReturnTypes.put(pm, iRet);
64+
}
65+
}
66+
}
67+
}
68+
69+
@Override
70+
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
71+
ProxyMethod pm = new ProxyMethod(method);
72+
Method targetMethod = targetMethods.get(pm);
73+
LOG.trace("Proxying invocation \n\t of: {} \n\t to: {}", method, targetMethod);
74+
Class<?> returnProxy = proxiedReturnTypes.get(pm);
75+
Object[] realArgs;
76+
if (args == null) {
77+
realArgs = null;
78+
} else {
79+
realArgs = new Object[args.length];
80+
for (int i = 0; i < args.length; i++) {
81+
realArgs[i] = ShadedProxy.getTarget(args[i]).orElse(args[i]);
82+
}
83+
}
84+
Object res = targetMethod.invoke(target, realArgs);
85+
if (returnProxy != null) {
86+
LOG.trace("proxying return type \n\t of: {} \n\t to: {}", targetMethod.getReturnType(), returnProxy);
87+
return ShadedProxy.of(returnProxy, res);
88+
} else {
89+
return res;
90+
}
91+
}
92+
93+
private static class ProxyMethod {
94+
private final String name;
95+
private final String simpleReturnType;
96+
private final String[] simpleParameterTypes;
97+
98+
public ProxyMethod(Method method) {
99+
name = method.getName();
100+
simpleReturnType = method.getReturnType().getSimpleName();
101+
simpleParameterTypes = new String[method.getParameterTypes().length];
102+
for (int i = 0; i < method.getParameterTypes().length; i++) {
103+
simpleParameterTypes[i] = method.getParameterTypes()[i].getSimpleName();
104+
}
105+
}
106+
107+
@Override
108+
public boolean equals(Object o) {
109+
if (this == o) return true;
110+
if (o == null || getClass() != o.getClass()) return false;
111+
ProxyMethod that = (ProxyMethod) o;
112+
return Objects.equals(name, that.name) && Objects.equals(simpleReturnType, that.simpleReturnType) && Arrays.equals(simpleParameterTypes, that.simpleParameterTypes);
113+
}
114+
115+
@Override
116+
public int hashCode() {
117+
int result = Objects.hash(name, simpleReturnType);
118+
result = 31 * result + Arrays.hashCode(simpleParameterTypes);
119+
return result;
120+
}
121+
}
122+
}
123+
124+
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,66 @@
11
package com.arangodb.internal.serde;
22

3+
import com.arangodb.internal.ShadedProxy;
34
import org.slf4j.Logger;
45
import org.slf4j.LoggerFactory;
56

6-
import java.lang.reflect.Method;
7-
import java.util.ArrayList;
8-
import java.util.List;
9-
107
public final class JacksonUtils {
118
private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class);
129

1310
private JacksonUtils() {
1411
}
1512

13+
public interface Version {
14+
int getMajorVersion();
15+
16+
int getMinorVersion();
17+
18+
String toString();
19+
}
20+
21+
public interface StreamReadConstraints {
22+
23+
interface Static {
24+
Builder builder();
25+
}
26+
27+
interface Builder {
28+
Builder maxNumberLength(final int maxNumLen);
29+
30+
Builder maxStringLength(int maxStringLen);
31+
32+
Builder maxNestingDepth(int maxNestingDepth);
33+
34+
Builder maxNameLength(int maxNameLen);
35+
36+
Builder maxDocumentLength(long maxDocLen);
37+
38+
StreamReadConstraints build();
39+
}
40+
}
41+
42+
public interface StreamWriteConstraints {
43+
interface Static {
44+
Builder builder();
45+
}
46+
47+
interface Builder {
48+
Builder maxNestingDepth(int maxNestingDepth);
49+
50+
StreamWriteConstraints build();
51+
}
52+
}
53+
54+
public interface JsonFactory {
55+
Version version();
56+
57+
@SuppressWarnings("UnusedReturnValue")
58+
JsonFactory setStreamReadConstraints(StreamReadConstraints src);
59+
60+
@SuppressWarnings("UnusedReturnValue")
61+
JsonFactory setStreamWriteConstraints(StreamWriteConstraints swc);
62+
}
63+
1664
/**
1765
* Configure JsonFactory with permissive StreamReadConstraints and StreamWriteConstraints.
1866
* It uses reflection to avoid compilation errors with older Jackson versions.
@@ -29,74 +77,51 @@ public static void tryConfigureJsonFactory(Object jf) {
2977
}
3078

3179
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));
80+
JsonFactory proxy = ShadedProxy.of(JsonFactory.class, jf);
81+
Version version = proxy.version();
82+
LOG.debug("Detected Jackson version: {}", version);
83+
84+
// get pkg name dynamically, to support shaded Jackson
85+
String basePkg = jf.getClass().getPackage().getName();
86+
87+
if (isAtLeastVersion(version, 2, 15)) {
88+
Class<?> srcClass = Class.forName(basePkg + "." + StreamReadConstraints.class.getSimpleName());
89+
StreamReadConstraints.Builder builder = ShadedProxy.of(StreamReadConstraints.Static.class, srcClass)
90+
.builder()
91+
.maxNumberLength(Integer.MAX_VALUE)
92+
.maxStringLength(Integer.MAX_VALUE)
93+
.maxNestingDepth(Integer.MAX_VALUE);
94+
if (isAtLeastVersion(version, 2, 16)) {
95+
builder = builder
96+
.maxNameLength(Integer.MAX_VALUE)
97+
.maxDocumentLength(Long.MAX_VALUE);
4298
} else {
4399
LOG.debug("Skipping configuring StreamReadConstraints maxNameLength");
44100
LOG.debug("Skipping configuring StreamReadConstraints maxDocumentLength");
45101
}
46-
configureStreamConstraints(jf, "StreamReadConstraints", readConf);
102+
proxy.setStreamReadConstraints(builder.build());
47103
} else {
48104
LOG.debug("Skipping configuring StreamReadConstraints");
49105
}
50106

51-
if (isAtLeastVersion(jf, 2, 16)) {
107+
if (isAtLeastVersion(version, 2, 16)) {
52108
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);
109+
Class<?> swcClass = Class.forName(basePkg + "." + StreamWriteConstraints.class.getSimpleName());
110+
StreamWriteConstraints swc = ShadedProxy.of(StreamWriteConstraints.Static.class, swcClass)
111+
.builder()
112+
.maxNestingDepth(Integer.MAX_VALUE)
113+
.build();
114+
proxy.setStreamWriteConstraints(swc);
56115
} else {
57116
LOG.debug("Skipping configuring StreamWriteConstraints");
58117
}
59118
}
60119

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-
120+
@SuppressWarnings("SameParameterValue")
121+
private static boolean isAtLeastVersion(Version version, int major, int minor) {
122+
int currentMajor = version.getMajorVersion();
123+
int currentMinor = version.getMinorVersion();
71124
return currentMajor > major || (currentMajor == major && currentMinor >= minor);
72125
}
73126

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-
}
102127
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Args=\
22
-H:ResourceConfigurationResources=${.}/resource-config.json,${.}/resource-config-spi.json \
33
-H:ReflectionConfigurationResources=${.}/reflect-config.json,${.}/reflect-config-serde.json,${.}/reflect-config-spi.json,${.}/reflect-config-mp-config.json \
44
-H:SerializationConfigurationResources=${.}/serialization-config.json \
5+
-H:DynamicProxyConfigurationResources=${.}/proxy-config.json \
56
--initialize-at-build-time=\
67
org.slf4j \
78
--initialize-at-run-time=\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$JsonFactory"]
4+
},
5+
{
6+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$StreamReadConstraints"]
7+
},
8+
{
9+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$StreamReadConstraints$Builder"]
10+
},
11+
{
12+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$StreamReadConstraints$Static"]
13+
},
14+
{
15+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$StreamWriteConstraints"]
16+
},
17+
{
18+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$StreamWriteConstraints$Builder"]
19+
},
20+
{
21+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$StreamWriteConstraints$Static"]
22+
},
23+
{
24+
"interfaces":["com.arangodb.internal.serde.JacksonUtils$Version"]
25+
}
26+
]

0 commit comments

Comments
 (0)