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 10import java.io.*; 11import java.sql.*; 12import java.util.*; 13import java.util.Date; 14 15/** *//** 16 * This class is a Singleton that provides access to one or many 17 * connection pools defined in a Property file. A client gets 18 * access to the single instance through the static getInstance() 19 * method and can then check-out and check-in connections from a pool. 20 * When the client shuts down it should call the release() method 21 * to close all open connections and do other clean up. 22*/ 23publicclass DBConnectionManager { 24staticprivate DBConnectionManager instance; // The single instance 25staticprivateint clients; 26 27private Vector drivers =new Vector(); 28private PrintWriter log; 29private Hashtable pools =new Hashtable(); 30 31/** *//** 32 * Returns the single instance, creating one if it's the 33 * first time this method is called. 34 * 35 * @return DBConnectionManager The single instance. 36*/ 37staticsynchronizedpublic DBConnectionManager getInstance() { 38if (instance ==null) { 39 instance =new DBConnectionManager(); 40 } 41 clients++; 42return instance; 43 } 44 45/** *//** 46 * A private constructor since this is a Singleton 47*/ 48private DBConnectionManager() { 49 init(); 50 } 51 52/** *//** 53 * Returns a connection to the named pool. 54 * 55 * @param name The pool name as defined in the properties file 56 * @param con The Connection 57*/ 58publicvoid freeConnection(String name, Connection con) { 59 DBConnectionPool pool = (DBConnectionPool) pools.get(name); 60if (pool !=null) { 61 pool.freeConnection(con); 62 } 63 } 64 65/** *//** 66 * Returns an open connection. If no one is available, and the max 67 * number of connections has not been reached, a new connection is 68 * created. 69 * 70 * @param name The pool name as defined in the properties file 71 * @return Connection The connection or null 72*/ 73public Connection getConnection(String name) { 74 DBConnectionPool pool = (DBConnectionPool) pools.get(name); 75if (pool !=null) { 76return pool.getConnection(); 77 } 78returnnull; 79 } 80 81/** *//** 82 * Returns an open connection. If no one is available, and the max 83 * number of connections has not been reached, a new connection is 84 * created. If the max number has been reached, waits until one 85 * is available or the specified time has elapsed. 86 * 87 * @param name The pool name as defined in the properties file 88 * @param time The number of milliseconds to wait 89 * @return Connection The connection or null 90*/ 91public Connection getConnection(String name, long time) { 92 DBConnectionPool pool = (DBConnectionPool) pools.get(name); 93if (pool !=null) { 94return pool.getConnection(time); 95 } 96returnnull; 97 } 98 99/** *//** 100 * Closes all open connections and deregisters all drivers. 101*/ 102publicsynchronizedvoid release() { 103// Wait until called by the last client 104if (--clients !=0) { 105return; 106 } 107 108 Enumeration allPools = pools.elements(); 109while (allPools.hasMoreElements()) { 110 DBConnectionPool pool = (DBConnectionPool) allPools.nextElement(); 111 pool.release(); 112 } 113 Enumeration allDrivers = drivers.elements(); 114while (allDrivers.hasMoreElements()) { 115 Driver driver = (Driver) allDrivers.nextElement(); 116try{ 117 DriverManager.deregisterDriver(driver); 118 log("Deregistered JDBC driver "+ driver.getClass().getName()); 119 } 120catch (SQLException e) { 121 log(e, "Can't deregister JDBC driver: "+ driver.getClass().getName()); 122 } 123 } 124 } 125 126/** *//** 127 * Creates instances of DBConnectionPool based on the properties. 128 * A DBConnectionPool can be defined with the following properties: 129 * <PRE> 130 * <poolname>.url The JDBC URL for the database 131 * <poolname>.user A database user (optional) 132 * <poolname>.password A database user password (if user specified) 133 * <poolname>.maxconn The maximal number of connections (optional) 134 * </PRE> 135 * 136 * @param props The connection pool properties 137*/ 138privatevoid createPools(Properties props) { 139 Enumeration propNames = props.propertyNames(); 140while (propNames.hasMoreElements()) { 141 String name = (String) propNames.nextElement(); 142if (name.endsWith(".url")) { 143 String poolName = name.substring(0, name.lastIndexOf(".")); 144 String url = props.getProperty(poolName +".url"); 145if (url ==null) { 146 log("No URL specified for "+ poolName); 147continue; 148 } 149 String user = props.getProperty(poolName +".user"); 150 String password = props.getProperty(poolName +".password"); 151 String maxconn = props.getProperty(poolName +".maxconn", "0"); 152int max; 153try{ 154 max = Integer.valueOf(maxconn).intValue(); 155 } 156catch (NumberFormatException e) { 157 log("Invalid maxconn value "+ maxconn +" for "+ poolName); 158 max =0; 159 } 160 DBConnectionPool pool = 161new DBConnectionPool(poolName, url, user, password, max); 162 pools.put(poolName, pool); 163 log("Initialized pool "+ poolName); 164 } 165 } 166 } 167 168/** *//** 169 * Loads properties and initializes the instance with its values. 170*/ 171privatevoid init() { 172 InputStream is = getClass().getResourceAsStream("/db.properties"); 173 Properties dbProps =new Properties(); 174try{ 175 dbProps.load(is); 176 } 177catch (Exception e) { 178 System.err.println("Can't read the properties file. "+ 179"Make sure db.properties is in the CLASSPATH"); 180return; 181 } 182 String logFile = dbProps.getProperty("logfile", "DBConnectionManager.log"); 183try{ 184 log =new PrintWriter(new FileWriter(logFile, true), true); 185 } 186catch (IOException e) { 187 System.err.println("Can't open the log file: "+ logFile); 188 log =new PrintWriter(System.err); 189 } 190 loadDrivers(dbProps); 191 createPools(dbProps); 192 } 193 194/** *//** 195 * Loads and registers all JDBC drivers. This is done by the 196 * DBConnectionManager, as opposed to the DBConnectionPool, 197 * since many pools may share the same driver. 198 * 199 * @param props The connection pool properties 200*/ 201privatevoid loadDrivers(Properties props) { 202 String driverClasses = props.getProperty("drivers"); 203 StringTokenizer st =new StringTokenizer(driverClasses); 204while (st.hasMoreElements()) { 205 String driverClassName = st.nextToken().trim(); 206try{ 207 Driver driver = (Driver) 208 Class.forName(driverClassName).newInstance(); 209 DriverManager.registerDriver(driver); 210 drivers.addElement(driver); 211 log("Registered JDBC driver "+ driverClassName); 212 } 213catch (Exception e) { 214 log("Can't register JDBC driver: "+ 215 driverClassName +", Exception: "+ e); 216 } 217 } 218 } 219 220/** *//** 221 * Writes a message to the log file. 222*/ 223privatevoid log(String msg) { 224 log.println(new Date() +": "+ msg); 225 } 226 227/** *//** 228 * Writes a message with an Exception to the log file. 229*/ 230privatevoid log(Throwable e, String msg) { 231 log.println(new Date() +": "+ msg); 232 e.printStackTrace(log); 233 } 234 235/** *//** 236 * This inner class represents a connection pool. It creates new 237 * connections on demand, up to a max number if specified. 238 * It also makes sure a connection is still open before it is 239 * returned to a client. 240*/ 241class DBConnectionPool { 242privateint checkedOut; 243private Vector freeConnections =new Vector(); 244privateint maxConn; 245private String name; 246private String password; 247private String URL; 248private String user; 249 250/** *//** 251 * Creates new connection pool. 252 * 253 * @param name The pool name 254 * @param URL The JDBC URL for the database 255 * @param user The database user, or null 256 * @param password The database user password, or null 257 * @param maxConn The maximal number of connections, or 0 258 * for no limit 259*/ 260public DBConnectionPool(String name, String URL, String user, String password, 261int maxConn) { 262this.name = name; 263this.URL = URL; 264this.user = user; 265this.password = password; 266this.maxConn = maxConn; 267 } 268 269/** *//** 270 * Checks in a connection to the pool. Notify other Threads that 271 * may be waiting for a connection. 272 * 273 * @param con The connection to check in 274*/ 275publicsynchronizedvoid freeConnection(Connection con) { 276// Put the connection at the end of the Vector 277 freeConnections.addElement(con); 278 checkedOut--; 279 notifyAll(); 280 } 281 282/** *//** 283 * Checks out a connection from the pool. If no free connection 284 * is available, a new connection is created unless the max 285 * number of connections has been reached. If a free connection 286 * has been closed by the database, it's removed from the pool 287 * and this method is called again recursively. 288*/ 289publicsynchronized Connection getConnection() { 290 Connection con =null; 291if (freeConnections.size() >0) { 292// Pick the first Connection in the Vector 293// to get round-robin usage 294 con = (Connection) freeConnections.firstElement(); 295 freeConnections.removeElementAt(0); 296try{ 297if (con.isClosed()) { 298 log("Removed bad connection from "+ name); 299// Try again recursively 300 con = getConnection(); 301 } 302 } 303catch (SQLException e) { 304 log("Removed bad connection from "+ name); 305// Try again recursively 306 con = getConnection(); 307 } 308 } 309elseif (maxConn ==0|| checkedOut < maxConn) { 310 con = newConnection(); 311 } 312if (con !=null) { 313 checkedOut++; 314 } 315return con; 316 } 317 318/** *//** 319 * Checks out a connection from the pool. If no free connection 320 * is available, a new connection is created unless the max 321 * number of connections has been reached. If a free connection 322 * has been closed by the database, it's removed from the pool 323 * and this method is called again recursively. 324 * <P> 325 * If no connection is available and the max number has been 326 * reached, this method waits the specified time for one to be 327 * checked in. 328 * 329 * @param timeout The timeout value in milliseconds 330*/ 331publicsynchronized Connection getConnection(long timeout) { 332long startTime =new Date().getTime(); 333 Connection con; 334while ((con = getConnection()) ==null) { 335try{ 336 wait(timeout); 337 } 338catch (InterruptedException e) {} 339if ((new Date().getTime() - startTime) >= timeout) { 340// Timeout has expired 341returnnull; 342 } 343 } 344return con; 345 } 346 347/** *//** 348 * Closes all available connections. 349*/ 350publicsynchronizedvoid release() { 351 Enumeration allConnections = freeConnections.elements(); 352while (allConnections.hasMoreElements()) { 353 Connection con = (Connection) allConnections.nextElement(); 354try{ 355 con.close(); 356 log("Closed connection for pool "+ name); 357 } 358catch (SQLException e) { 359 log(e, "Can't close connection for pool "+ name); 360 } 361 } 362 freeConnections.removeAllElements(); 363 } 364 365/** *//** 366 * Creates a new connection, using a userid and password 367 * if specified. 368*/ 369private Connection newConnection() { 370 Connection con =null; 371try{ 372if (user ==null) { 373 con = DriverManager.getConnection(URL); 374 } 375else{ 376 con = DriverManager.getConnection(URL, user, password); 377 } 378 log("Created a new connection in pool "+ name); 379 } 380catch (SQLException e) { 381 log(e, "Can't create a new connection for "+ URL); 382returnnull; 383 } 384return con; 385 } 386 } 387}