001: /*
002: * This software is released under a licence similar to the Apache Software Licence.
003: * See org.logicalcobwebs.proxool.package.html for details.
004: * The latest version is available at http://proxool.sourceforge.net
005: */
006: package org.logicalcobwebs.proxool;
007:
008: import org.apache.commons.logging.Log;
009: import org.apache.commons.logging.LogFactory;
010:
011: import java.sql.SQLException;
012: import java.sql.Statement;
013: import java.sql.Connection;
014: import java.util.*;
015: import java.text.DateFormat;
016: import java.text.SimpleDateFormat;
017:
018: /**
019: * Contains most of the functionality that we require to manipilate the
020: * statement. The subclass of this defines how we delegate to the
021: * real statement.
022: * @version $Revision: 1.22 $, $Date: 2006/03/03 09:58:26 $
023: * @author bill
024: * @author $Author: billhorsman $ (current maintainer)
025: * @since Proxool 0.7
026: */
027: abstract class AbstractProxyStatement {
028:
029: private static final Log LOG = LogFactory
030: .getLog(ProxyStatement.class);
031:
032: private static final DateFormat DATE_FORMAT = new SimpleDateFormat(
033: "dd-MMM-yyyy.HH:mm:ss");
034:
035: private Statement statement;
036:
037: private ConnectionPool connectionPool;
038:
039: private ProxyConnectionIF proxyConnection;
040:
041: private Map parameters;
042:
043: private String sqlStatement;
044:
045: private StringBuffer sqlLog = new StringBuffer();
046:
047: /**
048: * @param statement the real statement that we will delegate to
049: * @param connectionPool the connection pool that we are using
050: * @param proxyConnection the connection that was used to create the statement
051: * @param sqlStatement the SQL statement that was used to create this statement
052: * (optional, can be null) so that we can use if for tracing.
053: */
054: public AbstractProxyStatement(Statement statement,
055: ConnectionPool connectionPool,
056: ProxyConnectionIF proxyConnection, String sqlStatement) {
057: this .statement = statement;
058: this .connectionPool = connectionPool;
059: this .proxyConnection = proxyConnection;
060: this .sqlStatement = sqlStatement;
061: }
062:
063: /**
064: * Check to see whether an exception is a fatal one. If it is, then throw the connection
065: * away (and it won't be made available again)
066: * @param t the exception to test
067: */
068: protected boolean testException(Throwable t) {
069: if (FatalSqlExceptionHelper.testException(connectionPool
070: .getDefinition(), t)) {
071: // This SQL exception indicates a fatal problem with this connection. We should probably
072: // just junk it.
073: try {
074: statement.close();
075: connectionPool.throwConnection(proxyConnection,
076: "Fatal SQL Exception has been detected");
077:
078: // We should check all the existing connections as soon as possible
079: HouseKeeperController.sweepNow(connectionPool
080: .getDefinition().getAlias());
081:
082: LOG
083: .warn(
084: "Connection has been thrown away because fatal exception was detected",
085: t);
086: } catch (SQLException e2) {
087: LOG
088: .error(
089: "Problem trying to throw away suspect connection",
090: e2);
091: }
092: return true;
093: } else {
094: return false;
095: }
096: }
097:
098: /**
099: * Gets the real Statement that we got from the delegate driver
100: * @return delegate statement
101: */
102: public Statement getDelegateStatement() {
103: return statement;
104: }
105:
106: /**
107: * The connection pool we are using
108: * @return connectionPool
109: */
110: protected ConnectionPool getConnectionPool() {
111: return connectionPool;
112: }
113:
114: /**
115: * The real, delegate statement
116: * @return statement
117: */
118: protected Statement getStatement() {
119: return statement;
120: }
121:
122: /**
123: * Close the statement and tell the ProxyConnection that it did so.
124: * @throws SQLException if it couldn't be closed
125: * @see ProxyConnectionIF#registerClosedStatement
126: */
127: public void close() throws SQLException {
128: statement.close();
129: proxyConnection.registerClosedStatement(statement);
130: }
131:
132: protected Connection getConnection() {
133: return ProxyFactory
134: .getWrappedConnection((ProxyConnection) proxyConnection);
135: }
136:
137: /**
138: * Whether the delegate statements are the same
139: * @see Object#equals
140: */
141: public boolean equals(Object obj) {
142: return (statement.hashCode() == obj.hashCode());
143: }
144:
145: /**
146: * Add a parameter so that we can show its value when tracing
147: * @param index within the procedure
148: * @param value an object describing its value
149: */
150: protected void putParameter(int index, Object value) {
151:
152: // Lazily instantiate parameters if necessary
153: if (parameters == null) {
154: parameters = new TreeMap(new Comparator() {
155: public int compare(Object o1, Object o2) {
156: int c = 0;
157:
158: if (o1 instanceof Integer && o2 instanceof Integer) {
159: c = ((Integer) o1).compareTo(((Integer) o2));
160: }
161:
162: return c;
163: }
164: });
165: }
166:
167: Object key = new Integer(index);
168: if (value == null) {
169: parameters.put(key, "NULL");
170: } else if (value instanceof String) {
171: parameters.put(key, "'" + value + "'");
172: } else if (value instanceof Number) {
173: parameters.put(key, value);
174: } else if (value instanceof Boolean) {
175: parameters.put(key, ((Boolean) value).toString());
176: } else if (value instanceof Date) {
177: parameters.put(key, "'" + getDateAsString((Date) value)
178: + "'");
179: } else {
180: String className = value.getClass().getName();
181: StringTokenizer st = new StringTokenizer(className, ".");
182: while (st.hasMoreTokens()) {
183: className = st.nextToken();
184: }
185: parameters.put(key, className);
186: }
187: }
188:
189: /**
190: * Trace the call that was just made
191: * @param startTime so we can log how long it took
192: * @param exception if anything went wrong during execution
193: * @throws SQLException if the {@link ConnectionPool#onExecute onExecute} method threw one.
194: */
195: protected void trace(long startTime, Exception exception)
196: throws SQLException {
197:
198: if (isTrace()) {
199: // Log if configured to
200: if (connectionPool.getLog().isDebugEnabled()
201: && connectionPool.getDefinition().isTrace()) {
202: connectionPool
203: .getLog()
204: .debug(
205: sqlLog.toString()
206: + " ("
207: + (System.currentTimeMillis() - startTime)
208: + " milliseconds"
209: + (exception != null ? ", threw a "
210: + exception.getClass()
211: .getName()
212: + ": "
213: + exception
214: .getMessage()
215: + ")"
216: : ")"));
217: }
218: // Send to any listener
219: connectionPool.onExecute(sqlLog.toString(), (System
220: .currentTimeMillis() - startTime), exception);
221: }
222:
223: // Clear parameters for next time
224: if (parameters != null) {
225: parameters.clear();
226: }
227: sqlStatement = null;
228: sqlLog.setLength(0);
229:
230: }
231:
232: protected void startExecute() {
233: if (isTrace()) {
234: ((ProxyConnection) proxyConnection).addSqlCall(sqlLog
235: .toString());
236: }
237: }
238:
239: /**
240: * Get the parameters that have been built up and use them to fill in any parameters
241: * withing the sqlStatement and produce a log. If the log already exists (for instance,
242: * if a batch is being peformed) then it is appended to the end.
243: */
244: protected void appendToSqlLog() {
245: if (sqlStatement != null && sqlStatement.length() > 0
246: && isTrace()) {
247: int parameterIndex = 0;
248: StringTokenizer st = new StringTokenizer(sqlStatement, "?");
249: while (st.hasMoreTokens()) {
250: if (parameterIndex > 0) {
251: if (parameters != null) {
252: final Object value = parameters
253: .get(new Integer(parameterIndex));
254: if (value != null) {
255: sqlLog.append(value);
256: } else {
257: sqlLog.append("?");
258: }
259: } else {
260: sqlLog.append("?");
261: }
262: }
263: parameterIndex++;
264: sqlLog.append(st.nextToken());
265: }
266: if (sqlStatement.endsWith("?")) {
267: if (parameterIndex > 0) {
268: if (parameters != null) {
269: final Object value = parameters
270: .get(new Integer(parameterIndex));
271: if (value != null) {
272: sqlLog.append(value);
273: } else {
274: sqlLog.append("?");
275: }
276: } else {
277: sqlLog.append("?");
278: }
279: }
280: }
281: if (sqlStatement != null
282: && !sqlStatement.trim().endsWith(";")) {
283: sqlLog.append("; ");
284: }
285: }
286: if (parameters != null) {
287: parameters.clear();
288: }
289: }
290:
291: protected boolean isTrace() {
292: return getConnectionPool().isConnectionListenedTo()
293: || (getConnectionPool().getDefinition().isTrace());
294: }
295:
296: /**
297: * Sets sqlStatement if it isn't already set
298: * @param sqlStatement the statement we are sending the database
299: */
300: protected void setSqlStatementIfNull(String sqlStatement) {
301: if (this .sqlStatement == null) {
302: this .sqlStatement = sqlStatement;
303: }
304: }
305:
306: protected static String getDateAsString(Date date) {
307: return DATE_FORMAT.format(date);
308: }
309:
310: }
311:
312: /*
313: Revision history:
314: $Log: AbstractProxyStatement.java,v $
315: Revision 1.22 2006/03/03 09:58:26 billhorsman
316: Fix for statement.getConnection(). See bug 1149834.
317:
318: Revision 1.21 2006/01/18 14:40:00 billhorsman
319: Unbundled Jakarta's Commons Logging.
320:
321: Revision 1.20 2005/10/07 08:25:15 billhorsman
322: Support new sqlCalls list and isTrace() is now true if the connection pool is being listened to or if trace is on. It no longer depends on the log level. This is because the sqlCalls are available in AdminServlet and not just the logs.
323:
324: Revision 1.19 2005/09/26 10:01:31 billhorsman
325: Added lastSqlCall when trace is on.
326:
327: Revision 1.18 2004/06/02 20:04:54 billhorsman
328: Fixed sql log: boolean and date now supported, and last parameter is included
329:
330: Revision 1.17 2003/11/04 13:54:02 billhorsman
331: checkstyle
332:
333: Revision 1.16 2003/10/27 12:21:59 billhorsman
334: Optimisation to avoid preparing sql log if tracing is off.
335:
336: Revision 1.15 2003/10/27 11:18:42 billhorsman
337: Fix for sqlStatement being null.
338:
339: Revision 1.14 2003/10/19 09:50:08 billhorsman
340: Debug exception displays class name.
341:
342: Revision 1.13 2003/10/18 20:44:48 billhorsman
343: Better SQL logging (embed parameter values within SQL call) and works properly with batched statements now.
344:
345: Revision 1.12 2003/09/30 18:39:07 billhorsman
346: New test-before-use, test-after-use and fatal-sql-exception-wrapper-class properties.
347:
348: Revision 1.11 2003/09/05 16:26:50 billhorsman
349: testException() now returns true if a fatal exception was detected.
350:
351: Revision 1.10 2003/03/10 23:43:09 billhorsman
352: reapplied checkstyle that i'd inadvertently let
353: IntelliJ change...
354:
355: Revision 1.9 2003/03/10 15:26:42 billhorsman
356: refactoringn of concurrency stuff (and some import
357: optimisation)
358:
359: Revision 1.8 2003/03/05 18:42:32 billhorsman
360: big refactor of prototyping and house keeping to
361: drastically reduce the number of threads when using
362: many pools
363:
364: Revision 1.7 2003/03/03 11:11:56 billhorsman
365: fixed licence
366:
367: Revision 1.6 2003/02/26 16:05:52 billhorsman
368: widespread changes caused by refactoring the way we
369: update and redefine pool definitions.
370:
371: Revision 1.5 2003/02/19 22:38:32 billhorsman
372: fatal sql exception causes house keeper to run
373: immediately
374:
375: Revision 1.4 2003/02/13 17:06:42 billhorsman
376: allow for sqlStatement in execute() method
377:
378: Revision 1.3 2003/02/06 17:41:04 billhorsman
379: now uses imported logging
380:
381: Revision 1.2 2003/01/28 11:47:08 billhorsman
382: new isTrace() and made close() public
383:
384: Revision 1.1 2003/01/27 18:26:35 billhorsman
385: refactoring of ProxyConnection and ProxyStatement to
386: make it easier to write JDK 1.2 patch
387:
388: */
|