From 1c68ebdcb86d0eadc9e4d012f7803be177d9032f Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Tue, 15 Aug 2017 14:05:22 -0400 Subject: [PATCH] Enable serialization of proxied entity objects. Enable the use of default implementation for getter methods to supply default values when the map contains a 'null' (or doesn't contain that key). --- pom.xml | 2 +- .../core/reflect/MapperInvocationHandler.java | 54 ++++++++++++------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index 555b44b..7be0cc1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 net.helenus helenus-core - 2.0.17-SNAPSHOT + 2.0.19-SNAPSHOT jar helenus diff --git a/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java b/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java index c1e55bd..1c6a166 100644 --- a/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java +++ b/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java @@ -15,6 +15,7 @@ */ package net.helenus.core.reflect; +import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; @@ -24,9 +25,11 @@ import java.lang.reflect.Proxy; import java.util.Collections; import java.util.Map; +import net.helenus.mapping.annotation.Transient; import net.helenus.support.HelenusException; -public class MapperInvocationHandler implements InvocationHandler { +public class MapperInvocationHandler implements InvocationHandler, Serializable { + private static final long serialVersionUID = -7044209982830584984L; private final Map src; private final Class iface; @@ -36,25 +39,31 @@ public class MapperInvocationHandler implements InvocationHandler { this.iface = iface; } + private Object invokeDefault(Object proxy, Method method, Object[] args) throws Throwable { + // NOTE: This is reflection magic to invoke (non-recursively) a default method implemented on an interface + // that we've proxied (in ReflectionDslInstantiator). I found the answer in this article. + // https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles/ + + // First, we need an instance of a private inner-class found in MethodHandles. + Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, + int.class); + constructor.setAccessible(true); + + // Now we need to lookup and invoke special the default method on the interface class. + final Class declaringClass = method.getDeclaringClass(); + Object result = constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) + .unreflectSpecial(method, declaringClass) + .bindTo(proxy) + .invokeWithArguments(args); + return result; + } + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (method.isDefault()) { - // NOTE: This is reflection magic to invoke (non-recursively) a default method implemented on an interface - // that we've proxied (in ReflectionDslInstantiator). I found the answer in this article. - // https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles/ - - // First, we need an instance of a private inner-class found in MethodHandles. - Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); - constructor.setAccessible(true); - - // Now we need to lookup and invoke special the default method on the interface class. - final Class declaringClass = method.getDeclaringClass(); - Object result = constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) - .unreflectSpecial(method, declaringClass) - .bindTo(proxy) - .invokeWithArguments(args); - return result; + // Transient, default methods should simply be invoked as-is. + if (method.isDefault() && method.getDeclaredAnnotation(Transient.class) == null) { + return invokeDefault(proxy, method, args); } String methodName = method.getName(); @@ -92,15 +101,20 @@ public class MapperInvocationHandler implements InvocationHandler { if (value == null) { - if (returnType.isPrimitive()) { + // Default implementations of non-Transient methods in entities are the default value when the + // map contains 'null'. + if (method.isDefault()) { + return invokeDefault(proxy, method, args); + } + // Otherwise, if the return type of the method is a primitive Java type then we'll return the standard + // default values to avoid a NPE in user code. + if (returnType.isPrimitive()) { DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType); if (type == null) { throw new HelenusException("unknown primitive type " + returnType); } - return type.getDefaultValue(); - } }