KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jahia > services > database > ConnectionPool


1 package org.jahia.services.database;
2
3 import java.io.PrintWriter JavaDoc;
4 import java.io.StringWriter JavaDoc;
5 import java.sql.Connection JavaDoc;
6 import java.sql.DriverManager JavaDoc;
7 import java.sql.SQLException JavaDoc;
8 import java.util.Vector JavaDoc;
9
10 /**
11  * <p>Title: ConnectionPool </p>
12  * <p>Description: manages a connection pool to a JDBC database. This
13  * implementation uses threads to create connection on demand, and
14  * synchronisation to wait for the created connections</p>
15  * <p>Basically the only interesting methods in here are the constructor,
16  * the getConnection and the free methods. The rest will only be needed
17  * in some status cases, etc..</p>
18  * <p>Copyright: Copyright (c) 2002</p>
19  * <p>Company: Jahia Inc.</p>
20  */

21 public class ConnectionPool implements Runnable JavaDoc
22 {
23     private static org.apache.log4j.Logger logger =
24             org.apache.log4j.Logger.getLogger(ConnectionPool.class);
25
26     private String JavaDoc db_driver;
27     private String JavaDoc db_url;
28     private String JavaDoc db_username;
29     private String JavaDoc db_password;
30     private int minConnections;
31     private int maxConnections;
32     private boolean waitIfBusy;
33     private long waitTimeOut = 60*1000; // default timeout is 1 minute
34
private int maxTries = 10; // maximum retry count is 10
35

36     // the vector of connections that are available in the pool
37
private Vector JavaDoc availableConnections = new Vector JavaDoc();
38
39     // the vector of connections that are unavailable in the pool,
40
// currently busy processing requests
41
private Vector JavaDoc busyConnections = new Vector JavaDoc();
42
43     // connectionPending is true if there is a thread currently
44
// opening a connection to the database
45
private boolean connectionPending = false;
46
47     // used to display debug messages if activated in constructor
48
private boolean verbose = true;
49
50     // used to control display of debug message generated by database driver
51
private boolean veryVerbose = false;
52
53     /**
54      * Constructor for a connection pool object.
55      * @param db_driver JDBC driver name to use
56      * @param db_url JDBC URL to use to connect to the database
57      * @param db_username JDBC username to connect to the database
58      * @param db_password JDBC password to connect to the database
59      * @param initialConnections number of initial connection to initialize
60      * the pool with
61      * @param maxConnections maximum number of database connections open
62      * simultaneously in pool
63      * @param waitIfBusy boolean that indicates if we should wait if all
64      * the connections in the pool are already occupied, otherwise will
65      * return with an exception
66      * @param verbose boolean that activates debug messages for the
67      * pool, helping debugging and diagnosticating database connection
68      * problems (such as connection timeout, etc...)
69      * @throws SQLException
70      */

71     public ConnectionPool( String JavaDoc db_driver,
72                             String JavaDoc db_url,
73                             String JavaDoc db_username,
74                             String JavaDoc db_password,
75                             int initialConnections,
76                             int maxConnections,
77                             boolean waitIfBusy,
78                             boolean verbose,
79                             boolean veryVerbose )
80     throws SQLException JavaDoc
81     {
82         if (verbose) {
83             logger.debug("New connection pool object in creation... ");
84         }
85         // sets private vars
86
this.db_driver = db_driver;
87         this.db_url = db_url;
88         this.db_username = db_username;
89         this.db_password = db_password;
90         this.minConnections = initialConnections;
91         this.maxConnections = maxConnections;
92         this.waitIfBusy = waitIfBusy;
93         this.verbose = verbose;
94         this.veryVerbose = veryVerbose;
95
96         if (verbose) {
97             logger.debug("verbose is " + verbose);
98         }
99         // nothing's foolproof...
100
if (initialConnections > maxConnections) {
101             initialConnections = maxConnections;
102         }
103
104         availableConnections = new Vector JavaDoc(initialConnections);
105
106         // create initial connections
107
for (int i=0; i < initialConnections; i++) {
108             availableConnections.addElement( makeNewConnection() );
109         }
110
111     } // end ConnectionPool
112

113
114
115     /**
116      * Retrieves a connection from the connection pool, creating it if
117      * necessary
118      * @param debugID an identifier useful for debugging retrieval and
119      * freeing connection pool entries
120      * @return a JDBC connection object that is an open connection to the
121      * database
122      *
123      * @todo we still have problems with this code if the number of requests
124      * for connections grows faster than the new connections being created.
125      * One way to work around this is to start the connection pool with a
126      * higher initial count of connections.
127      *
128      * @throws SQLException in the case we cannot retrieve a connection, for
129      * example once we have reached the maximum retry count.
130      */

131     protected synchronized Connection JavaDoc getConnection(int debugID)
132     throws SQLException JavaDoc
133     {
134         int tryCount = 0;
135         while (true) {
136             tryCount++;
137             if (tryCount > maxTries) {
138                 if (verbose) {
139                     logger.debug("Maximum try count (" + maxTries + ") reached while trying to retrieve a connection. Failing call.");
140                 }
141                 throw new SQLException JavaDoc("Maximum try count (" + maxTries + ") reached while trying to retrieve a connection. Failing call.");
142             }
143             if (verbose) {
144                 logger.debug("Trying to get a database connection (DEBUGID="+debugID+", available=" +
145                              availableConnections.size() + ", busy=" +
146                              busyConnections.size() + ", try=" + tryCount + ")"
147                              );
148             }
149
150             // quick sanity check
151
if (getTotalConnections() < minConnections) {
152                 if (verbose) {
153                     logger.debug("Warning : current number of connnections below minimum pool size !");
154                 }
155             }
156
157             // checks it there are available connections left
158
if (!availableConnections.isEmpty()) {
159                 // if yes, grab a connection
160
Connection JavaDoc existingConnection = (Connection JavaDoc) availableConnections.lastElement();
161                 int lastIndex = availableConnections.size() - 1;
162                 // remove the connection we just taked
163
availableConnections.removeElementAt( lastIndex );
164                 // check if the connection is still open
165
if (existingConnection.isClosed()) {
166                     if (verbose) {
167                         logger.debug("Connection retrieved from available connection is already closed, waiting for a new one ...");
168                     }
169                     // if closed, notify all waiting threads that a connection can be created
170
notifyAll();
171                     // then retry
172
if (verbose) {
173                         logger.debug("looping to wait for a connection that isn't already closed...");
174                     }
175                     continue;
176                 } else {
177                     // not closed, add the connection to the busyConnections array
178
// and return it
179
busyConnections.addElement( existingConnection );
180                     return existingConnection;
181                 }
182             } else {
183                 // if no more connections available
184
// check if we can add a connection (max not reached)
185
if ((getTotalConnections() < maxConnections) && (!connectionPending)) {
186                     // create one connection in the background
187
makeBackgroundConnection();
188                 } else if (!waitIfBusy) {
189                     // can't add new connections
190
// can't wait
191
// => scream !!
192
throw new SQLException JavaDoc( "Connection limit reached" );
193                 }
194                 // try to grab one
195
try {
196                     // wait that one frees up
197
if (verbose) {
198                         logger.debug("Waiting for a connection to be freed...");
199                     }
200                     wait(waitTimeOut);
201                     if (verbose) {
202                         logger.debug("Awoke from waiting for available connection...");
203                     }
204                 } catch (InterruptedException JavaDoc ie) {
205                     // then go and grab it !
206
if (verbose) {
207                         logger.debug("Interruption timeout while waiting for free connection.", ie);
208                     }
209                 }
210             }
211             if (verbose) {
212                 logger.debug("looping to wait for a connection...");
213             }
214         } // return to while
215
} // end getConnection
216

217
218
219     /**
220      * Creates a database connection using a thread to initialize it.
221      * @todo FIXME : what happens when OutOfMemoryError occurs ?
222      */

223     private void makeBackgroundConnection()
224     {
225         if (verbose) {
226             logger.debug("Creating a background Connection via a thread");
227         }
228         // we're trying to create a new connection here
229
connectionPending = true;
230         // launch the background thread
231
try {
232             Thread JavaDoc connectionThread = new Thread JavaDoc( this );
233             connectionThread.start(); // calls the run() method
234
} catch (OutOfMemoryError JavaDoc memError) {
235             logger.debug("Out of memory exception during background thread connnection creation", memError);
236             // scream !
237
}
238     } // makeBackgrounConnection
239

240
241
242     /**
243      * Method called when the thread is created to run the creation of a
244      * single database connection.
245      * @todo FIXME : what happens when SQLException or OutOfMemoryError ?
246      */

247     public void run()
248     {
249         if (verbose) {
250             logger.debug("Starting a new ConnectionPool Thread to create a database connnection");
251         }
252         try {
253             // try to create a new connection
254
Connection JavaDoc newConnection = makeNewConnection();
255             synchronized( this ) {
256                 // stores it in the available connection array
257
availableConnections.addElement( newConnection );
258                 // end of the operation -> connection is no more pending
259
connectionPending = false;
260                 // say to all waiting threads that a new connection is ready
261
notifyAll();
262             }
263         } catch (Throwable JavaDoc t) {
264             logger.debug("Exception while trying to open connection to the database", t);
265             // SQLException or OutOfMemoryError
266
// scream !
267
}
268     } // end run
269

270
271     /**
272      * Creates a single database connection and returns it
273      * @return a JDBC connection object that has been opened
274      * @throws SQLException an exception generated in case of problems
275      * opening the connection to the database
276      */

277     private Connection JavaDoc makeNewConnection()
278     throws SQLException JavaDoc
279     {
280         if (verbose) {
281             logger.debug("Creating a new connection");
282         }
283         try {
284             // load database driver
285
Class.forName( db_driver );
286             // establish connection
287
if (veryVerbose) {
288                 PrintWriter JavaDoc databaseLog = new PrintWriter JavaDoc(System.out);
289                 DriverManager.setLogWriter(databaseLog);
290             }
291             Connection JavaDoc newConnection = DriverManager.getConnection( db_url, db_username, db_password );
292             return newConnection;
293         } catch (ClassNotFoundException JavaDoc cnfe) {
294             // scream !!
295
StringWriter JavaDoc strWriter = new StringWriter JavaDoc();
296             PrintWriter JavaDoc ptrWriter = new PrintWriter JavaDoc(strWriter);
297             cnfe.printStackTrace(ptrWriter);
298             throw new SQLException JavaDoc( "Can't find class to driver : " + db_driver + " exception:" + strWriter.toString());
299         }
300     } // end run
301

302     /**
303      * Frees a connection and returns it into the pool of connections.
304      * @param theConnection the connnection to return into the available
305      * pool
306      * @todo FIXME does not verify the status of the connection, maybe we
307      * want to introduce that later for better stability ?
308      */

309     protected synchronized void free( Connection JavaDoc theConnection )
310     {
311         if (verbose) {
312             logger.debug("Trying to free a connection (available : "
313                          + availableConnections.size() +
314                          "; busy : " + busyConnections.size() + ")");
315         }
316         // remove connection form busyConnections array
317
busyConnections.removeElement( theConnection );
318         try {
319             if (!theConnection.isClosed()) {
320                 if (!availableConnections.contains(theConnection)) {
321                     // add it to the availableConnecitons array
322
availableConnections.addElement(theConnection);
323                 } else {
324                     logger.debug("Connection is already present in pool, ignoring second insert.");
325                 }
326             } else {
327                 if (verbose) {
328                     logger.debug("Freeing an already closed connection, we won't add it to the available connection list.");
329                 }
330             }
331             // notify all waiting threads that a new connection is free
332
notifyAll();
333         } catch (SQLException JavaDoc sqle) {
334             logger.debug("Error while testing if connection is closed", sqle);
335         }
336     } // end free
337

338
339
340     /**
341      * Returns the number of connections both in the busy state and in
342      * the available pool. Basically this returns the number of connections
343      * to the database that are handled by this connection pool at the
344      * current time
345      * @return an integer representing the sum of the available pool and
346      * the currently used connections.
347      */

348     public synchronized int getTotalConnections()
349     {
350         return ( availableConnections.size() + busyConnections.size() );
351     } // end totalConnections
352

353
354     /**
355      * Returns the number of available connections in the pool.
356      * @return an integer that represents the size of the available
357      * connections.
358      */

359     public int getNbFreeConnections()
360     {
361         return ( availableConnections.size() );
362     } // end totalFreeConnections
363

364
365     /**
366      * Returns the number of connections managed by this pool that are not
367      * available (ie are busy handling SQL queries)
368      * @return an integer representing the number of busy queries
369      */

370     public int getBusyConnections() {
371         return busyConnections.size();
372     }
373
374     /**
375      * Returns the configured minimum size of the connection pool
376      * @return an integer representing the minimum size of the pool as
377      * configured via the constructor
378      */

379     public int getMinConnections() {
380         return this.minConnections;
381     }
382
383     /**
384      * Returns the configured maximum size of the connection pool
385      * @return an integer representing the maximum size of the pool as configured
386      * via the constructor
387      */

388     public int getMaxConnections() {
389         return this.maxConnections;
390     }
391
392
393     /**
394      * This method is used mostly to destroy all the connections managed
395      * by this pool. This should ONLY be called in extreme cases such as
396      * a requested shutdown as it does NOT reinitialize the pool and doesn't
397      * re-open any connections. Please use with caution.
398      */

399     public synchronized void closeAllConnections()
400     {
401         if (verbose) {
402             logger.debug("Closing all connections !");
403         }
404         closeConnections( availableConnections );
405         availableConnections = new Vector JavaDoc();
406         closeConnections( busyConnections );
407         busyConnections = new Vector JavaDoc();
408     } // end closeAllConnections
409

410
411
412     /**
413      * Closes a vector of connections.
414      * @param connections a vector of connection objects
415      */

416     private void closeConnections( Vector JavaDoc connections )
417     {
418         if (verbose) {
419             logger.debug("Closing " + connections.size() + " connections");
420         }
421         try {
422             for (int i=0; i < connections.size(); i++) {
423                 Connection JavaDoc theConnection = (Connection JavaDoc) connections.elementAt( i );
424                 if (!theConnection.isClosed()) {
425                     theConnection.close();
426                 }
427             }
428         } catch (SQLException JavaDoc se) {
429             logger.debug("Error while closing connection", se);
430             // anyway... garbage collector's taking care of the dirty work
431
}
432     } // end closeConnections
433

434 }
435
Popular Tags