001package org.avaje.datasource.pool;
002
003import org.avaje.datasource.delegate.ConnectionDelegator;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007import java.sql.CallableStatement;
008import java.sql.Connection;
009import java.sql.DatabaseMetaData;
010import java.sql.PreparedStatement;
011import java.sql.SQLException;
012import java.sql.SQLWarning;
013import java.sql.Savepoint;
014import java.sql.Statement;
015import java.util.ArrayList;
016import java.util.Arrays;
017import java.util.Map;
018
019/**
020 * Is a connection that belongs to a DataSourcePool.
021 * <p/>
022 * <p>
023 * It is designed to be part of DataSourcePool. Closing the connection puts it
024 * back into the pool.
025 * </p>
026 * <p/>
027 * <p>
028 * It defaults autoCommit and Transaction Isolation to the defaults of the
029 * DataSourcePool.
030 * </p>
031 * <p/>
032 * <p>
033 * It has caching of Statements and PreparedStatements. Remembers the last
034 * statement that was executed. Keeps statistics on how long it is in use.
035 * </p>
036 */
037public class PooledConnection extends ConnectionDelegator {
038
039  private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class);
040
041  private static final String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: ";
042
043  /**
044   * Marker for when connection is closed due to exceeding the max allowed age.
045   */
046  private static final String REASON_MAXAGE = "maxAge";
047
048  /**
049   * Marker for when connection is closed due to exceeding the max inactive time.
050   */
051  private static final String REASON_IDLE = "idleTime";
052
053  /**
054   * Marker for when the connection is closed due to a reset.
055   */
056  private static final String REASON_RESET = "reset";
057
058  /**
059   * Set when connection is idle in the pool. In general when in the pool the
060   * connection should not be modified.
061   */
062  private static final int STATUS_IDLE = 88;
063
064  /**
065   * Set when connection given to client.
066   */
067  private static final int STATUS_ACTIVE = 89;
068
069  /**
070   * Set when commit() or rollback() called.
071   */
072  private static final int STATUS_ENDED = 87;
073
074  /**
075   * Name used to identify the PooledConnection for logging.
076   */
077  private final String name;
078
079  /**
080   * The pool this connection belongs to.
081   */
082  private final ConnectionPool pool;
083
084  /**
085   * The underlying connection.
086   */
087  private final Connection connection;
088
089  /**
090   * The time this connection was created.
091   */
092  private final long creationTime;
093
094  /**
095   * Cache of the PreparedStatements
096   */
097  private final PstmtCache pstmtCache;
098
099  private final Object pstmtMonitor = new Object();
100
101  /**
102   * Helper for statistics collection.
103   */
104  private final PooledConnectionStatistics stats = new PooledConnectionStatistics();
105
106  /**
107   * The status of the connection. IDLE, ACTIVE or ENDED.
108   */
109  private int status = STATUS_IDLE;
110
111  /**
112   * The reason for a connection closing.
113   */
114  private String closeReason;
115
116  /**
117   * Set this to true if the connection will be busy for a long time.
118   * <p>
119   * This means it should skip the suspected connection pool leak checking.
120   * </p>
121   */
122  private boolean longRunning;
123
124  /**
125   * Flag to indicate that this connection had errors and should be checked to
126   * make sure it is okay.
127   */
128  private boolean hadErrors;
129
130  /**
131   * The last start time. When the connection was given to a thread.
132   */
133  private long startUseTime;
134
135  /**
136   * The last end time of this connection. This is to calculate the usage
137   * time.
138   */
139  private long lastUseTime;
140
141  private long exeStartNanos;
142
143  /**
144   * The last statement executed by this connection.
145   */
146  private String lastStatement;
147
148  /**
149   * The non avaje method that created the connection.
150   */
151  private String createdByMethod;
152
153  /**
154   * Used to find connection pool leaks.
155   */
156  private StackTraceElement[] stackTrace;
157
158  private final int maxStackTrace;
159
160  /**
161   * Slot position in the BusyConnectionBuffer.
162   */
163  private int slotId;
164
165  private boolean resetIsolationReadOnlyRequired;
166
167
168  /**
169   * Construct the connection that can refer back to the pool it belongs to.
170   * <p>
171   * close() will return the connection back to the pool , while
172   * closeDestroy() will close() the underlining connection properly.
173   * </p>
174   */
175  public PooledConnection(ConnectionPool pool, int uniqueId, Connection connection) {
176    super(connection);
177
178    this.pool = pool;
179    this.connection = connection;
180    this.name = pool.getName() + "" + uniqueId;
181    this.pstmtCache = new PstmtCache(pool.getPstmtCacheSize());
182    this.maxStackTrace = pool.getMaxStackTraceSize();
183    this.creationTime = System.currentTimeMillis();
184    this.lastUseTime = creationTime;
185  }
186
187  /**
188   * For testing the pool without real connections.
189   */
190  protected PooledConnection(String name) {
191    super(null);
192    this.name = name;
193    this.pool = null;
194    this.connection = null;
195    this.pstmtCache = null;
196    this.maxStackTrace = 0;
197    this.creationTime = System.currentTimeMillis();
198    this.lastUseTime = creationTime;
199  }
200
201  /**
202   * Return the slot position in the busy buffer.
203   */
204  int getSlotId() {
205    return slotId;
206  }
207
208  /**
209   * Set the slot position in the busy buffer.
210   */
211  void setSlotId(int slotId) {
212    this.slotId = slotId;
213  }
214
215  /**
216   * Return a string to identify the connection.
217   */
218  String getName() {
219    return name;
220  }
221
222  private String getNameSlot() {
223    return name + ":" + slotId;
224  }
225
226  public String toString() {
227    return getDescription();
228  }
229
230  private long getBusySeconds() {
231    return (System.currentTimeMillis() - startUseTime) / 1000;
232  }
233
234  String getDescription() {
235    return "name[" + name + "] slot[" + slotId + "] startTime[" + getStartUseTime() + "] busySeconds[" + getBusySeconds() + "] createdBy[" + getCreatedByMethod() + "] stmt[" + getLastStatement() + "]";
236  }
237
238  String getFullDescription() {
239    return "name[" + name + "] slot[" + slotId + "] startTime[" + getStartUseTime() + "] busySeconds[" + getBusySeconds() + "] stackTrace[" + getStackTraceAsString() + "] stmt[" + getLastStatement() + "]";
240  }
241
242  PooledConnectionStatistics getStatistics() {
243    return stats;
244  }
245
246  /**
247   * Return true if the connection should be treated as long running (skip connection pool leak check).
248   */
249  boolean isLongRunning() {
250    return longRunning;
251  }
252
253  /**
254   * Set this to true if the connection is a long running connection and should skip the
255   * 'suspected connection pool leak' checking.
256   */
257  public void setLongRunning(boolean longRunning) {
258    this.longRunning = longRunning;
259  }
260
261  /**
262   * Close the connection fully NOT putting in back into the pool.
263   * <p>
264   * The logErrors parameter exists so that expected errors are not logged
265   * such as when the database is known to be down.
266   * </p>
267   *
268   * @param logErrors if false then don't log errors when closing
269   */
270  void closeConnectionFully(boolean logErrors) {
271
272    if (pool != null) {
273      // allow collection of load statistics
274      pool.reportClosingConnection(this);
275    }
276
277    if (logger.isDebugEnabled()) {
278      logger.debug("Closing Connection[{}] slot[{}] reason[{}] stats: {} , pstmtStats: {} ", name, slotId, closeReason, stats.getValues(false), pstmtCache.getDescription());
279    }
280
281    try {
282      if (connection.isClosed()) {
283        // Typically the JDBC Driver has its own JVM shutdown hook and already
284        // closed the connections in our DataSource pool so making this DEBUG level
285        logger.debug("Closing Connection[{}] that is already closed?", name);
286        return;
287      }
288    } catch (SQLException ex) {
289      if (logErrors) {
290        logger.error("Error checking if connection [" + getNameSlot() + "] is closed", ex);
291      }
292    }
293
294    try {
295      for (ExtendedPreparedStatement ps : pstmtCache.values()) {
296        ps.closeDestroy();
297      }
298
299    } catch (SQLException ex) {
300      if (logErrors) {
301        logger.warn("Error when closing connection Statements", ex);
302      }
303    }
304
305    try {
306      connection.close();
307    } catch (SQLException ex) {
308      if (logErrors || logger.isDebugEnabled()) {
309        logger.error("Error when fully closing connection [" + getFullDescription() + "]", ex);
310      }
311    }
312  }
313
314  /**
315   * Creates a wrapper ExtendedStatement so that I can get the executed sql. I
316   * want to do this so that I can get the slowest query statments etc, and
317   * log that information.
318   */
319  public Statement createStatement() throws SQLException {
320    if (status == STATUS_IDLE) {
321      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()");
322    }
323    try {
324      return connection.createStatement();
325    } catch (SQLException ex) {
326      markWithError();
327      throw ex;
328    }
329  }
330
331  public Statement createStatement(int resultSetType, int resultSetConcurreny) throws SQLException {
332    if (status == STATUS_IDLE) {
333      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()");
334    }
335    try {
336      return connection.createStatement(resultSetType, resultSetConcurreny);
337
338    } catch (SQLException ex) {
339      markWithError();
340      throw ex;
341    }
342  }
343
344  /**
345   * Return a PreparedStatement back into the cache.
346   */
347  void returnPreparedStatement(ExtendedPreparedStatement pstmt) {
348
349    synchronized (pstmtMonitor) {
350      if (!pstmtCache.returnStatement(pstmt)) {
351        try {
352          // Already an entry in the cache with the exact same SQL...
353          pstmt.closeDestroy();
354
355        } catch (SQLException e) {
356          logger.error("Error closing Pstmt", e);
357        }
358      }
359    }
360  }
361
362  /**
363   * This will try to use a cache of PreparedStatements.
364   */
365  public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException {
366    StringBuilder cacheKey = new StringBuilder(sql.length() + 50);
367    cacheKey.append(sql);
368    cacheKey.append(':').append(currentSchema);
369    cacheKey.append(':').append(returnKeysFlag);
370    return prepareStatement(sql, true, returnKeysFlag, cacheKey.toString());
371  }
372
373  /**
374   * This will try to use a cache of PreparedStatements.
375   */
376  public PreparedStatement prepareStatement(String sql) throws SQLException {
377    StringBuilder cacheKey = new StringBuilder(sql.length() + 50);
378    cacheKey.append(sql);
379    cacheKey.append(':').append(currentSchema);
380    return prepareStatement(sql, false, 0, cacheKey.toString());
381  }
382
383  /**
384   * This will try to use a cache of PreparedStatements.
385   */
386  private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException {
387
388    if (status == STATUS_IDLE) {
389      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
390    }
391    try {
392      synchronized (pstmtMonitor) {
393        lastStatement = sql;
394
395        // try to get a matching cached PStmt from the cache.
396        ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey);
397
398        if (pstmt != null) {
399          return pstmt;
400        }
401
402        // create a new PreparedStatement
403        PreparedStatement actualPstmt;
404        if (useFlag) {
405          actualPstmt = connection.prepareStatement(sql, flag);
406        } else {
407          actualPstmt = connection.prepareStatement(sql);
408        }
409        return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey);
410      }
411
412    } catch (SQLException ex) {
413      markWithError();
414      throw ex;
415    }
416  }
417
418  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
419
420    if (status == STATUS_IDLE) {
421      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
422    }
423    try {
424      // no caching when creating PreparedStatements this way
425      lastStatement = sql;
426      return connection.prepareStatement(sql, resultSetType, resultSetConcurreny);
427    } catch (SQLException ex) {
428      markWithError();
429      throw ex;
430    }
431  }
432
433  /**
434   * Reset the connection for returning to the client. Resets the status,
435   * startUseTime and hadErrors.
436   */
437  void resetForUse() {
438    this.status = STATUS_ACTIVE;
439    this.startUseTime = System.currentTimeMillis();
440    this.exeStartNanos = System.nanoTime();
441    this.createdByMethod = null;
442    this.lastStatement = null;
443    this.hadErrors = false;
444    this.longRunning = false;
445  }
446
447  /**
448   * When an error occurs during use add it the connection.
449   * <p>
450   * Any PooledConnection that has an error is checked to make sure it works
451   * before it is placed back into the connection pool.
452   * </p>
453   */
454  void markWithError() {
455    hadErrors = true;
456  }
457
458  /**
459   * close the connection putting it back into the connection pool.
460   * <p>
461   * Note that to ensure that the next transaction starts at the correct time
462   * a commit() or rollback() should be called. If neither has occured at this
463   * time then a rollback() is used (to end the transaction).
464   * </p>
465   * <p>
466   * To close the connection fully use closeConnectionFully().
467   * </p>
468   */
469  public void close() throws SQLException {
470    if (status == STATUS_IDLE) {
471      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()");
472    }
473
474    long durationNanos = System.nanoTime() - exeStartNanos;
475    stats.add(durationNanos, hadErrors);
476
477    if (hadErrors) {
478      if (!pool.validateConnection(this)) {
479        // the connection is BAD, remove it, close it and test the pool
480        pool.returnConnectionForceClose(this);
481        return;
482      }
483    }
484
485    try {
486      // reset the autoCommit back if client code changed it
487      if (connection.getAutoCommit() != pool.isAutoCommit()) {
488        connection.setAutoCommit(pool.isAutoCommit());
489      }
490      // Generally resetting Isolation level seems expensive.
491      // Hence using resetIsolationReadOnlyRequired flag
492      // performance reasons.
493      if (resetIsolationReadOnlyRequired) {
494        resetIsolationReadOnly();
495        resetIsolationReadOnlyRequired = false;
496      }
497
498      // the connection is assumed GOOD so put it back in the pool
499      lastUseTime = System.currentTimeMillis();
500      // connection.clearWarnings();
501      status = STATUS_IDLE;
502      pool.returnConnection(this);
503
504    } catch (Exception ex) {
505      // the connection is BAD, remove it, close it and test the pool
506      logger.warn("Error when trying to return connection to pool, closing fully.", ex);
507      pool.returnConnectionForceClose(this);
508    }
509  }
510
511  private void resetIsolationReadOnly() throws SQLException {
512    // reset the transaction isolation if the client code changed it
513    //noinspection MagicConstant
514    if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) {
515      //noinspection MagicConstant
516      connection.setTransactionIsolation(pool.getTransactionIsolation());
517    }
518    // reset readonly to false
519    if (connection.isReadOnly()) {
520      connection.setReadOnly(false);
521    }
522  }
523
524  protected void finalize() throws Throwable {
525    try {
526      if (connection != null && !connection.isClosed()) {
527        // connect leak?
528        logger.warn("Closing Connection on finalize() - {}", getFullDescription());
529        closeConnectionFully(false);
530      }
531    } catch (Exception e) {
532      logger.error("Error when finalize is closing a connection? (unexpected)", e);
533    }
534    super.finalize();
535  }
536
537  /**
538   * Return true if the connection is too old.
539   */
540  private boolean exceedsMaxAge(long maxAgeMillis) {
541    if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))) {
542      this.closeReason = REASON_MAXAGE;
543      return true;
544    }
545    return false;
546  }
547
548  boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) {
549    if (creationTime <= lastResetTime) {
550      this.closeReason = REASON_RESET;
551      return true;
552    }
553    return exceedsMaxAge(maxAgeMillis);
554  }
555
556  /**
557   * Return true if the connection has been idle for too long or is too old.
558   */
559  boolean shouldTrim(long usedSince, long createdSince) {
560    if (lastUseTime < usedSince) {
561      // been idle for too long so trim it
562      this.closeReason = REASON_IDLE;
563      return true;
564    }
565    if (createdSince > 0 && createdSince > creationTime) {
566      // exceeds max age so trim it
567      this.closeReason = REASON_MAXAGE;
568      return true;
569    }
570    return false;
571  }
572
573  /**
574   * Return the time the connection was passed to the client code.
575   * <p>
576   * Used to detect busy connections that could be leaks.
577   * </p>
578   */
579  private long getStartUseTime() {
580    return startUseTime;
581  }
582
583  /**
584   * Returns the time the connection was last used.
585   * <p>
586   * Used to close connections that have been idle for some time. Typically 5
587   * minutes.
588   * </p>
589   */
590  long getLastUsedTime() {
591    return lastUseTime;
592  }
593
594  /**
595   * Returns the last sql statement executed.
596   */
597  private String getLastStatement() {
598    return lastStatement;
599  }
600
601  /**
602   * Called by ExtendedStatement to trace the sql being executed.
603   * <p>
604   * Note with addBatch() this will not really work.
605   * </p>
606   */
607  void setLastStatement(String lastStatement) {
608    this.lastStatement = lastStatement;
609    if (logger.isTraceEnabled()) {
610      logger.trace(".setLastStatement[" + lastStatement + "]");
611    }
612  }
613
614
615  /**
616   * Also note the read only status needs to be reset when put back into the
617   * pool.
618   */
619  public void setReadOnly(boolean readOnly) throws SQLException {
620    // A bit loose not checking for STATUS_IDLE
621    // if (status == STATUS_IDLE) {
622    // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR +
623    // "setReadOnly()");
624    // }
625    resetIsolationReadOnlyRequired = true;
626    connection.setReadOnly(readOnly);
627  }
628
629  /**
630   * Also note the Isolation level needs to be reset when put back into the
631   * pool.
632   */
633  public void setTransactionIsolation(int level) throws SQLException {
634    if (status == STATUS_IDLE) {
635      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()");
636    }
637    try {
638      resetIsolationReadOnlyRequired = true;
639      connection.setTransactionIsolation(level);
640    } catch (SQLException ex) {
641      markWithError();
642      throw ex;
643    }
644  }
645
646  //
647  //
648  // Simple wrapper methods which pass a method call onto the acutal
649  // connection object. These methods are safe-guarded to prevent use of
650  // the methods whilst the connection is in the connection pool.
651  //
652  //
653  public void clearWarnings() throws SQLException {
654    if (status == STATUS_IDLE) {
655      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()");
656    }
657    connection.clearWarnings();
658  }
659
660  public void commit() throws SQLException {
661    if (status == STATUS_IDLE) {
662      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()");
663    }
664    try {
665      status = STATUS_ENDED;
666      connection.commit();
667    } catch (SQLException ex) {
668      markWithError();
669      throw ex;
670    }
671  }
672
673  public boolean getAutoCommit() throws SQLException {
674    if (status == STATUS_IDLE) {
675      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()");
676    }
677    return connection.getAutoCommit();
678  }
679
680  public String getCatalog() throws SQLException {
681    if (status == STATUS_IDLE) {
682      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()");
683    }
684    return connection.getCatalog();
685  }
686
687  public DatabaseMetaData getMetaData() throws SQLException {
688    if (status == STATUS_IDLE) {
689      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()");
690    }
691    return connection.getMetaData();
692  }
693
694  public int getTransactionIsolation() throws SQLException {
695    if (status == STATUS_IDLE) {
696      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()");
697    }
698    return connection.getTransactionIsolation();
699  }
700
701  public Map<String, Class<?>> getTypeMap() throws SQLException {
702    if (status == STATUS_IDLE) {
703      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()");
704    }
705    return connection.getTypeMap();
706  }
707
708  public SQLWarning getWarnings() throws SQLException {
709    if (status == STATUS_IDLE) {
710      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()");
711    }
712    return connection.getWarnings();
713  }
714
715  public boolean isClosed() throws SQLException {
716    if (status == STATUS_IDLE) {
717      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()");
718    }
719    return connection.isClosed();
720  }
721
722  public boolean isReadOnly() throws SQLException {
723    if (status == STATUS_IDLE) {
724      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()");
725    }
726    return connection.isReadOnly();
727  }
728
729  public String nativeSQL(String sql) throws SQLException {
730    if (status == STATUS_IDLE) {
731      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()");
732    }
733    lastStatement = sql;
734    return connection.nativeSQL(sql);
735  }
736
737  public CallableStatement prepareCall(String sql) throws SQLException {
738    if (status == STATUS_IDLE) {
739      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
740    }
741    lastStatement = sql;
742    return connection.prepareCall(sql);
743  }
744
745  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
746    if (status == STATUS_IDLE) {
747      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
748    }
749    lastStatement = sql;
750    return connection.prepareCall(sql, resultSetType, resultSetConcurreny);
751  }
752
753  public void rollback() throws SQLException {
754    if (status == STATUS_IDLE) {
755      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()");
756    }
757    try {
758      status = STATUS_ENDED;
759      connection.rollback();
760    } catch (SQLException ex) {
761      markWithError();
762      throw ex;
763    }
764  }
765
766  public void setAutoCommit(boolean autoCommit) throws SQLException {
767    if (status == STATUS_IDLE) {
768      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()");
769    }
770    try {
771      connection.setAutoCommit(autoCommit);
772    } catch (SQLException ex) {
773      markWithError();
774      throw ex;
775    }
776  }
777
778  public void setCatalog(String catalog) throws SQLException {
779    if (status == STATUS_IDLE) {
780      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()");
781    }
782    connection.setCatalog(catalog);
783  }
784
785  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
786    if (status == STATUS_IDLE) {
787      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()");
788    }
789    connection.setTypeMap(map);
790  }
791
792  public Savepoint setSavepoint() throws SQLException {
793    try {
794      return connection.setSavepoint();
795    } catch (SQLException ex) {
796      markWithError();
797      throw ex;
798    }
799  }
800
801  public Savepoint setSavepoint(String savepointName) throws SQLException {
802    try {
803      return connection.setSavepoint(savepointName);
804    } catch (SQLException ex) {
805      markWithError();
806      throw ex;
807    }
808  }
809
810  public void rollback(Savepoint sp) throws SQLException {
811    try {
812      connection.rollback(sp);
813    } catch (SQLException ex) {
814      markWithError();
815      throw ex;
816    }
817  }
818
819  public void releaseSavepoint(Savepoint sp) throws SQLException {
820    try {
821      connection.releaseSavepoint(sp);
822    } catch (SQLException ex) {
823      markWithError();
824      throw ex;
825    }
826  }
827
828  public void setHoldability(int i) throws SQLException {
829    try {
830      connection.setHoldability(i);
831    } catch (SQLException ex) {
832      markWithError();
833      throw ex;
834    }
835  }
836
837  public int getHoldability() throws SQLException {
838    try {
839      return connection.getHoldability();
840    } catch (SQLException ex) {
841      markWithError();
842      throw ex;
843    }
844  }
845
846  public Statement createStatement(int i, int x, int y) throws SQLException {
847    try {
848      return connection.createStatement(i, x, y);
849    } catch (SQLException ex) {
850      markWithError();
851      throw ex;
852    }
853  }
854
855  public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException {
856    try {
857      return connection.prepareStatement(s, i, x, y);
858    } catch (SQLException ex) {
859      markWithError();
860      throw ex;
861    }
862  }
863
864  public PreparedStatement prepareStatement(String s, int[] i) throws SQLException {
865    try {
866      return connection.prepareStatement(s, i);
867    } catch (SQLException ex) {
868      markWithError();
869      throw ex;
870    }
871  }
872
873  public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException {
874    try {
875      return connection.prepareStatement(s, s2);
876    } catch (SQLException ex) {
877      markWithError();
878      throw ex;
879    }
880  }
881
882  public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException {
883    try {
884      return connection.prepareCall(s, i, x, y);
885    } catch (SQLException ex) {
886      markWithError();
887      throw ex;
888    }
889  }
890
891  /**
892   * Returns the method that created the connection.
893   * <p>
894   * Used to help finding connection pool leaks.
895   * </p>
896   */
897  private String getCreatedByMethod() {
898    if (createdByMethod != null) {
899      return createdByMethod;
900    }
901    if (stackTrace == null) {
902      return null;
903    }
904
905    for (int j = 0; j < stackTrace.length; j++) {
906      String methodLine = stackTrace[j].toString();
907      if (!skipElement(methodLine)) {
908        createdByMethod = methodLine;
909        return createdByMethod;
910      }
911    }
912
913    return null;
914  }
915
916  private boolean skipElement(String methodLine) {
917    if (methodLine.startsWith("java.lang.")) {
918      return true;
919    } else if (methodLine.startsWith("java.util.")) {
920      return true;
921    } else if (methodLine.startsWith("org.avaje.datasource.")) {
922      return true;
923    }
924    return methodLine.startsWith("com.avaje.ebean");
925  }
926
927  /**
928   * Set the stack trace to help find connection pool leaks.
929   */
930  void setStackTrace(StackTraceElement[] stackTrace) {
931    this.stackTrace = stackTrace;
932  }
933
934  /**
935   * Return the stackTrace as a String for logging purposes.
936   */
937  private String getStackTraceAsString() {
938    StackTraceElement[] stackTrace = getStackTrace();
939    if (stackTrace == null) {
940      return "";
941    }
942    return Arrays.toString(stackTrace);
943  }
944
945  /**
946   * Return the full stack trace that got the connection from the pool. You
947   * could use this if getCreatedByMethod() doesn't work for you.
948   */
949  private StackTraceElement[] getStackTrace() {
950
951    if (stackTrace == null) {
952      return null;
953    }
954
955    // filter off the top of the stack that we are not interested in
956    ArrayList<StackTraceElement> filteredList = new ArrayList<StackTraceElement>();
957    boolean include = false;
958    for (int i = 0; i < stackTrace.length; i++) {
959      if (!include && !skipElement(stackTrace[i].toString())) {
960        include = true;
961      }
962      if (include && filteredList.size() < maxStackTrace) {
963        filteredList.add(stackTrace[i]);
964      }
965    }
966    return filteredList.toArray(new StackTraceElement[filteredList.size()]);
967
968  }
969
970}