KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > conn > PoolManager


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56
57 package org.objectstyle.cayenne.conn;
58
59 import java.io.PrintWriter JavaDoc;
60 import java.sql.Connection JavaDoc;
61 import java.sql.SQLException JavaDoc;
62 import java.util.LinkedList JavaDoc;
63 import java.util.List JavaDoc;
64 import java.util.ListIterator JavaDoc;
65
66 import javax.sql.ConnectionEvent JavaDoc;
67 import javax.sql.ConnectionEventListener JavaDoc;
68 import javax.sql.ConnectionPoolDataSource JavaDoc;
69 import javax.sql.DataSource JavaDoc;
70 import javax.sql.PooledConnection JavaDoc;
71
72 import org.apache.log4j.Logger;
73
74 /**
75  * PoolManager is a pooling DataSource impementation.
76  * Internally to obtain connections PoolManager uses either a JDBC driver
77  * or another pooling datasource.
78  *
79  * <p>TODO: create a low priority thread that will do pool maintenance.</p>
80  *
81  * @author Andrei Adamchik
82  */

83 public class PoolManager implements DataSource JavaDoc, ConnectionEventListener JavaDoc {
84     private static Logger logObj = Logger.getLogger(PoolManager.class);
85     
86     /**
87      * Defines a maximum time in milliseconds that a connection
88      * request could wait in the connection queue. After this period
89      * expires, an exception will be thrown in the calling method.
90      * In the future this parameter should be made configurable.
91      */

92     public static final int MAX_QUEUE_WAIT = 20000;
93
94     protected ConnectionPoolDataSource JavaDoc poolDataSource;
95     protected int minConnections;
96     protected int maxConnections;
97     protected String JavaDoc dataSourceUrl;
98     protected String JavaDoc jdbcDriver;
99     protected String JavaDoc password;
100     protected String JavaDoc userName;
101
102     protected List JavaDoc unusedPool;
103     protected List JavaDoc usedPool;
104
105     private PoolMaintenanceThread poolMaintenanceThread;
106
107
108     /**
109      * Creates new PoolManager using org.objectstyle.cayenne.conn.PoolDataSource
110      * for an underlying ConnectionPoolDataSource.
111      */

112     public PoolManager(
113         String JavaDoc jdbcDriver,
114         String JavaDoc dataSourceUrl,
115         int minCons,
116         int maxCons,
117         String JavaDoc userName,
118         String JavaDoc password)
119         throws SQLException JavaDoc {
120
121         this(jdbcDriver, dataSourceUrl, minCons, maxCons, userName, password, null);
122     }
123
124     public PoolManager(
125         String JavaDoc jdbcDriver,
126         String JavaDoc dataSourceUrl,
127         int minCons,
128         int maxCons,
129         String JavaDoc userName,
130         String JavaDoc password,
131         ConnectionEventLoggingDelegate logger)
132         throws SQLException JavaDoc {
133
134         if (logger != null) {
135             DataSourceInfo info = new DataSourceInfo();
136             info.setJdbcDriver(jdbcDriver);
137             info.setDataSourceUrl(dataSourceUrl);
138             info.setMinConnections(minCons);
139             info.setMaxConnections(maxCons);
140             info.setUserName(userName);
141             info.setPassword(password);
142             logger.logPoolCreated(info);
143         }
144
145         this.jdbcDriver = jdbcDriver;
146         this.dataSourceUrl = dataSourceUrl;
147         DriverDataSource driverDS = new DriverDataSource(jdbcDriver, dataSourceUrl);
148         driverDS.setLogger(logger);
149         PoolDataSource poolDS = new PoolDataSource(driverDS);
150         init(poolDS, minCons, maxCons, userName, password);
151     }
152
153     /** Creates new PoolManager with the specified policy for
154      * connection pooling and a ConnectionPoolDataSource object.
155      *
156      * @param poolDataSource data source for pooled connections
157      * @param minCons Non-negative integer that specifies a minimum number of open connections
158      * to keep in the pool at all times
159      * @param maxCons Non-negative integer that specifies maximum number of simultaneuosly open connections
160      *
161      * @throws SQLException if pool manager can not be created.
162      */

163     public PoolManager(
164         ConnectionPoolDataSource JavaDoc poolDataSource,
165         int minCons,
166         int maxCons,
167         String JavaDoc userName,
168         String JavaDoc password)
169         throws SQLException JavaDoc {
170         init(poolDataSource, minCons, maxCons, userName, password);
171     }
172
173     /** Initializes pool. Normally called from constructor. */
174     protected void init(
175         ConnectionPoolDataSource JavaDoc poolDataSource,
176         int minCons,
177         int maxCons,
178         String JavaDoc userName,
179         String JavaDoc password)
180         throws SQLException JavaDoc {
181
182         // do sanity checks...
183
if (maxConnections < 0) {
184             throw new SQLException JavaDoc(
185                 "Maximum number of connections can not be negative (" + maxCons + ").");
186         }
187
188         if (minConnections < 0) {
189             throw new SQLException JavaDoc(
190                 "Minimum number of connections can not be negative (" + minCons + ").");
191         }
192
193         if (minConnections > maxConnections) {
194             throw new SQLException JavaDoc("Minimum number of connections can not be bigger then maximum.");
195         }
196
197         // init properties
198
this.userName = userName;
199         this.password = password;
200         this.minConnections = minCons;
201         this.maxConnections = maxCons;
202         this.poolDataSource = poolDataSource;
203
204         // init pool... use linked lists to use the queue in the FIFO manner
205
usedPool = new LinkedList JavaDoc();
206         unusedPool = new LinkedList JavaDoc();
207         growPool(minConnections, userName, password);
208
209         startMaintenanceThread();
210     }
211     
212     protected synchronized void startMaintenanceThread() {
213         disposeOfMaintenanceThread();
214         this.poolMaintenanceThread = new PoolMaintenanceThread(this);
215         this.poolMaintenanceThread.start();
216     }
217
218     /**
219      * Creates and returns new PooledConnection object, adding itself as a listener
220      * for connection events.
221      */

222     protected PooledConnection JavaDoc newPooledConnection(String JavaDoc userName, String JavaDoc password)
223         throws SQLException JavaDoc {
224         PooledConnection JavaDoc connection =
225             (userName != null)
226                 ? poolDataSource.getPooledConnection(userName, password)
227                 : poolDataSource.getPooledConnection();
228         connection.addConnectionEventListener(this);
229         return connection;
230     }
231
232     /** Closes all existing connections, removes them from the pool. */
233     public void dispose() throws SQLException JavaDoc {
234         synchronized (this) {
235             // clean connections from the pool
236
ListIterator JavaDoc unusedIterator = unusedPool.listIterator();
237             while (unusedIterator.hasNext()) {
238                 PooledConnection JavaDoc con = (PooledConnection JavaDoc) unusedIterator.next();
239                 // close connection
240
con.close();
241                 // remove connection from the list
242
unusedIterator.remove();
243             }
244
245             // clean used connections
246
ListIterator JavaDoc usedIterator = usedPool.listIterator();
247             while (usedIterator.hasNext()) {
248                 PooledConnection JavaDoc con = (PooledConnection JavaDoc) usedIterator.next();
249                 // stop listening for connection events
250
con.removeConnectionEventListener(this);
251                 // close connection
252
con.close();
253                 // remove connection from the list
254
usedIterator.remove();
255             }
256         }
257
258         disposeOfMaintenanceThread();
259     }
260     
261     protected void disposeOfMaintenanceThread() {
262         if (poolMaintenanceThread != null) {
263             this.poolMaintenanceThread.dispose();
264         }
265     }
266
267     /**
268      * @return true if at least one more connection can be added to the pool.
269      */

270     protected synchronized boolean canGrowPool() {
271         return getPoolSize() < maxConnections;
272     }
273
274     /**
275      * Increases connection pool by the specified number of connections.
276      *
277      * @return the actual number of created connections.
278      * @throws SQLException if an error happens when creating a new connection.
279      */

280     protected synchronized int growPool(
281         int addConnections,
282         String JavaDoc userName,
283         String JavaDoc password)
284         throws SQLException JavaDoc {
285
286         int i = 0;
287         int startPoolSize = getPoolSize();
288         for (; i < addConnections && startPoolSize + i < maxConnections; i++) {
289             PooledConnection JavaDoc newConnection = newPooledConnection(userName, password);
290             unusedPool.add(newConnection);
291         }
292
293         return i;
294     }
295
296     protected synchronized void shrinkPool(int closeConnections) {
297         int idleSize = unusedPool.size();
298         for (int i = 0; i < closeConnections && i < idleSize; i++) {
299             PooledConnection JavaDoc con = (PooledConnection JavaDoc) unusedPool.remove(i);
300
301             try {
302                 con.close();
303             } catch (SQLException JavaDoc ex) {
304                 logObj.info("Error closing connection. Ignoring.", ex);
305             }
306         }
307     }
308
309     /**
310      * Returns maximum number of connections this pool can keep.
311      * This parameter when configured allows to limit the number of simultaneously
312      * open connections.
313      */

314     public int getMaxConnections() {
315         return maxConnections;
316     }
317
318     public void setMaxConnections(int maxConnections) {
319         this.maxConnections = maxConnections;
320     }
321
322     /** Returns the absolute minimum number of connections allowed
323       * in this pool at any moment in time. */

324     public int getMinConnections() {
325         return minConnections;
326     }
327
328     public void setMinConnections(int minConnections) {
329         this.minConnections = minConnections;
330     }
331
332     /** Returns a database URL used to initialize this pool.
333       * Will return null if the pool was initialized with ConnectionPoolDataSource. */

334     public String JavaDoc getDataSourceUrl() {
335         return dataSourceUrl;
336     }
337
338     /** Returns a name of a JDBC driver used to initialize this pool.
339       * Will return null if the pool was initialized with ConnectionPoolDataSource. */

340     public String JavaDoc getJdbcDriver() {
341         return jdbcDriver;
342     }
343
344     /** Returns a data source password used to initialize this pool. */
345     public String JavaDoc getPassword() {
346         return password;
347     }
348
349     /** Returns a data source user name used to initialize this pool. */
350     public String JavaDoc getUserName() {
351         return userName;
352     }
353
354     /**
355      * Returns current number of connections.
356      */

357     public synchronized int getPoolSize() {
358         return usedPool.size() + unusedPool.size();
359     }
360
361     /**
362      * Returns the number of connections obtained via this DataSource
363      * that are currently in use by the DataSource clients.
364      */

365     public synchronized int getCurrentlyInUse() {
366         return usedPool.size();
367     }
368
369     /**
370      * Returns the number of connections maintained in the
371      * pool that are currently not used by any clients and are
372      * available immediately via <code>getConnection</code> method.
373      */

374     public synchronized int getCurrentlyUnused() {
375         return unusedPool.size();
376     }
377
378     /**
379      * Returns connection from the pool using internal values of user name
380      * and password. Eqivalent to calling:
381      *
382      * <p><code>ds.getConnection(ds.getUserName(), ds.getPassword())</code></p>
383      */

384     public Connection JavaDoc getConnection() throws SQLException JavaDoc {
385         return getConnection(userName, password);
386     }
387
388     /** Returns connection from the pool. */
389     public synchronized Connection JavaDoc getConnection(String JavaDoc userName, String JavaDoc password)
390             throws SQLException JavaDoc {
391
392         PooledConnection JavaDoc pooledConnection = uncheckPooledConnection(userName, password);
393
394         try {
395             return uncheckConnection(pooledConnection);
396         }
397         catch (SQLException JavaDoc ex) {
398             logObj.info("Error getting connection", ex);
399
400             try {
401                 pooledConnection.close();
402             }
403             catch (SQLException JavaDoc ignored) {
404             }
405
406             logObj.info("Reconnecting...");
407             
408             // do one reconnect attempt...
409
pooledConnection = uncheckPooledConnection(userName, password);
410             try {
411                 return uncheckConnection(pooledConnection);
412             }
413             catch (SQLException JavaDoc reconnectEx) {
414                 try {
415                     pooledConnection.close();
416                 }
417                 catch (SQLException JavaDoc ignored) {
418                 }
419                 
420                 throw reconnectEx;
421             }
422         }
423     }
424     
425     private Connection JavaDoc uncheckConnection(PooledConnection JavaDoc pooledConnection)
426             throws SQLException JavaDoc {
427         Connection JavaDoc c = pooledConnection.getConnection();
428
429         // only do that on successfully unchecked connection...
430
usedPool.add(pooledConnection);
431         return c;
432     }
433     
434     private PooledConnection JavaDoc uncheckPooledConnection(String JavaDoc userName, String JavaDoc password)
435             throws SQLException JavaDoc {
436         // wait for returned connections or the maintenance thread
437
// to bump the pool size...
438

439         if (unusedPool.size() == 0) {
440             
441             // first try to open a new connection
442
if (canGrowPool()) {
443                 return newPooledConnection(userName, password);
444             }
445             
446             // can't open no more... will have to wait for others to return a connection
447

448             // note that if we were woken up
449
// before the full wait period expired, and no connections are
450
// available yet, go back to sleep. Otherwise we don't give a maintenance
451
// thread a chance to increase pool size
452
long waitTill =
453                 System.currentTimeMillis()
454                 + MAX_QUEUE_WAIT;
455
456             do {
457                 try {
458                     wait(MAX_QUEUE_WAIT);
459                 } catch (InterruptedException JavaDoc iex) {
460                     // ignoring
461
}
462
463             } while (unusedPool.size() == 0 && waitTill > System.currentTimeMillis());
464
465             if (unusedPool.size() == 0) {
466                 throw new SQLException JavaDoc(
467                     "Can't obtain connection. Request timed out. Total used connections: "
468                         + usedPool.size());
469             }
470         }
471
472         // get first connection... lets cycle them in FIFO manner
473
return (PooledConnection JavaDoc) unusedPool.remove(0);
474     }
475
476     public int getLoginTimeout() throws java.sql.SQLException JavaDoc {
477         return poolDataSource.getLoginTimeout();
478     }
479
480     public void setLoginTimeout(int seconds) throws java.sql.SQLException JavaDoc {
481         poolDataSource.setLoginTimeout(seconds);
482     }
483
484     public PrintWriter JavaDoc getLogWriter() throws java.sql.SQLException JavaDoc {
485         return poolDataSource.getLogWriter();
486     }
487
488     public void setLogWriter(PrintWriter JavaDoc out) throws java.sql.SQLException JavaDoc {
489         poolDataSource.setLogWriter(out);
490     }
491
492     /**
493      * Returns closed connection to the pool.
494      */

495     public synchronized void connectionClosed(ConnectionEvent JavaDoc event) {
496         // return connection to the pool
497
PooledConnection JavaDoc closedConn = (PooledConnection JavaDoc) event.getSource();
498         
499         // remove this connection from the list of connections
500
// managed by this pool...
501
int usedInd = usedPool.indexOf(closedConn);
502         if (usedInd >= 0) {
503             usedPool.remove(usedInd);
504             unusedPool.add(closedConn);
505
506             // notify threads waiting for connections
507
notifyAll();
508         }
509         // else ....
510
// other possibility is that this is a bad connection, so just ignore its closing event,
511
// since it was unregistered in "connectionErrorOccurred"
512
}
513
514     /**
515      * Removes connection with an error from the pool. This method
516      * is called by PoolManager connections on connection errors
517      * to notify PoolManager that connection is in invalid state.
518      */

519     public synchronized void connectionErrorOccurred(ConnectionEvent JavaDoc event) {
520         // later on we should analyze the error to see if this
521
// is fatal... right now just kill this PooledConnection
522

523         PooledConnection JavaDoc errorSrc = (PooledConnection JavaDoc) event.getSource();
524
525         // remove this connection from the list of connections
526
// managed by this pool...
527

528         int usedInd = usedPool.indexOf(errorSrc);
529         if (usedInd >= 0) {
530             usedPool.remove(usedInd);
531         } else {
532             int unusedInd = unusedPool.indexOf(errorSrc);
533             if (unusedInd >= 0)
534                 unusedPool.remove(unusedInd);
535         }
536
537         // do not close connection,
538
// let the code that catches the exception handle it
539
// ....
540
}
541
542     static class PoolMaintenanceThread extends Thread JavaDoc {
543         protected boolean shouldDie;
544         protected PoolManager pool;
545
546         PoolMaintenanceThread(PoolManager pool) {
547             super.setName("PoolManagerCleanup-" + pool.hashCode());
548             super.setDaemon(true);
549             this.pool = pool;
550         }
551
552         public void run() {
553             // periodically wakes up to check if the pool should grow or shrink
554
while (true) {
555
556                 try {
557                     // don't do it too often
558
sleep(600000);
559                 } catch (InterruptedException JavaDoc iex) {
560                     // ignore...
561
}
562
563                 if (shouldDie) {
564                     break;
565                 }
566
567                 synchronized (pool) {
568                     // TODO: implement a smarter algorithm for pool management...
569
// right now it will simply close one connection if the count is
570
// above median and there are any idle connections.
571

572                     int unused = pool.getCurrentlyUnused();
573                     int used = pool.getCurrentlyInUse();
574                     int total = unused + used;
575                     int median =
576                         pool.minConnections
577                             + 1
578                             + (pool.maxConnections - pool.minConnections) / 2;
579
580                     if (unused > 0 && total > median) {
581                         pool.shrinkPool(1);
582                         logObj.debug("decreased pool size to " + (total - 1) + " connections.");
583                     }
584                 }
585             }
586         }
587
588         /**
589          * Stops the thread.
590          */

591         public void dispose() {
592             shouldDie = true;
593         }
594     }
595 }
Popular Tags