001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library 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 GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data.jdbc;
017:
018: import java.sql.Connection;
019: import java.sql.SQLException;
020: import java.util.Iterator;
021: import java.util.LinkedList;
022: import java.util.logging.Level;
023: import java.util.logging.Logger;
024:
025: import javax.sql.ConnectionEvent;
026: import javax.sql.ConnectionEventListener;
027: import javax.sql.ConnectionPoolDataSource;
028: import javax.sql.DataSource;
029: import javax.sql.PooledConnection;
030:
031: import org.geotools.data.jdbc.datasource.DataSourceFinder;
032: import org.geotools.data.jdbc.datasource.DataSourceUtil;
033:
034: /**
035: * Provides a ConnectionPool that can be used by multiple data sources to get connections to a
036: * single database.
037: *
038: * <p>
039: * This class should not be subclassed.
040: * </p>
041: *
042: * @author Sean Geoghegan, Defence Science and Technology Organisation
043: * @author Chris Holmes
044: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/ConnectionPool.java $
045: * @version $Id: ConnectionPool.java 27862 2007-11-12 19:51:19Z desruisseaux $
046: * @deprecated Use {@link DataSource}, {@link DataSourceUtil} and {@link DataSourceFinder} instead
047: */
048: public final class ConnectionPool {
049: /** A logger */
050: private static final Logger LOGGER = org.geotools.util.logging.Logging
051: .getLogger("org.geotools.data.jdbc");
052: /** The default wait time for cleaning the Pool - defaults to 30 secs */
053: private static final long DEFAULT_POOL_CLEANER_WAIT = 30000;
054: /** A mutex for synchronizing */
055: private Object mutex = new Object();
056: /** The data source we get the pooled connections from */
057: private ConnectionPoolDataSource cpDataSource;
058: /** List of available connections */
059: private LinkedList availableConnections = new LinkedList();
060: /** This of connections that are in use */
061: private LinkedList usedConnections = new LinkedList();
062: /** Handles ConnectionEvents and manages the lists */
063: private ConnectionListManager listManager = new ConnectionListManager();
064: /** Cleans the list of dead connections */
065: private ConnectionPoolCleaner poolCleaner;
066: /** Indicates that this Connection Pool is closed and it should not
067: * return connections on calls to getConnection()
068: */
069: private boolean closed = false;
070:
071: /**
072: * Creates a new Connection Pool using a ConnectionPoolDataSource.
073: *
074: * <p>
075: * This constructor will also spawn a thread for cleaning the connection pool every 30 seconds.
076: * </p>
077: *
078: * @param cpDataSource The Connection Pool Data Source to get PooledConnections from.
079: */
080: public ConnectionPool(ConnectionPoolDataSource cpDataSource) {
081: this .cpDataSource = cpDataSource;
082: poolCleaner = new ConnectionPoolCleaner(
083: DEFAULT_POOL_CLEANER_WAIT);
084:
085: Thread cleanerThread = new Thread(poolCleaner);
086: cleanerThread.setDaemon(true);
087: cleanerThread.start();
088: }
089:
090: /**
091: * Gets a Connection from the Connection Pool.
092: *
093: * <p>
094: * If no available connections exist a new connection will be created and added to the pool.
095: * When the returned connection is closed it will be added to the connection pool for other
096: * requests to this method.
097: * </p>
098: *
099: * @return A Connection from the ConnectionPool.
100: *
101: * @throws SQLException If an error occurs getting the connection or if the
102: * connection pool has been closed by a previous call to close().
103: */
104: public Connection getConnection() throws SQLException {
105: if (closed) {
106: throw new SQLException(
107: "The ConnectionPool has been closed.");
108: }
109:
110: Connection conn = null;
111:
112: synchronized (mutex) {
113: if (availableConnections.size() > 0) {
114: LOGGER.fine("Getting available connection.");
115:
116: ManagedPooledConnection mConn = (ManagedPooledConnection) availableConnections
117: .removeFirst();
118:
119: conn = mConn.pooledConn.getConnection();
120:
121: mConn.lastUsed = System.currentTimeMillis();
122: mConn.inUse = true;
123: usedConnections.add(mConn);
124: } else {
125: LOGGER
126: .fine("No available connections, creating a new one.");
127:
128: PooledConnection pConn = cpDataSource
129: .getPooledConnection();
130:
131: conn = pConn.getConnection();
132:
133: pConn.addConnectionEventListener(listManager);
134:
135: ManagedPooledConnection mConn = new ManagedPooledConnection(
136: pConn);
137:
138: mConn.inUse = true;
139: mConn.lastUsed = System.currentTimeMillis();
140: usedConnections.add(mConn);
141: }
142: }
143:
144: return conn;
145: }
146:
147: /**
148: * Helper method to get the ManagedPooledConnection for a given PooledConnection.
149: *
150: * @param conn The PooledConnection
151: *
152: * @return The ManagedPooledConnection that contains the PooledConnection.
153: */
154: private ManagedPooledConnection getInUseManagedPooledConnection(
155: PooledConnection conn) {
156: ManagedPooledConnection returnConn = null;
157:
158: for (Iterator iter = usedConnections.iterator(); iter.hasNext();) {
159: ManagedPooledConnection mConn = (ManagedPooledConnection) iter
160: .next();
161:
162: if (mConn.pooledConn == conn) {
163: returnConn = mConn;
164: }
165: }
166:
167: return returnConn;
168: }
169:
170: /** Closes all the PooledConnections in the the ConnectionPool.
171: * The current behaviour is to first close all the used connections,
172: * then close all the available connections. This method will also set
173: * the state of the ConnectionPool to closed, caused any future calls
174: * to getConnection to throw an SQLException.
175: */
176: public void close() {
177: if (closed) {
178: return;
179: }
180: synchronized (mutex) {
181: int size = usedConnections.size();
182: for (int i = 0; i < size; i++) {
183: ManagedPooledConnection mPool = (ManagedPooledConnection) usedConnections
184: .removeFirst();
185: mPool.pooledConn
186: .removeConnectionEventListener(listManager);
187: try {
188: mPool.pooledConn.close();
189: } catch (SQLException e) {
190: LOGGER.warning("Failed to close PooledConnection: "
191: + e);
192: }
193: }
194:
195: size = availableConnections.size();
196: for (int i = 0; i < size; i++) {
197: ManagedPooledConnection mPool = (ManagedPooledConnection) availableConnections
198: .removeFirst();
199: mPool.pooledConn
200: .removeConnectionEventListener(listManager);
201: try {
202: mPool.pooledConn.close();
203: } catch (SQLException e) {
204: LOGGER.warning("Failed to close PooledConnection: "
205: + e);
206: }
207: }
208: closed = true;
209: }
210: }
211:
212: /** Checks whether the ConnectionPool has been closed.
213: *
214: * @return True if the connection pool is closed. If the Pool is closed
215: * future calls to getConnection will throw an SQLException.
216: */
217: public boolean isClosed() {
218: return closed;
219: }
220:
221: /**
222: * A ConnectionEventListener for managing the list of connections in the pool.
223: *
224: * @author Sean Geoghegan, Defence Science and Technology Organisation
225: * @author Chris Holmes
226: * @version $Id: ConnectionPool.java 27862 2007-11-12 19:51:19Z desruisseaux $
227: */
228: private class ConnectionListManager implements
229: ConnectionEventListener {
230: /**
231: * This is called when a logical connection is closed. The pooled connection is returned
232: * to the list of available connections.
233: *
234: * @param event The Connection event.
235: *
236: * @see javax.sql.ConnectionEventListener#connectionClosed(javax.sql.ConnectionEvent)
237: */
238: public void connectionClosed(ConnectionEvent event) {
239: LOGGER
240: .fine("Connection closed - adding to available connections.");
241:
242: PooledConnection conn = (PooledConnection) event
243: .getSource();
244: synchronized (mutex) {
245: ManagedPooledConnection mConn = getInUseManagedPooledConnection(conn);
246:
247: mConn.inUse = false;
248:
249: usedConnections.remove(mConn);
250: availableConnections.addLast(mConn);
251: }
252: }
253:
254: /**
255: * Called when an error occurs on the Connection. This removes the connection from the
256: * pool.
257: *
258: * @param event The ConnectionEvent indicating an error.
259: *
260: * @see javax.sql.ConnectionEventListener#connectionErrorOccurred(javax.sql.ConnectionEvent)
261: */
262: public void connectionErrorOccurred(ConnectionEvent event) {
263: PooledConnection conn = (PooledConnection) event
264: .getSource();
265: synchronized (mutex) {
266: ManagedPooledConnection mConn = getInUseManagedPooledConnection(conn);
267:
268: conn.removeConnectionEventListener(this );
269:
270: try {
271: conn.close();
272: } catch (SQLException e) {
273: // don't need to do anything here, just log it
274: LOGGER.log(Level.WARNING,
275: "Error closing a connection", e);
276: }
277:
278: usedConnections.remove(mConn);
279: }
280: }
281: }
282:
283: /**
284: * Runnable class that handles cleaning of invalid connections from the connection pool. A
285: * connection is removed from the pool when its isValid method return false. The Constructor
286: * for the ConnectionPoolCleaner has a parameter that defines how often the pool cleaner will
287: * run.
288: *
289: * <p>
290: * The Pool Cleaner has come to clean ze pool.
291: * </p>
292: *
293: * @author Sean Geoghegan, Defence Science and Technology Organisation
294: * @author Chris Holmes
295: * @version $Id: ConnectionPool.java 27862 2007-11-12 19:51:19Z desruisseaux $
296: */
297: private class ConnectionPoolCleaner implements Runnable {
298: /** Time to wait between cleaning */
299: private long waitTime;
300: /** Run loop flag */
301: private boolean active = true;
302:
303: /** Creates a new ConnectionPoolCleaner
304: *
305: * @param waitTime The frequency of the cleaning.
306: */
307: ConnectionPoolCleaner(long waitTime) {
308: this .waitTime = waitTime;
309: }
310:
311: /** Stops the Pool cleaner.
312: *
313: */
314: void disable() {
315: active = false;
316: }
317:
318: /**
319: * Executes the Connection Pool Cleaning.
320: */
321: public void run() {
322: while (active) {
323: synchronized (mutex) {
324: for (Iterator iter = availableConnections
325: .iterator(); iter.hasNext();) {
326: ManagedPooledConnection conn = (ManagedPooledConnection) iter
327: .next();
328:
329: conn.pooledConn
330: .removeConnectionEventListener(listManager);
331:
332: if (!conn.isValid()) {
333: LOGGER
334: .fine("Connection invalid, removing from pool");
335:
336: try {
337: conn.pooledConn.close();
338: } catch (SQLException e) {
339: LOGGER
340: .log(
341: Level.WARNING,
342: "Error closing dead connection",
343: e);
344: }
345:
346: iter.remove();
347: } else {
348: // Connection is fine, add the list manager again.
349: conn.pooledConn
350: .addConnectionEventListener(listManager);
351: }
352: }
353: }
354:
355: try {
356: Thread.sleep(waitTime);
357: } catch (InterruptedException e) {
358: LOGGER
359: .log(
360: Level.WARNING,
361: "Interrupted exception when wait in Pool Cleaner",
362: e);
363: }
364: }
365: }
366: }
367: }
|