001package org.javasimon.jdbc4;
002
003import java.sql.Connection;
004import java.sql.ResultSet;
005import java.sql.SQLException;
006import java.sql.SQLWarning;
007import java.sql.Statement;
008import java.util.LinkedList;
009import java.util.List;
010
011import org.javasimon.Manager;
012import org.javasimon.SimonManager;
013import org.javasimon.Split;
014import org.javasimon.Stopwatch;
015
016/**
017 * Simon JDBC proxy statement implementation class.
018 *
019 * @author Radovan Sninsky
020 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
021 * @see java.sql.Statement
022 * @since 2.4
023 */
024public class SimonStatement implements Statement {
025        /**
026         * List of batched SQL statements.
027         */
028        protected final List<String> batchSql = new LinkedList<>();
029
030        /**
031         * SQL connection.
032         */
033        protected Connection conn;
034
035        /**
036         * Hierarchy prefix for JDBC Simons.
037         */
038        protected String prefix;
039
040        /**
041         * SQL statement label containing part up to the SQL command type.
042         */
043        protected String sqlCmdLabel;
044
045        /**
046         * SQL normalizer helper object.
047         */
048        protected SqlNormalizer sqlNormalizer;
049
050        /**
051         * Stopwatch split measuring the lifespan of the statement until it is closed across all executes.
052         */
053        protected Split split;
054
055        private final Statement stmt;
056
057        private final WrapperSupport<Statement> wrapperSupport;
058
059        /**
060         * Class constructor, initializes Simons (lifespan, active) related to statement.
061         *
062         * @param conn database connection (simon impl.)
063         * @param stmt real statement
064         * @param prefix hierarchy preffix for JDBC Simons
065         */
066        SimonStatement(Connection conn, Statement stmt, String prefix) {
067                this.conn = conn;
068                this.stmt = stmt;
069                this.prefix = prefix;
070                this.wrapperSupport = new WrapperSupport<>(stmt, Statement.class);
071                split = SimonManager.getStopwatch(prefix + ".stmt").start();
072        }
073
074        /**
075         * Closes real statement, stops lifespan Simon and decrease active Simon.
076         *
077         * @throws java.sql.SQLException if real operation fails
078         */
079        @Override
080        public final void close() throws SQLException {
081                stmt.close();
082
083                split.stop();
084        }
085
086        /**
087         * Returns a connection object (simon impl.).
088         *
089         * @return connection object
090         */
091        @Override
092        public final Connection getConnection() {
093                return conn;
094        }
095
096        /**
097         * Called before each SQL command execution. Prepares (obtains and starts) {@link org.javasimon.Stopwatch Stopwatch Simon}
098         * for measure SQL operation.
099         *
100         * @param sql sql command for execution
101         * @return Simon stopwatch object or null if sql is null or empty
102         */
103        protected final Split prepare(String sql) {
104                if (sql != null && !sql.equals("")) {
105                        sqlNormalizer = new SqlNormalizer(sql);
106                        sqlCmdLabel = prefix + ".sql." + sqlNormalizer.getType();
107                        return startSplit();
108                } else {
109                        return null;
110                }
111        }
112
113        /**
114         * Called before each SQL command execution. Prepares (obtains and starts) {@link org.javasimon.Stopwatch Stopwatch Simon}
115         * for measure bach SQL operations.
116         *
117         * @param sqls list of sql commands
118         * @return Simon stopwatch object or null if sql is null or empty
119         */
120        protected final Split prepare(List<String> sqls) {
121                if (!sqls.isEmpty()) {
122                        sqlNormalizer = sqls.size() == 1 ? new SqlNormalizer(sqls.get(0)) : new SqlNormalizer(sqls);
123                        sqlCmdLabel = prefix + ".sql." + sqlNormalizer.getType();
124                        return startSplit();
125                } else {
126                        return null;
127                }
128        }
129
130        /**
131         * Starts the split for the SQL specific stopwatch, sets the note and returns the split.
132         * Used in the statment and prepared statement classes to measure runs of "execute" methods.
133         *
134         * @return split for the execution of the specific SQL command
135         */
136        protected Split startSplit() {
137                Stopwatch stopwatch = SimonManager.getStopwatch(sqlCmdLabel + Manager.HIERARCHY_DELIMITER + sqlNormalizer.getNormalizedSql().hashCode());
138                if (stopwatch.getNote() == null) {
139                        stopwatch.setNote(sqlNormalizer.getNormalizedSql());
140                }
141                return stopwatch.start();
142        }
143
144        /**
145         * Called after each SQL command execution. Stops concrete SQL stopwatch (started in {@link #prepare(String)}),
146         * also adds time to SQL command type Simon and sets human readable SQL cmd as note.
147         *
148         * @param split started Stopwatch split
149         */
150        protected final void finish(Split split) {
151                if (split != null) {
152                        SimonManager.getStopwatch(sqlCmdLabel).addSplit(split.stop());
153                }
154        }
155
156        /**
157         * Measure and execute SQL operation.
158         *
159         * @param sql sql command
160         * @return database rows and columns
161         * @throws java.sql.SQLException if real calls fails
162         * @see SimonResultSet
163         */
164        @Override
165        public final ResultSet executeQuery(String sql) throws SQLException {
166                Split s = prepare(sql);
167                try {
168                        return new SimonResultSet(stmt.executeQuery(sql), this, prefix, s.getStopwatch().getName());
169                } finally {
170                        finish(s);
171                }
172        }
173
174        /**
175         * Measure and execute SQL operation.
176         *
177         * @param sql sql command
178         * @return count of updated rows
179         * @throws java.sql.SQLException if real calls fails
180         */
181        @Override
182        public final int executeUpdate(String sql) throws SQLException {
183                Split s = prepare(sql);
184                try {
185                        return stmt.executeUpdate(sql);
186                } finally {
187                        finish(s);
188                }
189        }
190
191        /**
192         * Measure and execute SQL operation.
193         *
194         * @param sql sql command
195         * @param autoGeneratedKeys autoGeneratedKeys flag
196         * @return count of updated rows
197         * @throws java.sql.SQLException if real calls fails
198         */
199        @Override
200        public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
201                Split s = prepare(sql);
202                try {
203                        return stmt.executeUpdate(sql, autoGeneratedKeys);
204                } finally {
205                        finish(s);
206                }
207        }
208
209        /**
210         * Measure and execute SQL operation.
211         *
212         * @param sql sql command
213         * @param columnIndexes an array of column indexes indicating the columns that should be
214         * returned from the inserted row
215         * @return count of updated rows
216         * @throws java.sql.SQLException if real calls fails
217         */
218        @Override
219        public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
220                Split s = prepare(sql);
221                try {
222                        return stmt.executeUpdate(sql, columnIndexes);
223                } finally {
224                        finish(s);
225                }
226        }
227
228        /**
229         * Measure and execute SQL operation.
230         *
231         * @param sql sql command
232         * @param columnNames an array of column indexes indicating the columns that should be
233         * returned from the inserted row
234         * @return count of updated rows
235         * @throws java.sql.SQLException if real calls fails
236         */
237        @Override
238        public final int executeUpdate(String sql, String[] columnNames) throws SQLException {
239                Split s = prepare(sql);
240                try {
241                        return stmt.executeUpdate(sql, columnNames);
242                } finally {
243                        finish(s);
244                }
245        }
246
247        /**
248         * Measure and execute SQL operation.
249         *
250         * @param sql sql command
251         * @return <code>true</code> if the first result is a <code>ResultSet</code> object;
252         *         <code>false</code> if it is an update count or there are no results
253         * @throws java.sql.SQLException if real calls fails
254         */
255        @Override
256        public final boolean execute(String sql) throws SQLException {
257                Split s = prepare(sql);
258                try {
259                        return stmt.execute(sql);
260                } finally {
261                        finish(s);
262                }
263        }
264
265        /**
266         * Measure and execute SQL operation.
267         *
268         * @param sql sql command
269         * @param autoGeneratedKeys autoGeneratedKeys flag
270         * @return <code>true</code> if the first result is a <code>ResultSet</code> object;
271         *         <code>false</code> if it is an update count or there are no results
272         * @throws java.sql.SQLException if real calls fails
273         */
274        @Override
275        public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
276                Split s = prepare(sql);
277                try {
278                        return stmt.execute(sql, autoGeneratedKeys);
279                } finally {
280                        finish(s);
281                }
282        }
283
284        /**
285         * Measure and execute SQL operation.
286         *
287         * @param sql sql command
288         * @param columnIndexes an array of column indexes indicating the columns that should be
289         * returned from the inserted row
290         * @return <code>true</code> if the first result is a <code>ResultSet</code> object;
291         *         <code>false</code> if it is an update count or there are no results
292         * @throws java.sql.SQLException if real calls fails
293         */
294        @Override
295        public final boolean execute(String sql, int[] columnIndexes) throws SQLException {
296                Split s = prepare(sql);
297                try {
298                        return stmt.execute(sql, columnIndexes);
299                } finally {
300                        finish(s);
301                }
302        }
303
304        /**
305         * Measure and execute SQL operation.
306         *
307         * @param sql sql command
308         * @param columnNames an array of column indexes indicating the columns that should be
309         * returned from the inserted row
310         * @return <code>true</code> if the first result is a <code>ResultSet</code> object;
311         *         <code>false</code> if it is an update count or there are no results
312         * @throws java.sql.SQLException if real calls fails
313         */
314        @Override
315        public final boolean execute(String sql, String[] columnNames) throws SQLException {
316                Split s = prepare(sql);
317                try {
318                        return stmt.execute(sql, columnNames);
319                } finally {
320                        finish(s);
321                }
322        }
323
324        /**
325         * Adds given SQL command into batch list of sql and also into real batch.
326         *
327         * @param s sql command
328         * @throws java.sql.SQLException if real calls fails
329         */
330        @Override
331        public final void addBatch(String s) throws SQLException {
332                batchSql.add(s);
333
334                stmt.addBatch(s);
335        }
336
337        /**
338         * Measure and execute SQL operation.
339         *
340         * @return an array of update counts containing one element for each
341         *         command in the batch.
342         * @throws java.sql.SQLException if real calls fails
343         */
344        @Override
345        public int[] executeBatch() throws SQLException {
346                Split s = prepare(batchSql);
347                try {
348                        return stmt.executeBatch();
349                } finally {
350                        finish(s);
351                }
352        }
353
354        /**
355         * Clears batch sql list and real batch too.
356         *
357         * @throws java.sql.SQLException if real calls fails
358         */
359        @Override
360        public void clearBatch() throws SQLException {
361                batchSql.clear();
362
363                stmt.clearBatch();
364        }
365
366        //// NOT MONITORED
367
368        @Override
369        public final int getMaxFieldSize() throws SQLException {
370                return stmt.getMaxFieldSize();
371        }
372
373        @Override
374        public final void setMaxFieldSize(int i) throws SQLException {
375                stmt.setMaxFieldSize(i);
376        }
377
378        @Override
379        public final int getMaxRows() throws SQLException {
380                return stmt.getMaxRows();
381        }
382
383        @Override
384        public final void setMaxRows(int i) throws SQLException {
385                stmt.setMaxRows(i);
386        }
387
388        @Override
389        public final void setEscapeProcessing(boolean b) throws SQLException {
390                stmt.setEscapeProcessing(b);
391        }
392
393        @Override
394        public final int getQueryTimeout() throws SQLException {
395                return stmt.getQueryTimeout();
396        }
397
398        @Override
399        public final void setQueryTimeout(int i) throws SQLException {
400                stmt.setQueryTimeout(i);
401        }
402
403        @Override
404        public final void cancel() throws SQLException {
405                stmt.cancel();
406        }
407
408        @Override
409        public final SQLWarning getWarnings() throws SQLException {
410                return stmt.getWarnings();
411        }
412
413        @Override
414        public final void clearWarnings() throws SQLException {
415                stmt.clearWarnings();
416        }
417
418        @Override
419        public final void setCursorName(String s) throws SQLException {
420                stmt.setCursorName(s);
421        }
422
423        @Override
424        public final ResultSet getResultSet() throws SQLException {
425                return stmt.getResultSet();
426        }
427
428        @Override
429        public final int getUpdateCount() throws SQLException {
430                return stmt.getUpdateCount();
431        }
432
433        @Override
434        public final boolean getMoreResults() throws SQLException {
435                return stmt.getMoreResults();
436        }
437
438        @Override
439        public final void setFetchDirection(int i) throws SQLException {
440                stmt.setFetchDirection(i);
441        }
442
443        @Override
444        public final int getFetchDirection() throws SQLException {
445                return stmt.getFetchDirection();
446        }
447
448        @Override
449        public final void setFetchSize(int i) throws SQLException {
450                stmt.setFetchSize(i);
451        }
452
453        @Override
454        public final int getFetchSize() throws SQLException {
455                return stmt.getFetchSize();
456        }
457
458        @Override
459        public final int getResultSetConcurrency() throws SQLException {
460                return stmt.getResultSetConcurrency();
461        }
462
463        @Override
464        public final int getResultSetType() throws SQLException {
465                return stmt.getResultSetType();
466        }
467
468        @Override
469        public final boolean getMoreResults(int i) throws SQLException {
470                return stmt.getMoreResults(i);
471        }
472
473        @Override
474        public final ResultSet getGeneratedKeys() throws SQLException {
475                return stmt.getGeneratedKeys();
476        }
477
478        @Override
479        public final int getResultSetHoldability() throws SQLException {
480                return stmt.getResultSetHoldability();
481        }
482
483        @Override
484        public final boolean isClosed() throws SQLException {
485                return stmt.isClosed();
486        }
487
488        @Override
489        public final void setPoolable(boolean b) throws SQLException {
490                stmt.setPoolable(b);
491        }
492
493        @Override
494        public final boolean isPoolable() throws SQLException {
495                return stmt.isPoolable();
496        }
497
498    @Override
499    public void closeOnCompletion() throws SQLException {
500        stmt.closeOnCompletion();
501    }
502
503    @Override
504    public boolean isCloseOnCompletion() throws SQLException {
505        return stmt.isCloseOnCompletion();
506    }
507
508    @Override
509        public final <T> T unwrap(Class<T> tClass) throws SQLException {
510                return wrapperSupport.unwrap(tClass);
511        }
512
513        @Override
514        public final boolean isWrapperFor(Class<?> aClass) throws SQLException {
515                return wrapperSupport.isWrapperFor(aClass);
516        }
517}