diff --git a/src/main/java/net/helenus/core/AbstractUnitOfWork.java b/src/main/java/net/helenus/core/AbstractUnitOfWork.java index 9182054..fa09f30 100644 --- a/src/main/java/net/helenus/core/AbstractUnitOfWork.java +++ b/src/main/java/net/helenus/core/AbstractUnitOfWork.java @@ -44,6 +44,7 @@ public abstract class AbstractUnitOfWork implements UnitOfW private final AbstractUnitOfWork parent; private final Table>> cache = HashBasedTable.create(); protected String purpose; + protected List nestedPurposes = new ArrayList(); protected int cacheHits = 0; protected int cacheMisses = 0; protected int databaseLookups = 0; @@ -90,6 +91,11 @@ public abstract class AbstractUnitOfWork implements UnitOfW return this; } + @Override + public String getPurpose() { + return purpose; + } + @Override public UnitOfWork setPurpose(String purpose) { this.purpose = purpose; @@ -137,10 +143,11 @@ public abstract class AbstractUnitOfWork implements UnitOfW double daf = (dat / e) * 100; da = String.format(" consuming %,.3fms for data access, or %,2.2f%% of total UOW time.", dat, daf); } + String x = nestedPurposes.stream().distinct().collect(Collectors.joining(", ")); String n = nested.stream().map(uow -> String.valueOf(uow.hashCode())).collect(Collectors.joining(", ")); - String s = String.format(Locale.US, "UOW(%s%s) %s in %,.3fms%s%s%s%s", hashCode(), + String s = String.format(Locale.US, "UOW(%s%s) %s in %,.3fms%s%s%s%s%s", hashCode(), (nested.size() > 0 ? ", [" + n + "]" : ""), what, e, cache, database, da, - (purpose == null ? "" : " " + purpose)); + (purpose == null ? "" : " " + purpose), (nestedPurposes.isEmpty()) ? "" : ", " + x); return s; } @@ -277,7 +284,7 @@ public abstract class AbstractUnitOfWork implements UnitOfW // Merge cache and statistics into parent if there is one. parent.mergeCache(cache); - + if (purpose != null) {parent.nestedPurposes.add(purpose);} parent.cacheHits += cacheHits; parent.cacheMisses += cacheMisses; parent.databaseLookups += databaseLookups; diff --git a/src/main/java/net/helenus/core/HelenusSession.java b/src/main/java/net/helenus/core/HelenusSession.java index 7dd63e9..6b33f60 100644 --- a/src/main/java/net/helenus/core/HelenusSession.java +++ b/src/main/java/net/helenus/core/HelenusSession.java @@ -311,24 +311,22 @@ public final class HelenusSession extends AbstractSessionOperations implements C } public synchronized UnitOfWork begin(UnitOfWork parent) { - StringBuilder purpose = null; - if (LOG.isInfoEnabled()) { - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - int frame = 2; - if (trace[2].getMethodName().equals("begin")) { - frame = 3; - } - purpose = new StringBuilder().append(trace[frame].getClassName()).append(".") - .append(trace[frame].getMethodName()).append("(").append(trace[frame].getFileName()).append(":") - .append(trace[frame].getLineNumber()).append(")"); - } try { Class clazz = unitOfWorkClass; Constructor ctor = clazz.getConstructor(HelenusSession.class, UnitOfWork.class); UnitOfWork uow = ctor.newInstance(this, parent); - if (LOG.isInfoEnabled() && purpose != null) { - uow.setPurpose(purpose.toString()); - } + if (LOG.isInfoEnabled() && uow.getPurpose() == null) { + StringBuilder purpose = null; + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + int frame = 2; + if (trace[2].getMethodName().equals("begin")) { + frame = 3; + } + purpose = new StringBuilder().append(trace[frame].getClassName()).append(".") + .append(trace[frame].getMethodName()).append("(").append(trace[frame].getFileName()).append(":") + .append(trace[frame].getLineNumber()).append(")"); + uow.setPurpose(purpose.toString()); + } if (parent != null) { parent.addNestedUnitOfWork(uow); } diff --git a/src/main/java/net/helenus/core/UnitOfWork.java b/src/main/java/net/helenus/core/UnitOfWork.java index 5a1c7bc..5d55853 100644 --- a/src/main/java/net/helenus/core/UnitOfWork.java +++ b/src/main/java/net/helenus/core/UnitOfWork.java @@ -61,6 +61,7 @@ public interface UnitOfWork extends AutoCloseable { List cacheEvict(List facets); + String getPurpose(); UnitOfWork setPurpose(String purpose); void addDatabaseTime(String name, Stopwatch amount); diff --git a/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java b/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java index 9bde57c..a1e5094 100644 --- a/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java +++ b/src/main/java/net/helenus/core/reflect/MapperInvocationHandler.java @@ -15,16 +15,21 @@ */ package net.helenus.core.reflect; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.HashMap; import java.util.Map; import net.helenus.core.Helenus; import net.helenus.mapping.annotation.Transient; +import net.helenus.mapping.value.ValueProviderMap; import net.helenus.support.HelenusException; public class MapperInvocationHandler implements InvocationHandler, Serializable { @@ -50,14 +55,20 @@ public class MapperInvocationHandler implements InvocationHandler, Serializab int.class); constructor.setAccessible(true); - // Now we need to lookup and invoke special the default method on the interface - // class. + // 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; } + private Object writeReplace() { + return new SerializationProxy(this); + } + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Proxy required."); + } + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { @@ -96,13 +107,20 @@ public class MapperInvocationHandler implements InvocationHandler, Serializab return iface.getSimpleName() + ": " + src.toString(); } + if ("writeReplace".equals(methodName)) { + return new SerializationProxy(this); + } + + if ("readObject".equals(methodName)) { + throw new InvalidObjectException("Proxy required."); + } + if ("dsl".equals(methodName)) { return Helenus.dsl(iface); } if (MapExportable.TO_MAP_METHOD.equals(methodName)) { - // return Collections.unmodifiableMap(src); - return src; + return src; // return Collections.unmodifiableMap(src); } Object value = src.get(methodName); @@ -132,4 +150,27 @@ public class MapperInvocationHandler implements InvocationHandler, Serializab return value; } + + static class SerializationProxy implements Serializable { + + private static final long serialVersionUID = -5617583940055969353L; + + private final Class iface; + private final Map src; + + public SerializationProxy(MapperInvocationHandler mapper) { + this.iface = mapper.iface; + if (mapper.src instanceof ValueProviderMap) { + this.src = new HashMap(mapper.src.size()); + this.src.putAll(src); + } else { + this.src = mapper.src; + } + } + + Object readResolve() throws ObjectStreamException { + return Helenus.map(iface, src); + } + + } } diff --git a/src/main/java/net/helenus/core/reflect/ReflectionMapperInstantiator.java b/src/main/java/net/helenus/core/reflect/ReflectionMapperInstantiator.java index 811a682..4485d0d 100644 --- a/src/main/java/net/helenus/core/reflect/ReflectionMapperInstantiator.java +++ b/src/main/java/net/helenus/core/reflect/ReflectionMapperInstantiator.java @@ -15,6 +15,7 @@ */ package net.helenus.core.reflect; +import java.io.Serializable; import java.lang.reflect.Proxy; import java.util.Map; @@ -28,7 +29,7 @@ public enum ReflectionMapperInstantiator implements MapperInstantiator { public E instantiate(Class iface, Map src, ClassLoader classLoader) { MapperInvocationHandler handler = new MapperInvocationHandler(iface, src); - E proxy = (E) Proxy.newProxyInstance(classLoader, new Class[]{iface, MapExportable.class}, handler); + E proxy = (E) Proxy.newProxyInstance(classLoader, new Class[]{iface, MapExportable.class, Serializable.class}, handler); return proxy; } }