KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > controller > connection > VariablePoolConnectionManager


1 /**
2  * C-JDBC: Clustered JDBC.
3  * Copyright (C) 2002-2005 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Contact: c-jdbc@objectweb.org
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by the
9  * Free Software Foundation; either version 2.1 of the License, or any later
10  * version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20  *
21  * Initial developer(s): Emmanuel Cecchet.
22  * Contributor(s): Mathieu Peltier.
23  */

24
25 package org.objectweb.cjdbc.controller.connection;
26
27 import java.sql.Connection JavaDoc;
28 import java.sql.SQLException JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.LinkedList JavaDoc;
31 import java.util.NoSuchElementException JavaDoc;
32
33 import org.objectweb.cjdbc.common.exceptions.UnreachableBackendException;
34 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
35
36 /**
37  * This connection manager provides connection pooling with a dynamically
38  * adjustable pool size.
39  * <p>
40  * If the maximum number of active connections is not reached, the
41  * {@link #getConnection()}method creates a connection. Else, the execution is
42  * blocked until a connection is freed or the timeout expires. blocked until a
43  * connection is freed or the timeout expires.
44  * <p>
45  * Idle connections in the pool are removed after the timeout idleTimeout if the
46  * minimum pool size has not been reached.
47  *
48  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
49  * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
50  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
51  * @version 1.0
52  */

