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}