0001: /* ====================================================================
0002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
0003: *
0004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * 1. Redistributions of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * 2. Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * 3. The end-user documentation included with the redistribution,
0019: * if any, must include the following acknowledgment:
0020: * "This product includes software developed by Jcorporate Ltd.
0021: * (http://www.jcorporate.com/)."
0022: * Alternately, this acknowledgment may appear in the software itself,
0023: * if and wherever such third-party acknowledgments normally appear.
0024: *
0025: * 4. "Jcorporate" and product names such as "Expresso" must
0026: * not be used to endorse or promote products derived from this
0027: * software without prior written permission. For written permission,
0028: * please contact info@jcorporate.com.
0029: *
0030: * 5. Products derived from this software may not be called "Expresso",
0031: * or other Jcorporate product names; nor may "Expresso" or other
0032: * Jcorporate product names appear in their name, without prior
0033: * written permission of Jcorporate Ltd.
0034: *
0035: * 6. No product derived from this software may compete in the same
0036: * market space, i.e. framework, without prior written permission
0037: * of Jcorporate Ltd. For written permission, please contact
0038: * partners@jcorporate.com.
0039: *
0040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
0044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
0046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0051: * SUCH DAMAGE.
0052: * ====================================================================
0053: *
0054: * This software consists of voluntary contributions made by many
0055: * individuals on behalf of the Jcorporate Ltd. Contributions back
0056: * to the project(s) are encouraged when you make modifications.
0057: * Please send them to support@jcorporate.com. For more information
0058: * on Jcorporate Ltd. and its products, please see
0059: * <http://www.jcorporate.com/>.
0060: *
0061: * Portions of this software are based upon other open source
0062: * products and are subject to their respective licenses.
0063: */
0064:
0065: package com.jcorporate.expresso.core.db;
0066:
0067: import com.jcorporate.expresso.core.dataobjects.PersistenceManager;
0068: import com.jcorporate.expresso.core.db.config.JDBCConfig;
0069: import com.jcorporate.expresso.core.db.config.JNDIConfig;
0070: import com.jcorporate.expresso.core.db.datasource.DSException;
0071: import com.jcorporate.expresso.core.db.datasource.JndiDataSource;
0072: import com.jcorporate.expresso.core.db.exception.ConnectionPoolException;
0073: import com.jcorporate.expresso.core.db.exception.PoolFullException;
0074: import com.jcorporate.expresso.core.misc.ConfigManager;
0075: import com.jcorporate.expresso.core.misc.ConfigurationException;
0076: import com.jcorporate.expresso.core.misc.DateTime;
0077: import com.jcorporate.expresso.core.misc.StringUtil;
0078: import com.jcorporate.expresso.kernel.RootContainerInterface;
0079: import com.jcorporate.expresso.kernel.management.ExpressoRuntimeMap;
0080: import com.jcorporate.expresso.kernel.util.ClassLocator;
0081: import com.jcorporate.expresso.kernel.util.LocatorUtils;
0082: import org.apache.log4j.Logger;
0083:
0084: import java.util.ArrayList;
0085: import java.util.Date;
0086: import java.util.Enumeration;
0087: import java.util.HashMap;
0088: import java.util.Iterator;
0089: import java.util.LinkedList;
0090: import java.util.List;
0091: import java.util.Map;
0092:
0093: /**
0094: * a generic database connection pooling
0095: * object.</p>
0096: * <p>Any object requiring connection to the database can request a connection
0097: * from the connection pool, which will re-use connections and allocate
0098: * new connections as required. </p>
0099: * <p>A connection pool will automatically drop connections that have been idle
0100: * for more than a certain number of seconds when the pool reaches it's
0101: * maximum size & a new connection is required. </p>
0102: * <p>It is the responsibility of the object that requests the connection
0103: * to release it again as soon as possible.</p>
0104: *
0105: * @author Michael Nash
0106: */
0107: public class DBConnectionPool extends Thread {
0108: static private int nextConnectionId = 1;
0109:
0110: /**
0111: * The Log4j log.
0112: */
0113: private static Logger log = Logger
0114: .getLogger(DBConnectionPool.class);
0115:
0116: /*
0117: * Does this particular instance of the pool connect to
0118: * a database which supports transactions?
0119: */
0120: private boolean supportsTransactions = false;
0121:
0122: /**
0123: * Our hash of db pools connecting to other databases
0124: */
0125: static private HashMap otherDBPools = new HashMap();
0126:
0127: /**
0128: * Name of this class
0129: */
0130: private static final String THIS_CLASS = DBConnectionPool.class
0131: .getName();
0132:
0133: /**
0134: * How many milliseconds seconds must a connection be idle before it is released?
0135: */
0136: private long interval = 30 * 1000;
0137:
0138: /**
0139: * Maximum time that a currently unused connection can live before it is recycled. This
0140: * is irregardless of past usage.
0141: */
0142: private long maxttl = interval * 10;
0143:
0144: /**
0145: * The maximum number of retries to attempt to find a connection before
0146: * we throw an exception
0147: */
0148: private static final int GET_CONNECTION_RETRIES = 30;
0149:
0150: /* parameters that we need to make the connection to the database */
0151: private String dbDriverType = null;
0152:
0153: /**
0154: * Database Driver Class
0155: */
0156: private String dbDriver = null;
0157:
0158: /**
0159: * URL to the database
0160: */
0161: private String dbURL = null;
0162:
0163: /**
0164: * Database Connection Format as per the expresso-config.xml
0165: */
0166: private String dbConnectFormat = null;
0167:
0168: /**
0169: * Database Login Name
0170: */
0171: private String dbLogin = null;
0172:
0173: /**
0174: * Password to access the database
0175: */
0176: private String dbPassword = null;
0177:
0178: /**
0179: * Name of this database connection/config key
0180: */
0181: private String dbName = "";
0182:
0183: /**
0184: * we hold a count of how many times we've served up a connection. When
0185: * it reaches some maximum, we check the pool for "old" connections &
0186: * drop 'em
0187: */
0188: private int issuedConnectionsCount = 0;
0189:
0190: /**
0191: * clean pool every N connections
0192: */
0193: private static final int CLEAN_POOL_MAX = 50;
0194:
0195: /**
0196: * start with a max of 6 connections
0197: * as soon as the first connection is made, a setup value is read to give
0198: * the true maximum
0199: */
0200: private int maxPoolSize = 6;
0201:
0202: /**
0203: * Flag to indicate if we've been initialized (e.g. given parameters
0204: * to connect to the database & made the first connection) yet
0205: */
0206: private boolean initialized = false;
0207:
0208: /**
0209: * A vector of all of the possible wildcard characters
0210: */
0211: private ArrayList wildCards = new ArrayList(2);
0212:
0213: /**
0214: * Linked list of available connections to use from the connection pool
0215: */
0216: protected LinkedList available = new LinkedList();
0217:
0218: /**
0219: * Map of connections that are currently is use.
0220: */
0221: protected Map inUse = new HashMap(3);
0222:
0223: /**
0224: * Object that is used as a lock for both available and isUse lists
0225: * in a single object.
0226: */
0227: protected Object poolLock = new Object();
0228:
0229: /**
0230: * object to lock finding current timestamp
0231: */
0232: protected Object timestampLock = new Object();
0233:
0234: /**
0235: * The query to execute against the database that defines a minimal test
0236: * query that doesn't cause the target db to do a lot a work. The purpose
0237: * of this is to test if the connection is still alive.
0238: */
0239: private String testQuery = null;
0240:
0241: /*
0242: * SQL keyword that removes duplicate rows int a query - may be specific to this
0243: * database engine implementation
0244: */
0245: private static String uniqueRowKeyword = "DISTINCT";
0246:
0247: /**
0248: * Limitation syntax database vendor specific optimisation is disabled
0249: * <p/>
0250: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0251: */
0252: public final static int LIMITATION_DISABLED = 0;
0253:
0254: /**
0255: * Insert the limitation syntax after TABLE nomination
0256: * <code>
0257: * SELECT {COLUMNS}... FROM {TABLE}
0258: * <font color="#660066">{limitation-syntax}</font>
0259: * [ WHERE {where_clause}... ]
0260: * [ ORDER BY {order_by_clause}... ]
0261: * </code>
0262: * <p/>
0263: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0264: */
0265: public final static int LIMITATION_AFTER_TABLE = 1;
0266:
0267: /**
0268: * Insert the limitation syntax after WHERE key word
0269: * <code>
0270: * SELECT {COLUMNS}... FROM {TABLE}
0271: * [ WHERE {where_clause}... ]
0272: * AND <font color="#660066">{limitation-syntax}</font>
0273: * [ ORDER BY {order_by_clause}... ]
0274: * </code>
0275: * <p/>
0276: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0277: */
0278: public final static int LIMITATION_AFTER_WHERE = 2;
0279:
0280: /**
0281: * Insert the limitation syntax after ORDER BY key words
0282: * <code>
0283: * SELECT {COLUMNS}... FROM {TABLE}
0284: * [ WHERE {where_clause}... ]
0285: * [ ORDER BY {order_by_clause}... ]
0286: * <font color="#660066">{limitation-syntax}</font>
0287: * </code>
0288: * <p/>
0289: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0290: */
0291: public final static int LIMITATION_AFTER_ORDER_BY = 3;
0292:
0293: /**
0294: * Insert the limitation syntax after TABLE nomination
0295: * <code>
0296: * SELECT <font color="#660066">{limitation-syntax}</font> {COLUMNS}... FROM {TABLE}
0297: * [ WHERE {where_clause}... ]
0298: * [ ORDER BY {order_by_clause}... ]
0299: * </code>
0300: * <p/>
0301: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0302: *
0303: * @since Expresso 4.0
0304: */
0305: public final static int LIMITATION_AFTER_SELECT = 4;
0306:
0307: /**
0308: * Rowset Limitation Optimisation Syntax Position.
0309: * Specifies a where in the SQL command the limitation string should be
0310: * inserted.
0311: * <p/>
0312: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0313: *
0314: * @see #LIMITATION_DISABLED
0315: * @see #LIMITATION_AFTER_TABLE
0316: * @see #LIMITATION_AFTER_WHERE
0317: * @see #LIMITATION_AFTER_ORDER_BY
0318: * @see com.jcorporate.expresso.core.dbobj.DBObject#searchAndRetrieveList()
0319: * @see com.jcorporate.expresso.core.dbobj.DBObject#getOffsetRecord()
0320: * @see com.jcorporate.expresso.core.dbobj.DBObject#setOffsetRecord( int )
0321: * @see com.jcorporate.expresso.core.dbobj.DBObject#setMaxRecords(int))
0322: */
0323: protected int limitationPosition = LIMITATION_DISABLED;
0324:
0325: /**
0326: * Rowset Limitation Optimisation Syntax String.
0327: * Specifies a string to add database query to retrieve
0328: * only a finite number of rows from the <code>ResultSet</code>.
0329: * <p/>
0330: * <p>For example for MYSQL the string should be
0331: * <code>"LIMIT %offset% , %maxrecord%"</code>
0332: * </p>
0333: * <p/>
0334: * <p>For example for ORACLE the string should be
0335: * <code>"ROWNUM >= %offset% AND ROWNUM <=%endrecord%"</code>
0336: * </p>
0337: * <p/>
0338: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0339: *
0340: * @see #limitationPosition
0341: * @see com.jcorporate.expresso.core.dbobj.DBObject#searchAndRetrieveList()
0342: * @see com.jcorporate.expresso.core.dbobj.DBObject#setOffsetRecord( int )
0343: * @see com.jcorporate.expresso.core.dbobj.DBObject#setMaxRecords( int )
0344: */
0345: protected String limitationSyntax = null;
0346:
0347: /**
0348: * Check zero update setting
0349: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
0350: */
0351: protected boolean checkZeroUpdate = false;
0352:
0353: /**
0354: * Class that performs appropriate escaping in the strings being sent
0355: * to the partuclar database.
0356: */
0357: protected EscapeHandler escapeHandler = null;
0358:
0359: /**
0360: * This is a simple tag for when the last time the pool was ever used. If
0361: * it has elapsed the timeout time specified in the setup table, then
0362: * we execute a clean before we try to hand out a connection.
0363: */
0364: protected long lastUsed;
0365:
0366: /**
0367: * JNDI DataSource Context instance to a DataSOurce Pool used inside
0368: * Application server or Web Container
0369: */
0370: private JndiDataSource jndiDS = null;
0371:
0372: /**
0373: * Default Constructor
0374: */
0375: public DBConnectionPool() {
0376: super ();
0377: lastUsed = System.currentTimeMillis();
0378: } /* DBConnectionPool() */
0379:
0380: /**
0381: * Creates a new database connection. It just rather blindly does this without
0382: * regards to database maximums, so it should rarely be used. Normally
0383: * it is used internally by createNewConnection().
0384: *
0385: * @return DBConnection object
0386: * @throws DBException upon error
0387: */
0388: protected DBConnection buildNewConnection() throws DBException {
0389: DBConnection oneConnection = null;
0390: //
0391: //todo FIXME: Refactor so that DBConnection simply takes a ConfigJdbc
0392: //bean object and have it self configure itself.
0393: //
0394: if (dbDriverType.equalsIgnoreCase("datasource")) {
0395: JDBCConfig myJdbc = getJDBCConfig(getDataContext());
0396: oneConnection = new DBConnection(myJdbc);
0397: } else {
0398: oneConnection = new DBConnection(dbDriver, dbURL,
0399: dbConnectFormat);
0400: oneConnection.connect(dbLogin, dbPassword);
0401: }
0402: oneConnection.setDataContext(getDataContext());
0403: oneConnection.setId(nextConnectionId);
0404: nextConnectionId++;
0405: oneConnection.setDescription("New Connection");
0406: return oneConnection;
0407: }
0408:
0409: /**
0410: * Creates a new connection to the data source
0411: *
0412: * @return a newly instantiated and populated DBConnection, or null if maxconnections is exceeded,
0413: * or null if "available" list has items, so that we should not be creating.
0414: * @throws DBException upon error
0415: */
0416: protected synchronized DBConnection createNewConnection()
0417: throws DBException {
0418: DBConnection oneConnection = null;
0419:
0420: //
0421: // check number of existing connections; do not allow more than maxPoolSize
0422: //
0423: int totalConnections = 0;
0424: int avail = 0;
0425: int inuse = 0;
0426: boolean firstConnection = false;
0427: synchronized (poolLock) {
0428: avail = available.size();
0429: inuse = inUse.size();
0430: totalConnections = avail + inuse;
0431: firstConnection = totalConnections == 0;
0432: //
0433: //If there's one that's become available we'd rather try to get
0434: //that one instead.
0435: //
0436: if (avail > 0) {
0437: return null;
0438: }
0439:
0440: if (totalConnections >= this .maxPoolSize) {
0441: return null;
0442: }
0443:
0444: } // sync
0445:
0446: if (log.isInfoEnabled()) {
0447: log.info("Creating new connection Total size: "
0448: + totalConnections + "In Use Size: " + inuse
0449: + " Available: " + avail + " Name: "
0450: + this .getDataContext());
0451: }
0452:
0453: oneConnection = buildNewConnection();
0454: oneConnection.setParentPool(this );
0455:
0456: synchronized (poolLock) {
0457: // another thread could have created a connection
0458: // in the time between when we last peeked at number of connections, so recalc
0459: int totalPools = available.size() + inUse.size();
0460: if (totalPools >= this .maxPoolSize) {
0461: oneConnection.disconnect();
0462: log
0463: .warn("Got too many connections... abandoning one: inUse.size()="
0464: + inUse.size()
0465: + " available.size()="
0466: + available.size());
0467: return null;
0468: } else {
0469: // convention is that a new connection is marked unavailable,
0470: // since we do not want anyone else grabbing it out of the pool
0471: // before we hand it back to the requesting client
0472: oneConnection.setAvailable(false);
0473:
0474: inUse.put(new Integer(oneConnection.getId()),
0475: oneConnection);
0476: }
0477: }
0478:
0479: if (log.isDebugEnabled()) {
0480: log.debug("New connection " + oneConnection.getId()
0481: + " created successfully. " + "Now " + avail
0482: + " connections available in pool '"
0483: + getDataContext() + "', " + inuse
0484: + " currently connected");
0485: }
0486:
0487: /* if we've just initialized the connection pool */
0488: if (firstConnection) {
0489: initialized = true;
0490: supportsTransactions = oneConnection.supportsTransactions();
0491:
0492: JDBCConfig myConfig = getJDBCConfig(getDataContext());
0493:
0494: // Try to get the "limitationPosition" setting from Expresso
0495: // config properties if possible
0496: String limPosStr = StringUtil.notNull(myConfig
0497: .getLimitationPosition());
0498:
0499: if (limPosStr.length() > 0) {
0500: setLimitationPosition(limPosStr);
0501: }
0502:
0503: // Try to get the "limitationSyntax" setting from Expresso
0504: // config properties if possible
0505: String limSynStr = StringUtil.notNull(myConfig
0506: .getLimitationSyntax());
0507:
0508: if (limPosStr.length() > 0) {
0509: setLimitationSyntax(limSynStr);
0510: }
0511:
0512: // Try to get the "unique row keyword" from Expresso
0513: // config properties if possible
0514: String keyword = StringUtil.notNull(myConfig
0515: .getUniqueRowKeyword());
0516:
0517: if (keyword.length() < 1) {
0518: uniqueRowKeyword = "DISTINCT"; // Default SQL keyword
0519: } else {
0520: // todo why do we not set uniqueRowKeyword in this case????? LAH 6/03
0521: // uniqueRowKeyword = keyword;
0522: }
0523:
0524: // Try to get the "check zero update" from Expresso
0525: // config properties if possible
0526: String zeroUpdate = StringUtil.notNull(myConfig
0527: .getCheckZeroUpdate());
0528:
0529: if (zeroUpdate.length() > 0) {
0530: checkZeroUpdate = myConfig.checkZeroUpdate();
0531: }
0532:
0533: // Now call setWildCards to see if any user-specific
0534: // wildcards where loaded in the properties file.
0535: setWildCards();
0536:
0537: }
0538: //Added so that limitation syntax is properly set for the connection
0539: oneConnection.setLimitationPosition(this .limitationPosition);
0540: oneConnection.setLimitationSyntax(this .limitationSyntax);
0541: oneConnection.setEscapeHandler(this .escapeHandler);
0542:
0543: return oneConnection;
0544: }
0545:
0546: /**
0547: * Iterates once through the available connections and cleans them.
0548: */
0549: protected void cleanAvailable() throws ConnectionPoolException {
0550: if (log.isDebugEnabled()) {
0551: log
0552: .debug("Checking available pool for connections to remove");
0553: }
0554:
0555: long startTime = System.currentTimeMillis();
0556: boolean removedConnections = false;
0557: synchronized (poolLock) {
0558:
0559: // unusual iteration allows for removal of list item during iteration
0560: for (int i = 0; i < available.size();) {
0561: DBConnection dbc = (DBConnection) available.get(i);
0562: //
0563: //Check how long it has been since the connection was idle. If
0564: //it has been idle too long, then we need to kill it so we don't
0565: //hand out stale connections in the long run.
0566: //
0567: if (dbc.getCreatedTime() + maxttl < startTime) {
0568: try {
0569: dbc.disconnect();
0570: } catch (Throwable ex) {
0571: log.warn("Error disconnecting", ex);
0572: }
0573:
0574: available.remove(i);
0575: removedConnections = true;
0576: } else {
0577: i++;
0578: }
0579: }
0580:
0581: if (removedConnections) {
0582: poolLock.notify();
0583: }
0584: }
0585:
0586: }
0587:
0588: /**
0589: * Clean the connection pool - see if any connections have
0590: * been idle more than the allowed number of seconds . If so,
0591: * disconnect & de- allocate them
0592: *
0593: * @throws DBException If an error occurs releasing the stale connection
0594: * @throws ConnectionPoolException for other errors relating internally
0595: * to the connectionpool code.
0596: */
0597: public void clean() throws ConnectionPoolException, DBException {
0598:
0599: if (log.isDebugEnabled()) {
0600: log.debug("Checking connection pool for stale connections");
0601: }
0602:
0603: long now = System.currentTimeMillis();
0604: long lastTouched = 0;
0605: DBConnection oneConnection = null;
0606:
0607: ArrayList connectionsToRelease = null;
0608:
0609: /* Vector holding the identifiers of the connection objects */
0610: List connectionsToBeRemoved = new ArrayList();
0611: /* boolean to check whether the connection objects should be removed*/
0612:
0613: synchronized (poolLock) {
0614: for (Iterator it = inUse.values().iterator(); it.hasNext();) {
0615: oneConnection = (DBConnection) it.next();
0616: if (log.isDebugEnabled()) {
0617: log.debug("Checking '"
0618: + oneConnection.getDescription() + "'");
0619: }
0620:
0621: lastTouched = oneConnection.getLastTouched();
0622: long timeOutTime = lastTouched + interval;
0623:
0624: if (now > timeOutTime) {
0625: /* it's been too long */
0626: if (log.isDebugEnabled()) {
0627: log.debug("Closing connection");
0628: }
0629:
0630: if (!oneConnection.getImmortal()) {
0631: if (log.isDebugEnabled()) {
0632: log
0633: .debug("Connection "
0634: + oneConnection
0635: .getDescription()
0636: + " was idle more than "
0637: + (interval / 1000)
0638: + " seconds and was returned to the pool. "
0639: + "Last SQL executed was '"
0640: + oneConnection.getSQL()
0641: + "'");
0642: }
0643:
0644: if (connectionsToRelease == null) {
0645: connectionsToRelease = new ArrayList();
0646: }
0647: connectionsToRelease.add(oneConnection);
0648:
0649: } else {
0650: log.warn("Warning: 'Immortal' Connection "
0651: + oneConnection.getDescription()
0652: + " was idle more than "
0653: + (interval / 1000) + " seconds. "
0654: + "Last SQL executed was '"
0655: + oneConnection.getSQL() + "'");
0656: }
0657: }
0658:
0659: /* The block which was here to release the connections has been moved down
0660: after the iteration */
0661:
0662: /* If the connection is (interval * 3) MINUTES old, then close it */
0663: timeOutTime = lastTouched + (interval * 60 * 3);
0664: if (now > timeOutTime) {
0665:
0666: log
0667: .warn("Connection "
0668: + oneConnection.getDescription()
0669: + " was idle more than "
0670: + (interval / 1000)
0671: + " minutes and was disconnected and removed "
0672: + "from the pool");
0673:
0674: /* remove the elements only after iteration
0675: This is done to avoid the ConcurrentModificationException */
0676:
0677: connectionsToBeRemoved.add(oneConnection);
0678:
0679: }
0680: } /* for each connection in the pool */
0681:
0682: /* Now iterate that vector and remove the connection objects from the hashmap
0683: This is done to avoid the ConcurrentModificationException*/
0684: for (int i = 0; i < connectionsToBeRemoved.size(); i++) {
0685: DBConnection dbconn = (DBConnection) connectionsToBeRemoved
0686: .get(i);
0687: dbconn.disconnect();
0688: dbconn.setAvailable(true);
0689: inUse.remove(new Integer(dbconn.getId()));
0690: }
0691:
0692: /* This is the block which releases the connection and is done after the iteration*/
0693: DBConnection oneToRelease = null;
0694: if (connectionsToRelease != null) {
0695: for (Iterator rl = connectionsToRelease.iterator(); rl
0696: .hasNext();) {
0697:
0698: oneToRelease = (DBConnection) rl.next();
0699: release(oneToRelease);
0700: oneToRelease.clear();
0701: oneToRelease = null;
0702: }
0703: }
0704: } /* End Lock */
0705: } /* clean() */
0706:
0707: /**
0708: * Disconnect all of the current connections. Called when we shut down
0709: */
0710: public synchronized void disconnectAll() throws DBException {
0711: DBConnection oneConnection = null;
0712:
0713: for (Iterator it = getPoolList().iterator(); it.hasNext();) {
0714: oneConnection = (DBConnection) it.next();
0715:
0716: if (!oneConnection.isClosed()) {
0717: try {
0718: oneConnection.disconnect();
0719: } catch (DBException de) {
0720: log
0721: .error(
0722: "Unable to disconnect from "
0723: + "database successfully when clearing connection pool",
0724: de);
0725: }
0726: }
0727: } /* for each connection in the current pool */
0728:
0729: synchronized (poolLock) {
0730: available = new LinkedList();
0731: inUse = new HashMap(3);
0732: }
0733: } /* disconnectAll() */
0734:
0735: /**
0736: * This allows an exclusive updated to be done to a database. (Such as
0737: * drop table) should ensure that no tables are in use when this update
0738: * is executed.
0739: *
0740: * @param theSQL the SQL code to execute.
0741: */
0742: public synchronized void executeExclusiveUpdate(String theSQL)
0743: throws DBException {
0744: this .disconnectAll();
0745:
0746: try {
0747: yield();
0748: sleep(500); //We've got some sort of timing problem with connections closing
0749:
0750: //and being able to execute exclusive. Maybe this will
0751: //help for interbase at least.
0752: } catch (java.lang.InterruptedException ie) {
0753: if (log.isDebugEnabled()) {
0754: log.debug("Interrupted while sleeping");
0755: }
0756: }
0757:
0758: DBConnection dbc = null;
0759:
0760: try {
0761: dbc = this .getConnection();
0762: dbc.executeUpdate(theSQL);
0763: } finally {
0764: if (dbc != null) {
0765: this .release(dbc);
0766: }
0767: }
0768: }
0769:
0770: /**
0771: * Find a connection that is already in the pool that is available.
0772: * If there isn't one, return null
0773: *
0774: * @return The available connection if one is found, else null
0775: */
0776: private DBConnection findExistingConnection() {
0777: DBConnection oneConnection = null;
0778:
0779: try {
0780: while (available.size() > 0) {
0781: synchronized (poolLock) {
0782: if (available.size() == 0) {
0783: return null;
0784: }
0785: oneConnection = (DBConnection) available
0786: .removeFirst();
0787: oneConnection.setAvailable(false);
0788:
0789: /* Check for a dead connection */
0790: if (testQuery != null) {
0791: try {
0792: oneConnection.execute(testQuery);
0793: } catch (DBException de) {
0794: if (log.isDebugEnabled()) {
0795: log
0796: .debug("Test query '"
0797: + testQuery
0798: + "' failed:"
0799: + de.getMessage()
0800: + ", closing connection & trying again");
0801: }
0802: try {
0803: oneConnection.disconnect();
0804: } catch (Exception e) {
0805: log.error(
0806: "Unable to close connection that failed "
0807: + "test query", e);
0808: }
0809:
0810: // This was the cause of a bug because it returns a null while we could have
0811: // more available connections. What we have to do is to continue iterating
0812: // and decrease the number of total pools.
0813: continue;
0814: }
0815: }
0816:
0817: if (oneConnection.isClosed()) {
0818: if (log.isDebugEnabled()) {
0819: log.debug("Dead connection removed "
0820: + "from pool. Was previously '"
0821: + oneConnection.getDescription()
0822: + "'. Currently "
0823: + available.size()
0824: + " connections available, "
0825: + inUse.size()
0826: + " clients connected");
0827: }
0828: try {
0829: oneConnection.disconnect();
0830: } catch (Exception e) {
0831: //Log, but otherwise ignore the exception
0832: log.warn("Error Disconnecting", e);
0833: } finally {
0834: // This was the cause of a bug because it returns a null while we could have
0835: // more available connections. What we have to do is to continue iterating
0836: // and decrease the number of total pools.
0837: oneConnection = null;
0838: }
0839: continue;
0840: }
0841:
0842: //
0843: //New Logic since Expresso 5.1 ea2... If the connection was created
0844: //before the ttl interval, then we discard it and try to
0845: //get a new one.
0846: //
0847: if (oneConnection.getCreatedTime() + this .maxttl < System
0848: .currentTimeMillis()) {
0849: if (log.isInfoEnabled()) {
0850: log
0851: .info("Discarding connection. Timeout since created time reached");
0852: }
0853:
0854: try {
0855: oneConnection.disconnect();
0856: } catch (Exception e) {
0857: //Log, but otherwise ignore the exception
0858: log.warn("Error Disconnecting", e);
0859: } finally {
0860: // This was the cause of a bug because it returns a null while we could have
0861: // more available connections. What we have to do is to continue iterating
0862: // and decrease the number of total pools.
0863: oneConnection = null;
0864: }
0865: continue;
0866:
0867: }
0868:
0869: /* connection wasn't dead or expired all is OK */
0870: if (log.isDebugEnabled()) {
0871: log.debug("Available connection "
0872: + oneConnection.getId()
0873: + " found. Was previously '"
0874: + oneConnection.getDescription()
0875: + "'. Currently " + available.size()
0876: + " connections available, "
0877: + inUse.size() + " clients connected");
0878: }
0879:
0880: inUse.put(new Integer(oneConnection.getId()),
0881: oneConnection);
0882: }
0883:
0884: return oneConnection;
0885:
0886: } /* while */
0887:
0888: } catch (DBException de) {
0889: log.error("Unable to test if connection is closed", de);
0890: }
0891:
0892: return null;
0893: } /* findExistingConnection() */
0894:
0895: /**
0896: * Find an available connection in the default connection pool, if any.
0897: * <p>If none, make a new one if we can.</p>
0898: * <p>Does not set the new connection's description</p>
0899: * This method will sleep for "interval" number of seconds if no
0900: * connections are available and the pool is full. It then tries
0901: * again to find or allocate a connection before failing.
0902: *
0903: * @return DBConnection A database connection ready for use
0904: * @throws DBException If there's an error talking with the Database
0905: * @throws PoolFullException if we're unable to allocate a new connection because
0906: * the pool is full and we still haven't gotten a connection
0907: * @throws ConnectionPoolException for other connection-pool related errors.
0908: * author Yves Henri AMAIZO (Modification)
0909: * author Larry Hamel (Modification)
0910: */
0911: public DBConnection getConnection() throws ConnectionPoolException,
0912: PoolFullException, DBException {
0913: if (!isInitialized()) {
0914: throw new ConnectionPoolException("Connection pool '"
0915: + getDataContext()
0916: + "' is not initialized. Can't get connection.");
0917: }
0918:
0919: //
0920: //Parameter check
0921: //
0922: if (dbDriver == null) {
0923: throw new ConnectionPoolException(
0924: "Cannot make new connection - dbDriver is null");
0925: }
0926: if (dbURL == null) {
0927: throw new ConnectionPoolException(
0928: "Cannot make new connection - dbURL is null");
0929: }
0930: if (dbConnectFormat == null) {
0931: throw new ConnectionPoolException(
0932: "Cannot make new connection - dbConnectFormat is null");
0933: }
0934:
0935: DBConnection oneConnection = null;
0936: int connectionTries = 0;
0937: int numAvail, numInUse;
0938:
0939: while (connectionTries < GET_CONNECTION_RETRIES) {
0940: //
0941: //Moved the periodic clean to here. Will result in more cleans, but
0942: //may clearn out some of the locks we're having.
0943: //
0944: issuedConnectionsCount++;
0945:
0946: /* is it time to clean the pool? */
0947: if (issuedConnectionsCount >= CLEAN_POOL_MAX) {
0948: cleanAvailable();
0949: clean();
0950: issuedConnectionsCount = 0;
0951: // synchronize since lastused is a long
0952: synchronized (timestampLock) {
0953: lastUsed = System.currentTimeMillis();
0954: }
0955: }
0956: /**
0957: * If the connection pool has been all together idle for more than
0958: * the timeout period, we need to attempt a clean anyway, since many
0959: * connections may have already timed out.
0960: */
0961: if (isTimeToClean()) {
0962: cleanAvailable();
0963: clean();
0964: issuedConnectionsCount = 0;
0965: // synchronize since lastused is a long
0966: synchronized (timestampLock) {
0967: lastUsed = System.currentTimeMillis();
0968: }
0969: }
0970:
0971: synchronized (poolLock) {
0972: //
0973: //Ok, now try to find an available connection
0974: //
0975: oneConnection = this .findExistingConnection();
0976: if (oneConnection != null) {
0977: oneConnection.setDataContext(getDataContext());
0978: return oneConnection;
0979: }
0980:
0981: if (isFull()) {
0982: clean();
0983: if (isFull()) {
0984: //Sleep
0985: if (log.isInfoEnabled()) {
0986: log
0987: .info("--------------------------------");
0988: log.info("WARNING: DB Connection Pool '"
0989: + getDataContext() + "' is "
0990: + "full even after clean.");
0991: }
0992:
0993: if (log.isDebugEnabled()) {
0994: dumpDebugInfo();
0995: }
0996:
0997: long currentTime = System.currentTimeMillis();
0998: //
0999: //We wait for the maximum connection timeout divided by
1000: //two. This way we have
1001: //
1002: long waitInterval = interval / 2;
1003: long finaltime = waitInterval + currentTime;
1004: try {
1005: while (finaltime > currentTime && isFull()) {
1006: /* Now wait for a number of seconds, retrying again */
1007: poolLock.wait(waitInterval);
1008:
1009: if (System.currentTimeMillis() >= finaltime
1010: && isFull()) {
1011: //
1012: //Give ourselves a last chance to free something up.
1013: //
1014: clean();
1015: if (isFull()) {
1016: log.error("Pool '"
1017: + getDataContext()
1018: + "' still full");
1019: //We timed out waiting for connection:
1020: throw new PoolFullException(
1021: "Cannot allocate "
1022: + "another database connection. There are already "
1023: + "too many connections in use."
1024: + " Please report this problem to the System "
1025: + "Administrator");
1026: }
1027:
1028: } else {
1029: //
1030: //We got notified. time to try to find an existing connection.
1031: //
1032: oneConnection = findExistingConnection();
1033: }
1034:
1035: if (oneConnection != null) {
1036: if (log.isDebugEnabled()) {
1037: log
1038: .debug("Found existing connection after sleep");
1039: }
1040:
1041: oneConnection
1042: .setDataContext(getDataContext());
1043: return oneConnection;
1044: }
1045:
1046: currentTime = System
1047: .currentTimeMillis();
1048: }
1049: } catch (InterruptedException ie) {
1050: throw new ConnectionPoolException(
1051: "Interrupted while waiting for available connection");
1052: }
1053:
1054: }
1055: }
1056:
1057: // find sizes under sync
1058: numAvail = available.size();
1059: numInUse = inUse.size();
1060: } /* End Synchronized */
1061:
1062: //
1063: // no existing conn if we got here,
1064: // so if we have room to create a connection, then let's create it!
1065: //
1066: if (numAvail + numInUse < this .maxPoolSize) {
1067: //Create a new connection
1068: oneConnection = createNewConnection();
1069:
1070: if (oneConnection != null) {
1071: return oneConnection;
1072: }
1073: }
1074:
1075: //
1076: //OK, this time around, we couldn't create a new connection or
1077: //find an existing connection. This is fine because we're running
1078: //a series of 'controlled' race conditions. So we loop around again
1079: //looking for a connection to grab. first we sleep to reduce CPU
1080: //usage for those cases where the system is
1081: //
1082: if (log.isInfoEnabled()) {
1083: log
1084: .info("Couldn't find or create a new connection for this iteration.");
1085: }
1086:
1087: synchronized (this ) {
1088: connectionTries++;
1089: try {
1090: sleep(1);
1091: } catch (InterruptedException ex) {
1092: log.debug("Interrupted while sleeping", ex);
1093: }
1094: }
1095:
1096: } /* end while */
1097:
1098: throw new PoolFullException(
1099: "Unable to get database connection in "
1100: + GET_CONNECTION_RETRIES + " attempts");
1101:
1102: } /* getConnection() */
1103:
1104: private boolean isTimeToClean() {
1105: long now = System.currentTimeMillis();
1106: boolean isTimeToClean = false;
1107:
1108: // synchronize since lastused is a long
1109: synchronized (timestampLock) {
1110: isTimeToClean = now > this .lastUsed + this .interval;
1111: }
1112: return isTimeToClean;
1113: }
1114:
1115: /**
1116: * Helper function that dumps all the current contents of the inUse
1117: * connections.
1118: */
1119: protected void dumpDebugInfo() {
1120: //
1121: //Extensive Debugging Output if needed
1122: //
1123: if (log.isDebugEnabled()) {
1124: synchronized (poolLock) {
1125: int i = 0;
1126: log.debug("Current contents:");
1127: for (Iterator conns = inUse.values().iterator(); conns
1128: .hasNext();) {
1129: DBConnection oneConn = (DBConnection) conns.next();
1130: i++;
1131: Date dt = new Date(oneConn.getLastTouched());
1132: try {
1133: log.debug("Connection " + i + ":"
1134: + oneConn.getDescription() + ":"
1135: + DateTime.getDateTimeForDB(dt));
1136: } catch (DBException ex) {
1137: log.error("Error rendering date time object",
1138: ex);
1139: }
1140: }
1141:
1142: log.debug("Waiting " + (interval / 2000)
1143: + " seconds for a connection to free up");
1144: }
1145: }
1146:
1147: }
1148:
1149: /**
1150: * Helper function to determine if the pool is full.
1151: *
1152: * @return true if the connection pool is full
1153: */
1154: protected boolean isFull() {
1155: int inuse, avail;
1156: synchronized (poolLock) {
1157: inuse = inUse.size();
1158: avail = available.size();
1159: }
1160: if (log.isDebugEnabled()) {
1161: int totalConnections = avail + inuse;
1162: log.debug("Available connections: " + avail
1163: + " In Use Connections: " + inuse
1164: + " totalConnections " + totalConnections);
1165: }
1166: return (inuse >= maxPoolSize);
1167: }
1168:
1169: /**
1170: * Get a connection from the pool. If the pool does not have any free
1171: * connections (and has not reached it's max size), create a new one.
1172: * <p>This version of getConnection sets the new connections description
1173: * as well, avoiding a seperate call to setDescription </p>
1174: *
1175: * @param connectionDescrip A description of the use of the database
1176: * connection
1177: * @return DBConnection A database connection ready for use, or null
1178: * if no more connections can be allocated
1179: * @throws DBException If the pool is not initialized or the connection
1180: * cannot be established
1181: */
1182: public DBConnection getConnection(String connectionDescrip)
1183: throws DBException {
1184: DBConnection oneConnection = getConnection();
1185: oneConnection.setDescription(connectionDescrip);
1186:
1187: if (log.isDebugEnabled()) {
1188: log.debug("Connection for '" + connectionDescrip
1189: + "' established");
1190: }
1191:
1192: return oneConnection;
1193: } /* getConnection(String) */
1194:
1195: /**
1196: * Return the current database name/config key
1197: *
1198: * @return The db name
1199: */
1200: public String getDBName() {
1201: return dbName;
1202: } /* getDBName() */
1203:
1204: /**
1205: * Return the current database name/config key
1206: *
1207: * @return The db name
1208: */
1209: public String getDataContext() {
1210: return dbName;
1211: } /* getDBName() */
1212:
1213: /**
1214: * Does this database connection support commit/rollback?
1215: *
1216: * @param connName the connection name
1217: * @return true if the db supports transactions
1218: */
1219: static public boolean supportsTransactions(String connName)
1220: throws DBException {
1221: DBConnectionPool myPool = getInstance(connName);
1222:
1223: return myPool.supportsTransactions();
1224: }
1225:
1226: /**
1227: * Does this database connection support commit/rollback?
1228: *
1229: * @return true if the db supports transactions
1230: */
1231: public boolean supportsTransactions() throws DBException {
1232: return supportsTransactions;
1233: }
1234:
1235: /**
1236: * Version of getInstance that can only get an already initialized
1237: * connection pool to an alternate database
1238: *
1239: * @return DBConnectionPool The instance of a connection pool
1240: * @throws DBException If the alternate pool cannot be created,
1241: * for example if there is no connection by the given name
1242: */
1243: static synchronized public DBConnectionPool getInstance(
1244: String dataContext) throws DBException {
1245:
1246: synchronized (DBConnectionPool.otherDBPools) {
1247: if (dataContext == null || dataContext.length() == 0) {
1248: dataContext = DBConnection.DEFAULT_DB_CONTEXT_NAME;
1249: }
1250:
1251: DBConnectionPool altPool = (DBConnectionPool) otherDBPools
1252: .get(dataContext);
1253:
1254: if (altPool == null) {
1255: DBConnectionPool newPool = new DBConnectionPool();
1256: newPool.setDataContext(dataContext);
1257:
1258: JDBCConfig myConfig = DBConnectionPool
1259: .getJDBCConfig(dataContext);
1260:
1261: try {
1262: newPool.setParams(myConfig);
1263: } catch (DBException de) {
1264: throw new ConnectionPoolException(
1265: "Unable to initialize pool for configuration '"
1266: + dataContext + "':"
1267: + de.getMessage(), de);
1268: }
1269:
1270: otherDBPools.put(dataContext, newPool);
1271:
1272: return newPool;
1273: }
1274:
1275: return altPool;
1276: }
1277: } /* getInstance(String) */
1278:
1279: /**
1280: * Retrieve a dumb datasource implementation that is compatible with items such
1281: * as reporting tools, etc.
1282: * <p>Note that performance of the resulting DataSource may be very slow since
1283: * the datasource is a dumb connector, and does NOT consider the connection
1284: * pool (because Expresso, at this time, has no way to register for a java.sql.Connection
1285: * to be removed from the pool when the API programmer closes it)
1286: * </p>
1287: *
1288: * @param dataContext the data context to retrieve the data source for.
1289: * @return
1290: */
1291: public static synchronized javax.sql.DataSource getDataSource(
1292: String dataContext) throws java.sql.SQLException {
1293: try {
1294: return new SimpleDataSource(DBConnectionPool
1295: .getInstance(dataContext));
1296: } catch (DBException ex) {
1297: log.error("Error getting database connection pool", ex);
1298: throw new java.sql.SQLException(ex.getMessage());
1299: }
1300: }
1301:
1302: /**
1303: * Return the entire pool of connections as a Vector
1304: *
1305: * @return ArrayList An array list containing the DBConnection objects
1306: * @throws DBException If the pool cannot be returned
1307: */
1308: public synchronized ArrayList getPoolList() throws DBException {
1309: ArrayList al;
1310: synchronized (poolLock) {
1311: al = new ArrayList(available);
1312: for (Iterator it = inUse.values().iterator(); it.hasNext();) {
1313: al.add(it.next());
1314: }
1315: }
1316:
1317: return al;
1318: }
1319:
1320: public ArrayList getWildCardsList() {
1321: return new ArrayList(wildCards);
1322: }
1323:
1324: /**
1325: * Is this connection pool initialized, or does it require database
1326: * parameters?
1327: *
1328: * @return true if the DBConnectionPool is initialized
1329: */
1330: public boolean isInitialized() {
1331: return initialized;
1332: } /* isInitialized() */
1333:
1334: /**
1335: * Release the given connection
1336: * It is the object requesting the connection's responsibility to call
1337: * this method to release the connection(s) it requested.
1338: *
1339: * @param connectionToRelease The DBConnection to be released back to
1340: * the pool.
1341: */
1342: public void release(DBConnection connectionToRelease) {
1343: if (connectionToRelease == null) {
1344: return;
1345: }
1346: /* We always set a connection that's being returned to the
1347: * pool to auto-commit,
1348: * so that when it's
1349: * used next time it can be assumed to be in autocommit mode
1350:
1351: */
1352: try {
1353: if (!connectionToRelease.getAutoCommit()) {
1354: connectionToRelease.setAutoCommit(true);
1355: }
1356:
1357: connectionToRelease.clear();
1358: } catch (DBException dbe) {
1359: if (log.isDebugEnabled()) {
1360: log.debug("Error setting auto-commit to true", dbe);
1361: }
1362: /* ignore the exception - just means that transactions are not handled */
1363: /* by this db */
1364: }
1365: if (connectionToRelease.isAvailable()) {
1366: return;
1367: }
1368: if (log.isDebugEnabled()) {
1369: log.debug("Releasing connection "
1370: + connectionToRelease.getId() + " '"
1371: + connectionToRelease.getDescription() + "'");
1372: }
1373: synchronized (poolLock) {
1374: if (inUse.remove(new Integer(connectionToRelease.getId())) == null) {
1375: if (log.isDebugEnabled()) {
1376: log.debug("Connection "
1377: + connectionToRelease.getId()
1378: + " was not listed as "
1379: + "in use and could not be released");
1380: }
1381:
1382: return;
1383: }
1384: connectionToRelease.setAvailable(true);
1385: available.add(connectionToRelease);
1386: poolLock.notify();
1387: }
1388:
1389: if (log.isDebugEnabled()) {
1390: log.debug("Connection " + connectionToRelease.getId()
1391: + " '" + connectionToRelease.getDescription()
1392: + "' released back to pool. Now " + inUse.size()
1393: + " connected");
1394: }
1395: } /* release(DBConnection) */
1396:
1397: /**
1398: * Useful for querying the potential of the dbconnection pool's status
1399: *
1400: * @return maximum number of connections allowed.
1401: */
1402: public int getMaxConnections() {
1403: return maxPoolSize;
1404: }
1405:
1406: /**
1407: * Sets the maximum number of connections for this pool
1408: *
1409: * @param newMax The new maximum number of connections
1410: */
1411: public synchronized void setMaxConnections(int newMax)
1412: throws DBException {
1413: maxPoolSize = newMax;
1414: } /* setMaxConnections(int) */
1415:
1416: /**
1417: * Set the current database name/config key
1418: *
1419: * @param newDBName The new dataContext to set for this connection pool
1420: */
1421: protected synchronized void setDBName(String newDBName) {
1422: if (StringUtil.notNull(newDBName).equals("")) {
1423: newDBName = DBConnection.DEFAULT_DB_CONTEXT_NAME;
1424: }
1425:
1426: dbName = newDBName;
1427: } /* setDBName(String) */
1428:
1429: /**
1430: * Set the current database name/config key
1431: *
1432: * @param newDBName The new dataContext to set for this connection pool
1433: */
1434: protected synchronized void setDataContext(String newDBName) {
1435: if (StringUtil.notNull(newDBName).equals("")) {
1436: newDBName = DBConnection.DEFAULT_DB_CONTEXT_NAME;
1437: }
1438:
1439: dbName = newDBName;
1440: } /* setDBName(String) */
1441:
1442: /**
1443: * Set the parameters required to make database connections
1444: * The servlet that gets invoked first passes this info to the connection
1445: * pool from it's arguments, where they are used to create new connections
1446: * as required.
1447: *
1448: * @param theParams The JDBC Config bean as defined by the system configuration
1449: * @throws DBException If any parameters are invalid
1450: * @see com.jcorporate.expresso.core.db.DBConnection
1451: */
1452: private synchronized void setParams(JDBCConfig theParams)
1453: throws DBException {
1454: String myName = (THIS_CLASS + "setParams(String, String, String, String, String)");
1455: dbDriverType = theParams.getDriverType();
1456: dbDriver = theParams.getDriver();
1457: dbURL = theParams.getUrl();
1458: dbConnectFormat = theParams.getConnectFormat();
1459: dbLogin = theParams.getLogin();
1460: dbPassword = theParams.getPassword();
1461:
1462: if (dbDriverType == null) {
1463: dbDriverType = ("manager");
1464: }
1465:
1466: if (dbDriver == null) {
1467: throw new ConnectionPoolException(myName
1468: + ":Database driver name cannot be " + "null");
1469: }
1470: if (dbURL == null) {
1471: throw new ConnectionPoolException(myName
1472: + ":Database URL cannot be null");
1473: }
1474: if (dbConnectFormat == null) {
1475: throw new ConnectionPoolException(myName
1476: + ":Database connection format " + "cannot be null");
1477: }
1478: if (dbLogin == null) {
1479: throw new ConnectionPoolException(myName
1480: + ":Database login cannot be null");
1481: }
1482: if (dbPassword == null) {
1483: throw new ConnectionPoolException(myName
1484: + ":Database password cannot be null");
1485: }
1486:
1487: try {
1488: escapeHandler = (EscapeHandler) ClassLocator.loadClass(
1489: theParams.getEscapeHandler()).newInstance();
1490: } catch (Exception ex) {
1491: log.warn("Error instantiating escape handler "
1492: + theParams.getEscapeHandler());
1493: //Attempt to repair the
1494: //the situation by creating default escape handler
1495: escapeHandler = new DefaultEscapeHandler();
1496: }
1497:
1498: // JNDI DataSource Pool invocation for equivalent to Expresso DBConnectionPool
1499: // Each DBConnectionPool Has Equivalent to JNDI DataSOurce Pool and
1500: // and make mappings between each DataSource connection to DBConnection
1501: // Yves Henri AMAIZO 04/08/2002 02:10
1502: // @revision 1.12
1503: if (dbDriverType.equalsIgnoreCase("datasource")) {
1504: if (this .getJNDIConfig(theParams) == null) {
1505: throw new ConnectionPoolException(myName
1506: + "JNDI info configure for datasource");
1507: }
1508:
1509: try {
1510: jndiDS = new JndiDataSource(this
1511: .getJNDIConfig(theParams), theParams.getUrl());
1512: jndiDS.setupContext();
1513: } catch (DSException dse) {
1514: throw new ConnectionPoolException(myName
1515: + ":Cannot initialize jndi Context Factory");
1516: }
1517: }
1518:
1519: initialized = true;
1520: } /* setParams(String, String, String, String, String) */
1521:
1522: /**
1523: * Set a small query to be used to "test" a connection before it's handed out
1524: *
1525: * @param newTestQuery The string to execute as a test query
1526: */
1527: public synchronized void setTestQuery(String newTestQuery) {
1528: testQuery = newTestQuery;
1529: } /* setTestQuery(String) */
1530:
1531: /**
1532: * Set the number of seconds that a connection must remain idle
1533: * before it is considered "timed out" and cleared. It also sets the
1534: * maximum time-to-live for the connection to ten times the interval.
1535: * <p/>
1536: * although we are setting long values which are , no sync
1537: *
1538: * @param newInterval The new interval value in seconds
1539: */
1540: public synchronized void setTimeOutInterval(int newInterval)
1541: throws DBException {
1542:
1543: if (newInterval < 1) {
1544: String myName = (THIS_CLASS + "setTimeOutInterval(int)");
1545: throw new ConnectionPoolException(myName
1546: + ":Interval must be greater than 0");
1547: }
1548:
1549: interval = (long) newInterval * 1000;
1550: maxttl = interval * 10;
1551: } /* setTimeOutInterval(int) */
1552:
1553: /**
1554: * set wild cards from config, or default if no config definition exists
1555: */
1556: private synchronized void setWildCards() throws DBException {
1557: boolean propsFound = false;
1558:
1559: JDBCConfig myConfig = getJDBCConfig(this .getDataContext());
1560:
1561: for (Enumeration e = myConfig.getWildcards().elements(); e
1562: .hasMoreElements();) {
1563: propsFound = true;
1564: wildCards.add(e.nextElement());
1565: }
1566: /* The user can specify database wildcards in the config files
1567: * which will override
1568: * Note: If there is even just one dbWildCard, none of the defaults
1569: * are used. This is because it is potentially harmful to have bogus
1570: * wildchard characters defined (The LIKE operator will
1571: * be used instead of the = operator in the wrong place,
1572: * and you will not get a match).
1573: */
1574: if (!propsFound) {
1575:
1576: // we did not get any wildcards loaded from the props file...
1577: // load the default wildcards
1578: for (Iterator it = getDefaultWildCards().iterator(); it
1579: .hasNext();) {
1580: wildCards.add(it.next());
1581: }
1582: }
1583: } /* setWildCards(DBConnection) */
1584:
1585: /**
1586: * Close all existing connections & empty the pools
1587: */
1588: public synchronized static void reInitialize() throws DBException {
1589: synchronized (otherDBPools) {
1590:
1591: for (Iterator it = otherDBPools.values().iterator(); it
1592: .hasNext();) {
1593: DBConnectionPool onePool = (DBConnectionPool) it.next();
1594: onePool.disconnectAll();
1595: }
1596:
1597: otherDBPools = new HashMap();
1598: System.gc();
1599: }
1600: } /* reInitialize() */
1601:
1602: /**
1603: * programmatically gets the limitation optimisation insertion position
1604: * <p/>
1605: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
1606: *
1607: * @return an integer code for the limitation syntax position
1608: * @since Expresso 4.0
1609: */
1610: public synchronized int getLimitationPosition() {
1611: return limitationPosition;
1612: }
1613:
1614: /**
1615: * programmatically sets the limitation optimisation insertion position
1616: *
1617: * @param pos the position
1618: * @throws DBException author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
1619: * @since Expresso 4.0
1620: */
1621: public synchronized void setLimitationPosition(int pos)
1622: throws DBException {
1623: if (pos != LIMITATION_DISABLED && pos != LIMITATION_AFTER_TABLE
1624: && pos != LIMITATION_AFTER_WHERE
1625: && pos != LIMITATION_AFTER_ORDER_BY
1626: && pos != LIMITATION_AFTER_SELECT) {
1627: throw new ConnectionPoolException(
1628: "illegal argument for limitation optimisation position");
1629: }
1630:
1631: this .limitationPosition = pos;
1632: }
1633:
1634: /**
1635: * programmatically sets the limitation optimisation insertion position
1636: * as readable string.
1637: *
1638: * @param pos the position as a String
1639: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
1640: * @since Expresso 4.0
1641: */
1642: public synchronized void setLimitationPosition(String pos) {
1643: if (pos.equalsIgnoreCase("LIMITATION_DISABLED")) {
1644: this .limitationPosition = LIMITATION_DISABLED;
1645: } else if (pos.equalsIgnoreCase("LIMITATION_AFTER_TABLE")) {
1646: this .limitationPosition = LIMITATION_AFTER_TABLE;
1647: } else if (pos.equalsIgnoreCase("LIMITATION_AFTER_WHERE")) {
1648: this .limitationPosition = LIMITATION_AFTER_WHERE;
1649: } else if (pos.equalsIgnoreCase("LIMITATION_AFTER_ORDER_BY")) {
1650: this .limitationPosition = LIMITATION_AFTER_ORDER_BY;
1651: } else if (pos.equalsIgnoreCase("LIMITATION_AFTER_SELECT")) {
1652: this .limitationPosition = LIMITATION_AFTER_SELECT;
1653: } else {
1654: log
1655: .warn("DB Object '"
1656: + getClass().getName()
1657: + "' illegal string argument for limitation optimisation position.");
1658: }
1659: }
1660:
1661: /**
1662: * Programmatically gets the limitation syntax string
1663: * <p/>
1664: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
1665: *
1666: * @return the limitation syntax string
1667: * @since Expresso 4.0
1668: */
1669: public synchronized String getLimitationSyntax() {
1670: return limitationSyntax;
1671: }
1672:
1673: /**
1674: * Get the current character escape handler for this class.
1675: *
1676: * @return The <code>EscapeHandler</code> for this data context
1677: * @see com.jcorporate.expresso.core.db.EscapeHandler
1678: * @see com.jcorporate.expresso.core.db.DefaultEscapeHandler
1679: */
1680: public synchronized EscapeHandler getEscapeHandler() {
1681: return this .escapeHandler;
1682: }
1683:
1684: /**
1685: * Programmatically gets the limitation syntax string
1686: *
1687: * @param syntax the new syntax
1688: * <p/>
1689: * author Peter Pilgrim, Thu Jun 21 10:30:59 BST 2001
1690: * @since Expresso 4.0
1691: */
1692: public synchronized void setLimitationSyntax(String syntax) {
1693: this .limitationSyntax = syntax;
1694: }
1695:
1696: /**
1697: * <p>Sets the SQL keyword to remove duplicate records
1698: * from a database query. Most databases use the
1699: * <code>"DISTINCT" </code> keyword
1700: * (which is the default), other databases use
1701: * <code>"UNIQUE"</code>.</p>
1702: * <p/>
1703: * <p><b>Source</b>: An old tatty copy of
1704: * "Introduction to SQL"</p>
1705: *
1706: * @param keyword the SQL keyword
1707: * author Peter Pilgrim
1708: * @see #getDistinctRowsetKeyword()
1709: * @see com.jcorporate.expresso.core.dbobj.DBObject#searchAndRetrieveList()
1710: * @see com.jcorporate.expresso.core.dbobj.DBObject#searchAndRetrieveList( String )
1711: * @since Expresso 4.0
1712: */
1713: public void setDistinctRowsetKeyword(String keyword) {
1714: uniqueRowKeyword = keyword;
1715: } /* setDistinctRowsetKeyword(String) */
1716:
1717: /**
1718: * Gets the SQL keyword to remove duplicate records
1719: * from a database query.
1720: * <p/>
1721: * author Peter Pilgrim
1722: *
1723: * @return The distinct keyword
1724: * @see #setDistinctRowsetKeyword( String )
1725: * @see com.jcorporate.expresso.core.dbobj.DBObject#searchAndRetrieveList()
1726: * @see com.jcorporate.expresso.core.dbobj.DBObject#setFieldDistinct(java.lang.String, boolean))
1727: * @since Expresso 4.0
1728: */
1729: public String getDistinctRowsetKeyword() {
1730: return uniqueRowKeyword;
1731: } /* setDistinctRowsetKeyword() */
1732:
1733: /**
1734: * Sets the check zero update boolean flag for this database connection pool
1735: * author Peter Pilgrim
1736: *
1737: * @param newValue true if you want to check for zero updates
1738: */
1739: public void setCheckZeroUpdate(boolean newValue) {
1740: String myName = (THIS_CLASS + "setCheckZeroUpdate() ");
1741: System.out.println(myName + " newValue:" + newValue);
1742: this .checkZeroUpdate = newValue;
1743: }
1744:
1745: /**
1746: * Gets the check zero update boolean flag for this database connection pool
1747: * author Peter Pilgrim
1748: *
1749: * @return true if Zero updates are checked for
1750: * @since Expresso 4.0
1751: */
1752: public boolean getCheckZeroUpdate() {
1753: return this .checkZeroUpdate;
1754: }
1755:
1756: /**
1757: * Function that retrieves the JNDI config. If this pool is being configured
1758: * by ConfigManager, then we get the old ConfigJNDI version. If we are using
1759: * the new kernel package for configuration, then
1760: *
1761: * @param curConfig The current config that we hold.
1762: * @return a JNDI Configuration object. May be null if none is configured in
1763: * the system.
1764: * @throws ConnectionPoolException upon error getting configuration
1765: */
1766: protected JNDIConfig getJNDIConfig(JDBCConfig curConfig)
1767: throws ConnectionPoolException {
1768: if (curConfig instanceof com.jcorporate.expresso.core.misc.ConfigJdbc) {
1769: return ((com.jcorporate.expresso.core.misc.ConfigJdbc) curConfig)
1770: .getMyJndi();
1771: } else {
1772: RootContainerInterface runtime = ExpressoRuntimeMap
1773: .getDefaultRuntime();
1774: LocatorUtils lc = new LocatorUtils(runtime);
1775: JNDIConfig manager = (JNDIConfig) lc.locateComponent(this
1776: .getDataContext()
1777: + ".PersistenceManager.JNDIConfig");
1778: if (manager == null) {
1779: throw new ConnectionPoolException(
1780: "Unable to locate PersistenceManager for data context: "
1781: + this .getDataContext());
1782: }
1783: return null;
1784: }
1785: }
1786:
1787: /**
1788: * Return the JDBConfig regardless of initialization by kernel package or
1789: * DefaultInit servlet
1790: *
1791: * @param dataContext The name of the data context to retrieve
1792: * @return The filled out JDBCConfig object.
1793: * @throws ConnectionPoolException upon error getting configuration
1794: */
1795: static protected JDBCConfig getJDBCConfig(String dataContext)
1796: throws ConnectionPoolException {
1797: RootContainerInterface runtime = ExpressoRuntimeMap
1798: .getDefaultRuntime();
1799:
1800: //Check if there is a Default runtime installed or not;
1801: if (runtime == null) {
1802: try {
1803: return ConfigManager.getJdbcRequired(dataContext);
1804: } catch (ConfigurationException ex) {
1805: throw new ConnectionPoolException(
1806: "Unable to get Database Configuration Information for context "
1807: + dataContext, ex);
1808: }
1809: } else {
1810: LocatorUtils lc = new LocatorUtils(runtime);
1811: PersistenceManager manager = (PersistenceManager) lc
1812: .locateComponent(dataContext
1813: + ".PersistenceManager");
1814: if (manager == null) {
1815: throw new ConnectionPoolException(
1816: "Unable to locate PersistenceManager component for data context "
1817: + dataContext);
1818: }
1819:
1820: return manager.getDBConfig().getCurrentConfig();
1821: }
1822: }
1823:
1824: /**
1825: * Return a list of default wild card characters.
1826: * This can be used to determine if the search criteria supplied by a
1827: * user has wild-card characters in it or is an exact match.
1828: *
1829: * @return A list of the wild-card characters
1830: */
1831: public ArrayList getDefaultWildCards() {
1832: ArrayList newChars = new ArrayList(4);
1833: newChars.add(("%"));
1834: newChars.add(("_"));
1835: newChars.add(("["));
1836: newChars.add(("]"));
1837:
1838: return newChars;
1839: }
1840: } /* DBConnectionPool */
|