    001package com.hammurapi.common;
    002
    003import java.lang.reflect.InvocationHandler;
    004import java.lang.reflect.Method;
    005import java.lang.reflect.Proxy;
    006import java.util.Arrays;
    007import java.util.LinkedList;
    008import java.util.List;
    009
    010/**
    011 * This class creates a proxy which record all invocations of
    012 * the methods of the target object with stack traces, calling threads, timestamps, and arguments.
    013 * These records can be used for troubleshooing of concurrency problems.
    014 * @author Pavel Vlasov
    015 *
    016 */
    017public class InvocationRecordingProxyFactory {
    018
    019        /**
    020         * Callback interface to receive problem notifications.
    021         * @author Pavel Vlasov
    022         *
    023         */
    024        public interface ProblemListener {
    025
    026                void onProblem(Throwable th, List<InvocationRecord> records);
    027        }
    028
    029        public static class InvocationRecord {
    030
    031                @Override
    032                public String toString() {
    033                        return "InvocationRecord("+method.getName()+") [thread=" + thread + ", method=" + method
    034                                        + ", timestamp=" + timestamp + ", arguments="
    035                                        + Arrays.toString(arguments) + ", target=" + target + "]";
    036                }
    037
    038                private Thread thread;
    039                private StackTraceElement[] stackTrace;
    040                private Method method;
    041                private long timestamp;
    042                private Object[] arguments;
    043                private Object target;
    044
    045                InvocationRecord(Thread thread, StackTraceElement[] stackTrace, Method method, long timestamp, Object[] arguments, Object target) {
    046                        super();
    047                        this.thread = thread;
    048                        this.stackTrace = stackTrace;
    049                        this.method = method;
    050                        this.timestamp = timestamp;
    051                        if (arguments != null) {
    052                                this.arguments = Arrays.copyOf(arguments, arguments.length);
    053                        }
    054                        this.target = target;
    055                }
    056
    057                public Thread getThread() {
    058                        return thread;
    059                }
    060
    061                public StackTraceElement[] getStackTrace() {
    062                        return stackTrace;
    063                }
    064
    065                public Method getMethod() {
    066                        return method;
    067                }
    068
    069                public long getTimestamp() {
    070                        return timestamp;
    071                }
    072
    073                Object[] getArguments() {
    074                        return arguments;
    075                }
    076
    077                Object getTarget() {
    078                        return target;
    079                }
    080
    081        }
    082
    083        private static class RecordingInvocationHandler implements InvocationHandler {
    084
    085                private ProblemListener problemListener;
    086
    087                RecordingInvocationHandler(Object target, ProblemListener problemListener, LinkedList<InvocationRecord> records) {
    088                        this.target = target;
    089                        this.problemListener = problemListener;
    090                        this.records = records;
    091                }
    092
    093                private Object target;
    094
    095                private LinkedList<InvocationRecord> records;
    096
    097                @Override
    098                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    099
    100                        try {
    101                                synchronized (records) {
    102                                        Thread currentThread = Thread.currentThread();
    103                                        records.addFirst(new InvocationRecord(currentThread, currentThread.getStackTrace(), method, System.currentTimeMillis(), args, target));
    104                                }
    105                                Object ret = method.invoke(target, args);
    106                                Class<?> returnType = method.getReturnType();
    107                                if (returnType.isInterface()) {
    108                                        return Proxy.newProxyInstance(returnType.getClass().getClassLoader(), new Class[] {returnType}, new RecordingInvocationHandler(ret, problemListener, records));
    109                                }
    110                                return ret;
    111                        } catch (Throwable th) {
    112                                if (problemListener!=null) {
    113                                        problemListener.onProblem(th, records);
    114                                }
    115                                throw th;
    116                        }
    117                }
    118
    119                List<InvocationRecord> getRecords() {
    120                        return records;
    121                }
    122
    123        }
    124
    125        /**
    126         * Wraps target into a proxy for method call recording.
    127         * @param <I>
    128         * @param <T>
    129         * @param target
    130         * @param problemListener
    131         * @return
    132         */
    133        @SuppressWarnings("unchecked")
    134        public static <I, T extends I> I wrap(Class<I> proxyType, T target, ProblemListener problemListener) {
    135                return (I) Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[] {proxyType}, new RecordingInvocationHandler(target, problemListener, new LinkedList<InvocationRecord>()));
    136        }
    137
    138        public static List<InvocationRecord> getInvocationRecords(Object proxy) {
    139                return ((RecordingInvocationHandler) Proxy.getInvocationHandler(proxy)).getRecords();
    140        }
    141
    142}