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).

This commit is contained in:
Greg Burd 2017-08-15 14:05:22 -04:00
parent 89303f9179
commit 9c4d14cc93
2 changed files with 35 additions and 21 deletions

View file

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>net.helenus</groupId> <groupId>net.helenus</groupId>
<artifactId>helenus-core</artifactId> <artifactId>helenus-core</artifactId>
<version>2.0.17-SNAPSHOT</version> <version>2.0.19-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>helenus</name> <name>helenus</name>

View file

@ -15,6 +15,7 @@
*/ */
package net.helenus.core.reflect; package net.helenus.core.reflect;
import java.io.Serializable;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -24,9 +25,11 @@ import java.lang.reflect.Proxy;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import net.helenus.mapping.annotation.Transient;
import net.helenus.support.HelenusException; import net.helenus.support.HelenusException;
public class MapperInvocationHandler<E> implements InvocationHandler { public class MapperInvocationHandler<E> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -7044209982830584984L;
private final Map<String, Object> src; private final Map<String, Object> src;
private final Class<E> iface; private final Class<E> iface;
@ -36,25 +39,31 @@ public class MapperInvocationHandler<E> implements InvocationHandler {
this.iface = iface; 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<MethodHandles.Lookup> 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 @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isDefault()) { // Transient, default methods should simply be invoked as-is.
// NOTE: This is reflection magic to invoke (non-recursively) a default method implemented on an interface if (method.isDefault() && method.getDeclaredAnnotation(Transient.class) != null) {
// that we've proxied (in ReflectionDslInstantiator). I found the answer in this article. return invokeDefault(proxy, method, args);
// 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<MethodHandles.Lookup> 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;
} }
String methodName = method.getName(); String methodName = method.getName();
@ -92,15 +101,20 @@ public class MapperInvocationHandler<E> implements InvocationHandler {
if (value == null) { 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); DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType);
if (type == null) { if (type == null) {
throw new HelenusException("unknown primitive type " + returnType); throw new HelenusException("unknown primitive type " + returnType);
} }
return type.getDefaultValue(); return type.getDefaultValue();
} }
} }