001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/datastore/sql/PooledDataSource.java,v 1.7 2003/07/12 00:57:29 sbendar Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm.datastore.sql;
021:
022: import java.util.Iterator;
023: import java.util.Properties;
024: import java.util.LinkedList;
025: import java.util.logging.Logger;
026: import java.util.logging.Level;
027:
028: import java.io.PrintWriter;
029:
030: import java.sql.Connection;
031: import java.sql.Statement;
032: import java.sql.SQLException;
033: import java.sql.Driver;
034:
035: import javax.sql.DataSource;
036:
037: /**
038: * Wraps a JDBC driver manager and provides a pooling of connections.
039: *
040: * @author Scott Bendar
041: * @version $Revision: 1.7 $
042: */
043: public class PooledDataSource implements DataSource {
044:
045: private static final Logger logger = Logger
046: .getLogger("org.xorm.datastore.sql");
047:
048: /**
049: * Milliseconds to wait for a connection to become available
050: * before waking up and logging the wait before waiting again.
051: * This is overridden if the loginTimeout is less than this.
052: */
053: private static final int DEFAULT_TIMEOUT_MS = 5000;
054:
055: private String connectionURL = null;
056: private String driverName = null;
057: private int minPool = 0;
058: private int maxPool = 5;
059: private int loginTimeout = 0;
060: private boolean checkReturnedConnection = false;
061: private long idleCheck = 0;
062: private Integer isolationLevel;
063: private String idleCheckSQL = null;
064:
065: protected Properties props = new Properties();
066: protected int connCount = 0;
067:
068: protected Driver driver = null;
069:
070: protected LinkedList availableConnections = new LinkedList();
071:
072: /**
073: * Initializes a new pooled data source
074: */
075: public PooledDataSource() {
076: }
077:
078: /**
079: * Sets the user name to pass when making a db connection
080: */
081: public void setUser(String s) {
082: props.setProperty("user", s);
083: }
084:
085: public String getUser() {
086: return props.getProperty("user");
087: }
088:
089: /**
090: * Sets the password to use when creating a db connection. For
091: * security reasons, there is no way to get the password.
092: */
093: public void setPassword(String s) {
094: props.setProperty("password", s);
095: }
096:
097: /**
098: * Set the URL used to connect to the DB
099: */
100: public void setConnectionUrl(String s) {
101: connectionURL = s;
102: }
103:
104: public String getConnectionUrl() {
105: return connectionURL;
106: }
107:
108: /**
109: * Set the name of the driver to load when connecting to the DB
110: */
111: public void setDriverName(String s) {
112: driverName = s;
113: }
114:
115: public String getDriverName() {
116: return driverName;
117: }
118:
119: /**
120: * The maximum number of connections created by the data source.
121: * Once the max is exceeded callers must wait for a connection to
122: * become available.
123: */
124: public int getMaxPool() {
125: return maxPool;
126: }
127:
128: public void setMaxPool(int i) {
129: if (i < 1) {
130: logger
131: .info("Max pool size is disabled, setting to max int.");
132: i = Integer.MAX_VALUE;
133: }
134: maxPool = i;
135: }
136:
137: /**
138: * The minimum number of connections in the pool. It will create
139: * this number of connections when the pool is first initialized
140: * and the pool will never shrink below this many connections.
141: */
142: public int getMinPool() {
143: return minPool;
144: }
145:
146: public void setMinPool(int i) {
147: minPool = i;
148: }
149:
150: public int getLoginTimeout() {
151: return loginTimeout;
152: }
153:
154: /**
155: * Sets the maximum time to wait in seconds for a connection
156: * before throwing an exception.
157: */
158: public void setLoginTimeout(int timeout) {
159: loginTimeout = timeout;
160: }
161:
162: /**
163: * If set, instructs the connection to verify its valid when the
164: * caller is done with the connection (calls close). This is used
165: * for debugging purposes only to help catch folks to return closed
166: * connections to the pool.
167: */
168: public boolean getCheckReturnedConnection() {
169: return checkReturnedConnection;
170: }
171:
172: /**
173: * If set, instructs the connection to verify its valid when the
174: * caller is done with the connection (calls close). This is used
175: * for debugging purposes only to help catch folks to return closed
176: * connections to the pool.
177: */
178: public void setCheckReturnedConnection(boolean value) {
179: checkReturnedConnection = value;
180: }
181:
182: /**
183: * Gets the amount of time in milliseconds a connection has been
184: * idle before the pool tests the connection to see if its still
185: * valid.
186: *
187: * -1: Never check
188: * 0: Always check
189: *
190: */
191: public long getIdleCheck() {
192: return idleCheck;
193: }
194:
195: /**
196: * Sets the amount of time in milliseconds a connection has been
197: * idle before the pool tests the connection to see if its still
198: * valid.
199: *
200: * -1: Never check
201: * 0: Always check
202: *
203: */
204: public void setIdleCheck(long idle) {
205: idleCheck = idle;
206: }
207:
208: /**
209: * Gets a sql statement to be used to check a connection. If not
210: * specified, isConnectionClosed() is called instead.
211: */
212: public String getIdleCheckSQL() {
213: return idleCheckSQL;
214: }
215:
216: /**
217: * Sets a sql statement to be used to check a connection. If not
218: * specified, isConnectionClosed() is called instead.
219: */
220: public void setIdleCheckSQL(String sql) {
221: idleCheckSQL = sql;
222: }
223:
224: /**
225: * Sets the transaction isolation level. If null, no specific
226: * setting is made on a Connection instance; otherwise, the wrapped
227: * value is used.
228: */
229: public void setIsolationLevel(Integer isolationLevel) {
230: this .isolationLevel = isolationLevel;
231: }
232:
233: public Integer getIsolationLevel() {
234: return isolationLevel;
235: }
236:
237: /**
238: * Gets a DB connection, actually a wrapper that allows the
239: * connection to be pooled. The method first allocates a
240: * connection from the available pool, if there are none available
241: * it creates a new connection unless that would put it over the
242: * max pool size, in which case it forces the caller to wait.
243: *
244: * If the data source was configured with a loginTimeout value
245: * greater than 0, it will wait for loginTimeout seconds for a
246: * connection.
247: */
248: public Connection getConnection() throws SQLException {
249:
250: PooledConnection conn = null;
251:
252: int timeSpent = 0;
253:
254: // Don't wait forever, wake up and check things out periodically
255: int waitTime = DEFAULT_TIMEOUT_MS;
256:
257: // If there is a fixed time we are willing to wait, just wait that
258: // long
259: if (loginTimeout > 0)
260: waitTime = loginTimeout;
261:
262: synchronized (availableConnections) {
263:
264: // Initialize the driver the first time its used
265: if (driver == null)
266: initDriver();
267:
268: while (loginTimeout == 0 || timeSpent < loginTimeout) {
269:
270: long startTime = System.currentTimeMillis();
271:
272: if (availableConnections.size() > 0) {
273: logger
274: .fine("getConnection(): using available connection");
275: conn = (PooledConnection) availableConnections
276: .removeFirst();
277: conn.setClosed(false);
278: try {
279: testConnection(conn);
280: return conn;
281: } catch (SQLException e) {
282: /*
283: * If it fails the test, get rid of the connection
284: * so we will try and allocate a new one before
285: * returning a SQLException to the caller.
286: */
287: logger
288: .log(
289: Level.SEVERE,
290: "Connection failed test, dropping connection from pool!",
291: e);
292: dropConnection(conn);
293: }
294: } else {
295: if (connCount < maxPool) {
296: logger
297: .fine("getConnection(): creating new connection");
298: conn = createNewConnection();
299: return conn;
300: } else {
301: logger
302: .fine("getConnection(): waiting for available connection");
303: try {
304: availableConnections.wait(waitTime);
305: } catch (InterruptedException e) {
306: logger
307: .fine("getConnection(): wait interrupted!");
308: }
309: timeSpent += (int) (System.currentTimeMillis() - startTime);
310: logger
311: .fine("getConnection(): wait timeout, wait total is "
312: + timeSpent + " seconds");
313: }
314: }
315: }
316: }
317:
318: logger.warning("getConnection(): wait exceeded loginTimeout");
319: throw new SQLException(
320: "getConnection(): wait exceeded loginTimeout");
321: }
322:
323: /**
324: * This function is currently not implemented as it doesn't allow
325: * us to use the connections in the pool. The workaround is to
326: * create a new data source with a different user and password.
327: *
328: * Should this just pass back an unpooled real connection?
329: */
330: public Connection getConnection(String userName, String password)
331: throws SQLException {
332:
333: throw new SQLException(
334: "getConnection(user,pass) not supported, create a new data source with desired user and password");
335:
336: }
337:
338: /**
339: * The PooledDataSource class uses JDK 1.4 logging and ignores the
340: * log writer.
341: */
342: public PrintWriter getLogWriter() {
343: return null;
344: }
345:
346: public void setLogWriter(PrintWriter logger) {
347: }
348:
349: /**
350: * Initialize the driver and create the initial connections up to
351: * the min pool size.
352: */
353: private void initDriver() throws SQLException {
354:
355: if (driver != null)
356: return;
357:
358: logger.finer("initDriver() -- Entry");
359:
360: try {
361: Class driverClass = Class.forName(driverName);
362: driver = (Driver) driverClass.newInstance();
363:
364: // Create the minimum connections
365: while (connCount < minPool) {
366: availableConnections.add(createNewConnection());
367: }
368:
369: } catch (Throwable t) {
370: throw new SQLException("initDriver: " + t);
371: }
372:
373: }
374:
375: /**
376: * Creats a new physical db connection and wraps it in our
377: * PooledConnection
378: */
379: private PooledConnection createNewConnection() throws SQLException {
380:
381: PooledConnection conn = null;
382: if (driver != null) {
383: conn = new PooledConnection(this , driver.connect(
384: connectionURL, props));
385: if (isolationLevel != null) {
386: conn.setTransactionIsolation(isolationLevel.intValue());
387: }
388: connCount++;
389: }
390:
391: return conn;
392: }
393:
394: /**
395: * Tests if the connection is still valid or not.
396: */
397: private void testConnection(PooledConnection conn)
398: throws SQLException {
399: if (conn.hasReceivedException()
400: || (idleCheck >= 0 && (System.currentTimeMillis()
401: - conn.getLastUsedTime() > idleCheck))) {
402:
403: conn.clearReceivedException();
404:
405: if (!isConnectionValid(conn))
406: throw new SQLException(
407: "Connection is invalid, remove from pool.");
408: }
409: }
410:
411: /**
412: * Checks if a connection is active by running a SQL query specified
413: * by IdleCheckSQL or by calling isClosed() if no sql statement has
414: * been specified.
415: */
416: private boolean isConnectionValid(PooledConnection conn)
417: throws SQLException {
418:
419: if (idleCheckSQL == null) {
420: logger.fine("Checking connection using isClosed");
421: return !conn.isConnectionClosed();
422: } else {
423: logger
424: .fine("Checking connection with SQL: "
425: + idleCheckSQL);
426: Statement statement = conn.createStatement();
427:
428: try {
429: statement.execute(idleCheckSQL);
430: } finally {
431: statement.close();
432: }
433: return true;
434: }
435: }
436:
437: /**
438: * Makes the connection available again. This is called by the
439: * PooledConnection class when the user chooses to close a
440: * connection.
441: */
442: protected void makeAvailable(PooledConnection conn) {
443: synchronized (availableConnections) {
444: availableConnections.add(conn);
445: logger.fine("makeAvailable(): available="
446: + availableConnections.size() + " total="
447: + connCount);
448: availableConnections.notify();
449: }
450: }
451:
452: /** Releases all available connections. */
453: // TODO: handle closure of connections that are currently leased
454: public void close() {
455: synchronized (availableConnections) {
456: Iterator i = availableConnections.iterator();
457: while (i.hasNext()) {
458: PooledConnection conn = (PooledConnection) i.next();
459: conn.closeConnection();
460: i.remove();
461: connCount--;
462: }
463: }
464: }
465:
466: /** remove a connection from the pool */
467: protected void dropConnection(PooledConnection conn) {
468: synchronized (availableConnections) {
469: conn.closeConnection();
470: connCount--;
471: }
472: }
473: }
|