001: /*
002: * @(#) ConnectionPool 1.0 02/08/01
003: */
004:
005: package org.smartlib.pool.core;
006:
007: import org.apache.log4j.Logger;
008:
009: import java.util.*;
010: import java.sql.*;
011:
012: /**
013: * This class implements the Pool interface and thus is responsible for
014: * managing a single pool of connections.
015: */
016:
017: public class ConnectionPool implements Pool, PoolMonitor {
018:
019: private PoolConfig config;
020: private final String name;
021: private final Debugger debug;
022: private int currentPoolSize = 0;
023: private int usedConnections = 0;
024: private Vector connectionList;
025: private Vector connectionListenerList;
026: private Thread pollerThread;
027: private ConnectionProvider connProvider = null;
028: private String validatorQuery = null;
029: private volatile boolean shutDown = false;
030: private Hashtable connectionHash = new Hashtable();
031:
032: Logger logger = Logger.getLogger(ConnectionPool.class);
033:
034: /**
035: * This method draws a raw connection from the database.
036: */
037: private Connection loadConnection() throws ConnectionPoolException {
038:
039: try {
040: Class.forName(config.getDriver());
041: } catch (ClassNotFoundException classNotFound) {
042: throw new ConnectionPoolException("Could not load Driver",
043: classNotFound);
044: }
045: Connection con = null;
046: try {
047: con = DriverManager.getConnection(
048: config.getConnectionStringByName(name)
049: .getConnectString(), config.getUserName(),
050: config.getPassword());
051: } catch (Exception e) {
052: throw new ConnectionPoolException(
053: "Could not obtain Connection", e);
054: }
055: currentPoolSize++;
056: return con;
057:
058: }
059:
060: /**
061: * @return Vector of connections in use.
062: */
063: public Vector getConnectionsInUse() {
064:
065: return connectionList;
066:
067: }
068:
069: /**
070: * @return Vector of registered ConnectionLeakListeners.
071: */
072: public Vector getConnectionLeakListeners() {
073:
074: return connectionListenerList;
075:
076: }
077:
078: /**
079: * @return Number of free connections in the pool.
080: */
081: public int getNoOfFreeConnections() {
082:
083: return (currentPoolSize - usedConnections);
084:
085: }
086:
087: /**
088: * This method returns an instance of ConfigMonitor.
089: * @return ConfigMonitor for monitoring the configuration of the pool at
090: * runtime.
091: */
092: public ConfigMonitor getConfigMonitor() {
093:
094: return (ConfigMonitor) config;
095:
096: }
097:
098: public String getName() {
099: return name;
100: }
101:
102: /**
103: * This method draws the initial raw connections.
104: */
105: private void initialiseConnections() throws ConnectionPoolException {
106:
107: try {
108: int minConnections = config.getMinConnections();
109: for (int i = 0; i < minConnections; i++) {
110: connectionHash.put(loadConnection(), Boolean.TRUE);
111: }
112: } catch (Exception e) {
113: throw new ConnectionPoolException(
114: "Could not load initial connection", e);
115: }
116:
117: }
118:
119: /*
120: * This method is called when the existing connection is wrapped to another
121: * pool.
122: */
123: private void returnConnectionToOtherPool(Connection conn)
124: throws Exception {
125:
126: connProvider.returnConnection(conn);
127:
128: }
129:
130: /**
131: * Constructor initialises the pool as per the configuration specified by
132: * <code>config</code>
133: */
134: ConnectionPool(PoolConfig config, String name)
135: throws ConnectionPoolException {
136:
137: this .name = name;
138: this .config = config;
139: try {
140: if (config.getConnectionLoaderClass() != null)
141: connProvider = (ConnectionProvider) getClass()
142: .getClassLoader().loadClass(
143: config.getConnectionLoaderClassByName(
144: name)
145: .getConnectionLoaderClass())
146: .newInstance();
147: } catch (Exception exp) {
148: throw new ConnectionPoolException(
149: "Error loading Connection Loader Class", exp);
150: }
151: if (connProvider == null)
152: initialiseConnections();
153:
154: debug = new Debugger(name, true);
155: connectionList = new Vector(config.getMinConnections(), config
156: .getIncrement());
157: connectionListenerList = new Vector();
158: try {
159: if (config.getDefaultListener() != null) {
160: String defaultListener = config.getDefaultListener();
161: addConnectionLeakListener((ConnectionLeakListener) getClass()
162: .getClassLoader().loadClass(defaultListener)
163: .newInstance());
164: }
165: } catch (Exception e) {
166: throw new ConnectionPoolException("Could not load class "
167: + config.getDefaultListener(), e);
168: }
169: if (config.isDetectLeaks()) {
170: pollerThread = new Thread(new ConnectionLeakPollThread(
171: connectionList, connectionListenerList, name,
172: config.getPollThreadTime(),
173: config.getLeakTimeOut(), this ));
174: pollerThread.start();
175: }
176:
177: validatorQuery = config.getValidatorQuery();
178:
179: }
180:
181: /**
182: * This method returns the current size of the pool.
183: * @return Current size of the pool.
184: */
185: public int getCurrentPoolSize() {
186:
187: return currentPoolSize;
188:
189: }
190:
191: /**
192: * This method returns a Connection from the connection pool.
193: * The owner of this pool is marked as N/A indicating unknown/anonymous.
194: *
195: * <b>Note: This method blocks if the pool size has reached it's
196: * maximum size and no free connections are available
197: * until a free connection is available.</b> The time period for which this
198: * method blocks depends on the connection-wait-time-out specified in
199: * the configuration file.
200: *
201: *
202: * @return Connection from the pool.
203: * @exception ConnectionPoolException if there is any problem
204: * getting connection.
205: */
206: public Connection getConnection() throws ConnectionPoolException {
207:
208: if (config.isAllowAnonymousConnections())
209: return getConnection("N/A");
210: else
211: throw new ConnectionPoolException(
212: "You are not allowed to take anonumous connections, please provide an owner name");
213:
214: }
215:
216: // Checks if the Connection is valid
217: private boolean checkIfValid(Connection conn) {
218:
219: try {
220: debug.print(" Checking Connection for '" + validatorQuery
221: + "'");
222: if (validatorQuery != null
223: && !validatorQuery.trim().equals("")) {
224: Statement stmt = conn.createStatement();
225: boolean bool = stmt.execute(validatorQuery);
226: stmt.close();
227: return bool;
228: } else {
229: return true;
230: }
231: } catch (SQLException exp) {
232: if (logger.isDebugEnabled()) {
233: logger
234: .debug(
235: "Exception occurred while trying to test connection validity",
236: exp);
237: }
238: return false;
239: }
240:
241: }
242:
243: /*
244: * This method is called when the existing connection is wrapped to another
245: * pool.
246: */
247: private Connection getConnectionFromOtherPool(String owner)
248: throws ConnectionPoolException {
249:
250: try {
251: synchronized (this ) {
252: if (config.getMaxConnections() == usedConnections) {
253: try {
254: debug.print("Hey the value is "
255: + config.getConnectionWaitTimeOut());
256: wait(config.getConnectionWaitTimeOut());
257: if (config.getMaxConnections() == usedConnections) {
258: throw new TimeOutException(
259: "Timed-out while "
260: + "waiting for free connection");
261: }
262: } catch (InterruptedException ie) {
263: }
264: }
265:
266: Connection conn = connProvider.getConnection();
267: usedConnections++;
268: currentPoolSize++;
269: // Checking if the connection is still live and active
270: // if not get replace the old one with new one
271: if (checkIfValid(conn)) {
272: SmartConnection smt = new SmartConnection(conn,
273: this , owner, config.isAutoClose());
274: connectionList.add(smt);
275: return smt;
276: } else {
277: boolean valid = false;
278: int i = 1;
279: while (!valid) {
280: conn = connProvider.getConnection();
281: valid = checkIfValid(conn);
282: i++;
283: if (i == 3 && !valid)
284: throw new ConnectionPoolException(
285: "Three consecutive cnnections failes the Validator Query org.smartlib.pool.test");
286: }
287: SmartConnection smt = new SmartConnection(conn,
288: this , owner, config.isAutoClose());
289: connectionList.add(smt);
290: return smt;
291: }
292: }
293: } catch (ConnectionPoolException cpe) {
294: throw cpe;
295: } catch (Exception exp) {
296: throw new ConnectionPoolException(
297: "Error while getting connections from the Connection Loader Class",
298: exp);
299: }
300:
301: }
302:
303: /**
304: * This method returns a Connection from the pool.
305: * The owner of this connection is identified by <code>owner</code> .
306: *
307: * <b>Note: This method blocks if the pool size has reached it's
308: * maximum size and no free connections are available
309: * until a free connection is available</b>. The time period for which this
310: * method blocks depends on the connection-wait-time-out specified in
311: * the configuration file.
312: *
313: *
314: * @param owner String identifying the owner.
315: * @return Connection from the pool
316: *
317: * @exception ConnectionPoolException if there is any problem
318: * getting connection.
319: */
320: public Connection getConnection(String owner)
321: throws ConnectionPoolException {
322:
323: // Check for external pooling
324: if (connProvider != null)
325: return getConnectionFromOtherPool(owner);
326:
327: Enumeration cons = connectionHash.keys();
328: //debug.print("Getting Connection " + usedConnections);
329: synchronized (connectionHash) {
330: // Checking if max-conn is less than used connection , if not wait
331: if (config.getMaxConnections() == usedConnections) {
332: try {
333: //debug.print("Waiting for Connection " + usedConnections);
334: debug.print("Hey the value is "
335: + config.getConnectionWaitTimeOut());
336: connectionHash.wait(config
337: .getConnectionWaitTimeOut());
338: if (config.getMaxConnections() == usedConnections) {
339: throw new TimeOutException("Timed-out while "
340: + "waiting for free connection");
341: }
342:
343: } catch (InterruptedException ie) {
344: }
345: }
346:
347: // Reached here indicates that free conn is available or
348: // currentpool size is less and thus new conn can be added to pool
349: while (cons.hasMoreElements()) {
350: // Checking if any unused connection is available
351: Connection con = (Connection) cons.nextElement();
352: Boolean b = (Boolean) connectionHash.get(con);
353: if (b == Boolean.TRUE) {
354: //Unused Connection is available
355: connectionHash.put(con, Boolean.FALSE);
356: usedConnections++;
357: debug.print("Hey After Incrementing conn "
358: + usedConnections);
359: //debug.print("Connection Obtained " + usedConnections);
360: // Checking if the connection is still live and active
361: // if not get replace the old one with new one
362: if (checkIfValid(con)) {
363: SmartConnection smt = new SmartConnection(con,
364: this , owner, config.isAutoClose());
365: connectionList.add(smt);
366: return smt;
367: } else {
368: boolean valid = false;
369: int failCounter = 1;
370: while (!valid) {
371: connectionHash.remove(con);
372: con = loadConnection();
373: connectionHash.put(con, Boolean.FALSE);
374: failCounter++;
375: valid = checkIfValid(con);
376: if (failCounter == 3 && !valid)
377: throw new ConnectionPoolException(
378: "Three consecutive connections failed the Validator Query org.smartlib.pool.test");
379: }
380: SmartConnection smt = new SmartConnection(con,
381: this , owner, config.isAutoClose());
382: connectionList.add(smt);
383: return smt;
384: }
385: }
386: }
387:
388: // No Connection available hence increase the pool size
389: int increment = config.getIncrement();
390: Connection c = null;
391: SmartConnection smt = null;
392: for (int i = 0; i < increment
393: && i + currentPoolSize <= config
394: .getMaxConnections(); i++) {
395: c = loadConnection();
396: boolean valid = checkIfValid(c);
397: int failCounter = 1;
398: while (!valid) {
399: c = loadConnection();
400: failCounter++;
401: valid = checkIfValid(c);
402: if (failCounter == 3 && !valid)
403: throw new ConnectionPoolException(
404: "Three consecutive connections failed the Validator Query org.smartlib.pool.test");
405: }
406: if (i == 0) {
407: smt = new SmartConnection(c, this , owner, config
408: .isAutoClose());
409: connectionHash.put(c, Boolean.FALSE);
410: } else
411: connectionHash.put(c, Boolean.TRUE);
412: }
413:
414: //debug.print("Connection Incremented " + usedConnections);
415: //debug.print("Pool Size" + currentPoolSize);
416: usedConnections++;
417: connectionList.add(smt);
418: return smt;
419:
420: }
421:
422: }
423:
424: /**
425: * This method releases the connection back to the pool.
426: * @param ret connection to be released
427: */
428: public void returnConnection(Connection ret) {
429:
430: if (connProvider != null) {
431: try {
432: synchronized (this ) {
433: Connection conn = ((SmartConnection) ret)
434: .returnConnection();
435: returnConnectionToOtherPool(conn);
436: usedConnections--;
437: currentPoolSize--;
438: debug.print("Removed value is "
439: + connectionList.removeElement(ret));
440: notifyAll();
441: }
442: } catch (Exception exp) {
443: // Error while returning
444: debug.print("Error " + exp);
445: }
446: return;
447: }
448:
449: Object tempRef = ret;
450: SmartConnection smt = (SmartConnection) ret;
451: ret = smt.returnConnection();
452: Connection con;
453: Enumeration cons = connectionHash.keys();
454: synchronized (connectionHash) {
455: while (cons.hasMoreElements()) {
456: con = (Connection) cons.nextElement();
457: if (con == ret) {
458: connectionHash.put(con, Boolean.TRUE);
459: break;
460: }
461: }
462: debug.print("Connection Released " + usedConnections);
463: usedConnections--;
464: debug.print("Connection contains list "
465: + connectionList.contains(tempRef));
466: debug.print("Removed value is "
467: + connectionList.removeElement(tempRef));
468:
469: connectionHash.notifyAll();
470: }
471:
472: }
473:
474: /**
475: * This method adds a connection leak listener. The methods of
476: * <code>cle</code> will be called when a leak is detected as per the
477: * pool configuration.
478: *
479: * @param cle Class implementing ConnectionLeakListener interface.
480: * @exception ConnectionPoolException if there is any problem
481: * adding ConnectionLeakListener.
482: */
483: public void addConnectionLeakListener(ConnectionLeakListener cle)
484: throws ConnectionPoolException {
485:
486: if (cle == null)
487: throw new IllegalArgumentException(
488: "ConnectionLeakListener cannot be null");
489: debug.print("Added is " + cle);
490: connectionListenerList.add(cle);
491:
492: }
493:
494: /**
495: * This method removes a connection leak listener. <code>cle</code> will
496: * not get any further notifications.
497: *
498: * @param cle Class implementing ConnectionLeakListener interface.
499: * @exception ConnectionPoolException If there is any problem
500: * removing ConnectionLeakListener.
501: */
502: public void removeConnectionLeakListener(ConnectionLeakListener cle)
503: throws ConnectionPoolException {
504:
505: if (cle == null)
506: throw new IllegalArgumentException(
507: "ConnectionLeakListener cannot be null");
508: debug.print("Trying to remove " + cle);
509: boolean found = connectionListenerList.remove(cle);
510: if (!found)
511: throw new ConnectionPoolException("No Such Listener");
512:
513: }
514:
515: /**
516: * This method releases excessive connections, i.e it actully closes
517: * them.
518: */
519: public void releaseConnections() {
520:
521: if (config.getMaxConnectionsForRelease() == -1)
522: return;
523: if (config.getMaxConnectionsForRelease() < getNoOfFreeConnections()) {
524: int i = config.getIncrement();
525: synchronized (connectionHash) {
526: Enumeration cons = connectionHash.keys();
527: while (cons.hasMoreElements() && i > 0) {
528: Connection con = (Connection) cons.nextElement();
529: Boolean b = (Boolean) connectionHash.get(con);
530: if (b == Boolean.TRUE) {
531: connectionHash.remove(con);
532: try {
533: con.close();
534: i--;
535: currentPoolSize = connectionHash.size();
536: debug.print("Releasing conn" + con);
537: } catch (SQLException e) {
538: debug.print("Error in closing connection"
539: + e);
540: }
541: }
542: }
543: }
544: }
545:
546: }
547:
548: public void shutDown() {
549: if (logger.isDebugEnabled()) {
550: logger.debug("Shutting down connections for pool:" + name);
551: }
552: shutDown = true;
553: Enumeration cons = connectionHash.keys();
554: while (cons.hasMoreElements()) {
555: Connection con = (Connection) cons.nextElement();
556: try {
557: con.close();
558: } catch (Exception e) {
559: logger.warn(
560: "Exception occurred during connections close",
561: e);
562: }
563: }
564:
565: }
566:
567: }
|