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