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 * <poolname>.url The JDBC URL for the database
200 * <poolname>.user A database user (optional)
201 * <poolname>.password A database user password (if user specified)
202 * <poolname>.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 }