Log the purposes of nested UOWs as well as the parent. Create a serialization proxy for mapped objects.

This commit is contained in:
Greg Burd 2017-10-30 09:40:41 -04:00
parent 52dab5872c
commit c3f9b83770
5 changed files with 70 additions and 22 deletions

View file

@ -44,6 +44,7 @@ public abstract class AbstractUnitOfWork<E extends Exception> implements UnitOfW
private final AbstractUnitOfWork<E> parent; private final AbstractUnitOfWork<E> parent;
private final Table<String, String, Either<Object, List<Facet>>> cache = HashBasedTable.create(); private final Table<String, String, Either<Object, List<Facet>>> cache = HashBasedTable.create();
protected String purpose; protected String purpose;
protected List<String> nestedPurposes = new ArrayList<String>();
protected int cacheHits = 0; protected int cacheHits = 0;
protected int cacheMisses = 0; protected int cacheMisses = 0;
protected int databaseLookups = 0; protected int databaseLookups = 0;
@ -90,6 +91,11 @@ public abstract class AbstractUnitOfWork<E extends Exception> implements UnitOfW
return this; return this;
} }
@Override
public String getPurpose() {
return purpose;
}
@Override @Override
public UnitOfWork setPurpose(String purpose) { public UnitOfWork setPurpose(String purpose) {
this.purpose = purpose; this.purpose = purpose;
@ -137,10 +143,11 @@ public abstract class AbstractUnitOfWork<E extends Exception> implements UnitOfW
double daf = (dat / e) * 100; double daf = (dat / e) * 100;
da = String.format(" consuming %,.3fms for data access, or %,2.2f%% of total UOW time.", dat, daf); 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 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, (nested.size() > 0 ? ", [" + n + "]" : ""), what, e, cache, database, da,
(purpose == null ? "" : " " + purpose)); (purpose == null ? "" : " " + purpose), (nestedPurposes.isEmpty()) ? "" : ", " + x);
return s; return s;
} }
@ -277,7 +284,7 @@ public abstract class AbstractUnitOfWork<E extends Exception> implements UnitOfW
// Merge cache and statistics into parent if there is one. // Merge cache and statistics into parent if there is one.
parent.mergeCache(cache); parent.mergeCache(cache);
if (purpose != null) {parent.nestedPurposes.add(purpose);}
parent.cacheHits += cacheHits; parent.cacheHits += cacheHits;
parent.cacheMisses += cacheMisses; parent.cacheMisses += cacheMisses;
parent.databaseLookups += databaseLookups; parent.databaseLookups += databaseLookups;

View file

