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