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}