001package io.ebean.datasource.pool;
002
003import io.ebean.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        if (pstmt != null) {
400          return pstmt.reset();
401        }
402
403        // create a new PreparedStatement
404        PreparedStatement actualPstmt;
405        if (useFlag) {
406          actualPstmt = connection.prepareStatement(sql, flag);
407        } else {
408          actualPstmt = connection.prepareStatement(sql);
409        }
410        return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey);
411      }
412
413    } catch (SQLException ex) {
414      markWithError();
415      throw ex;
416    }
417  }
418
419  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
420
421    if (status == STATUS_IDLE) {
422      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()");
423    }
424    try {
425      // no caching when creating PreparedStatements this way
426      lastStatement = sql;
427      return connection.prepareStatement(sql, resultSetType, resultSetConcurreny);
428    } catch (SQLException ex) {
429      markWithError();
430      throw ex;
431    }
432  }
433
434  /**
435   * Reset the connection for returning to the client. Resets the status,
436   * startUseTime and hadErrors.
437   */
438  void resetForUse() {
439    this.status = STATUS_ACTIVE;
440    this.startUseTime = System.currentTimeMillis();
441    this.exeStartNanos = System.nanoTime();
442    this.createdByMethod = null;
443    this.lastStatement = null;
444    this.hadErrors = false;
445    this.longRunning = false;
446  }
447
448  /**
449   * When an error occurs during use add it the connection.
450   * <p>
451   * Any PooledConnection that has an error is checked to make sure it works
452   * before it is placed back into the connection pool.
453   * </p>
454   */
455  void markWithError() {
456    hadErrors = true;
457  }
458
459  /**
460   * close the connection putting it back into the connection pool.
461   * <p>
462   * Note that to ensure that the next transaction starts at the correct time
463   * a commit() or rollback() should be called. If neither has occured at this
464   * time then a rollback() is used (to end the transaction).
465   * </p>
466   * <p>
467   * To close the connection fully use closeConnectionFully().
468   * </p>
469   */
470  public void close() throws SQLException {
471    if (status == STATUS_IDLE) {
472      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()");
473    }
474
475    long durationNanos = System.nanoTime() - exeStartNanos;
476    stats.add(durationNanos, hadErrors);
477
478    if (hadErrors) {
479      if (!pool.validateConnection(this)) {
480        // the connection is BAD, remove it, close it and test the pool
481        pool.returnConnectionForceClose(this);
482        return;
483      }
484    }
485
486    try {
487      // reset the autoCommit back if client code changed it
488      if (resetAutoCommit) {
489        connection.setAutoCommit(pool.isAutoCommit());
490        resetAutoCommit = false;
491      }
492      // Generally resetting Isolation level seems expensive.
493      // Hence using resetIsolationReadOnlyRequired flag
494      // performance reasons.
495      if (resetIsolationReadOnlyRequired) {
496        resetIsolationReadOnly();
497        resetIsolationReadOnlyRequired = false;
498      }
499
500      // the connection is assumed GOOD so put it back in the pool
501      lastUseTime = System.currentTimeMillis();
502      connection.clearWarnings();
503      status = STATUS_IDLE;
504      pool.returnConnection(this);
505
506    } catch (Exception ex) {
507      // the connection is BAD, remove it, close it and test the pool
508      logger.warn("Error when trying to return connection to pool, closing fully.", ex);
509      pool.returnConnectionForceClose(this);
510    }
511  }
512
513  private void resetIsolationReadOnly() throws SQLException {
514    // reset the transaction isolation if the client code changed it
515    //noinspection MagicConstant
516    if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) {
517      //noinspection MagicConstant
518      connection.setTransactionIsolation(pool.getTransactionIsolation());
519    }
520    // reset readonly to false
521    if (connection.isReadOnly()) {
522      connection.setReadOnly(false);
523    }
524  }
525
526  protected void finalize() throws Throwable {
527    try {
528      if (connection != null && !connection.isClosed()) {
529        // connect leak?
530        logger.warn("Closing Connection on finalize() - {}", getFullDescription());
531        closeConnectionFully(false);
532      }
533    } catch (Exception e) {
534      logger.error("Error when finalize is closing a connection? (unexpected)", e);
535    }
536    super.finalize();
537  }
538
539  /**
540   * Return true if the connection is too old.
541   */
542  private boolean exceedsMaxAge(long maxAgeMillis) {
543    if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))) {
544      this.closeReason = REASON_MAXAGE;
545      return true;
546    }
547    return false;
548  }
549
550  boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) {
551    if (creationTime <= lastResetTime) {
552      this.closeReason = REASON_RESET;
553      return true;
554    }
555    return exceedsMaxAge(maxAgeMillis);
556  }
557
558  /**
559   * Return true if the connection has been idle for too long or is too old.
560   */
561  boolean shouldTrim(long usedSince, long createdSince) {
562    if (lastUseTime < usedSince) {
563      // been idle for too long so trim it
564      this.closeReason = REASON_IDLE;
565      return true;
566    }
567    if (createdSince > 0 && createdSince > creationTime) {
568      // exceeds max age so trim it
569      this.closeReason = REASON_MAXAGE;
570      return true;
571    }
572    return false;
573  }
574
575  /**
576   * Return the time the connection was passed to the client code.
577   * <p>
578   * Used to detect busy connections that could be leaks.
579   * </p>
580   */
581  private long getStartUseTime() {
582    return startUseTime;
583  }
584
585  /**
586   * Returns the time the connection was last used.
587   * <p>
588   * Used to close connections that have been idle for some time. Typically 5
589   * minutes.
590   * </p>
591   */
592  long getLastUsedTime() {
593    return lastUseTime;
594  }
595
596  /**
597   * Returns the last sql statement executed.
598   */
599  private String getLastStatement() {
600    return lastStatement;
601  }
602
603  /**
604   * Called by ExtendedStatement to trace the sql being executed.
605   * <p>
606   * Note with addBatch() this will not really work.
607   * </p>
608   */
609  void setLastStatement(String lastStatement) {
610    this.lastStatement = lastStatement;
611    if (logger.isTraceEnabled()) {
612      logger.trace(".setLastStatement[" + lastStatement + "]");
613    }
614  }
615
616
617  /**
618   * Also note the read only status needs to be reset when put back into the
619   * pool.
620   */
621  public void setReadOnly(boolean readOnly) throws SQLException {
622    // A bit loose not checking for STATUS_IDLE
623    // if (status == STATUS_IDLE) {
624    // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR +
625    // "setReadOnly()");
626    // }
627    resetIsolationReadOnlyRequired = true;
628    connection.setReadOnly(readOnly);
629  }
630
631  /**
632   * Also note the Isolation level needs to be reset when put back into the
633   * pool.
634   */
635  public void setTransactionIsolation(int level) throws SQLException {
636    if (status == STATUS_IDLE) {
637      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()");
638    }
639    try {
640      resetIsolationReadOnlyRequired = true;
641      connection.setTransactionIsolation(level);
642    } catch (SQLException ex) {
643      markWithError();
644      throw ex;
645    }
646  }
647
648  //
649  //
650  // Simple wrapper methods which pass a method call onto the acutal
651  // connection object. These methods are safe-guarded to prevent use of
652  // the methods whilst the connection is in the connection pool.
653  //
654  //
655  public void clearWarnings() throws SQLException {
656    if (status == STATUS_IDLE) {
657      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()");
658    }
659    connection.clearWarnings();
660  }
661
662  public void commit() throws SQLException {
663    if (status == STATUS_IDLE) {
664      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()");
665    }
666    try {
667      status = STATUS_ENDED;
668      connection.commit();
669    } catch (SQLException ex) {
670      markWithError();
671      throw ex;
672    }
673  }
674
675  public boolean getAutoCommit() throws SQLException {
676    if (status == STATUS_IDLE) {
677      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()");
678    }
679    return connection.getAutoCommit();
680  }
681
682  public String getCatalog() throws SQLException {
683    if (status == STATUS_IDLE) {
684      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()");
685    }
686    return connection.getCatalog();
687  }
688
689  public DatabaseMetaData getMetaData() throws SQLException {
690    if (status == STATUS_IDLE) {
691      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()");
692    }
693    return connection.getMetaData();
694  }
695
696  public int getTransactionIsolation() throws SQLException {
697    if (status == STATUS_IDLE) {
698      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()");
699    }
700    return connection.getTransactionIsolation();
701  }
702
703  public Map<String, Class<?>> getTypeMap() throws SQLException {
704    if (status == STATUS_IDLE) {
705      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()");
706    }
707    return connection.getTypeMap();
708  }
709
710  public SQLWarning getWarnings() throws SQLException {
711    if (status == STATUS_IDLE) {
712      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()");
713    }
714    return connection.getWarnings();
715  }
716
717  public boolean isClosed() throws SQLException {
718    if (status == STATUS_IDLE) {
719      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()");
720    }
721    return connection.isClosed();
722  }
723
724  public boolean isReadOnly() throws SQLException {
725    if (status == STATUS_IDLE) {
726      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()");
727    }
728    return connection.isReadOnly();
729  }
730
731  public String nativeSQL(String sql) throws SQLException {
732    if (status == STATUS_IDLE) {
733      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()");
734    }
735    lastStatement = sql;
736    return connection.nativeSQL(sql);
737  }
738
739  public CallableStatement prepareCall(String sql) throws SQLException {
740    if (status == STATUS_IDLE) {
741      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
742    }
743    lastStatement = sql;
744    return connection.prepareCall(sql);
745  }
746
747  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException {
748    if (status == STATUS_IDLE) {
749      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()");
750    }
751    lastStatement = sql;
752    return connection.prepareCall(sql, resultSetType, resultSetConcurreny);
753  }
754
755  public void rollback() throws SQLException {
756    if (status == STATUS_IDLE) {
757      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()");
758    }
759    try {
760      status = STATUS_ENDED;
761      connection.rollback();
762    } catch (SQLException ex) {
763      markWithError();
764      throw ex;
765    }
766  }
767
768  public void setAutoCommit(boolean autoCommit) throws SQLException {
769    if (status == STATUS_IDLE) {
770      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()");
771    }
772    try {
773      connection.setAutoCommit(autoCommit);
774      resetAutoCommit = true;
775    } catch (SQLException ex) {
776      markWithError();
777      throw ex;
778    }
779  }
780
781  public void setCatalog(String catalog) throws SQLException {
782    if (status == STATUS_IDLE) {
783      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()");
784    }
785    connection.setCatalog(catalog);
786  }
787
788  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
789    if (status == STATUS_IDLE) {
790      throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()");
791    }
792    connection.setTypeMap(map);
793  }
794
795  public Savepoint setSavepoint() throws SQLException {
796    try {
797      return connection.setSavepoint();
798    } catch (SQLException ex) {
799      markWithError();
800      throw ex;
801    }
802  }
803
804  public Savepoint setSavepoint(String savepointName) throws SQLException {
805    try {
806      return connection.setSavepoint(savepointName);
807    } catch (SQLException ex) {
808      markWithError();
809      throw ex;
810    }
811  }
812
813  public void rollback(Savepoint sp) throws SQLException {
814    try {
815      connection.rollback(sp);
816    } catch (SQLException ex) {
817      markWithError();
818      throw ex;
819    }
820  }
821
822  public void releaseSavepoint(Savepoint sp) throws SQLException {
823    try {
824      connection.releaseSavepoint(sp);
825    } catch (SQLException ex) {
826      markWithError();
827      throw ex;
828    }
829  }
830
831  public void setHoldability(int i) throws SQLException {
832    try {
833      connection.setHoldability(i);
834    } catch (SQLException ex) {
835      markWithError();
836      throw ex;
837    }
838  }
839
840  public int getHoldability() throws SQLException {
841    try {
842      return connection.getHoldability();
843    } catch (SQLException ex) {
844      markWithError();
845      throw ex;
846    }
847  }
848
849  public Statement createStatement(int i, int x, int y) throws SQLException {
850    try {
851      return connection.createStatement(i, x, y);
852    } catch (SQLException ex) {
853      markWithError();
854      throw ex;
855    }
856  }
857
858  public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException {
859    try {
860      return connection.prepareStatement(s, i, x, y);
861    } catch (SQLException ex) {
862      markWithError();
863      throw ex;
864    }
865  }
866
867  public PreparedStatement prepareStatement(String s, int[] i) throws SQLException {
868    try {
869      return connection.prepareStatement(s, i);
870    } catch (SQLException ex) {
871      markWithError();
872      throw ex;
873    }
874  }
875
876  public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException {
877    try {
878      return connection.prepareStatement(s, s2);
879    } catch (SQLException ex) {
880      markWithError();
881      throw ex;
882    }
883  }
884
885  public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException {
886    try {
887      return connection.prepareCall(s, i, x, y);
888    } catch (SQLException ex) {
889      markWithError();
890      throw ex;
891    }
892  }
893
894  /**
895   * Returns the method that created the connection.
896   * <p>
897   * Used to help finding connection pool leaks.
898   * </p>
899   */
900  private String getCreatedByMethod() {
901    if (createdByMethod != null) {
902      return createdByMethod;
903    }
904    if (stackTrace == null) {
905      return null;
906    }
907
908    for (int j = 0; j < stackTrace.length; j++) {
909      String methodLine = stackTrace[j].toString();
910      if (!skipElement(methodLine)) {
911        createdByMethod = methodLine;
912        return createdByMethod;
913      }
914    }
915
916    return null;
917  }
918
919  private boolean skipElement(String methodLine) {
920    if (methodLine.startsWith("java.lang.")) {
921      return true;
922    } else if (methodLine.startsWith("java.util.")) {
923      return true;
924    } else if (methodLine.startsWith("org.avaje.datasource.")) {
925      return true;
926    }
927    return methodLine.startsWith("com.avaje.ebean");
928  }
929
930  /**
931   * Set the stack trace to help find connection pool leaks.
932   */
933  void setStackTrace(StackTraceElement[] stackTrace) {
934    this.stackTrace = stackTrace;
935  }
936
937  /**
938   * Return the stackTrace as a String for logging purposes.
939   */
940  private String getStackTraceAsString() {
941    StackTraceElement[] stackTrace = getStackTrace();
942    if (stackTrace == null) {
943      return "";
944    }
945    return Arrays.toString(stackTrace);
946  }
947
948  /**
949   * Return the full stack trace that got the connection from the pool. You
950   * could use this if getCreatedByMethod() doesn't work for you.
951   */
952  private StackTraceElement[] getStackTrace() {
953
954    if (stackTrace == null) {
955      return null;
956    }
957
958    // filter off the top of the stack that we are not interested in
959    ArrayList<StackTraceElement> filteredList = new ArrayList<StackTraceElement>();
960    boolean include = false;
961    for (int i = 0; i < stackTrace.length; i++) {
962      if (!include && !skipElement(stackTrace[i].toString())) {
963        include = true;
964      }
965      if (include && filteredList.size() < maxStackTrace) {
966        filteredList.add(stackTrace[i]);
967      }
968    }
969    return filteredList.toArray(new StackTraceElement[filteredList.size()]);
970
971  }
972
973}