001: package com.jamonapi.proxy;
002:
003: import com.jamonapi.*;
004: import java.lang.reflect.Proxy;
005: import java.sql.*;
006: import java.util.*;
007:
008: /**
009: * MonProxyFactory allows developers to monitor ANY interface by simply passing the Object implementing
010: * the interface to the monitor method. (note the object passed MUST implement an interface or it will
011: * be a runtime error). A great use of this is to monitor jdbc interfaces and to aid in this there are
012: * overloaded methods that take Connections, Statements, PreparedStatements, CallableStatements, and
013: * ResultSets. These overloaded methods take advantage of knowledege of SQL and track additional statistics.
014: * The following capabilities can be acquired by using MonProxyFactory. All can individually be enabled/disabled.
015: *
016: * 0) Overall monitoring can be enabled/disabled by calling MonProxyFactory.enable(...). This will enable/disable monitors.
017: * You can start out by enabling all monitors and disabling the ones you wish or vice versa.
018: *
019: * 1) All methods of a given interface will have a jamon rows. Any jamon row will have the label (in this
020: * case the method signature), hits, avg/min/max execution time, active/avg active/max active, last value and
021: * more. By default interface monitoring is on. It can be enabled/disabled by calling MonProxyFactory.enableInterfaceM(...).
022: * JDBC classes such as Connections, Statements, PreparedStatemetns, CallableStatements, and ResultsSets
023: * will also automatically be monitored if the class returning them was monitored. If you don't wish this
024: * to occur then you can use the monitor method that takes an Object.
025: *
026: * 2) ResultSet interfaces can be monitored however methods like getObject and next are called so much and
027: * typically don't cause performance problems, so there is a seperate enable/disable capability for them. Note for
028: * ResultSet monitoring to occur interface monitoring must also be enabled.
029: * However it can be enabled if interface monitoring is enabled and MonProxyFactory.enabledResultSets(true) is called.
030: * ResultSet's are by default not monitored.
031: *
032: * 3) SQLSummary will add a jamon row for all sql text issued against a PreparedStatement, CallableStatement,
033: * or Statement. Argument values are replaced with ? for Statements to ensure the logical queries are matched.
034: * PreparedStatements need not be changed to do this, but Statements may look a little different
035: * For example: select * from table where name='jeff beck' would become select * from table where name=?
036: * This is a powerful monitor as it allows you to see hits, avg/min/max query times for all queries in your
037: * application. This is enabled/disabled by calling MonProxyFactory.enableSQLSummary(...).
038: *
039: * 4) SQLDetail puts the last N (configurable) queries that have been run into a rolling buffer. The SQL buffer
040: * will have the actual query in the case of a Statement and the version with ? for PreparedStatements. In addition
041: * other stats will be in this buffer such as how long the query took to execute, how many times has the PreparedStatement
042: * been reused, the jdbc method that executed the sql, and the exception stack trace if it occured. This can be enabled/disabled
043: * by calling MonProxyFactory.enableSQLDetail(...)
044: *
045: * 5) Exception Summary will add several jamon rows when an exception occurs. Note the Exception buffer is used for any kind of Exception
046: * including SQLExceptions. The exceptions added are 1. One containing the method that through the exception as well as the exception.
047: * 2. One indicating how many exceptions total have been thrown through proxies, 3) One containing the exception type that was thrown.
048: * This can be enabled/disabled by calling MonProxyFactory.enableExceptionSummary(...)
049: *
050: * 6) ExceptionDetail puts the last N (configurable) exceptions that have occured to any interface that is being monitored into a buffer.
051: * The stack trace is in the row as well as when it was thrown and what method threw it. This can be enabled/disabled by calling
052: * MonProxyFactory.enableExceptionDetail(...).
053: *
054: * Sample code:
055: * ResultSet rs= MonProxyFactory.monitor(resultSet);
056: * Connection conn=MonProxyFactory.monitor(connection);
057: * MyInterface my=(MyInterface) MonProxyFactory.monitor(myObject);//myObject implements MyInterface
058: * YourInterface your=(YourInterface) MonProxyFactory.monitor(yourObject);//myObject implements MyInterface
059: *
060: *
061: * @author steve souza
062: *
063: */
064:
065: public class MonProxyFactory {
066: private static final Class[] CLASS_ARRAY = new Class[0];
067: private static Params params = new Params();
068:
069: /** By passing any interface to the monitor method, all public method calls and exceptions
070: * will be monitored. It will be a runtime error if the Object passed does not implement an interface. Note that
071: * only interfaces implemented directly by the passed in Object are monitored. Should you want to cast to an interface
072: * implemented by a base class to the passed in Object you should call one of the more explicit monitor(..) methods
073: *
074: * Sample call:
075: * MyInterface myProxyObject=(MyInterface) MonProxyFactory.monitor(myObject);
076: */
077:
078: public static Object monitor(Object object) {
079: if (!isEnabled() || object == null) // if not enabled return the original object unchanged, not the proxy
080: return object;
081: else
082: return monitorNoCheck(object, getInterfaces(object
083: .getClass()));// proxy will implement ALL interfaces of this class
084: }
085:
086: /** By passing any interface to the monitor method, and an array of interfaces to implement then all public method calls and exceptions
087: * will be monitored. It will be a runtime error if the Object passed does not implement an interface.
088: *
089: * Sample call:
090: * MyInterface myProxyObject=(MyInterface) MonProxyFactory.monitor(myObject, ineterfaces);
091: */
092: public static Object monitor(Object object, Class[] interfaces) {
093: if (!isEnabled() || object == null) // if not enabled return the original object unchanged, not the proxy
094: return object;
095: else
096: return monitorNoCheck(object, interfaces);
097: }
098:
099: private static Object monitorNoCheck(Object object,
100: Class[] interfaces) {
101: return Proxy.newProxyInstance(object.getClass()
102: .getClassLoader(), interfaces,// proxy will implement ALL interfaces in array
103: new MonProxy(object, params));
104:
105: }
106:
107: /** By passing any interface to the monitor method, and an interface to implement then all public method calls and exceptions
108: * will be monitored. It will be a runtime error if the Object passed does not implement an interface.
109: *
110: * Sample call:
111: * MyInterface myProxyObject=(MyInterface) MonProxyFactory.monitor(myObject, com.mypackage.MyInterface.class);
112: */
113:
114: public static Object monitor(Object object, Class iface) {
115: return monitor(object, new Class[] { iface });
116: }
117:
118: /** Note if a connection object is monitored any Statements, PreparedStatements, CallableStatements, and
119: * optionally ResultSets that it creates will automatically be monitored. So for jdbc interfaces usually it will be sufficient
120: * to simply monitor the connection. You could also call MonProxyFactory.monitor((Object)conn);
121: * and the connection would be monitored however other child objects wouldn't be and recently executed sql would not be put
122: * in a buffer. The same applies to the other overloaded sql monitor(...) method calls below. For sql monitored objects
123: * to be monitored both the overall monitoring must be enabled. Monitoring rules apply as discussed in the top of this document.
124:
125: *
126: */
127: public static Connection monitor(Connection conn) {
128: return (Connection) monitorJDBC(conn);
129: }
130:
131: /** Monitor a resultSets methods. Note the version that takes an explicit class is used for when the class is a proxy*/
132: public static ResultSet monitor(ResultSet rs) {
133: return (ResultSet) monitorJDBC(rs);
134: }
135:
136: /** Monitor a Statements methods, as well as any ResultSets it returns (assuming the proper monitoring options are enabled) */
137: public static Statement monitor(Statement statement) {
138: return (Statement) monitorJDBC(statement);
139: }
140:
141: /** Monitor a PreparedStatements methods, as well as any ResultSets it returns (assuming the proper monitoring options are enabled) */
142: public static PreparedStatement monitor(PreparedStatement statement) {
143: return (PreparedStatement) monitorJDBC(statement);
144: }
145:
146: /** Monitor a CallableStatements methods, as well as any ResultSets it returns (assuming the proper monitoring options are enabled) */
147: public static CallableStatement monitor(CallableStatement statement) {
148: return (CallableStatement) monitorJDBC(statement);
149: }
150:
151: static Object monitorJDBC(Object object) {
152:
153: // if monitoring is not enabled, sql monitoring is not enabled, the object is a null or already an instance of a proxy then return
154: // the original object unchanged. (it would have to be a proxy object with a JDBCMonProxy invocation handler
155: if (!params.isEnabled
156: || (!params.isSQLSummaryEnabled && !params.isSQLDetailEnabled)
157: || object == null
158: || (object instanceof Proxy && Proxy
159: .getInvocationHandler(object) instanceof JDBCMonProxy)) // if not enabled return the original object unchanged, not the proxy
160: return object;
161: else
162: return Proxy.newProxyInstance(object.getClass()
163: .getClassLoader(),
164: getInterfaces(object.getClass()),// proxy will implement passed in interfaces
165: new JDBCMonProxy(object, params));
166: }
167:
168: /** For every class in the Object/Interface heirarchy find its implemented interfaces. All interfaces this class
169: * implements are returned. Either the Class of an Object or interfaces Class may be passed to
170: * this method. The difference between this method and the method 'Class.getInterfaces()' is
171: * that this one returns ALL implemented interfaces whereas that one only returns interfaces that
172: * are directly implemented by the Class.
173: */
174: public static Class[] getInterfaces(Class cls) {
175: if (cls == null)
176: return null;
177:
178: Set interfaceHeirarchy = new HashSet();
179: // Get class heirarchy and loop through it and its interfaces adding each interface to the passed
180: // in Set.
181: Class[] objTree = getClassHeirarchy(cls);
182: for (int i = 0; i < objTree.length; i++)
183: getInterfaces(objTree[i], interfaceHeirarchy);
184:
185: return toClassArray(interfaceHeirarchy);
186:
187: }
188:
189: /** Convert a Collection to a Class[] array */
190: private static Class[] toClassArray(Collection coll) {
191: if (coll == null || coll.size() == 0)
192: return null;
193: else
194: return (Class[]) coll.toArray(CLASS_ARRAY);// convert the Set to Class[]
195:
196: }
197:
198: /*
199: * Returns the inheritance heirarchy of the specified Class that was passed in. For example if there
200: * are three levels such as Base1, Base2, Base3 then it would return an array of these 3 Class elements.
201: */
202:
203: private static Class[] getClassHeirarchy(Class cls) {
204: if (cls == null)
205: return null;
206:
207: // classes will contain the inheritance chain of Objects.
208: List classes = new ArrayList();
209: // Loop through super classes until null is found which indicates there are no more Objects
210: // in the inheritance chain.
211: while (cls != null) {
212: classes.add(cls);
213: cls = cls.getSuperclass();
214: }
215:
216: return toClassArray(classes);
217: }
218:
219: /** A recursive method called for each Object or interface in the heirarchy. All interfaces are added into the
220: * passed in Set. When either a Class is null, or it implements no other interfaces the recursive method bubbles
221: * up the chain.
222: *
223: */
224: private static void getInterfaces(Class cls, Set heirarchy) {
225: if (cls != null) {
226: Class[] heir = cls.getInterfaces();// gets immediate implemented interfaces of passed in Class or interface
227: int len = (heir == null) ? 0 : heir.length;
228: for (int i = 0; i < len; i++) {
229: heirarchy.add(heir[i]);
230: getInterfaces(heir[i], heirarchy);// recursive
231: }
232: }
233:
234: }
235:
236: // Standard and Exception monitoring methods
237:
238: /**
239: * Get the number of Exceptions that can be stored in the buffer before the oldest entries must
240: * be removed.
241: *
242: */
243: public static int getExceptionBufferSize() {
244: return params.exceptionBuffer.getBufferSize();
245: }
246:
247: /**
248: * Set the number of Exceptions that can be stored in the buffer before the oldest entries must
249: * be removed. A value of 0 will disable collecting of Exceptions in the buffer.
250: *
251: */
252:
253: public static void setExceptionBufferSize(int exceptionBufferSize) {
254: params.exceptionBuffer.setBufferSize(exceptionBufferSize);
255: }
256:
257: /**
258: * Remove all Exceptions from the buffer.
259: *
260: */
261: public static void resetExceptionDetail() {
262: params.exceptionBuffer.reset();
263: }
264:
265: /** Inidicates whether methods of the interface are monitored or not */
266: public static boolean isInterfaceEnabled() {
267: return params.isInterfaceEnabled;
268: }
269:
270: /** Enables/disables whether methods of the interface are monitored or not */
271: public static void enableInterface(boolean enable) {
272: params.isInterfaceEnabled = enable;
273: if (enable)
274: enable(true);
275: }
276:
277: /** Indicates whether jamon summary stats are kept for exceptions */
278: public static boolean isExceptionSummaryEnabled() {
279: return params.isExceptionSummaryEnabled;
280: }
281:
282: /** Enables/disables jamon summary stats for exceptions */
283: public static void enableExceptionSummary(boolean enable) {
284: params.isExceptionSummaryEnabled = enable;
285: if (enable)
286: enable(true);
287: }
288:
289: /** Indicates whether exceptions are tracked in a rolling buffer */
290: public static boolean isExceptionDetailEnabled() {
291: return params.isExceptionDetailEnabled;
292: }
293:
294: /** Enables/Disables whether exceptions are tracked in a rolling buffer */
295: public static void enableExceptionDetail(boolean enable) {
296: params.isExceptionDetailEnabled = enable;
297: if (enable)
298: params.exceptionBuffer.enable();
299: else
300: params.exceptionBuffer.disable();
301:
302: if (enable)
303: enable(true);
304:
305: }
306:
307: /** Indicates whether jamon summary stats are kept for SQL */
308: public static boolean isSQLSummaryEnabled() {
309: return params.isSQLSummaryEnabled;
310: }
311:
312: /** Enables/Disables jamon summary stats for SQL */
313: public static void enableSQLSummary(boolean enable) {
314: params.isSQLSummaryEnabled = enable;
315: if (enable)
316: enable(true);
317: }
318:
319: /** Indicates whether sql command details (time, sql, stack trace, ...) are kept in a rolling buffer */
320: public static boolean isSQLDetailEnabled() {
321: return params.isSQLDetailEnabled;
322: }
323:
324: /** Enables/disables whether sql command details (time, sql, stack trace, ...) are kept in a rolling buffer */
325: public static void enableSQLDetail(boolean enable) {
326: params.isSQLDetailEnabled = enable;
327: if (enable)
328: params.sqlBuffer.enable();
329: else
330: params.sqlBuffer.disable();
331:
332: if (enable)
333: enable(true);
334: }
335:
336: /** Indicates whether ResultSet methods are monitored. Note interface monitoring must also be enabled for ResultSet monitoring to occur */
337: public static boolean isResultSetEnabled() {
338: return params.isResultSetEnabled;
339: }
340:
341: /** Enables/disables whether ResultSet methods are monitored. Note interface monitoring must also be enabled for ResultSet monitoring to occur */
342: public static void enableResultSet(boolean enable) {
343: params.isResultSetEnabled = enable;
344: if (enable) {
345: enableInterface(true);
346: enable(true);
347: }
348: }
349:
350: /** Returns true if MonProxyFactory is enabled. */
351: public static boolean isEnabled() {
352: return params.isEnabled;
353: }
354:
355: /** Enables all monitors. This overrides all other monitor booleans. It never enables ResultSet monitoring
356: * That must be done as a separates step as that is not the enabled monitoring by default. All other monitors will be disabled/enabled when
357: * calling this method.
358: * */
359: public static void enableAll(boolean enable) {
360: enable(enable);
361: enableInterface(enable);
362: enableExceptionSummary(enable);
363: enableExceptionDetail(enable);
364: enableSQLSummary(enable);
365: enableSQLDetail(enable);
366: enableResultSet(enable);
367: }
368:
369: public static boolean isAllEnabled() {
370: return (params.isEnabled && params.isExceptionDetailEnabled
371: && params.isExceptionSummaryEnabled
372: && params.isSQLSummaryEnabled
373: && params.isSQLDetailEnabled
374: && params.isInterfaceEnabled && params.isResultSetEnabled);
375: }
376:
377: /** Enables all monitors except ResultSet monitoring. This overrides all other monitor booleans. It never enables ResultSet monitoring
378: * That must be done as a separates step as that is not the enabled monitoring by default. All other monitors will be disabled/enabled when
379: * calling this method.
380: * */
381: public static void enable(boolean enable) {
382: params.isEnabled = enable;
383: }
384:
385: static Params getParams() {
386: return params;
387: }
388:
389: /** Get the header that can be used to display the Exceptions buffer */
390: public static String[] getExceptionDetailHeader() {
391: return params.exceptionBuffer.getHeader();
392: }
393:
394: /** Get the exception buffer as an array, so it can be displayed */
395: public static Object[][] getExceptionDetail() {
396: return params.exceptionBuffer.getData();
397: }
398:
399: // JDBC Monitoroing methods
400:
401: /**
402: * Get the number of SQL statements that can be stored in the buffer before the
403: * oldest entries must be removed.
404: *
405: */
406: public static int getSQLBufferSize() {
407: return params.sqlBuffer.getBufferSize();
408: }
409:
410: /**
411: * Set the number of SQL Statements that can be stored in the buffer before the oldest entries must
412: * be removed. A value of 0 will disable the collection of Exceptions in the buffer. Note if
413: * monitoring is disabled exceptions will also not be put in the buffer.
414: *
415: */
416:
417: public static void setSQLBufferSize(int sqlBufferSize) {
418: params.sqlBuffer.setBufferSize(sqlBufferSize);
419: }
420:
421: /**
422: * Remove all SQL from the buffer.
423: *
424: */
425: public static void resetSQLDetail() {
426: params.sqlBuffer.reset();
427: }
428:
429: /** Get the header that can be used to display the SQL buffer */
430: public static String[] getSQLDetailHeader() {
431: return params.sqlBuffer.getHeader();
432: }
433:
434: /** Get the sql buffer as an array, so it can be displayed */
435: public static Object[][] getSQLDetail() {
436: return params.sqlBuffer.getData();
437: }
438:
439: /** Get a list of the strings to match in the parsed query. This can be used to count tables hits and times of queries that hit them for example
440: *
441: *
442: */
443:
444: public static List getMatchStrings() {
445: return params.matchStrings;
446: }
447:
448: /** Set the strings to match */
449: public static void setMatchStrings(List ms) {
450: params.matchStrings = ms;
451: }
452:
453: }
|