Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/lilacsoftware/orca/DBManager/DBConnectionManager.java


1   /*
2    * Copyright (c) 1998 by Gefion software.
3    *
4    * Permission to use, copy, and distribute this software for
5    * NON-COMMERCIAL purposes and without fee is hereby granted
6    * provided that this copyright notice appears in all copies.
7    *
8    *
9    * Customized by Tom Wadzinski <orca_twadzins@yahoo.com> for application specific purposes 
10   */
11  
12  
13  package com.lilacsoftware.orca.DBManager;
14  
15  import java.io.*;
16  import java.sql.*;
17  import java.util.*;
18  import java.util.Date;
19  import java.util.Properties;
20  import com.lilacsoftware.orca.OrcaProperties;
21  import com.lilacsoftware.orca.Debug;
22  import com.lilacsoftware.orca.Constants;
23  
24  /**
25   * This class is a Singleton that provides access to one or many
26   * connection pools defined in a Property file. A client gets
27   * access to the single instance through the static getInstance()
28   * method and can then check-out and check-in connections from a pool.
29   * When the client shuts down it should call the release() method
30   * to close all open connections and do other clean up.
31   */
32  public class DBConnectionManager {
33      static private DBConnectionManager instance;       // The single instance
34      static private int clients;
35  
36      private Vector drivers = new Vector();
37      private PrintWriter log;
38      private Hashtable pools = new Hashtable();
39      
40      /**
41       * Returns the single instance, creating one if it's the
42       * first time this method is called.
43       *
44       * @return DBConnectionManager The single instance.
45       */
46      static synchronized protected DBConnectionManager getInstance() {
47          if (instance == null) {
48              instance = new DBConnectionManager();
49          }
50          clients++;
51          return instance;
52      }
53      
54      /**
55       * A private constructor since this is a Singleton
56       */
57      private DBConnectionManager() {
58          init();
59      }
60      
61      /**
62       * Returns a connection to the named pool.
63       *
64       * @param name The pool name as defined in the properties file
65       * @param con The Connection
66       */
67      protected void freeConnection(String name, Connection con) {
68          DBConnectionPool pool = (DBConnectionPool) pools.get(name);
69          if (pool != null) {
70              pool.freeLocalConnection(con);
71          }
72      }
73    
74      /**
75       * Returns a connection to the pool name by the app prop dbPool.
76       *
77       * @param con The Connection
78       */
79      protected void freeConnection(Connection con) {
80          String dbPool;
81    Properties appProps = OrcaProperties.getInstance().getProperties();
82    dbPool = appProps.getProperty("dbPool");
83  
84          DBConnectionPool pool = (DBConnectionPool) pools.get(dbPool);
85          if (pool != null) {
86              pool.freeLocalConnection(con);
87          }
88      }      
89  
90      /**
91       * Returns an open connection. If no one is available, and the max
92       * number of connections has not been reached, a new connection is
93       * created. Uses dbPool app property as pool choice. 
94       * If the max number has been reached, waits until one
95       * is available or the specified time has elapsed.
96       *
97       * 
98       * @return Connection The connection or null
99       */
100     protected Connection getConnection(long time) {
101   String dbPool;
102   Properties appProps = OrcaProperties.getInstance().getProperties();
103   dbPool = appProps.getProperty("dbPool");
104 
105         DBConnectionPool pool = (DBConnectionPool) pools.get(dbPool);
106         if (pool != null) {
107             return pool.getConnection(time);
108         }
109         return null;
110     }
111 
112     /**
113      * Returns an open connection. If no one is available, and the max
114      * number of connections has not been reached, a new connection is
115      * created. Uses dbPool app property as pool choice
116      *
117      * 
118      * @return Connection The connection or null
119      */
120     protected Connection getConnection() {
121   String dbPool;
122   Properties appProps = OrcaProperties.getInstance().getProperties();
123   dbPool = appProps.getProperty("dbPool");
124         DBConnectionPool pool = (DBConnectionPool) pools.get(dbPool);
125         if (pool != null) {
126             return pool.getConnection();
127         }
128         return null;
129     }
130    
131     /**
132      * Returns an open connection. If no one is available, and the max
133      * number of connections has not been reached, a new connection is
134      * created.
135      *
136      * @param name The pool name as defined in the properties file
137      * @return Connection The connection or null
138      */
139     protected Connection getConnection(String name) {
140         DBConnectionPool pool = (DBConnectionPool) pools.get(name);
141         if (pool != null) {
142             return pool.getConnection();
143         }
144         return null;
145     }
146  
147     /**
148      * Returns an open connection. If no one is available, and the max
149      * number of connections has not been reached, a new connection is
150      * created. If the max number has been reached, waits until one
151      * is available or the specified time has elapsed.
152      *
153      * @param name The pool name as defined in the properties file
154      * @param time The number of milliseconds to wait
155      * @return Connection The connection or null
156      */
157     protected Connection getConnection(String name, long time) {
158         DBConnectionPool pool = (DBConnectionPool) pools.get(name);
159         if (pool != null) {
160             return pool.getConnection(time);
161         }
162         return null;
163     }
164     
165     /**
166      * Closes all open connections and deregisters all drivers.
167      */
168     protected synchronized void release() {
169         // Wait until called by the last client - skip this, TBD
170   // Review this, I've always assumed I could grab and
171   // instance of connmgr but this suggests each instance must do its
172   // own release.  Hsql requires close.
173         //if (--clients != 0) {
174   //   return;
175         //}
176         
177         Enumeration allPools = pools.elements();
178         while (allPools.hasMoreElements()) {
179             DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
180             pool.release();
181         }
182         Enumeration allDrivers = drivers.elements();
183         while (allDrivers.hasMoreElements()) {
184             Driver driver = (Driver) allDrivers.nextElement();
185             try {
186                 DriverManager.deregisterDriver(driver);
187                 log("Deregistered JDBC driver " + driver.getClass().getName());
188             }
189             catch (SQLException e) {
190                 log(e, "Can't deregister JDBC driver: " + driver.getClass().getName());
191             }
192         }
193     }
194     
195     /**
196      * Creates instances of DBConnectionPool based on the properties.
197      * A DBConnectionPool can be defined with the following properties:
198      * <PRE>
199      * &lt;poolname&gt;.url         The JDBC URL for the database
200      * &lt;poolname&gt;.user        A database user (optional)
201      * &lt;poolname&gt;.password    A database user password (if user specified)
202      * &lt;poolname&gt;.maxconn     The maximal number of connections (optional)
203      * </PRE>
204      *
205      * @param props The connection pool properties
206      */
207     private void createPools(Properties props) {
208         Enumeration propNames = props.propertyNames();
209         while (propNames.hasMoreElements()) {
210             String name = (String) propNames.nextElement();
211             if (name.endsWith(".url")) {
212                 String poolName = name.substring(0, name.lastIndexOf("."));
213                 String url = props.getProperty(poolName + ".url");
214     //System.out.println("db pool url: " + url);
215                 if (url == null) {
216                     log("No URL specified for " + poolName);
217                     continue;
218                 }
219     //Replace '~appdir~' with app dir, should really use regex package
220     StringTokenizer st = new StringTokenizer(url, "~");
221     if (st.countTokens() == 3) {
222         File appDir = new File(System.getProperty("user.home"), Constants.APPDIR);
223         String newURL = st.nextToken();
224         st.nextToken();
225         newURL = newURL + appDir.toString();
226         newURL = newURL + st.nextToken();
227         log("db url with appdir expansion: " + newURL);
228         url = newURL;
229     }
230     
231                 String user = props.getProperty(poolName + ".user");
232                 String password = props.getProperty(poolName + ".password");
233                 String maxconn = props.getProperty(poolName + ".maxconn", "0");
234                 int max;
235                 try {
236                     max = Integer.valueOf(maxconn).intValue();
237                 }
238                 catch (NumberFormatException e) {
239                     log("Invalid maxconn value " + maxconn + " for " + poolName);
240                     max = 0;
241                 }
242                 DBConnectionPool pool = 
243                     new DBConnectionPool(poolName, url, user, password, max);
244                 pools.put(poolName, pool);
245                 log("Initialized pool " + poolName);
246             }
247         }
248     }
249     
250     /**
251      * Loads properties and initializes the instance with its values.
252      */
253     private void init() {
254 //          InputStream is = getClass().getResourceAsStream("/app.properties");
255 //          Properties appProps = new Properties();
256 //          try {
257 //              appProps.load(is);
258 //          }
259 //          catch (Exception e) {
260 //              System.err.println("Can't read the properties file. ");
261 //          }
262   
263   //Load properties if needed 
264   if (!OrcaProperties.getInstance().isInitialized() ) {
265       System.err.println("Can't load app properties. ");
266       return;
267   }
268 
269   //TBD fix this prop crap...
270   Properties appProps = OrcaProperties.getInstance().getProperties();
271   if (appProps == null ) {
272       System.err.println("Can't load app properties. ");
273       return;
274   }
275 
276         String logFile = appProps.getProperty("logfile", "DBConnectionManager.log");
277         try {
278             //orig: log = new PrintWriter(new FileWriter(logFile, true), true);
279       log = new PrintWriter(System.out, true);
280         }
281 //          catch (IOException e) {
282 //              System.err.println("Can't open the log file: " + logFile);
283 //              log = new PrintWriter(System.err);
284 //          }
285   finally {
286   }
287         loadDrivers(appProps);
288         createPools(appProps);
289     }
290     
291     /**
292      * Loads and registers all JDBC drivers. This is done by the
293      * DBConnectionManager, as opposed to the DBConnectionPool,
294      * since many pools may share the same driver.
295      *
296      * @param props The connection pool properties
297      */
298     private void loadDrivers(Properties props) {
299         String driverClasses = props.getProperty("drivers");
300   trace(1, getClass().getName(), "drivers: " + driverClasses);
301   StringTokenizer st = new StringTokenizer(driverClasses);
302         while (st.hasMoreElements()) {
303             String driverClassName = st.nextToken().trim();
304             try {
305                 Driver driver = (Driver) 
306                     Class.forName(driverClassName).newInstance();
307                 DriverManager.registerDriver(driver);
308                 drivers.addElement(driver);
309                 log("Registered JDBC driver " + driverClassName);
310             }
311             catch (Exception e) {
312                 log("Can't register JDBC driver: " +
313                     driverClassName + ", Exception: " + e);
314             }
315         }
316     }
317     
318     /**
319      * Writes a message to the log file.
320      */
321     private void log(String msg) {
322         log.println(new Date() + ": " + msg);
323     }
324     
325     /**
326      * Writes a message with an Exception to the log file.
327      */
328     private void log(Throwable e, String msg) {
329         log.println(new Date() + ": " + msg);
330         e.printStackTrace(log);
331     }
332     
333     /**
334      * This inner class represents a connection pool. It creates new
335      * connections on demand, up to a max number if specified.
336      * It also makes sure a connection is still open before it is
337      * returned to a client.
338      */
339     class DBConnectionPool {
340         private int checkedOut;
341         private Vector freeConnections = new Vector();
342         private int maxConn;
343         private String name;
344         private String password;
345         private String URL;
346         private String user;
347         
348         /**
349          * Creates new connection pool.
350          *
351          * @param name The pool name
352          * @param URL The JDBC URL for the database
353          * @param user The database user, or null
354          * @param password The database user password, or null
355          * @param maxConn The maximal number of connections, or 0
356          *   for no limit
357          */
358         protected DBConnectionPool(String name, String URL, String user, String password, 
359                 int maxConn) {
360             this.name = name;
361             this.URL = URL;
362             this.user = user;
363             this.password = password;
364             this.maxConn = maxConn;
365         }
366         
367         /**
368          * Checks in a connection to the pool. Notify other Threads that
369          * may be waiting for a connection.
370          *
371          * @param con The connection to check in
372          */
373         protected synchronized void freeLocalConnection(Connection con) {
374             // Put the connection at the end of the Vector
375             freeConnections.addElement(con);
376             checkedOut--;
377             this.notifyAll();
378         }
379         
380         /**
381          * Checks out a connection from the pool. If no free connection
382          * is available, a new connection is created unless the max
383          * number of connections has been reached. If a free connection
384          * has been closed by the database, it's removed from the pool
385          * and this method is called again recursively.
386          */
387         protected synchronized Connection getConnection() {
388             Connection con = null;
389             if (freeConnections.size() > 0) {
390                 // Pick the first Connection in the Vector
391                 // to get round-robin usage
392                 con = (Connection) freeConnections.firstElement();
393                 freeConnections.removeElementAt(0);
394                 try {
395                     if (con.isClosed()) {
396                         log("Removed bad connection from " + name);
397                         // Try again recursively
398                         con = getConnection();
399                     }
400                 }
401                 catch (SQLException e) {
402                     log("Removed bad connection from " + name);
403                     // Try again recursively
404                     con = getConnection();
405                 }
406             }
407             else if (maxConn == 0 || checkedOut < maxConn) {
408                 con = newConnection();
409             }
410             if (con != null) {
411                 checkedOut++;
412             }
413             return con;
414         }
415         
416         /**
417          * Checks out a connection from the pool. If no free connection
418          * is available, a new connection is created unless the max
419          * number of connections has been reached. If a free connection
420          * has been closed by the database, it's removed from the pool
421          * and this method is called again recursively.
422          * <P>
423          * If no connection is available and the max number has been 
424          * reached, this method waits the specified time for one to be
425          * checked in.
426          *
427          * @param timeout The timeout value in milliseconds
428          */
429         protected synchronized Connection getConnection(long timeout) {
430             long startTime = new Date().getTime();
431             Connection con;
432             while ((con = getConnection()) == null) {
433                 try {
434                     this.wait(timeout);
435                 }
436                 catch (InterruptedException e) {}
437                 if ((new Date().getTime() - startTime) >= timeout) {
438                     // Timeout has expired
439                     return null;
440                 }
441             }
442             return con;
443         }
444         
445         /**
446          * Closes all available connections.
447          */
448         protected synchronized void release() {
449             Enumeration allConnections = freeConnections.elements();
450             while (allConnections.hasMoreElements()) {
451                 Connection con = (Connection) allConnections.nextElement();
452                 try {
453                     con.close();
454                     log("Closed connection for pool " + name);
455                 }
456                 catch (SQLException e) {
457                     log(e, "Can't close connection for pool " + name);
458                 }
459             }
460             freeConnections.removeAllElements();
461         }
462         
463         /**
464          * Creates a new connection, using a userid and password
465          * if specified.
466          */
467         private Connection newConnection() {
468             Connection con = null;
469             try {
470                 if (user == null) {
471                     con = DriverManager.getConnection(URL);
472                 }
473                 else {
474                     con = DriverManager.getConnection(URL, user, password);
475                 }
476                 log("Created a new connection in pool " + name);
477             }
478             catch (SQLException e) {
479                 log(e, "Can't create a new connection for " + URL);
480                 return null;
481             }
482             return con;
483         }
484     }
485 
486     /**
487      * Sends traces to Debug.
488      */
489     void trace(int level, String msg1, String msg2)
490     {
491   Debug dbg = Debug.getInstance();
492   dbg.log(level,msg1+":"+msg2);
493     }
494 
495 }