@ -311,8 +311,12 @@ public final class HelenusSession extends AbstractSessionOperations implements C
} }
public synchronized UnitOfWork begin(UnitOfWork parent) { public synchronized UnitOfWork begin(UnitOfWork parent) {
try {
Class<? extends UnitOfWork> clazz = unitOfWorkClass;
Constructor<? extends UnitOfWork> ctor = clazz.getConstructor(HelenusSession.class, UnitOfWork.class);
UnitOfWork uow = ctor.newInstance(this, parent);
if (LOG.isInfoEnabled() && uow.getPurpose() == null) {
StringBuilder purpose = null; StringBuilder purpose = null;
if (LOG.isInfoEnabled()) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace(); StackTraceElement[] trace = Thread.currentThread().getStackTrace();
int frame = 2; int frame = 2;
if (trace[2].getMethodName().equals("begin")) { if (trace[2].getMethodName().equals("begin")) {
@ -321,12 +325,6 @@ public final class HelenusSession extends AbstractSessionOperations implements C
purpose = new StringBuilder().append(trace[frame].getClassName()).append(".") purpose = new StringBuilder().append(trace[frame].getClassName()).append(".")
.append(trace[frame].getMethodName()).append("(").append(trace[frame].getFileName()).append(":") .append(trace[frame].getMethodName()).append("(").append(trace[frame].getFileName()).append(":")
.append(trace[frame].getLineNumber()).append(")"); .append(trace[frame].getLineNumber()).append(")");
}
try {
Class<? extends UnitOfWork> clazz = unitOfWorkClass;
Constructor<? extends UnitOfWork> ctor = clazz.getConstructor(HelenusSession.class, UnitOfWork.class);
UnitOfWork uow = ctor.newInstance(this, parent);
if (LOG.isInfoEnabled() && purpose != null) {
uow.setPurpose(purpose.toString()); uow.setPurpose(purpose.toString());
} }
if (parent != null) { if (parent != null) {

View file

@ -61,6 +61,7 @@ public interface UnitOfWork<X extends Exception> extends AutoCloseable {
List<Facet> cacheEvict(List<Facet> facets); List<Facet> cacheEvict(List<Facet> facets);
String getPurpose();
UnitOfWork setPurpose(String purpose); UnitOfWork setPurpose(String purpose);
void addDatabaseTime(String name, Stopwatch amount); void addDatabaseTime(String name, Stopwatch amount);

View file

@ -15,16 +15,21 @@
*/ */
package net.helenus.core.reflect; package net.helenus.core.reflect;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import net.helenus.core.Helenus; import net.helenus.core.Helenus;
import net.helenus.mapping.annotation.Transient; import net.helenus.mapping.annotation.Transient;
import net.helenus.mapping.value.ValueProviderMap;
import net.helenus.support.HelenusException; import net.helenus.support.HelenusException;
public class MapperInvocationHandler<E> implements InvocationHandler, Serializable { public class MapperInvocationHandler<E> implements InvocationHandler, Serializable {
@ -50,14 +55,20 @@ public class MapperInvocationHandler<E> implements InvocationHandler, Serializab
int.class); int.class);
constructor.setAccessible(true); constructor.setAccessible(true);
// Now we need to lookup and invoke special the default method on the interface // Now we need to lookup and invoke special the default method on the interface class.
// class.
final Class<?> declaringClass = method.getDeclaringClass(); final Class<?> declaringClass = method.getDeclaringClass();
Object result = constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) Object result = constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args); .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
return result; return result;
} }
private Object writeReplace() {
return new SerializationProxy(this);
}
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required.");
}
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
@ -96,13 +107,20 @@ public class MapperInvocationHandler<E> implements InvocationHandler, Serializab
return iface.getSimpleName() + ": " + src.toString(); 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)) { if ("dsl".equals(methodName)) {
return Helenus.dsl(iface); return Helenus.dsl(iface);
} }
if (MapExportable.TO_MAP_METHOD.equals(methodName)) { if (MapExportable.TO_MAP_METHOD.equals(methodName)) {
// return Collections.unmodifiableMap(src); return src; // return Collections.unmodifiableMap(src);
return src;
} }
Object value = src.get(methodName); Object value = src.get(methodName);
@ -132,4 +150,27 @@ public class MapperInvocationHandler<E> implements InvocationHandler, Serializab
return value; return value;
} }
static class SerializationProxy implements Serializable {
private static final long serialVersionUID = -5617583940055969353L;
private final Class<?> iface;
private final Map<String, Object> src;
public SerializationProxy(MapperInvocationHandler mapper) {
this.iface = mapper.iface;
if (mapper.src instanceof ValueProviderMap) {
this.src = new HashMap<String, Object>(mapper.src.size());
this.src.putAll(src);
} else {
this.src = mapper.src;
}
}
Object readResolve() throws ObjectStreamException {
return Helenus.map(iface, src);
}
}
} }

View file

@ -15,6 +15,7 @@
*/ */
package net.helenus.core.reflect; package net.helenus.core.reflect;
import java.io.Serializable;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Map; import java.util.Map;
@ -28,7 +29,7 @@ public enum ReflectionMapperInstantiator implements MapperInstantiator {
public <E> E instantiate(Class<E> iface, Map<String, Object> src, ClassLoader classLoader) { public <E> E instantiate(Class<E> iface, Map<String, Object> src, ClassLoader classLoader) {
MapperInvocationHandler<E> handler = new MapperInvocationHandler<E>(iface, src); MapperInvocationHandler<E> handler = new MapperInvocationHandler<E>(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; return proxy;
} }
} }