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