001: /*
002: * $Header: /cvsroot/mvnforum/myvietnam/src/net/myvietnam/mvncore/db/DBConnectionManager.java,v 1.22 2007/11/22 04:40:29 minhnn Exp $
003: * $Author: minhnn $
004: * $Revision: 1.22 $
005: * $Date: 2007/11/22 04:40:29 $
006: *
007: * ====================================================================
008: *
009: * Copyright (C) 2002-2007 by MyVietnam.net
010: *
011: * All copyright notices regarding MyVietnam and MyVietnam CoreLib
012: * MUST remain intact in the scripts and source code.
013: *
014: * This library is free software; you can redistribute it and/or
015: * modify it under the terms of the GNU Lesser General Public
016: * License as published by the Free Software Foundation; either
017: * version 2.1 of the License, or (at your option) any later version.
018: *
019: * This library is distributed in the hope that it will be useful,
020: * but WITHOUT ANY WARRANTY; without even the implied warranty of
021: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
022: * Lesser General Public License for more details.
023: *
024: * You should have received a copy of the GNU Lesser General Public
025: * License along with this library; if not, write to the Free Software
026: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
027: *
028: * Correspondence and Marketing Questions can be sent to:
029: * info at MyVietnam net
030: *
031: * @author: Minh Nguyen
032: * @author: Mai Nguyen
033: */
034: package net.myvietnam.mvncore.db;
035:
036: import java.sql.*;
037: import java.util.*;
038:
039: import net.myvietnam.mvncore.MVNCoreConfig;
040: import org.apache.commons.logging.Log;
041: import org.apache.commons.logging.LogFactory;
042:
043: /**
044: * This class is a Singleton that provides access to the
045: * connection pool. A client gets access to the single
046: * instance through the static getInstance() method
047: * and can then check-out and check-in connections from a pool.
048: * When the client shuts down it should call the release() method
049: * to close all opened connections and do other clean up.
050: */
051: class DBConnectionManager {
052:
053: private static Log log = LogFactory
054: .getLog(DBConnectionManager.class);
055:
056: private static final int TIME_BETWEEN_RETRIES = 500; // O.5 second
057:
058: // static variable
059: static private DBConnectionManager instance = null; // The single instance
060:
061: // instance variable
062: private DBConnectionPool pool = null;// please be careful if u want to make this variable static
063:
064: private static Map dbManagers = new HashMap();
065: private static final int MANAGER_MAX = 5;
066:
067: /**
068: * A private constructor since this is a Singleton
069: * Note: This constructor is lightweight since DBConnectionPool is lightweight,
070: * so no connection is created until the first time getConnection() is called
071: */
072: private DBConnectionManager() {
073: String driverClassName = MVNCoreConfig.getDriverClassName();
074: try {
075: Class.forName(driverClassName).newInstance();
076: } catch (Exception e) {
077: log.fatal("DBConnectionManager: Unable to load driver = "
078: + driverClassName, e);
079: }
080:
081: String url = MVNCoreConfig.getDatabaseURL();
082: String user = MVNCoreConfig.getDatabaseUser();
083: String password = MVNCoreConfig.getDatabasePassword();
084: int maxConnection = MVNCoreConfig.getMaxConnection();
085:
086: //always new the pool because pool is an instance variable
087: pool = new DBConnectionPool(url, user, password, maxConnection);
088: }
089:
090: private DBConnectionManager(DBOptions dbOptions) {
091: String driverClassName = dbOptions.getDriverClass();
092: try {
093: Class.forName(driverClassName).newInstance();
094: } catch (Exception e) {
095: log.fatal("DBConnectionManager: Unable to load driver = "
096: + driverClassName, e);
097: }
098:
099: String url = dbOptions.getDbUrl();
100: String user = dbOptions.getUsername();
101: String password = dbOptions.getPassword();
102: int maxConnection = dbOptions.getConMax();
103:
104: //always new the pool because pool is an instance variable
105: pool = new DBConnectionPool(url, user, password, maxConnection);
106: }
107:
108: /**
109: * Returns the single instance, creating one if it's the
110: * first time this method is called.
111: *
112: * @return DBConnectionManager The single instance.
113: */
114: /*
115: public static synchronized DBConnectionManager getInstance() {
116: if (instance == null) {
117: DBOptions option = new DBOptions();
118: instance = new DBConnectionManager(option);
119: }
120: return instance;
121: }*/
122:
123: /**
124: * Returns the single instance, creating one if it's the
125: * first time this method is called.
126: *
127: * @return DBConnectionManager The single instance.
128: */
129: /*
130: private static synchronized DBConnectionManager getInstance(DBOptions option) {
131: if (instance == null) {
132: if (option == null) {
133: option = new DBOptions();
134: }
135: instance = new DBConnectionManager(option);
136: }
137: return instance;
138: }*/
139:
140: /**
141: * DBUtil use this method
142: */
143: public static synchronized DBConnectionManager getInstance(
144: boolean useConfig) {
145: if (instance == null) {
146: instance = new DBConnectionManager();
147: }
148: return instance;
149: }
150:
151: /**
152: * DBUtil2 use this method
153: */
154: public static synchronized DBConnectionManager getDBConnectionManager(
155: DBOptions dbOptions) {
156:
157: if (dbOptions == null) {
158: throw new IllegalArgumentException(
159: "Cannot get DBConnectionManager. Missing DBOptions.");
160: }
161:
162: if ((dbOptions.getDbManagerName() == null)
163: || (dbOptions.getDbManagerName().length() == 0)) {
164: throw new IllegalArgumentException(
165: "Cannot get DBConnectionManager. Missing [Database Connection Manager Name].");
166: }
167:
168: DBConnectionManager dbManager = (DBConnectionManager) dbManagers
169: .get(dbOptions.getDbManagerName());
170: if (dbManager == null) {
171: dbManager = createDbConnectionManager(dbOptions);
172: }
173: return dbManager;
174: }
175:
176: private static DBConnectionManager createDbConnectionManager(
177: DBOptions dbOptions) {
178:
179: if (dbManagers.size() >= MANAGER_MAX) {
180: throw new IllegalStateException("System only support max "
181: + MANAGER_MAX + " DBConnectionManager(s)");
182: }
183:
184: DBConnectionManager instance = new DBConnectionManager(
185: dbOptions);
186: dbManagers.put(dbOptions.getDbManagerName(), instance);
187:
188: return instance;
189: }
190:
191: /**
192: * Returns a connection to the pool.
193: *
194: * @param con The Connection
195: */
196: void freeConnection(Connection con) {
197: pool.freeConnection(con);
198: }
199:
200: /**
201: * Returns an open connection. If no one is available, and the max
202: * number of connections has not been reached, a new connection is
203: * created.
204: *
205: * @return Connection The connection or null
206: */
207: Connection getConnection() {
208: return getConnection(0);
209: }
210:
211: /**
212: * Returns an open connection. If no one is available, and the max
213: * number of connections has not been reached, a new connection is
214: * created. If the max number has been reached, waits until one
215: * is available or the specified time has elapsed.
216: *
217: * @param time The number of milliseconds to wait
218: * @return Connection The connection or null
219: */
220: Connection getConnection(long time) {
221: Connection connection = pool.getConnection(time);
222: if (connection == null) {
223: return null;
224: }
225:
226: try {
227: // we always setAutoCommit(true) for backward compatible with mvnForum
228: connection.setAutoCommit(true);
229: } catch (SQLException e) {
230: log.error("Cannot setAutoCommit", e);
231: }
232: ConnectionWrapper wrapper = new ConnectionWrapper(connection,
233: this );
234: return wrapper;
235: }
236:
237: /**
238: * Closes all open connections.
239: * @return true if the pool is empty and balance
240: * false if the pool has returned some connection to outside
241: */
242: boolean release() {
243: return pool.release();
244: }
245:
246: /**
247: * This inner class represents a connection pool. It creates new
248: * connections on demand, up to a max number if specified.
249: * It also checks to make sure that the connection is still open
250: * before it is returned to a client.
251: */
252: class DBConnectionPool {
253: private int checkedOut = 0;//NOTE: this variable should be changed in synchronized method only
254: private Vector freeConnections = new Vector();
255:
256: private int maxConn = 0;
257: private String password = null;
258: private String URL = null;
259: private String user = null;
260:
261: /**
262: * Creates new connection pool.
263: * NOTE: new an instance of this class is lightweight since it does not create any connections
264: *
265: * @param URL The JDBC URL for the database
266: * @param user The database user, or null
267: * @param password The database user password, or null
268: * @param maxConn The maximal number of connections, or 0 for no limit
269: */
270: public DBConnectionPool(String URL, String user,
271: String password, int maxConn) {
272: this .URL = URL;
273: this .user = user;
274: this .password = password;
275: this .maxConn = maxConn;
276: }
277:
278: /**
279: * Checks in a connection to the pool. Notify other Threads that
280: * may be waiting for a connection.
281: *
282: * @todo: Maybe we dont need notifyAll(); ???
283: *
284: * @param con The connection to check in
285: */
286: synchronized void freeConnection(Connection con) {
287: // Put the connection at the end of the Vector
288: if (con != null) {//make sure that the connection is not null
289: if (checkedOut <= 0) {
290: // this means that connection is open too much
291: // There are 2 cases:
292: // 1. Not get from this connection pool (maybe get directly)
293: // 2. this connection is gotten and then the whole pool is released
294: // In these case, just close the connection
295: try {
296: log
297: .debug("DBConnectionManager: about to close the orphan connection.");
298: con.close();
299: } catch (SQLException ex) {
300: }
301: } else {
302: // Return this connection to the pool
303: // note that we dont have to check if the connection is not connected
304: // this will be check in the getConnection method
305: freeConnections.addElement(con);
306: // FIXME: posible negative value
307: // NOTE: checkOut should never be negative here
308: checkedOut--; // NOTE: this number can be negative (in case connection does not come from the pool)
309: notifyAll(); // can I remove it ???
310: }
311: }
312: }
313:
314: /**
315: * Checks out a connection from the pool. If no free connection
316: * is available, a new connection is created unless the max
317: * number of connections has been reached. If a free connection
318: * has been closed by the database, it's removed from the pool
319: * and this method is called again recursively.
320: */
321: synchronized Connection getConnection() {
322: Connection con = null;
323:
324: while ((freeConnections.size() > 0) && (con == null)) {
325: // Pick the first Connection in the Vector
326: // to get round-robin usage
327: con = (Connection) freeConnections.firstElement();
328: freeConnections.removeElementAt(0);
329: try {
330: if (con.isClosed()) {
331: log
332: .info("Removed bad connection in DBConnectionPool.");
333: con = null; // to make the while loop to continue
334: }
335: } catch (SQLException e) {
336: con = null; // to make the while loop to continue
337: }
338: } // while
339:
340: if (con == null) {// cannot get any connection from the pool
341: if (maxConn == 0 || checkedOut < maxConn) {// maxConn = 0 means unlimited connections
342: con = newConnection();
343: }
344: }
345: if (con != null) {
346: checkedOut++;
347: }
348: return con;
349: }
350:
351: /**
352: * Checks out a connection from the pool. If no free connection
353: * is available, a new connection is created unless the max
354: * number of connections has been reached. If a free connection
355: * has been closed by the database, it's removed from the pool
356: * and this method is called again recursively.
357: * <P>
358: * If no connection is available and the max number has been
359: * reached, this method waits the specified time for one to be
360: * checked in.
361: *
362: * @param timeout The timeout value in milliseconds
363: */
364: /**
365: * Note that this method is not synchronized since it relies on the getConnection(void) method
366: * I also believe that this method SHOULD NOT synchronized because I use #sleep() method
367: * @todo: check if we should synchronize this method and use wait instead of sleep ???
368: */
369: Connection getConnection(long timeout) {
370: long startTime = System.currentTimeMillis();
371: Connection con;
372: while ((con = getConnection()) == null) {
373: long elapsedTime = System.currentTimeMillis()
374: - startTime;
375: if (elapsedTime >= timeout) {
376: // Timeout has expired
377: return null;
378: }
379:
380: long timeToWait = timeout - elapsedTime;
381: if (timeToWait > TIME_BETWEEN_RETRIES)
382: timeToWait = TIME_BETWEEN_RETRIES;// we dont want to wait for more than TIME_BETWEEN_RETRIES second each time
383: try {
384: Thread.sleep(timeToWait);
385: } catch (InterruptedException e) {
386: }
387: }
388: return con;
389: }
390:
391: /**
392: * Closes all available connections.
393: * @return true if the pool is empty and balance
394: * false if the pool has returned some connection to outside
395: */
396: synchronized boolean release() {
397: boolean retValue = true;
398: Enumeration allConnections = freeConnections.elements();
399: while (allConnections.hasMoreElements()) {
400: Connection con = (Connection) allConnections
401: .nextElement();
402: try {
403: con.close();
404: } catch (SQLException e) {
405: log
406: .error("Cannot close connection in DBConnectionPool.");
407: }
408: }
409: freeConnections.removeAllElements();
410: if (checkedOut != 0) {
411: retValue = false;
412: log
413: .warn("DBConnectionManager: the built-in connection pool is not balanced.");
414: }
415: checkedOut = 0;
416: return retValue;
417: }
418:
419: /**
420: * Creates a new connection, using a userid and password
421: * if specified.
422: * @todo: check if this method need synchronized
423: */
424: private Connection newConnection() {
425: Connection con = null;
426: try {
427: if (user == null) {
428: con = DriverManager.getConnection(URL);
429: } else {
430: con = DriverManager.getConnection(URL, user,
431: password);
432: }
433: // Note that we dont need to call setAutoCommit here because we
434: // will call it at DBConnectionManager.getConnection()
435: //con.setAutoCommit(true);//thread 804 by trulore
436: } catch (SQLException e) {
437: log.error(
438: "Cannot create a new connection in DBConnectionPool. URL = "
439: + URL, e);
440: return null;
441: }
442: return con;
443: }
444: }
445:
446: }
|