KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > database > ConnectionPool


1 /**
2  * $RCSfile: ConnectionPool.java,v $
3  * $Revision: 1.4 $
4  * $Date: 2005/07/22 21:33:01 $
5  *
6  * Copyright (C) 2004 Jive Software. All rights reserved.
7  *
8  * This software is published under the terms of the GNU Public License (GPL),
9  * a copy of which is included in this distribution.
10  */

11
12 package org.jivesoftware.database;
13
14 import org.jivesoftware.util.ClassUtils;
15 import org.jivesoftware.util.Log;
16 import org.jivesoftware.util.JiveGlobals;
17
18 import java.io.IOException JavaDoc;
19 import java.sql.*;
20 import java.util.*;
21
22 /**
23  * Database connection pool.
24  *
25  * @author Jive Software
26  */

27 public class ConnectionPool implements Runnable JavaDoc {
28
29     private String JavaDoc driver;
30     private String JavaDoc serverURL;
31     private String JavaDoc username;
32     private String JavaDoc password;
33     private int minCon;
34     private int maxCon;
35     private int conTimeout;
36     private boolean mysqlUseUnicode;
37
38     private Thread JavaDoc houseKeeper;
39     private boolean shutdownStarted = false;
40
41     private int conCount = 0;
42     private int waitingForCon = 0;
43     private Connection[] cons;
44     private ConnectionWrapper[] wrappers;
45     private Object JavaDoc waitLock = new Object JavaDoc();
46     private Object JavaDoc conCountLock = new Object JavaDoc();
47
48     public ConnectionPool(String JavaDoc driver, String JavaDoc serverURL, String JavaDoc username,
49                           String JavaDoc password, int minCon, int maxCon,
50                           double conTimeout, boolean mysqlUseUnicode) throws IOException JavaDoc {
51         this.driver = driver;
52         this.serverURL = serverURL;
53         this.username = username;
54         this.password = password;
55         this.minCon = minCon;
56         this.maxCon = maxCon;
57         // Setting the timeout to 3 hours
58
this.conTimeout = (int)(conTimeout * 1000 * 60 * 60 * 3); // convert to milliseconds
59
this.mysqlUseUnicode = mysqlUseUnicode;
60
61         if (driver == null) {
62             Log.error("JDBC driver value is null.");
63         }
64         try {
65             ClassUtils.forName(driver);
66             DriverManager.getDriver(serverURL);
67         }
68         catch (ClassNotFoundException JavaDoc e) {
69             Log.error("Could not load JDBC driver class: " + driver);
70         }
71         catch (SQLException e) {
72             Log.error("Error starting connection pool.", e);
73         }
74
75         // Setup pool, open minimum number of connections
76
wrappers = new ConnectionWrapper[maxCon];
77         cons = new Connection[maxCon];
78
79         boolean success = false;
80         int maxTry = 3;
81
82         for (int i = 0; i < maxTry; i++) {
83             try {
84                 for (int j = 0; j < minCon; j++) {
85                     createCon(j);
86                     conCount++;
87                 }
88
89                 success = true;
90                 break;
91             }
92             catch (SQLException e) {
93                 // close any open connections
94
for (int j = 0; j < minCon; j++) {
95                     if (cons[j] != null) {
96                         try {
97                             cons[j].close();
98                             cons[j] = null;
99                             wrappers[j] = null;
100                             conCount--;
101                         }
102                         catch (SQLException e1) { /* ignore */
103                         }
104                     }
105                 }
106
107                 // let admin know that there was a problem
108
Log.error("Failed to create new connections on startup. " +
109                         "Attempt " + i + " of " + maxTry, e);
110
111                 try {
112                     Thread.sleep(10000);
113                 }
114                 catch (InterruptedException JavaDoc e1) { /* ignore */
115                 }
116             }
117         }
118
119         if (!success) {
120             throw new IOException JavaDoc();
121         }
122
123         // Start the background housekeeping thread
124
houseKeeper = new Thread JavaDoc(this);
125         houseKeeper.setDaemon(true);
126         houseKeeper.start();
127     }
128
129     public Connection getConnection() throws SQLException {
130         // if we're shutting down, don't create any connections
131
if (shutdownStarted) {
132             return null;
133         }
134
135         // Check to see if there are any connections available. If not, then enter wait-based
136
// retry loop
137
ConnectionWrapper con = getCon();
138
139         if (con != null) {
140             synchronized (con) {
141                 con.checkedout = true;
142                 con.lockTime = System.currentTimeMillis();
143             }
144             return con;
145         }
146         else {
147             synchronized (waitLock) {
148                 try {
149                     waitingForCon++;
150                     while (true) {
151                         con = getCon();
152
153                         if (con != null) {
154                             --waitingForCon;
155                             synchronized (con) {
156                                 con.checkedout = true;
157                                 con.lockTime = System.currentTimeMillis();
158                             }
159                             return con;
160                         }
161                         else {
162                             waitLock.wait();
163                         }
164                     }
165                 }
166                 catch (InterruptedException JavaDoc ex) {
167                     --waitingForCon;
168                     waitLock.notify();
169
170                     throw new SQLException("Interrupted while waiting for connection to " +
171                             "become available.");
172                 }
173             }
174         }
175     }
176
177     public void freeConnection() {
178         synchronized (waitLock) {
179             if (waitingForCon > 0) {
180                 waitLock.notify();
181             }
182         }
183     }
184
185     public void destroy() throws SQLException {
186         // set shutdown flag
187
shutdownStarted = true;
188
189         // shut down the background housekeeping thread
190
houseKeeper.interrupt();
191
192         // wait 1/2 second for housekeeper to die
193
try {
194             houseKeeper.join(500);
195         }
196         catch (InterruptedException JavaDoc e) { /* ignore */
197         }
198
199         // check to see if there's any currently open connections to close
200
for (int i = 0; i < conCount; i++) {
201             ConnectionWrapper wrapper = wrappers[i];
202
203             // null means that the connection hasn't been initialized, which will only occur
204
// if the current index is greater than the current connection count
205
if (wrapper == null) {
206                 break;
207             }
208
209             // if it's currently checked out, wait 1/2 second then close it anyways
210
if (wrapper.checkedout) {
211                 try {
212                     Thread.sleep(500);
213                 }
214                 catch (InterruptedException JavaDoc e) {/* ignore */
215                 }
216
217                 if (wrapper.checkedout) {
218                     Log.info("Forcefully closing connection " + i);
219                 }
220             }
221
222             cons[i].close();
223             cons[i] = null;
224             wrappers[i] = null;
225         }
226     }
227
228     public int getSize() {
229         return conCount;
230     }
231
232     /**
233      * Housekeeping thread. This thread runs every 30 seconds and checks connections for the
234      * following conditions:<BR>
235      * <p/>
236      * <ul>
237      * <li>Connection has been open too long - it'll be closed and another connection created.
238      * <li>Connection hasn't been used for 30 seconds and the number of open connections is
239      * greater than the minimum number of connections. The connection will be closed. This
240      * is done so that the pool can shrink back to the minimum number of connections if the
241      * pool isn't being used extensively.
242      * <li>Unable to create a statement with the connection - it'll be reset.
243      * </ul>
244      */

245     public void run() {
246         while (true) {
247             // print warnings on connections
248
for (int i = 0; i < maxCon; i++) {
249                 if (cons[i] == null) {
250                     continue;
251                 }
252
253                 try {
254                     SQLWarning warning = cons[i].getWarnings();
255                     if (warning != null) {
256                         Log.warn("Connection " + i + " had warnings: " + warning);
257                         cons[i].clearWarnings();
258                     }
259                 }
260                 catch (SQLException e) {
261                     Log.warn("Unable to get warning for connection: ", e);
262                 }
263             }
264
265             int lastOpen = -1;
266
267             // go over every connection, check it's health
268
for (int i = maxCon - 1; i >= 0; i--) {
269                 if (wrappers[i] == null) {
270                     continue;
271                 }
272
273                 try {
274                     long time = System.currentTimeMillis();
275
276                     synchronized (wrappers[i]) {
277                         if (wrappers[i].checkedout) {
278                             if (lastOpen < i) {
279                                 lastOpen = i;
280                             }
281
282
283                             // if the jive property "database.defaultProvider.checkOpenConnections"
284
// is true check open connections to make sure they haven't been open
285
// for more than XX seconds (600 by default)
286
if ("true".equals(JiveGlobals.getXMLProperty("database.defaultProvider.checkOpenConnections"))
287                                     && !wrappers[i].hasLoggedException)
288                             {
289                                 int timeout = 600;
290                                 try { timeout = Integer.parseInt(JiveGlobals.getXMLProperty("database.defaultProvider.openConnectionTimeLimit")); }
291                                 catch (Exception JavaDoc e) { /* ignore */ }
292
293                                 if (time - wrappers[i].lockTime > timeout * 1000) {
294                                     wrappers[i].hasLoggedException = true;
295                                     Log.warn("Connection has been held open for too long: ",
296                                             wrappers[i].exception);
297                                 }
298                             }
299
300                             continue;
301                         }
302                         wrappers[i].checkedout = true;
303                     }
304
305                     // test health of connection
306
Statement stmt = null;
307                     try {
308                         stmt = cons[i].createStatement();
309                     }
310                     finally {
311                         if (stmt != null) {
312                             stmt.close();
313                         }
314                     }
315
316                     // Can never tell
317
if (cons[i].isClosed()) {
318                         throw new SQLException();
319                     }
320
321                     // check the age of the connection
322
if (time - wrappers[i].createTime > conTimeout) {
323                         throw new SQLException();
324                     }
325
326                     // check to see if it's the last connection and if it's been idle for
327
// more than 60 secounds
328
if ((time - wrappers[i].checkinTime > 60 * 1000) && i > minCon &&
329                             lastOpen <= i) {
330                         synchronized (conCountLock) {
331                             cons[i].close();
332                             wrappers[i] = null;
333                             cons[i] = null;
334                             conCount--;
335                         }
336                     }
337
338                     // Flag the last open connection
339
lastOpen = i;
340
341                     // Unlock the connection
342
if (wrappers[i] != null) {
343                         wrappers[i].checkedout = false;
344                     }
345
346                 }
347                 catch (SQLException e) {
348                     try {
349                         synchronized (conCountLock) {
350                             cons[i].close();
351                             wrappers[i] = createCon(i);
352
353                             // unlock the connection
354
wrappers[i].checkedout = false;
355                         }
356                     }
357                     catch (SQLException sqle) {
358                         Log.warn("Failed to reopen connection", sqle);
359
360                         synchronized (conCountLock) {
361                             wrappers[i] = null;
362                             cons[i] = null;
363                             conCount--;
364                         }
365                     }
366                 }
367             }
368
369             try {
370                 Thread.sleep(30 * 1000);
371             }
372             catch (InterruptedException JavaDoc e) {
373                 return;
374             }
375         }
376     }
377
378     private synchronized ConnectionWrapper getCon() throws SQLException {
379         // check to see if there's a connection already available
380
for (int i = 0; i < conCount; i++) {
381             ConnectionWrapper wrapper = wrappers[i];
382
383             // null means that the connection hasn't been initialized, which will only occur
384
// if the current index is greater than the current connection count
385
if (wrapper == null) {
386                 break;
387             }
388
389             synchronized (wrapper) {
390                 if (!wrapper.checkedout) {
391                     wrapper.setConnection(cons[i]);
392                     wrapper.checkedout = true;
393                     wrapper.lockTime = System.currentTimeMillis();
394                     if ("true".equals(JiveGlobals.getXMLProperty("database.defaultProvider.checkOpenConnections"))) {
395                         wrapper.exception = new Exception JavaDoc();
396                         wrapper.hasLoggedException = false;
397                     }
398
399                     return wrapper;
400                 }
401             }
402         }
403
404         // won't create more than maxConnections
405
synchronized (conCountLock) {
406             if (conCount >= maxCon) {
407                 return null;
408             }
409
410             ConnectionWrapper con = createCon(conCount);
411             conCount++;
412             return con;
413         }
414     }
415
416     /**
417      * Create a connection, wrap it and add it to the array of open wrappers
418      */

419     private ConnectionWrapper createCon(int index) throws SQLException {
420         try {
421             Connection con = null;
422             ClassUtils.forName(driver);
423
424             if (mysqlUseUnicode) {
425                 Properties props = new Properties();
426                 props.put("characterEncoding", "UTF-8");
427                 props.put("useUnicode", "true");
428                 if (username != null) {
429                     props.put("user", username);
430                 }
431                 if (password != null) {
432                     props.put("password", password);
433                 }
434                 con = DriverManager.getConnection(serverURL, props);
435             }
436             else {
437                 con = DriverManager.getConnection(serverURL, username, password);
438             }
439
440             if (con == null) {
441                 throw new SQLException("Unable to retrieve connection from DriverManager");
442             }
443
444
445             try {
446                 con.setAutoCommit(true);
447             }
448             catch (SQLException e) {/* ignored */
449             }
450
451
452             // A few people have been having problems because the default transaction
453
// isolation level on databases is too high. READ_COMMITTED is a good
454
// value for everyone to use because it provides the minimum amount of
455
// locking that Jive needs to work well.
456
try {
457                 // Supports transactions?
458
if (con.getMetaData().supportsTransactions()) {
459                     con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
460                 }
461             }
462             catch (SQLException e) {
463                 // Ignore errors. A few databases don't support setting the transaction
464
// isolation level, but ignoring the error shouldn't cause problems.
465
}
466
467             // create the wrapper object and mark it as checked out
468
ConnectionWrapper wrapper = new ConnectionWrapper(con, this);
469             if ("true".equals(JiveGlobals.getXMLProperty("database.defaultProvider.checkOpenConnections"))) {
470                 wrapper.exception = new Exception JavaDoc();
471             }
472
473             synchronized (conCountLock) {
474                 cons[index] = con;
475                 wrappers[index] = wrapper;
476             }
477
478             return wrapper;
479         }
480         catch (ClassNotFoundException JavaDoc e) {
481             Log.error(e);
482             throw new SQLException(e.getMessage());
483         }
484     }
485
486 }
487
Popular Tags