53 public class VariablePoolConnectionManager
54     extends AbstractPoolConnectionManager
55 {
56   /** Default maximum pool size: default is 0 and means no limit. */
57   public static final int DEFAULT_MAX_POOL_SIZE = 0;
58
59   /**
60    * Default idle timeout in milliseconds: default is 0 and means that once
61    * allocated, connections are never released.
62    */

63   public static final int DEFAULT_IDLE_TIMEOUT = 0;
64
65   /**
66    * Default wait timeout in milliseconds: the default is 0 and means no
67    * timeout: waits until one connection is freed.
68    */

69   public static final int DEFAULT_WAIT_TIMEOUT = 0;
70
71   /** Initial pool size to be initialized at startup. */
72   private int initPoolSize;
73
74   /** Minimum pool size. */
75   private int minPoolSize;
76
77   /** Maximum pool size. */
78   private int maxPoolSize;
79
80   /**
81    * Time a connection can stay idle before begin released (removed from the
82    * pool) in milliseconds (0 means forever)
83    */

84   private int idleTimeout;
85
86   /** Maximum time to wait for a connection in milliseconds. */
87   private int waitTimeout;
88
89   /** Stores the time on which connections have been released. */
90   private LinkedList JavaDoc releaseTimes;
91
92   /** Allow to remove idle connections in the pool. */
93   private RemoveIdleConnectionsThread removeIdleConnectionsThread;
94
95   /**
96    * Creates a new <code>VariablePoolConnectionManager</code> instance with
97    * the default minPoolSize(initial pool size to be initialized at startup).
98    *
99    * @param backendUrl URL of the <code>DatabaseBackend</code> owning this
100    * connection manager
101    * @param backendName name of the <code>DatabaseBackend</code> owning this
102    * connection manager
103    * @param rLogin backend connection login to be used by this connection
104    * manager
105    * @param rPassword backend connection password to be used by this connection
106    * manager
107    * @param driverPath path for driver
108    * @param driverClassName class name for driver
109    * @param minPoolSize minimum pool size.
110    * @param maxPoolSize maximum pool size. 0 means no limit.
111    * @param idleTimeout time a connection can stay idle before begin released
112    * (removed from the pool) in seconds. 0 means no timeout: once
113    * allocated, connections are never released.
114    * @param waitTimeout maximum time to wait for a connection in seconds. 0
115    * means no timeout: waits until one connection is freed.
116    */

117   public VariablePoolConnectionManager(String JavaDoc backendUrl, String JavaDoc backendName,
118       String JavaDoc rLogin, String JavaDoc rPassword, String JavaDoc driverPath,
119       String JavaDoc driverClassName, int minPoolSize, int maxPoolSize,
120       int idleTimeout, int waitTimeout)
121   {
122     this(backendUrl, backendName, rLogin, rPassword, driverPath,
123         driverClassName, minPoolSize, minPoolSize, maxPoolSize, idleTimeout,
124         waitTimeout);
125   }
126
127   /**
128    * @see java.lang.Object#clone()
129    */

130   protected Object JavaDoc clone() throws CloneNotSupportedException JavaDoc
131   {
132     return new VariablePoolConnectionManager(backendUrl, backendName, rLogin,
133         rPassword, driverPath, driverClassName, minPoolSize, maxPoolSize,
134         idleTimeout, waitTimeout);
135   }
136
137   /**
138    * Creates a new <code>VariablePoolConnectionManager</code> instance.
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    * @param initPoolSize initial pool size to be intialized at startup
151    * @param minPoolSize minimum pool size.
152    * @param maxPoolSize maximum pool size. 0 means no limit.
153    * @param idleTimeout time a connection can stay idle before begin released
154    * (removed from the pool) in seconds. 0 means no timeout: once
155    * allocated, connections are never released.
156    * @param waitTimeout maximum time to wait for a connection in seconds. 0
157    * means no timeout: waits until one connection is freed.
158    */

159   public VariablePoolConnectionManager(String JavaDoc backendUrl, String JavaDoc backendName,
160       String JavaDoc rLogin, String JavaDoc rPassword, String JavaDoc driverPath,
161       String JavaDoc driverClassName, int initPoolSize, int minPoolSize,
162       int maxPoolSize, int idleTimeout, int waitTimeout)
163   {
164     super(backendUrl, backendName, rLogin, rPassword, driverPath,
165         driverClassName, maxPoolSize == 0 ? (initPoolSize > minPoolSize
166             ? initPoolSize
167             : minPoolSize) : maxPoolSize);
168     this.initPoolSize = initPoolSize;
169     this.minPoolSize = minPoolSize;
170     this.maxPoolSize = maxPoolSize;
171     this.idleTimeout = idleTimeout * 1000;
172     this.waitTimeout = waitTimeout * 1000;
173   }
174
175   /**
176    * Gets the max pool size.
177    *
178    * @return a <code>int</code> value.
179    */

180   public int getMaxPoolSize()
181   {
182     return maxPoolSize;
183   }
184
185   /**
186    * Gets the min pool size.
187    *
188    * @return a <code>int</code> value.
189    */

190   public int getMinPoolSize()
191   {
192     return minPoolSize;
193   }
194
195   /**
196    * Gets the idle timeout.
197    *
198    * @return a <code>int</code> value.
199    */

200   public int getIdleTimeout()
201   {
202     return idleTimeout;
203   }
204
205   /**
206    * Gets the wait timeout.
207    *
208    * @return a <code>int</code> value.
209    */

210   public int getWaitTimeout()
211   {
212     return waitTimeout;
213   }
214
215   /**
216    * @see org.objectweb.cjdbc.controller.connection.AbstractPoolConnectionManager#initializeConnections()
217    */

218   public void initializeConnections() throws SQLException JavaDoc
219   {
220     poolSize = maxPoolSize == 0 ? (initPoolSize > minPoolSize
221         ? initPoolSize
222         : minPoolSize) : maxPoolSize;
223     synchronized (this)
224     {
225       super.initializeConnections(initPoolSize);
226
227       if (idleTimeout != 0)
228       {
229         // Create the thread which manages the free connections
230
removeIdleConnectionsThread = new RemoveIdleConnectionsThread(
231             this.backendName, this);
232
233         // Intialize release time for the initial connections if an idleTimeout
234
// is set
235
releaseTimes = new LinkedList JavaDoc();
236         Iterator JavaDoc it = freeConnections.iterator();
237         Long JavaDoc currentTime = new Long JavaDoc(System.currentTimeMillis());
238         while (it.hasNext())
239         {
240           it.next();
241           releaseTimes.addLast(currentTime);
242         }
243       }
244     }
245
246     // Start the thread outside synchronized(this)
247
if (removeIdleConnectionsThread != null)
248     {
249       removeIdleConnectionsThread.start();
250
251       synchronized (removeIdleConnectionsThread)
252       {
253         if (releaseTimes.size() > 0)
254         {
255           removeIdleConnectionsThread.notify();
256         }
257       }
258     }
259   }
260
261   /**
262    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#finalizeConnections()
263    */

264   public void finalizeConnections() throws SQLException JavaDoc
265   {
266     synchronized (this)
267     {
268       super.finalizeConnections();
269     }
270
271     if (removeIdleConnectionsThread != null)
272     {
273       synchronized (removeIdleConnectionsThread)
274       {
275         removeIdleConnectionsThread.isKilled = true;
276         idleTimeout = 0;
277         removeIdleConnectionsThread.notify();
278       }
279       try
280       {
281         removeIdleConnectionsThread.join();
282       }
283       catch (InterruptedException JavaDoc e)
284       {
285       }
286     }
287   }
288
289   /**
290    * Gets a connection from the pool.
291    * <p>
292    * If the current number of active connections is lower than the maximum pool
293    * size, a new connection is created. If the creation fails, this method waits
294    * for a connection to be freed.
295    * <p>
296    * If the maximum number of active connections is reached, this methods blocks
297    * until a connection is freed or the timeout expires.
298    *
299    * @return a connection from the pool or <code>null</code> if the timeout
300    * has expired.
301    * @throws UnreachableBackendException if the backend must be disabled
302    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#getConnection()
303    */

304   public synchronized Connection JavaDoc getConnection()
305       throws UnreachableBackendException
306   {
307     if (!initialized)
308     {
309       logger
310           .error("Requesting a connection from a non-initialized connection manager");
311       return null;
312     }
313
314     long lTimeout = waitTimeout;
315     if (freeConnections.isEmpty())
316     {
317       if ((maxPoolSize == 0) || (activeConnections.size() < maxPoolSize))
318       {
319         Connection JavaDoc c = getConnectionFromDriver();
320         if (c == null)
321         {
322           if (activeConnections.size() == 0)
323           { // No connection active and backend unreachable, the backend
324
// is probably dead
325
logger.error("Backend " + backendName + " is no more accessible.");
326             throw new UnreachableBackendException();
327           }
328           // If it fails, just wait for a connection to be freed
329
if (logger.isWarnEnabled())
330             logger.warn("Failed to create new connection on backend '"
331                 + backendName + "', waiting for a connection to be freed.");
332         }
333         else
334         {
335           freeConnections.addLast(c);
336           if (idleTimeout != 0)
337           {
338             releaseTimes.add(new Long JavaDoc(System.currentTimeMillis()));
339           }
340           poolSize++;
341         }
342       }
343
344       /*
345        * We have to do a while loop() because there is a potential race here.
346        * When freeConnections is notified in releaseConnection, a new thread can
347        * take the lock on freeConnections before we wake up/reacquire the lock
348        * on freeConnections. Therefore, we could wake up and have no connection
349        * to take! We ensure that everything is correct with a while statement
350        * and recomputing the timeout between 2 wakeup.
351        */

352       while (freeConnections.isEmpty())
353       {
354         // Wait
355
try
356         {
357           if (lTimeout > 0)
358           {
359             long start = System.currentTimeMillis();
360             // Convert seconds to milliseconds for wait call
361
this.wait(waitTimeout);
362             long end = System.currentTimeMillis();
363             lTimeout -= end - start;
364             if (lTimeout <= 0)
365             {
366               if (logger.isWarnEnabled())
367                 logger.warn("Timeout expired for connection on backend '"
368                     + backendName
369                     + "', consider increasing pool size (current size is "
370                     + poolSize + ") or timeout (current timeout is "
371                     + (waitTimeout / 1000) + " seconds)");
372               return null;
373             }
374           }
375           else
376           {
377             this.wait();
378           }
379         }
380         catch (InterruptedException JavaDoc e)
381         {
382           logger
383               .error("Wait on freeConnections interrupted in VariablePoolConnectionManager");
384           return null;
385         }
386       }
387     }
388
389     // Get the connection
390
try
391     {
392       Connection JavaDoc c = (Connection JavaDoc) freeConnections.removeLast();
393       if (idleTimeout != 0)
394         releaseTimes.removeLast();
395       activeConnections.add(c);
396       return c;
397     }
398     catch (NoSuchElementException JavaDoc e)
399     {
400       if (logger.isErrorEnabled())
401         logger.error("Failed to get a connection on backend '" + backendName
402             + "' but an idle connection was expected");
403       return null;
404     }
405   }
406
407   /**
408    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#releaseConnection(Connection)
409    */

410   public void releaseConnection(Connection JavaDoc c)
411   {
412     boolean notifyThread = false;
413     synchronized (this)
414     {
415       if (!initialized)
416         return; // We probably have been disabled
417

418       if (activeConnections.remove(c))
419       {
420         if (idleTimeout != 0)
421         {
422           notifyThread = freeConnections.isEmpty()
423               || (freeConnections.size() == minPoolSize);
424           releaseTimes.addLast(new Long JavaDoc(System.currentTimeMillis()));
425         }
426         freeConnections.addLast(c);
427         this.notify();
428       }
429       else
430         logger.error("Failed to release connection " + c
431             + " (not found in active pool)");
432     }
433
434     if (notifyThread)
435       synchronized (removeIdleConnectionsThread)
436       {
437         removeIdleConnectionsThread.notify();
438       }
439   }
440
441   /**
442    * @see org.objectweb.cjdbc.controller.connection.AbstractPoolConnectionManager#deleteConnection(Connection)
443    */

444   public synchronized void deleteConnection(Connection JavaDoc c)
445   {
446     if (!initialized)
447       return; // We probably have been disabled
448

449     if (activeConnections.remove(c))
450     {
451       poolSize--;
452       if (poolSize < minPoolSize)
453       {
454         Connection JavaDoc newConnection = getConnectionFromDriver();
455         if (newConnection == null)
456         {
457           if (logger.isDebugEnabled())
458             logger.error("Bad connection " + c
459                 + " has been removed but cannot be replaced.");
460         }
461         else
462         {
463           poolSize++;
464           freeConnections.addLast(newConnection);
465           if (idleTimeout != 0)
466             releaseTimes.addLast(new Long JavaDoc(System.currentTimeMillis()));
467           this.notify();
468           if (logger.isDebugEnabled())
469             logger.debug("Bad connection " + c
470                 + " has been replaced by a new connection.");
471         }
472       }
473       else if (logger.isDebugEnabled())
474         logger.debug("Bad connection " + c + " has been removed.");
475     }
476     else
477       logger.error("Failed to release connection " + c
478           + " (not found in active pool)");
479   }
480
481   /**
482    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#getXmlImpl()
483    */

484   public String JavaDoc getXmlImpl()
485   {
486     StringBuffer JavaDoc info = new StringBuffer JavaDoc();
487     info.append("<" + DatabasesXmlTags.ELT_VariablePoolConnectionManager + " "
488         + DatabasesXmlTags.ATT_initPoolSize + "=\"" + initPoolSize + "\" "
489         + DatabasesXmlTags.ATT_minPoolSize + "=\"" + minPoolSize + "\" "
490         + DatabasesXmlTags.ATT_maxPoolSize + "=\"" + maxPoolSize + "\" "
491         + DatabasesXmlTags.ATT_idleTimeout + "=\"" + idleTimeout / 1000 + "\" "
492         + DatabasesXmlTags.ATT_waitTimeout + "=\"" + waitTimeout / 1000
493         + "\"/>");
494     return info.toString();
495   }
496
497   /**
498    * Allows to remove idle free connections after the idleTimeout timeout.
499    *
500    * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
501    */

502   protected class RemoveIdleConnectionsThread extends Thread JavaDoc
503   {
504     private boolean isKilled = false;
505     private VariablePoolConnectionManager thisPool;
506
507     protected RemoveIdleConnectionsThread(String JavaDoc pBackendName,
508         VariablePoolConnectionManager thisPool)
509     {
510       super("RemoveIdleConnectionsThread for backend:" + pBackendName);
511       this.thisPool = thisPool;
512     }
513
514     /**
515      * @see java.lang.Runnable#run()
516      */

517     public void run()
518     {
519       long idleTime, releaseTime;
520       synchronized (this)
521       {
522         try
523         {
524           while (!isKilled)
525           {
526             // the thread is not launched if idleTimeout equals to 0 (the
527
// connections are never released in this case)
528
if (freeConnections.isEmpty()
529                 || (freeConnections.size() == minPoolSize))
530             {
531               wait(); // wait on the thread RemoveIdleConnectionsThread
532
}
533             if (isKilled)
534               continue; // Just exit
535

536             Connection JavaDoc c = null;
537             synchronized (thisPool)
538             {
539               if (releaseTimes.isEmpty())
540                 continue; // Sanity check
541

542               releaseTime = ((Long JavaDoc) releaseTimes.get(0)).longValue();
543               idleTime = System.currentTimeMillis() - releaseTime;
544
545               if (idleTime >= idleTimeout)
546                 c = (Connection JavaDoc) freeConnections.remove(0);
547             }
548
549             if (c == null)
550             { // Nothing to free, wait for next deadline
551
wait(idleTimeout - idleTime);
552             }
553             else
554             { // Free the connection out of the synchronized block
555
try
556               {
557                 c.close();
558               }
559               catch (SQLException JavaDoc e)
560               {
561                 String JavaDoc msg = "An error occured while closing idle connection after the timeout: "
562                     + e;
563                 logger.error(msg);
564               }
565               finally
566               {
567                 releaseTimes.remove(0);
568                 poolSize--;
569               }
570               logger.debug("Released idle connection (idle timeout reached)");
571               continue;
572
573             }
574           }
575         }
576         catch (InterruptedException JavaDoc e)
577         {
578           logger
579               .error("Wait on removeIdleConnectionsThread interrupted in VariablePoolConnectionManager: "
580                   + e);
581         }
582       }
583     }
584   }
585
586 }
Popular Tags