0001: /*
0002: * Copyright 2004 Clinton Begin
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package com.ibatis.common.jdbc;
0017:
0018: import com.ibatis.common.beans.ClassInfo;
0019:
0020: import com.ibatis.common.resources.Resources;
0021: import com.ibatis.common.logging.LogFactory;
0022: import com.ibatis.common.logging.Log;
0023:
0024: import javax.sql.DataSource;
0025: import java.io.PrintWriter;
0026: import java.lang.reflect.InvocationHandler;
0027: import java.lang.reflect.Method;
0028: import java.lang.reflect.Proxy;
0029: import java.sql.*;
0030: import java.util.*;
0031:
0032: /**
0033: * This is a simple, synchronous, thread-safe database connection pool.
0034: * <p/>
0035: * REQUIRED PROPERTIES
0036: * -------------------
0037: * JDBC.Driver
0038: * JDBC.ConnectionURL
0039: * JDBC.Username
0040: * JDBC.Password
0041: * <p/>
0042: * Pool.MaximumActiveConnections
0043: * Pool.MaximumIdleConnections
0044: * Pool.MaximumCheckoutTime
0045: * Pool.TimeToWait
0046: * Pool.PingQuery
0047: * Pool.PingEnabled
0048: * Pool.PingConnectionsOlderThan
0049: * Pool.PingConnectionsNotUsedFor
0050: * Pool.QuietMode
0051: */
0052: public class SimpleDataSource implements DataSource {
0053:
0054: private static final Log log = LogFactory
0055: .getLog(SimpleDataSource.class);
0056:
0057: // Required Properties
0058: private static final String PROP_JDBC_DRIVER = "JDBC.Driver";
0059: private static final String PROP_JDBC_URL = "JDBC.ConnectionURL";
0060: private static final String PROP_JDBC_USERNAME = "JDBC.Username";
0061: private static final String PROP_JDBC_PASSWORD = "JDBC.Password";
0062: private static final String PROP_JDBC_DEFAULT_AUTOCOMMIT = "JDBC.DefaultAutoCommit";
0063:
0064: // Optional Properties
0065: private static final String PROP_POOL_MAX_ACTIVE_CONN = "Pool.MaximumActiveConnections";
0066: private static final String PROP_POOL_MAX_IDLE_CONN = "Pool.MaximumIdleConnections";
0067: private static final String PROP_POOL_MAX_CHECKOUT_TIME = "Pool.MaximumCheckoutTime";
0068: private static final String PROP_POOL_TIME_TO_WAIT = "Pool.TimeToWait";
0069: private static final String PROP_POOL_PING_QUERY = "Pool.PingQuery";
0070: private static final String PROP_POOL_PING_CONN_OLDER_THAN = "Pool.PingConnectionsOlderThan";
0071: private static final String PROP_POOL_PING_ENABLED = "Pool.PingEnabled";
0072: private static final String PROP_POOL_PING_CONN_NOT_USED_FOR = "Pool.PingConnectionsNotUsedFor";
0073: private int expectedConnectionTypeCode;
0074: // Additional Driver Properties prefix
0075: private static final String ADD_DRIVER_PROPS_PREFIX = "Driver.";
0076: private static final int ADD_DRIVER_PROPS_PREFIX_LENGTH = ADD_DRIVER_PROPS_PREFIX
0077: .length();
0078:
0079: // ----- BEGIN: FIELDS LOCKED BY POOL_LOCK -----
0080: private final Object POOL_LOCK = new Object();
0081: private List idleConnections = new ArrayList();
0082: private List activeConnections = new ArrayList();
0083: private long requestCount = 0;
0084: private long accumulatedRequestTime = 0;
0085: private long accumulatedCheckoutTime = 0;
0086: private long claimedOverdueConnectionCount = 0;
0087: private long accumulatedCheckoutTimeOfOverdueConnections = 0;
0088: private long accumulatedWaitTime = 0;
0089: private long hadToWaitCount = 0;
0090: private long badConnectionCount = 0;
0091: // ----- END: FIELDS LOCKED BY POOL_LOCK -----
0092:
0093: // ----- BEGIN: PROPERTY FIELDS FOR CONFIGURATION -----
0094: private String jdbcDriver;
0095: private String jdbcUrl;
0096: private String jdbcUsername;
0097: private String jdbcPassword;
0098: private boolean jdbcDefaultAutoCommit;
0099: private Properties driverProps;
0100: private boolean useDriverProps;
0101:
0102: private int poolMaximumActiveConnections;
0103: private int poolMaximumIdleConnections;
0104: private int poolMaximumCheckoutTime;
0105: private int poolTimeToWait;
0106: private String poolPingQuery;
0107: private boolean poolPingEnabled;
0108: private int poolPingConnectionsOlderThan;
0109: private int poolPingConnectionsNotUsedFor;
0110:
0111: //----- END: PROPERTY FIELDS FOR CONFIGURATION -----
0112:
0113: /**
0114: * Constructor to allow passing in a map of properties for configuration
0115: *
0116: * @param props - the configuration parameters
0117: */
0118: public SimpleDataSource(Map props) {
0119: initialize(props);
0120: }
0121:
0122: private void initialize(Map props) {
0123: try {
0124: String prop_pool_ping_query = null;
0125:
0126: if (props == null) {
0127: throw new RuntimeException(
0128: "SimpleDataSource: The properties map passed to the initializer was null.");
0129: }
0130:
0131: if (!(props.containsKey(PROP_JDBC_DRIVER)
0132: && props.containsKey(PROP_JDBC_URL)
0133: && props.containsKey(PROP_JDBC_USERNAME) && props
0134: .containsKey(PROP_JDBC_PASSWORD))) {
0135: throw new RuntimeException(
0136: "SimpleDataSource: Some properties were not set.");
0137: } else {
0138:
0139: jdbcDriver = (String) props.get(PROP_JDBC_DRIVER);
0140: jdbcUrl = (String) props.get(PROP_JDBC_URL);
0141: jdbcUsername = (String) props.get(PROP_JDBC_USERNAME);
0142: jdbcPassword = (String) props.get(PROP_JDBC_PASSWORD);
0143:
0144: poolMaximumActiveConnections = props
0145: .containsKey(PROP_POOL_MAX_ACTIVE_CONN) ? Integer
0146: .parseInt((String) props
0147: .get(PROP_POOL_MAX_ACTIVE_CONN))
0148: : 10;
0149:
0150: poolMaximumIdleConnections = props
0151: .containsKey(PROP_POOL_MAX_IDLE_CONN) ? Integer
0152: .parseInt((String) props
0153: .get(PROP_POOL_MAX_IDLE_CONN)) : 5;
0154:
0155: poolMaximumCheckoutTime = props
0156: .containsKey(PROP_POOL_MAX_CHECKOUT_TIME) ? Integer
0157: .parseInt((String) props
0158: .get(PROP_POOL_MAX_CHECKOUT_TIME))
0159: : 20000;
0160:
0161: poolTimeToWait = props
0162: .containsKey(PROP_POOL_TIME_TO_WAIT) ? Integer
0163: .parseInt((String) props
0164: .get(PROP_POOL_TIME_TO_WAIT)) : 20000;
0165:
0166: poolPingEnabled = props
0167: .containsKey(PROP_POOL_PING_ENABLED)
0168: && Boolean.valueOf(
0169: (String) props
0170: .get(PROP_POOL_PING_ENABLED))
0171: .booleanValue();
0172:
0173: prop_pool_ping_query = (String) props
0174: .get(PROP_POOL_PING_QUERY);
0175: poolPingQuery = props.containsKey(PROP_POOL_PING_QUERY) ? prop_pool_ping_query
0176: : "NO PING QUERY SET";
0177:
0178: poolPingConnectionsOlderThan = props
0179: .containsKey(PROP_POOL_PING_CONN_OLDER_THAN) ? Integer
0180: .parseInt((String) props
0181: .get(PROP_POOL_PING_CONN_OLDER_THAN))
0182: : 0;
0183:
0184: poolPingConnectionsNotUsedFor = props
0185: .containsKey(PROP_POOL_PING_CONN_NOT_USED_FOR) ? Integer
0186: .parseInt((String) props
0187: .get(PROP_POOL_PING_CONN_NOT_USED_FOR))
0188: : 0;
0189:
0190: jdbcDefaultAutoCommit = props
0191: .containsKey(PROP_JDBC_DEFAULT_AUTOCOMMIT)
0192: && Boolean
0193: .valueOf(
0194: (String) props
0195: .get(PROP_JDBC_DEFAULT_AUTOCOMMIT))
0196: .booleanValue();
0197:
0198: useDriverProps = false;
0199: Iterator propIter = props.keySet().iterator();
0200: driverProps = new Properties();
0201: driverProps.put("user", jdbcUsername);
0202: driverProps.put("password", jdbcPassword);
0203: while (propIter.hasNext()) {
0204: String name = (String) propIter.next();
0205: String value = (String) props.get(name);
0206: if (name.startsWith(ADD_DRIVER_PROPS_PREFIX)) {
0207: driverProps
0208: .put(
0209: name
0210: .substring(ADD_DRIVER_PROPS_PREFIX_LENGTH),
0211: value);
0212: useDriverProps = true;
0213: }
0214: }
0215:
0216: expectedConnectionTypeCode = assembleConnectionTypeCode(
0217: jdbcUrl, jdbcUsername, jdbcPassword);
0218:
0219: Resources.instantiate(jdbcDriver);
0220:
0221: if (poolPingEnabled
0222: && (!props.containsKey(PROP_POOL_PING_QUERY) || prop_pool_ping_query
0223: .trim().length() == 0)) {
0224: throw new RuntimeException(
0225: "SimpleDataSource: property '"
0226: + PROP_POOL_PING_ENABLED
0227: + "' is true, but property '"
0228: + PROP_POOL_PING_QUERY
0229: + "' is not set correctly.");
0230: }
0231: }
0232:
0233: } catch (Exception e) {
0234: log.error(
0235: "SimpleDataSource: Error while loading properties. Cause: "
0236: + e.toString(), e);
0237: throw new RuntimeException(
0238: "SimpleDataSource: Error while loading properties. Cause: "
0239: + e, e);
0240: }
0241: }
0242:
0243: private int assembleConnectionTypeCode(String url, String username,
0244: String password) {
0245: return ("" + url + username + password).hashCode();
0246: }
0247:
0248: /**
0249: * @see javax.sql.DataSource#getConnection()
0250: */
0251: public Connection getConnection() throws SQLException {
0252: return popConnection(jdbcUsername, jdbcPassword)
0253: .getProxyConnection();
0254: }
0255:
0256: /**
0257: * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
0258: */
0259: public Connection getConnection(String username, String password)
0260: throws SQLException {
0261: return popConnection(username, password).getProxyConnection();
0262: }
0263:
0264: /**
0265: * @see javax.sql.DataSource#setLoginTimeout(int)
0266: */
0267: public void setLoginTimeout(int loginTimeout) throws SQLException {
0268: DriverManager.setLoginTimeout(loginTimeout);
0269: }
0270:
0271: /**
0272: * @see javax.sql.DataSource#getLoginTimeout()
0273: */
0274: public int getLoginTimeout() throws SQLException {
0275: return DriverManager.getLoginTimeout();
0276: }
0277:
0278: /**
0279: * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
0280: */
0281: public void setLogWriter(PrintWriter logWriter) throws SQLException {
0282: DriverManager.setLogWriter(logWriter);
0283: }
0284:
0285: /**
0286: * @see javax.sql.DataSource#getLogWriter()
0287: */
0288: public PrintWriter getLogWriter() throws SQLException {
0289: return DriverManager.getLogWriter();
0290: }
0291:
0292: /**
0293: * If a connection has not been used in this many milliseconds, ping the
0294: * database to make sure the connection is still good.
0295: *
0296: * @return the number of milliseconds of inactivity that will trigger a ping
0297: */
0298: public int getPoolPingConnectionsNotUsedFor() {
0299: return poolPingConnectionsNotUsedFor;
0300: }
0301:
0302: /**
0303: * Getter for the name of the JDBC driver class used
0304: * @return The name of the class
0305: */
0306: public String getJdbcDriver() {
0307: return jdbcDriver;
0308: }
0309:
0310: /**
0311: * Getter of the JDBC URL used
0312: * @return The JDBC URL
0313: */
0314: public String getJdbcUrl() {
0315: return jdbcUrl;
0316: }
0317:
0318: /**
0319: * Getter for the JDBC user name used
0320: * @return The user name
0321: */
0322: public String getJdbcUsername() {
0323: return jdbcUsername;
0324: }
0325:
0326: /**
0327: * Getter for the JDBC password used
0328: * @return The password
0329: */
0330: public String getJdbcPassword() {
0331: return jdbcPassword;
0332: }
0333:
0334: /**
0335: * Getter for the maximum number of active connections
0336: * @return The maximum number of active connections
0337: */
0338: public int getPoolMaximumActiveConnections() {
0339: return poolMaximumActiveConnections;
0340: }
0341:
0342: /**
0343: * Getter for the maximum number of idle connections
0344: * @return The maximum number of idle connections
0345: */
0346: public int getPoolMaximumIdleConnections() {
0347: return poolMaximumIdleConnections;
0348: }
0349:
0350: /**
0351: * Getter for the maximum time a connection can be used before it *may* be
0352: * given away again.
0353: * @return The maximum time
0354: */
0355: public int getPoolMaximumCheckoutTime() {
0356: return poolMaximumCheckoutTime;
0357: }
0358:
0359: /**
0360: * Getter for the time to wait before retrying to get a connection
0361: * @return The time to wait
0362: */
0363: public int getPoolTimeToWait() {
0364: return poolTimeToWait;
0365: }
0366:
0367: /**
0368: * Getter for the query to be used to check a connection
0369: * @return The query
0370: */
0371: public String getPoolPingQuery() {
0372: return poolPingQuery;
0373: }
0374:
0375: /**
0376: * Getter to tell if we should use the ping query
0377: * @return True if we need to check a connection before using it
0378: */
0379: public boolean isPoolPingEnabled() {
0380: return poolPingEnabled;
0381: }
0382:
0383: /**
0384: * Getter for the age of connections that should be pinged before using
0385: * @return The age
0386: */
0387: public int getPoolPingConnectionsOlderThan() {
0388: return poolPingConnectionsOlderThan;
0389: }
0390:
0391: private int getExpectedConnectionTypeCode() {
0392: return expectedConnectionTypeCode;
0393: }
0394:
0395: /**
0396: * Getter for the number of connection requests made
0397: * @return The number of connection requests made
0398: */
0399: public long getRequestCount() {
0400: synchronized (POOL_LOCK) {
0401: return requestCount;
0402: }
0403: }
0404:
0405: /**
0406: * Getter for the average time required to get a connection to the database
0407: * @return The average time
0408: */
0409: public long getAverageRequestTime() {
0410: synchronized (POOL_LOCK) {
0411: return requestCount == 0 ? 0 : accumulatedRequestTime
0412: / requestCount;
0413: }
0414: }
0415:
0416: /**
0417: * Getter for the average time spent waiting for connections that were in use
0418: * @return The average time
0419: */
0420: public long getAverageWaitTime() {
0421: synchronized (POOL_LOCK) {
0422: return hadToWaitCount == 0 ? 0 : accumulatedWaitTime
0423: / hadToWaitCount;
0424: }
0425: }
0426:
0427: /**
0428: * Getter for the number of requests that had to wait for connections that were in use
0429: * @return The number of requests that had to wait
0430: */
0431: public long getHadToWaitCount() {
0432: synchronized (POOL_LOCK) {
0433: return hadToWaitCount;
0434: }
0435: }
0436:
0437: /**
0438: * Getter for the number of invalid connections that were found in the pool
0439: * @return The number of invalid connections
0440: */
0441: public long getBadConnectionCount() {
0442: synchronized (POOL_LOCK) {
0443: return badConnectionCount;
0444: }
0445: }
0446:
0447: /**
0448: * Getter for the number of connections that were claimed before they were returned
0449: * @return The number of connections
0450: */
0451: public long getClaimedOverdueConnectionCount() {
0452: synchronized (POOL_LOCK) {
0453: return claimedOverdueConnectionCount;
0454: }
0455: }
0456:
0457: /**
0458: * Getter for the average age of overdue connections
0459: * @return The average age
0460: */
0461: public long getAverageOverdueCheckoutTime() {
0462: synchronized (POOL_LOCK) {
0463: return claimedOverdueConnectionCount == 0 ? 0
0464: : accumulatedCheckoutTimeOfOverdueConnections
0465: / claimedOverdueConnectionCount;
0466: }
0467: }
0468:
0469: /**
0470: * Getter for the average age of a connection checkout
0471: * @return The average age
0472: */
0473: public long getAverageCheckoutTime() {
0474: synchronized (POOL_LOCK) {
0475: return requestCount == 0 ? 0 : accumulatedCheckoutTime
0476: / requestCount;
0477: }
0478: }
0479:
0480: /**
0481: * Returns the status of the connection pool
0482: * @return The status
0483: */
0484: public String getStatus() {
0485: StringBuffer buffer = new StringBuffer();
0486:
0487: buffer
0488: .append("\n===============================================================");
0489: buffer.append("\n jdbcDriver ").append(
0490: jdbcDriver);
0491: buffer.append("\n jdbcUrl ").append(
0492: jdbcUrl);
0493: buffer.append("\n jdbcUsername ").append(
0494: jdbcUsername);
0495: buffer.append("\n jdbcPassword ").append(
0496: (jdbcPassword == null ? "NULL" : "************"));
0497: buffer.append("\n poolMaxActiveConnections ").append(
0498: poolMaximumActiveConnections);
0499: buffer.append("\n poolMaxIdleConnections ").append(
0500: poolMaximumIdleConnections);
0501: buffer.append("\n poolMaxCheckoutTime "
0502: + poolMaximumCheckoutTime);
0503: buffer.append("\n poolTimeToWait "
0504: + poolTimeToWait);
0505: buffer.append("\n poolPingEnabled "
0506: + poolPingEnabled);
0507: buffer.append("\n poolPingQuery "
0508: + poolPingQuery);
0509: buffer.append("\n poolPingConnectionsOlderThan "
0510: + poolPingConnectionsOlderThan);
0511: buffer.append("\n poolPingConnectionsNotUsedFor "
0512: + poolPingConnectionsNotUsedFor);
0513: buffer
0514: .append("\n --------------------------------------------------------------");
0515: buffer.append("\n activeConnections "
0516: + activeConnections.size());
0517: buffer.append("\n idleConnections "
0518: + idleConnections.size());
0519: buffer.append("\n requestCount "
0520: + getRequestCount());
0521: buffer.append("\n averageRequestTime "
0522: + getAverageRequestTime());
0523: buffer.append("\n averageCheckoutTime "
0524: + getAverageCheckoutTime());
0525: buffer.append("\n claimedOverdue "
0526: + getClaimedOverdueConnectionCount());
0527: buffer.append("\n averageOverdueCheckoutTime "
0528: + getAverageOverdueCheckoutTime());
0529: buffer.append("\n hadToWait "
0530: + getHadToWaitCount());
0531: buffer.append("\n averageWaitTime "
0532: + getAverageWaitTime());
0533: buffer.append("\n badConnectionCount "
0534: + getBadConnectionCount());
0535: buffer
0536: .append("\n===============================================================");
0537: return buffer.toString();
0538: }
0539:
0540: /**
0541: * Closes all of the connections in the pool
0542: */
0543: public void forceCloseAll() {
0544: synchronized (POOL_LOCK) {
0545: for (int i = activeConnections.size(); i > 0; i--) {
0546: try {
0547: SimplePooledConnection conn = (SimplePooledConnection) activeConnections
0548: .remove(i - 1);
0549: conn.invalidate();
0550:
0551: Connection realConn = conn.getRealConnection();
0552: if (!realConn.getAutoCommit()) {
0553: realConn.rollback();
0554: }
0555: realConn.close();
0556: } catch (Exception e) {
0557: // ignore
0558: }
0559: }
0560: for (int i = idleConnections.size(); i > 0; i--) {
0561: try {
0562: SimplePooledConnection conn = (SimplePooledConnection) idleConnections
0563: .remove(i - 1);
0564: conn.invalidate();
0565:
0566: Connection realConn = conn.getRealConnection();
0567: if (!realConn.getAutoCommit()) {
0568: realConn.rollback();
0569: }
0570: realConn.close();
0571: } catch (Exception e) {
0572: // ignore
0573: }
0574: }
0575: }
0576: if (log.isDebugEnabled()) {
0577: log
0578: .debug("SimpleDataSource forcefully closed/removed all connections.");
0579: }
0580: }
0581:
0582: private void pushConnection(SimplePooledConnection conn)
0583: throws SQLException {
0584:
0585: synchronized (POOL_LOCK) {
0586: activeConnections.remove(conn);
0587: if (conn.isValid()) {
0588: if (idleConnections.size() < poolMaximumIdleConnections
0589: && conn.getConnectionTypeCode() == getExpectedConnectionTypeCode()) {
0590: accumulatedCheckoutTime += conn.getCheckoutTime();
0591: if (!conn.getRealConnection().getAutoCommit()) {
0592: conn.getRealConnection().rollback();
0593: }
0594: SimplePooledConnection newConn = new SimplePooledConnection(
0595: conn.getRealConnection(), this );
0596: idleConnections.add(newConn);
0597: newConn.setCreatedTimestamp(conn
0598: .getCreatedTimestamp());
0599: newConn.setLastUsedTimestamp(conn
0600: .getLastUsedTimestamp());
0601: conn.invalidate();
0602: if (log.isDebugEnabled()) {
0603: log.debug("Returned connection "
0604: + newConn.getRealHashCode()
0605: + " to pool.");
0606: }
0607: POOL_LOCK.notifyAll();
0608: } else {
0609: accumulatedCheckoutTime += conn.getCheckoutTime();
0610: if (!conn.getRealConnection().getAutoCommit()) {
0611: conn.getRealConnection().rollback();
0612: }
0613: conn.getRealConnection().close();
0614: if (log.isDebugEnabled()) {
0615: log.debug("Closed connection "
0616: + conn.getRealHashCode() + ".");
0617: }
0618: conn.invalidate();
0619: }
0620: } else {
0621: if (log.isDebugEnabled()) {
0622: log
0623: .debug("A bad connection ("
0624: + conn.getRealHashCode()
0625: + ") attempted to return to the pool, discarding connection.");
0626: }
0627: badConnectionCount++;
0628: }
0629: }
0630: }
0631:
0632: private SimplePooledConnection popConnection(String username,
0633: String password) throws SQLException {
0634: boolean countedWait = false;
0635: SimplePooledConnection conn = null;
0636: long t = System.currentTimeMillis();
0637: int localBadConnectionCount = 0;
0638:
0639: while (conn == null) {
0640: synchronized (POOL_LOCK) {
0641: if (idleConnections.size() > 0) {
0642: // Pool has available connection
0643: conn = (SimplePooledConnection) idleConnections
0644: .remove(0);
0645: if (log.isDebugEnabled()) {
0646: log.debug("Checked out connection "
0647: + conn.getRealHashCode()
0648: + " from pool.");
0649: }
0650: } else {
0651: // Pool does not have available connection
0652: if (activeConnections.size() < poolMaximumActiveConnections) {
0653: // Can create new connection
0654: if (useDriverProps) {
0655: conn = new SimplePooledConnection(
0656: DriverManager.getConnection(
0657: jdbcUrl, driverProps), this );
0658: } else {
0659: conn = new SimplePooledConnection(
0660: DriverManager.getConnection(
0661: jdbcUrl, jdbcUsername,
0662: jdbcPassword), this );
0663: }
0664: Connection realConn = conn.getRealConnection();
0665: if (realConn.getAutoCommit() != jdbcDefaultAutoCommit) {
0666: realConn
0667: .setAutoCommit(jdbcDefaultAutoCommit);
0668: }
0669: if (log.isDebugEnabled()) {
0670: log.debug("Created connection "
0671: + conn.getRealHashCode() + ".");
0672: }
0673: } else {
0674: // Cannot create new connection
0675: SimplePooledConnection oldestActiveConnection = (SimplePooledConnection) activeConnections
0676: .get(0);
0677: long longestCheckoutTime = oldestActiveConnection
0678: .getCheckoutTime();
0679: if (longestCheckoutTime > poolMaximumCheckoutTime) {
0680: // Can claim overdue connection
0681: claimedOverdueConnectionCount++;
0682: accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
0683: accumulatedCheckoutTime += longestCheckoutTime;
0684: activeConnections
0685: .remove(oldestActiveConnection);
0686: if (!oldestActiveConnection
0687: .getRealConnection()
0688: .getAutoCommit()) {
0689: oldestActiveConnection
0690: .getRealConnection().rollback();
0691: }
0692: conn = new SimplePooledConnection(
0693: oldestActiveConnection
0694: .getRealConnection(), this );
0695: oldestActiveConnection.invalidate();
0696: if (log.isDebugEnabled()) {
0697: log.debug("Claimed overdue connection "
0698: + conn.getRealHashCode() + ".");
0699: }
0700: } else {
0701: // Must wait
0702: try {
0703: if (!countedWait) {
0704: hadToWaitCount++;
0705: countedWait = true;
0706: }
0707: if (log.isDebugEnabled()) {
0708: log
0709: .debug("Waiting as long as "
0710: + poolTimeToWait
0711: + " milliseconds for connection.");
0712: }
0713: long wt = System.currentTimeMillis();
0714: POOL_LOCK.wait(poolTimeToWait);
0715: accumulatedWaitTime += System
0716: .currentTimeMillis()
0717: - wt;
0718: } catch (InterruptedException e) {
0719: break;
0720: }
0721: }
0722: }
0723: }
0724: if (conn != null) {
0725: if (conn.isValid()) {
0726: if (!conn.getRealConnection().getAutoCommit()) {
0727: conn.getRealConnection().rollback();
0728: }
0729: conn
0730: .setConnectionTypeCode(assembleConnectionTypeCode(
0731: jdbcUrl, username, password));
0732: conn.setCheckoutTimestamp(System
0733: .currentTimeMillis());
0734: conn.setLastUsedTimestamp(System
0735: .currentTimeMillis());
0736: activeConnections.add(conn);
0737: requestCount++;
0738: accumulatedRequestTime += System
0739: .currentTimeMillis()
0740: - t;
0741: } else {
0742: if (log.isDebugEnabled()) {
0743: log
0744: .debug("A bad connection ("
0745: + conn.getRealHashCode()
0746: + ") was returned from the pool, getting another connection.");
0747: }
0748: badConnectionCount++;
0749: localBadConnectionCount++;
0750: conn = null;
0751: if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
0752: if (log.isDebugEnabled()) {
0753: log
0754: .debug("SimpleDataSource: Could not get a good connection to the database.");
0755: }
0756: throw new SQLException(
0757: "SimpleDataSource: Could not get a good connection to the database.");
0758: }
0759: }
0760: }
0761: }
0762:
0763: }
0764:
0765: if (conn == null) {
0766: if (log.isDebugEnabled()) {
0767: log
0768: .debug("SimpleDataSource: Unknown severe error condition. The connection pool returned a null connection.");
0769: }
0770: throw new SQLException(
0771: "SimpleDataSource: Unknown severe error condition. The connection pool returned a null connection.");
0772: }
0773:
0774: return conn;
0775: }
0776:
0777: /**
0778: * Method to check to see if a connection is still usable
0779: *
0780: * @param conn - the connection to check
0781: * @return True if the connection is still usable
0782: */
0783: private boolean pingConnection(SimplePooledConnection conn) {
0784: boolean result = true;
0785:
0786: try {
0787: result = !conn.getRealConnection().isClosed();
0788: } catch (SQLException e) {
0789: if (log.isDebugEnabled()) {
0790: log.debug("Connection " + conn.getRealHashCode()
0791: + " is BAD: " + e.getMessage());
0792: }
0793: result = false;
0794: }
0795:
0796: if (result) {
0797: if (poolPingEnabled) {
0798: if ((poolPingConnectionsOlderThan > 0 && conn.getAge() > poolPingConnectionsOlderThan)
0799: || (poolPingConnectionsNotUsedFor > 0 && conn
0800: .getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor)) {
0801:
0802: try {
0803: if (log.isDebugEnabled()) {
0804: log.debug("Testing connection "
0805: + conn.getRealHashCode() + " ...");
0806: }
0807: Connection realConn = conn.getRealConnection();
0808: Statement statement = realConn
0809: .createStatement();
0810: ResultSet rs = statement
0811: .executeQuery(poolPingQuery);
0812: rs.close();
0813: statement.close();
0814: if (!realConn.getAutoCommit()) {
0815: realConn.rollback();
0816: }
0817: result = true;
0818: if (log.isDebugEnabled()) {
0819: log.debug("Connection "
0820: + conn.getRealHashCode()
0821: + " is GOOD!");
0822: }
0823: } catch (Exception e) {
0824: log.warn("Execution of ping query '"
0825: + poolPingQuery + "' failed: "
0826: + e.getMessage());
0827: try {
0828: conn.getRealConnection().close();
0829: } catch (Exception e2) {
0830: //ignore
0831: }
0832: result = false;
0833: if (log.isDebugEnabled()) {
0834: log.debug("Connection "
0835: + conn.getRealHashCode()
0836: + " is BAD: " + e.getMessage());
0837: }
0838: }
0839: }
0840: }
0841: }
0842: return result;
0843: }
0844:
0845: /**
0846: * Unwraps a pooled connection to get to the 'real' connection
0847: *
0848: * @param conn - the pooled connection to unwrap
0849: * @return The 'real' connection
0850: */
0851: public static Connection unwrapConnection(Connection conn) {
0852: if (conn instanceof SimplePooledConnection) {
0853: return ((SimplePooledConnection) conn).getRealConnection();
0854: } else {
0855: return conn;
0856: }
0857: }
0858:
0859: protected void finalize() throws Throwable {
0860: forceCloseAll();
0861: }
0862:
0863: /**
0864: * ---------------------------------------------------------------------------------------
0865: * SimplePooledConnection
0866: * ---------------------------------------------------------------------------------------
0867: */
0868: private static class SimplePooledConnection implements
0869: InvocationHandler {
0870:
0871: private static final String CLOSE = "close";
0872: private static final Class[] IFACES = new Class[] { Connection.class };
0873:
0874: private int hashCode = 0;
0875: private SimpleDataSource dataSource;
0876: private Connection realConnection;
0877: private Connection proxyConnection;
0878: private long checkoutTimestamp;
0879: private long createdTimestamp;
0880: private long lastUsedTimestamp;
0881: private int connectionTypeCode;
0882: private boolean valid;
0883:
0884: /**
0885: * Constructor for SimplePooledConnection that uses the Connection and SimpleDataSource passed in
0886: *
0887: * @param connection - the connection that is to be presented as a pooled connection
0888: * @param dataSource - the dataSource that the connection is from
0889: */
0890: public SimplePooledConnection(Connection connection,
0891: SimpleDataSource dataSource) {
0892: this .hashCode = connection.hashCode();
0893: this .realConnection = connection;
0894: this .dataSource = dataSource;
0895: this .createdTimestamp = System.currentTimeMillis();
0896: this .lastUsedTimestamp = System.currentTimeMillis();
0897: this .valid = true;
0898:
0899: proxyConnection = (Connection) Proxy.newProxyInstance(
0900: Connection.class.getClassLoader(), IFACES, this );
0901: }
0902:
0903: /**
0904: * Invalidates the connection
0905: */
0906: public void invalidate() {
0907: valid = false;
0908: }
0909:
0910: /**
0911: * Method to see if the connection is usable
0912: *
0913: * @return True if the connection is usable
0914: */
0915: public boolean isValid() {
0916: return valid && realConnection != null
0917: && dataSource.pingConnection(this );
0918: }
0919:
0920: /**
0921: * Getter for the *real* connection that this wraps
0922: * @return The connection
0923: */
0924: public Connection getRealConnection() {
0925: return realConnection;
0926: }
0927:
0928: /**
0929: * Getter for the proxy for the connection
0930: * @return The proxy
0931: */
0932: public Connection getProxyConnection() {
0933: return proxyConnection;
0934: }
0935:
0936: /**
0937: * Gets the hashcode of the real connection (or 0 if it is null)
0938: *
0939: * @return The hashcode of the real connection (or 0 if it is null)
0940: */
0941: public int getRealHashCode() {
0942: if (realConnection == null) {
0943: return 0;
0944: } else {
0945: return realConnection.hashCode();
0946: }
0947: }
0948:
0949: /**
0950: * Getter for the connection type (based on url + user + password)
0951: * @return The connection type
0952: */
0953: public int getConnectionTypeCode() {
0954: return connectionTypeCode;
0955: }
0956:
0957: /**
0958: * Setter for the connection type
0959: * @param connectionTypeCode - the connection type
0960: */
0961: public void setConnectionTypeCode(int connectionTypeCode) {
0962: this .connectionTypeCode = connectionTypeCode;
0963: }
0964:
0965: /**
0966: * Getter for the time that the connection was created
0967: * @return The creation timestamp
0968: */
0969: public long getCreatedTimestamp() {
0970: return createdTimestamp;
0971: }
0972:
0973: /**
0974: * Setter for the time that the connection was created
0975: * @param createdTimestamp - the timestamp
0976: */
0977: public void setCreatedTimestamp(long createdTimestamp) {
0978: this .createdTimestamp = createdTimestamp;
0979: }
0980:
0981: /**
0982: * Getter for the time that the connection was last used
0983: * @return - the timestamp
0984: */
0985: public long getLastUsedTimestamp() {
0986: return lastUsedTimestamp;
0987: }
0988:
0989: /**
0990: * Setter for the time that the connection was last used
0991: * @param lastUsedTimestamp - the timestamp
0992: */
0993: public void setLastUsedTimestamp(long lastUsedTimestamp) {
0994: this .lastUsedTimestamp = lastUsedTimestamp;
0995: }
0996:
0997: /**
0998: * Getter for the time since this connection was last used
0999: * @return - the time since the last use
1000: */
1001: public long getTimeElapsedSinceLastUse() {
1002: return System.currentTimeMillis() - lastUsedTimestamp;
1003: }
1004:
1005: /**
1006: * Getter for the age of the connection
1007: * @return the age
1008: */
1009: public long getAge() {
1010: return System.currentTimeMillis() - createdTimestamp;
1011: }
1012:
1013: /**
1014: * Getter for the timestamp that this connection was checked out
1015: * @return the timestamp
1016: */
1017: public long getCheckoutTimestamp() {
1018: return checkoutTimestamp;
1019: }
1020:
1021: /**
1022: * Setter for the timestamp that this connection was checked out
1023: * @param timestamp the timestamp
1024: */
1025: public void setCheckoutTimestamp(long timestamp) {
1026: this .checkoutTimestamp = timestamp;
1027: }
1028:
1029: /**
1030: * Getter for the time that this connection has been checked out
1031: * @return the time
1032: */
1033: public long getCheckoutTime() {
1034: return System.currentTimeMillis() - checkoutTimestamp;
1035: }
1036:
1037: private Connection getValidConnection() {
1038: if (!valid) {
1039: throw new RuntimeException(
1040: "Error accessing SimplePooledConnection. Connection is invalid.");
1041: }
1042: return realConnection;
1043: }
1044:
1045: public int hashCode() {
1046: return hashCode;
1047: }
1048:
1049: /**
1050: * Allows comparing this connection to another
1051: *
1052: * @param obj - the other connection to test for equality
1053: * @see java.lang.Object#equals(java.lang.Object)
1054: */
1055: public boolean equals(Object obj) {
1056: if (obj instanceof SimplePooledConnection) {
1057: return realConnection.hashCode() == (((SimplePooledConnection) obj).realConnection
1058: .hashCode());
1059: } else if (obj instanceof Connection) {
1060: return hashCode == obj.hashCode();
1061: } else {
1062: return false;
1063: }
1064: }
1065:
1066: // **********************************
1067: // Implemented Connection Methods -- Now handled by proxy
1068: // **********************************
1069:
1070: /**
1071: * Required for InvocationHandler implementation.
1072: *
1073: * @param proxy - not used
1074: * @param method - the method to be executed
1075: * @param args - the parameters to be passed to the method
1076: * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
1077: */
1078: public Object invoke(Object proxy, Method method, Object[] args)
1079: throws Throwable {
1080: String methodName = method.getName();
1081: if (CLOSE.hashCode() == methodName.hashCode()
1082: && CLOSE.equals(methodName)) {
1083: dataSource.pushConnection(this );
1084: return null;
1085: } else {
1086: try {
1087: return method.invoke(getValidConnection(), args);
1088: } catch (Throwable t) {
1089: throw ClassInfo.unwrapThrowable(t);
1090: }
1091: }
1092: }
1093:
1094: public Statement createStatement() throws SQLException {
1095: return getValidConnection().createStatement();
1096: }
1097:
1098: public PreparedStatement prepareStatement(String sql)
1099: throws SQLException {
1100: return getValidConnection().prepareStatement(sql);
1101: }
1102:
1103: public CallableStatement prepareCall(String sql)
1104: throws SQLException {
1105: return getValidConnection().prepareCall(sql);
1106: }
1107:
1108: public String nativeSQL(String sql) throws SQLException {
1109: return getValidConnection().nativeSQL(sql);
1110: }
1111:
1112: public void setAutoCommit(boolean autoCommit)
1113: throws SQLException {
1114: getValidConnection().setAutoCommit(autoCommit);
1115: }
1116:
1117: public boolean getAutoCommit() throws SQLException {
1118: return getValidConnection().getAutoCommit();
1119: }
1120:
1121: public void commit() throws SQLException {
1122: getValidConnection().commit();
1123: }
1124:
1125: public void rollback() throws SQLException {
1126: getValidConnection().rollback();
1127: }
1128:
1129: public void close() throws SQLException {
1130: dataSource.pushConnection(this );
1131: }
1132:
1133: public boolean isClosed() throws SQLException {
1134: return getValidConnection().isClosed();
1135: }
1136:
1137: public DatabaseMetaData getMetaData() throws SQLException {
1138: return getValidConnection().getMetaData();
1139: }
1140:
1141: public void setReadOnly(boolean readOnly) throws SQLException {
1142: getValidConnection().setReadOnly(readOnly);
1143: }
1144:
1145: public boolean isReadOnly() throws SQLException {
1146: return getValidConnection().isReadOnly();
1147: }
1148:
1149: public void setCatalog(String catalog) throws SQLException {
1150: getValidConnection().setCatalog(catalog);
1151: }
1152:
1153: public String getCatalog() throws SQLException {
1154: return getValidConnection().getCatalog();
1155: }
1156:
1157: public void setTransactionIsolation(int level)
1158: throws SQLException {
1159: getValidConnection().setTransactionIsolation(level);
1160: }
1161:
1162: public int getTransactionIsolation() throws SQLException {
1163: return getValidConnection().getTransactionIsolation();
1164: }
1165:
1166: public SQLWarning getWarnings() throws SQLException {
1167: return getValidConnection().getWarnings();
1168: }
1169:
1170: public void clearWarnings() throws SQLException {
1171: getValidConnection().clearWarnings();
1172: }
1173:
1174: public Statement createStatement(int resultSetType,
1175: int resultSetConcurrency) throws SQLException {
1176: return getValidConnection().createStatement(resultSetType,
1177: resultSetConcurrency);
1178: }
1179:
1180: public PreparedStatement prepareStatement(String sql,
1181: int resultSetType, int resultSetConcurrency)
1182: throws SQLException {
1183: return getValidConnection().prepareCall(sql, resultSetType,
1184: resultSetConcurrency);
1185: }
1186:
1187: public CallableStatement prepareCall(String sql,
1188: int resultSetType, int resultSetConcurrency)
1189: throws SQLException {
1190: return getValidConnection().prepareCall(sql, resultSetType,
1191: resultSetConcurrency);
1192: }
1193:
1194: public Map getTypeMap() throws SQLException {
1195: return getValidConnection().getTypeMap();
1196: }
1197:
1198: public void setTypeMap(Map map) throws SQLException {
1199: getValidConnection().setTypeMap(map);
1200: }
1201:
1202: // **********************************
1203: // JDK 1.4 JDBC 3.0 Methods below
1204: // **********************************
1205:
1206: public void setHoldability(int holdability) throws SQLException {
1207: getValidConnection().setHoldability(holdability);
1208: }
1209:
1210: public int getHoldability() throws SQLException {
1211: return getValidConnection().getHoldability();
1212: }
1213:
1214: public Savepoint setSavepoint() throws SQLException {
1215: return getValidConnection().setSavepoint();
1216: }
1217:
1218: public Savepoint setSavepoint(String name) throws SQLException {
1219: return getValidConnection().setSavepoint(name);
1220: }
1221:
1222: public void rollback(Savepoint savepoint) throws SQLException {
1223: getValidConnection().rollback(savepoint);
1224: }
1225:
1226: public void releaseSavepoint(Savepoint savepoint)
1227: throws SQLException {
1228: getValidConnection().releaseSavepoint(savepoint);
1229: }
1230:
1231: public Statement createStatement(int resultSetType,
1232: int resultSetConcurrency, int resultSetHoldability)
1233: throws SQLException {
1234: return getValidConnection().createStatement(resultSetType,
1235: resultSetConcurrency, resultSetHoldability);
1236: }
1237:
1238: public PreparedStatement prepareStatement(String sql,
1239: int resultSetType, int resultSetConcurrency,
1240: int resultSetHoldability) throws SQLException {
1241: return getValidConnection().prepareStatement(sql,
1242: resultSetType, resultSetConcurrency,
1243: resultSetHoldability);
1244: }
1245:
1246: public CallableStatement prepareCall(String sql,
1247: int resultSetType, int resultSetConcurrency,
1248: int resultSetHoldability) throws SQLException {
1249: return getValidConnection().prepareCall(sql, resultSetType,
1250: resultSetConcurrency, resultSetHoldability);
1251: }
1252:
1253: public PreparedStatement prepareStatement(String sql,
1254: int autoGeneratedKeys) throws SQLException {
1255: return getValidConnection().prepareStatement(sql,
1256: autoGeneratedKeys);
1257: }
1258:
1259: public PreparedStatement prepareStatement(String sql,
1260: int columnIndexes[]) throws SQLException {
1261: return getValidConnection().prepareStatement(sql,
1262: columnIndexes);
1263: }
1264:
1265: public PreparedStatement prepareStatement(String sql,
1266: String columnNames[]) throws SQLException {
1267: return getValidConnection().prepareStatement(sql,
1268: columnNames);
1269: }
1270:
1271: }
1272: }
|