001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Contact: sequoia@continuent.org
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: * Initial developer(s): Emmanuel Cecchet.
021: * Contributor(s): Mathieu Peltier.
022: */package org.continuent.sequoia.controller.connection;
023:
024: import java.sql.Connection;
025: import java.sql.SQLException;
026: import java.util.Hashtable;
027: import java.util.LinkedList;
028:
029: import org.continuent.sequoia.common.exceptions.UnreachableBackendException;
030: import org.continuent.sequoia.common.i18n.Translate;
031: import org.continuent.sequoia.common.log.Trace;
032: import org.continuent.sequoia.common.xml.DatabasesXmlTags;
033: import org.continuent.sequoia.common.xml.XmlComponent;
034: import org.continuent.sequoia.controller.core.ControllerConstants;
035: import org.continuent.sequoia.controller.requests.AbstractRequest;
036:
037: /**
038: * A <code>ConnectionManager</code> object is responsible to talk directly
039: * with a database backend.
040: *
041: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
042: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
043: * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
044: * @author <a href="mailto:Stephane.Giron@continuent.com">Stephane Giron </a>
045: * @version 1.0
046: */
047: public abstract class AbstractConnectionManager implements
048: XmlComponent, Cloneable {
049: //
050: // How the code is organized ?
051: //
052: // 1. Member variables
053: // 2. Constructors/Destructors
054: // 3. Connection handling
055: // 4. Getter/Setter (possibly in alphabetical order)
056: //
057:
058: /** Logger instance. */
059: static Trace logger = Trace
060: .getLogger("org.continuent.sequoia.controller.connection");
061:
062: /** URL of the <code>DatabaseBackend</code> owning this connection manager. */
063: protected String backendUrl;
064:
065: /**
066: * Name of the <code>DatabaseBackend</code> owning this connection manager.
067: */
068: protected String backendName;
069:
070: /** Backend connection login to be used by this connection manager. */
071: protected String rLogin;
072:
073: /** Backend connection password to be used by this connection manager. */
074: protected String rPassword;
075:
076: /** The class name of the driver */
077: protected String driverClassName;
078:
079: /**
080: * The path to the driver if null the default directory is used
081: */
082: protected String driverPath;
083:
084: /** <code>true</code> if the connection pool has been initialized. */
085: protected boolean initialized;
086:
087: /**
088: * <code>true</code> if the connection manager has been shutdown and should
089: * no longer hand out Connections.
090: */
091: protected boolean isShutdown;
092:
093: /** Hastable of connections associated to a transaction (tid->Connection) */
094: private transient Hashtable connectionForTransaction;
095:
096: /**
097: * Hashtable<Long, PooledConnection> which associates a persistent
098: * connection id (encapsulated in a Long) with a PooledConnection.
099: */
100: protected transient Hashtable persistentConnections;
101:
102: /** Virtual Login used this connection manager */
103: private String vLogin;
104:
105: protected String connectionTestStatement;
106:
107: /**
108: * Stores the time on which persistent connections have been last used.
109: */
110: protected LinkedList persistentConnectionsIdleTime;
111:
112: /**
113: * Stack of idle persistent connections
114: */
115: protected LinkedList idlePersistentConnections;
116:
117: /**
118: * Allow to check idle persistent connections against the backend.
119: */
120: protected IdlePersistentConnectionsPingerThread persistentConnectionPingerThread;
121:
122: /**
123: * Time after which an idle persistent connection will be checked.
124: */
125: protected int idlePersistentConnectionPingInterval;
126:
127: /**
128: * Indicates if the idle persistent connections checker thread is running.
129: */
130: protected boolean idlePersistentConnectionPingRunning = false;
131:
132: /*
133: * Constructor(s)
134: */
135:
136: /**
137: * Creates a new <code>AbstractConnectionManager</code> instance: assigns
138: * login/password and instanciates transaction id/connection mapping.
139: *
140: * @param backendUrl URL of the <code>DatabaseBackend</code> owning this
141: * connection manager
142: * @param backendName name of the <code>DatabaseBackend</code> owning this
143: * connection manager
144: * @param rLogin backend connection login to be used by this connection
145: * manager
146: * @param rPassword backend connection password to be used by this connection
147: * manager
148: * @param driverPath path for driver
149: * @param driverClassName class name for driver
150: */
151: protected AbstractConnectionManager(String backendUrl,
152: String backendName, String rLogin, String rPassword,
153: String driverPath, String driverClassName) {
154: if (backendUrl == null)
155: throw new IllegalArgumentException(
156: "Illegal null database backend URL in AbstractConnectionManager constructor");
157:
158: if (backendName == null)
159: throw new IllegalArgumentException(
160: "Illegal null database backend name in AbstractConnectionManager constructor");
161:
162: if (rLogin == null)
163: throw new IllegalArgumentException(
164: "Illegal null database backend login in AbstractConnectionManager constructor");
165:
166: if (rPassword == null)
167: throw new IllegalArgumentException(
168: "Illegal null database backend password in AbstractConnectionManager constructor");
169:
170: if (driverPath != null) {
171: if (driverClassName == null) {
172: throw new IllegalArgumentException(
173: "Illegal null database backend driverClassName in AbstractConnectionManager constructor");
174: }
175: }
176: this .backendUrl = backendUrl;
177: this .backendName = backendName;
178: this .rLogin = rLogin;
179: this .rPassword = rPassword;
180: this .driverPath = driverPath;
181: this .driverClassName = driverClassName;
182: connectionForTransaction = new Hashtable();
183: persistentConnections = new Hashtable();
184:
185: // Get the IDLE_PERSISTENT_CONNECTION_PING_INTERVAL and convert it in ms
186: idlePersistentConnectionPingInterval = ControllerConstants.IDLE_PERSISTENT_CONNECTION_PING_INTERVAL * 1000;
187: if (idlePersistentConnectionPingInterval > 0) {
188: this .idlePersistentConnections = new LinkedList();
189: this .persistentConnectionsIdleTime = new LinkedList();
190: }
191:
192: }
193:
194: /**
195: * Ensures that the connections are closed when the object is garbage
196: * collected.
197: *
198: * @exception Throwable if an error occurs.
199: */
200: protected void finalize() throws Throwable {
201: if (isInitialized())
202: finalizeConnections();
203:
204: super .finalize();
205: }
206:
207: /**
208: * @see java.lang.Object#clone()
209: */
210: protected abstract Object clone() throws CloneNotSupportedException;
211:
212: /**
213: * Creates a new connection manager with the same parameters but a different
214: * backend user and password.
215: *
216: * @param rLogin real login
217: * @param rPassword real password
218: * @return an AbstractConnectionManager for the new user
219: */
220: public abstract AbstractConnectionManager clone(String rLogin,
221: String rPassword);
222:
223: /**
224: * Copy this connection manager and replace the name of the backend and its
225: * url Every other parameter is the same
226: *
227: * @param url the url to the backend associated to this ConnectionManager
228: * @param name the name of the backend
229: * @return <code>AbstractConnectionManager</code>
230: * @throws Exception if clone fails
231: */
232: public AbstractConnectionManager copy(String url, String name)
233: throws Exception {
234: AbstractConnectionManager connectionManager = (AbstractConnectionManager) this
235: .clone();
236: connectionManager.backendName = name;
237: connectionManager.backendUrl = url;
238: return connectionManager;
239: }
240:
241: /*
242: * Connection handling
243: */
244:
245: /**
246: * Delete a connection that is no more valid.
247: *
248: * @param connection the connection to delete.
249: */
250: public abstract void deleteConnection(PooledConnection connection);
251:
252: /**
253: * Close an unwanted connection. The connection may be in a bad state so we
254: * ignore all errors.
255: *
256: * @param connection to be closed
257: */
258: protected void closeConnection(PooledConnection connection) {
259: try {
260: connection.close();
261: } catch (SQLException e) {
262: logger.error(e.getMessage());
263: }
264: }
265:
266: /**
267: * Delete a bad connection that was used for a transaction. The corresponding
268: * connection is deleted by calling
269: * {@link #deleteConnection(PooledConnection)}.
270: *
271: * @param transactionId the transaction id.
272: * @see #releaseConnection(PooledConnection)
273: */
274: public void deleteConnection(long transactionId) {
275: // Underlying Hashtable is synchronized
276: PooledConnection c = (PooledConnection) connectionForTransaction
277: .remove(new Long(transactionId));
278:
279: if (c == null)
280: logger.error(Translate.get(
281: "connection.transaction.unknown", transactionId));
282: else
283: deleteConnection(c);
284: }
285:
286: /**
287: * Deletes a connection associated to the persistentConnectionId.<br />
288: * Do nothing if there is no connection associated to the
289: * persistentConnectionId
290: *
291: * @param persistentConnectionId the id of the persistent connection to delete
292: */
293: public void deletePersistentConnection(long persistentConnectionId) {
294: Long connectionId = new Long(persistentConnectionId);
295: if (idlePersistentConnectionPingRunning) {
296: PooledConnection c = (PooledConnection) persistentConnections
297: .get(connectionId);
298: synchronized (this ) {
299: if (idlePersistentConnections.contains(c)) {
300: persistentConnectionsIdleTime
301: .remove(idlePersistentConnections
302: .indexOf(c));
303: idlePersistentConnections.remove(c);
304: }
305: }
306: }
307: persistentConnections.remove(connectionId);
308: }
309:
310: /**
311: * Releases all the connections to the database.
312: *
313: * @exception SQLException if an error occurs.
314: */
315: public final void finalizeConnections() throws SQLException {
316: connectionForTransaction.clear();
317: doConnectionFinalization();
318: if (idlePersistentConnectionPingRunning) {
319: stopPersistentConnectionPingerThread();
320: synchronized (this ) {
321: idlePersistentConnections.clear();
322: persistentConnectionsIdleTime.clear();
323: }
324: }
325: }
326:
327: /**
328: * Must be implemented by class extending AbstractConnectionManager to
329: * finalize their connections.
330: *
331: * @throws SQLException if an error occurs
332: */
333: protected abstract void doConnectionFinalization()
334: throws SQLException;
335:
336: /**
337: * Force all connections to be renewed when they are used next. This just sets
338: * a flag and connections will be lazily replaced as needed.
339: */
340: public abstract void flagAllConnectionsForRenewal();
341:
342: /**
343: * Get a connection from DriverManager.
344: *
345: * @return a new connection or null if Driver.getConnection() failed.
346: * @see DriverManager#getConnection(String, String, String, String, String)
347: */
348: public Connection getConnectionFromDriver() {
349: try {
350: return DriverManager.getConnection(backendUrl, rLogin,
351: rPassword, driverPath, driverClassName);
352: } catch (SQLException ignore) {
353: if (logger.isWarnEnabled()) {
354: logger.warn("Failed to get connection for driver ",
355: ignore);
356: }
357: return null;
358: }
359: }
360:
361: /**
362: * Gets a connection from the pool (implementation specific).
363: *
364: * @return a <code>Connection</code> or <code>null</code> if no connection
365: * is available or if the connection has not been initialized.
366: * @throws UnreachableBackendException if the backend must be disabled
367: */
368: protected abstract PooledConnection getConnection()
369: throws UnreachableBackendException;
370:
371: /**
372: * Releases a connection.
373: *
374: * @param connection the connection to release.
375: */
376: protected abstract void releaseConnection(
377: PooledConnection connection);
378:
379: /**
380: * Releases a persistent connection. This will close this connection
381: * immediatly.
382: *
383: * @param connection the connection to release.
384: */
385: protected abstract void releasePersistentConnection(
386: PooledConnection connection);
387:
388: /**
389: * Gets a connection from the pool in autocommit mode. If connections needed
390: * to be renewed, they are renewed here.
391: *
392: * @return a <code>PooledConnection</code> or <code>null</code> if no
393: * connection is available or if the connection has not been
394: * initialized.
395: * @throws UnreachableBackendException if the backend must be disabled
396: */
397: private PooledConnection getRenewedConnectionInAutoCommit()
398: throws UnreachableBackendException {
399: PooledConnection c;
400: while (true) { // Loop until all connections that must be renewed have been renewed
401: c = getConnection();
402: if (c != null) {
403: if (c.mustBeRenewed())
404: deleteConnection(c);
405: else
406: break;
407: } else
408: break;
409: }
410:
411: return c;
412: }
413:
414: /**
415: * Gets a new connection for a transaction. This function calls
416: * {@link #getConnection()}to get the connection and store the mapping
417: * between the connection and the transaction id.
418: * <p>
419: * Note that this function does not start the transaction, it is up to the
420: * caller to call setAutoCommit(false) on the returned connection.
421: *
422: * @param transactionId the transaction id.
423: * @return a <code>Connection</code> or <code>null</code> if no connection
424: * is available .
425: * @throws UnreachableBackendException if the backend must be disabled
426: * @see #getConnection()
427: */
428: public PooledConnection getConnectionForTransaction(
429: long transactionId) throws UnreachableBackendException {
430: PooledConnection c = getRenewedConnectionInAutoCommit();
431: registerConnectionForTransaction(c, transactionId);
432: return c;
433: }
434:
435: /**
436: * Initializes the connection(s) to the database. The caller must ensure that
437: * the driver has already been loaded else an exception will be thrown.
438: *
439: * @exception SQLException if an error occurs.
440: */
441: public final void initializeConnections() throws SQLException {
442: isShutdown = false;
443: connectionForTransaction.clear();
444: doConnectionInitialization();
445: }
446:
447: /**
448: * Mark the ConnectionManager as shutdown. getConnection() should return null
449: * from now on and any threads waiting in getConnection() should wake up. This
450: * is used when a database is shutting down due to an error. It is not needed
451: * in the case of a clean shutdown, because everybody will stop requesting
452: * connections at the correct time.
453: */
454: public synchronized void shutdown() {
455: isShutdown = true;
456: notifyAll();
457: }
458:
459: /**
460: * Must be implemented by class extending AbstractConnectionManager to
461: * initalize their connections.
462: *
463: * @throws SQLException if an error occurs
464: */
465: protected abstract void doConnectionInitialization()
466: throws SQLException;
467:
468: /**
469: * Enlist a connection for a transaction (maintains the transaction id <->
470: * connection mapping).
471: *
472: * @param c a pooled connection
473: * @param transactionId the transaction id to register this connection for
474: */
475: public void registerConnectionForTransaction(PooledConnection c,
476: long transactionId) {
477: if (c != null) {
478: // Underlying Hashtable is synchronized
479: Long lTid = new Long(transactionId);
480: if (connectionForTransaction.put(lTid, c) != null) {
481: logger
482: .error("A new connection for transaction "
483: + lTid
484: + " has been opened but there was a remaining connection for this transaction that has not been closed.");
485: }
486: }
487: }
488:
489: /**
490: * Retrieves a connection used for a transaction. This connection must have
491: * been allocated by calling {@link #getConnectionForTransaction(long)}.
492: *
493: * @param transactionId the transaction id.
494: * @return a <code>Connection</code> or <code>null</code> if no connection
495: * has been found for this transaction id.
496: * @see #getConnectionForTransaction(long)
497: */
498: public PooledConnection retrieveConnectionForTransaction(
499: long transactionId) {
500: Long id = new Long(transactionId);
501: // Underlying Hashtable is synchronized
502: return (PooledConnection) connectionForTransaction.get(id);
503: }
504:
505: /**
506: * Releases a connection used for a transaction. The corresponding connection
507: * is released by calling {@link #releaseConnection(PooledConnection)}.
508: *
509: * @param transactionId the transaction id.
510: * @see #releaseConnection(PooledConnection)
511: */
512: public void releaseConnectionForTransaction(long transactionId) {
513: // Underlying Hashtable is synchronized
514: PooledConnection c = (PooledConnection) connectionForTransaction
515: .remove(new Long(transactionId));
516:
517: if (c == null)
518: logger.error(Translate.get(
519: "connection.transaction.unknown", transactionId));
520: else {
521: if (!c.isDefaultTransactionIsolation()) { // Reset transaction isolation (SEQUOIA-532)
522: try {
523: c.restoreDefaultTransactionIsolation();
524: } catch (Throwable e) {
525: logger.error("Error while resetting transaction ",
526: e);
527: }
528: }
529:
530: // Only release the connection if it is not persistent
531: if (!persistentConnections.containsValue(c))
532: releaseConnection(c);
533: }
534: }
535:
536: /**
537: * Gets a connection from the pool in autocommit mode. If a persistent id is
538: * defined for the request, the appropriate connection is retrieved.
539: *
540: * @param request the request asking for a connection (we will check its
541: * persistent connection id)
542: * @return a <code>PooledConnection</code> or <code>null</code> if no
543: * connection is available or if the connection has not been
544: * initialized.
545: * @throws UnreachableBackendException if the backend must be disabled
546: */
547: public PooledConnection retrieveConnectionInAutoCommit(
548: AbstractRequest request) throws UnreachableBackendException {
549: if ((request != null) && request.isPersistentConnection()) {
550: Long id = new Long(request.getPersistentConnectionId());
551: // Underlying Hashtable is synchronized
552: PooledConnection c = (PooledConnection) persistentConnections
553: .get(id);
554: if (c == null) {
555: c = getRenewedConnectionInAutoCommit();
556: if (c != null)
557: persistentConnections.put(id, c);
558: }
559: if (idlePersistentConnectionPingRunning)
560: synchronized (this ) {
561: if (idlePersistentConnections.contains(c)) {
562: persistentConnectionsIdleTime
563: .remove(idlePersistentConnections
564: .indexOf(c));
565: idlePersistentConnections.remove(c);
566: }
567: }
568:
569: return c;
570: } else
571: return getRenewedConnectionInAutoCommit();
572: }
573:
574: /**
575: * Releases a connection if it is not persistent. The corresponding connection
576: * is released by calling {@link #releaseConnection(PooledConnection)}.
577: *
578: * @param request the request asking for a connection (we will check its
579: * persistent connection id)
580: * @param c the connection to release.
581: * @see #releaseConnection(PooledConnection)
582: */
583: public void releaseConnectionInAutoCommit(AbstractRequest request,
584: PooledConnection c) {
585: if ((request != null) && request.isPersistentConnection()) {
586: if (idlePersistentConnectionPingRunning) {
587: boolean notifyPersistentConnectionPingerThread = false;
588: synchronized (this ) {
589: persistentConnectionsIdleTime.addLast(new Long(
590: System.currentTimeMillis()));
591: idlePersistentConnections.addLast(c);
592: notifyPersistentConnectionPingerThread = persistentConnectionsIdleTime
593: .size() > 0;
594: }
595:
596: if (notifyPersistentConnectionPingerThread)
597: notifyPersistentConnectionPingerThread();
598: }
599: return;
600: }
601:
602: try {
603: if (!c.getConnection().getAutoCommit()) {
604: c.getConnection().setAutoCommit(true);
605: if (logger.isDebugEnabled()) {
606: Exception e = new Exception(
607: "Connection returned with autocommit off");
608: logger.debug(e);
609: }
610: }
611: } catch (SQLException e) {
612: // ignore this for now somebody will deal with it later
613: }
614: releaseConnection(c);
615: }
616:
617: protected void notifyPersistentConnectionPingerThread() {
618: synchronized (persistentConnectionPingerThread) {
619: persistentConnectionPingerThread.notify();
620: }
621: }
622:
623: /**
624: * Releases a connection used for a persistent connection. The corresponding
625: * connection is released by calling
626: * {@link #releaseConnection(PooledConnection)}.
627: *
628: * @param persistentConnectionId the persistent connection id.
629: * @see #releaseConnection(PooledConnection)
630: */
631: public void releasePersistentConnectionInAutoCommit(
632: long persistentConnectionId) {
633: // Underlying Hashtable is synchronized
634: PooledConnection c = (PooledConnection) persistentConnections
635: .remove(new Long(persistentConnectionId));
636:
637: if (c == null)
638: logger.error(Translate.get(
639: "connection.persistent.id.unknown",
640: persistentConnectionId));
641: else {
642: if (idlePersistentConnectionPingRunning)
643: synchronized (this ) {
644: if (idlePersistentConnections.contains(c)) {
645: persistentConnectionsIdleTime
646: .remove(idlePersistentConnections
647: .indexOf(c));
648: idlePersistentConnections.remove(c);
649: }
650: }
651: releasePersistentConnection(c);
652: }
653: }
654:
655: /**
656: * Tests if the connections have been initialized.
657: *
658: * @return <code>true</code> if the connections have been initialized.
659: */
660: public boolean isInitialized() {
661: return initialized;
662: }
663:
664: /*
665: * Getter/setter methods
666: */
667:
668: /**
669: * Get the current number of connections open for this connection manager.
670: *
671: * @return the current number of open connections
672: */
673: public abstract int getCurrentNumberOfConnections();
674:
675: /**
676: * Returns the driverClassName value.
677: *
678: * @return Returns the driverClassName.
679: */
680: public String getDriverClassName() {
681: return driverClassName;
682: }
683:
684: /**
685: * Returns the driverPath value.
686: *
687: * @return Returns the driverPath.
688: */
689: public String getDriverPath() {
690: return driverPath;
691: }
692:
693: /**
694: * Returns the login used by this connection manager.
695: *
696: * @return a <code>String</code> value.
697: */
698: public String getLogin() {
699: return rLogin;
700: }
701:
702: /**
703: * Returns the password used by this connection manager.
704: *
705: * @return a <code>String</code> value.
706: */
707: public String getPassword() {
708: return rPassword;
709: }
710:
711: /*
712: * Debug/monitoring information
713: */
714:
715: /**
716: * @return Returns the vLogin.
717: */
718: public String getVLogin() {
719: return vLogin;
720: }
721:
722: /**
723: * @param login The vLogin to set.
724: */
725: public void setVLogin(String login) {
726: vLogin = login;
727: }
728:
729: /**
730: * Set the SQL string used to test whether or not the server is available.
731: *
732: * @param connectionTestStatement connection test statement to use for this
733: * pool
734: */
735: public void setConnectionTestStatement(
736: String connectionTestStatement) {
737: this .connectionTestStatement = connectionTestStatement;
738: }
739:
740: /**
741: * Gets xml formatted information on this connection manager
742: *
743: * @return xml formatted string that conforms to sequoia.dtd
744: */
745: protected abstract String getXmlImpl();
746:
747: /**
748: * @see org.continuent.sequoia.common.xml.XmlComponent#getXml()
749: */
750: public String getXml() {
751: StringBuffer info = new StringBuffer();
752: info.append("<" + DatabasesXmlTags.ELT_ConnectionManager + " "
753: + DatabasesXmlTags.ATT_vLogin + "=\"" + vLogin + "\" "
754: + "" + DatabasesXmlTags.ATT_rLogin + "=\"" + rLogin
755: + "\" " + "" + DatabasesXmlTags.ATT_rPassword + "=\""
756: + rPassword + "\" " + ">");
757: info.append(this .getXmlImpl());
758: info
759: .append("</" + DatabasesXmlTags.ELT_ConnectionManager
760: + ">");
761: return info.toString();
762: }
763:
764: protected void stopPersistentConnectionPingerThread() {
765: synchronized (persistentConnectionPingerThread) {
766: persistentConnectionPingerThread.isKilled = true;
767: idlePersistentConnectionPingRunning = false;
768: persistentConnectionPingerThread.notify();
769: }
770: try {
771: persistentConnectionPingerThread.join();
772: } catch (InterruptedException e) {
773: }
774: }
775:
776: /*
777: * this will test that persistent connections that are idled are still
778: * available
779: */
780: class IdlePersistentConnectionsPingerThread extends Thread {
781: private boolean isKilled = false;
782: private AbstractConnectionManager this Pool;
783:
784: protected IdlePersistentConnectionsPingerThread(
785: String pBackendName, AbstractConnectionManager this Pool) {
786: super ("IdlePersistentConnectionsPingerThread for backend:"
787: + pBackendName);
788: this .this Pool = this Pool;
789: }
790:
791: /**
792: * @see java.lang.Runnable#run()
793: */
794: public void run() {
795: long idleTime, releaseTime;
796: synchronized (this ) {
797: try {
798: while (!isKilled) {
799: if (idlePersistentConnections.isEmpty()) {
800: this .wait();
801: }
802:
803: if (isKilled)
804: continue; // Just exit
805:
806: PooledConnection c = null;
807:
808: synchronized (this Pool) {
809: if (persistentConnectionsIdleTime.isEmpty()) {
810: continue; // Sanity check
811: }
812:
813: releaseTime = ((Long) persistentConnectionsIdleTime
814: .getFirst()).longValue();
815: idleTime = System.currentTimeMillis()
816: - releaseTime;
817:
818: if (idleTime >= idlePersistentConnectionPingInterval) {
819:
820: c = (PooledConnection) idlePersistentConnections
821: .removeFirst();
822: persistentConnectionsIdleTime
823: .removeFirst();
824: }
825: }
826:
827: if (c == null) { // Nothing to free, wait for next deadline
828: wait(idlePersistentConnectionPingInterval
829: - idleTime);
830: } else {
831: try {
832: // check the connection is still valid
833: c
834: .getConnection()
835: .createStatement()
836: .execute(
837: connectionTestStatement);
838: // and put it again in the pool
839: synchronized (this Pool) {
840: persistentConnectionsIdleTime
841: .addLast(new Long(
842: System
843: .currentTimeMillis()));
844: idlePersistentConnections
845: .addLast(c);
846: }
847: } catch (SQLException e) {
848: // Connection lost... we release it... and open a new connection
849: try {
850: c.close();
851: } catch (SQLException e1) {
852: String msg = "An error occured while closing idle connection after the timeout: "
853: + e;
854: logger.error(msg);
855: }
856: }
857: }
858: }
859: } catch (InterruptedException e) {
860: logger
861: .error("Wait on IdlePersistentConnectionsPingerThread interrupted in ConnectionManager: "
862: + e);
863: }
864: }
865: }
866: }
867: }
|