/*
* XAPool: Open Source XA JDBC Pool
* Copyright (C) 2003 Objectweb.org
* Initial Developer: Lutris Technologies Inc.
* Contact: xapool-public@lists.debian-sf.objectweb.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package org.enhydra.jdbc.standard;
import org.enhydra.jdbc.util.Logger;
import org.apache.commons.logging.LogFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.Status;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import javax.transaction.TransactionManager;
/**
* Data source for creating StandardXAConnections.
*/
public class StandardXADataSource
extends StandardConnectionPoolDataSource
implements XADataSource {
public int minCon; // minimum number of connections
public int maxCon; // maximum number of connections
public long deadLockMaxWait;
// time (in ms) to wait before return an exception
Vector freeConnections; // connections not currently associated with an XID
Hashtable xidConnections; // connections currently associated with an XID
Hashtable deadConnections;
// connections which should be discarded when the transaction finishes
public int connectionCount = 0; // total number of connections created
public long deadLockRetryWait; // time to wait before 2 try of loop
transient public TransactionManager transactionManager;
private String transactionManagerName;
public static final int DEFAULT_MIN_CON = 50;
// minimum number of connections
public static final int DEFAULT_MAX_CON = 0;
// maximum number of connections
public static final long DEFAULT_DEADLOCKMAXWAIT = 300000; // 5 minutes
public static final int DEFAULT_DEADLOCKRETRYWAIT = 10000; // 10 seconds
/**
* Constructor
*/
public StandardXADataSource() {
super();
minCon = DEFAULT_MIN_CON;
maxCon = DEFAULT_MAX_CON;
deadLockMaxWait = DEFAULT_DEADLOCKMAXWAIT;
deadLockRetryWait = DEFAULT_DEADLOCKRETRYWAIT;
freeConnections = new Vector(minCon, 1);
// allow a reasonable size for free connections
xidConnections = new Hashtable(minCon * 2, 0.5f);
// ...and same for used connections
log = new Logger(LogFactory.getLog("org.enhydra.jdbc.xapool"));
log.debug("StandardXADataSource is created");
}
public int getConnectionCount() {
return connectionCount;
}
public Hashtable getXidConnections() {
return xidConnections;
}
/**
* Creates an XA connection using the default username and password.
*/
public XAConnection getXAConnection() throws SQLException {
log.debug(
"StandardXADataSource:getXAConnection(0) XA connection returned");
return getXAConnection(user, password);
}
/**
* Creates an XA connection using the supplied username and password.
*/
public synchronized XAConnection getXAConnection(
String user,
String password)
throws SQLException {
log.debug("StandardXADataSource:getXAConnection(user, password)");
StandardXAConnection xac =
new StandardXAConnection(this, user, password);
xac.setTransactionManager(transactionManager);
xac.setLogger(log);
connectionCount++;
return xac;
}
public void setTransactionManager(TransactionManager tm) {
log.debug("StandardXADataSource:setTransactionManager");
this.transactionManager = tm;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManagerName(String tmName) {
log.debug("StandardXADataSource:setTransactionManagerName");
transactionManagerName = tmName;
}
public void setUser(String user) {
log.debug("StandardXADataSource:setUser");
if (((user == null) || (getUser() == null))
? user != getUser()
: !user.equals(getUser())) {
super.setUser(user);
resetCache();
}
}
public void setPassword(String password) {
log.debug("StandardXADataSource:setPassword");
if (((password == null) || (getPassword() == null))
? password != getPassword()
: !password.equals(getPassword())) {
super.setPassword(password);
resetCache();
}
}
public void setUrl(String url) {
if (((url == null) || (getUrl() == null))
? url != getUrl()
: !url.equals(getUrl())) {
super.setUrl(url);
resetCache();
}
}
public void setDriverName(String driverName) throws SQLException {
if ((driverName == null && getDriverName() != null)
|| (!driverName.equals(getDriverName()))) {
super.setDriverName(driverName);
resetCache();
}
}
private synchronized void resetCache() {
log.debug("StandardXADataSource:resetCache");
// deadConnections will temporarily hold pointers to the
// current ongoing transactions. These will be discarded when
// freed
deadConnections = (Hashtable) xidConnections.clone();
deadConnections.putAll(xidConnections);
// now we'll just clear out the freeConnections
Enumeration enumeration = freeConnections.elements();
while (enumeration.hasMoreElements()) {
StandardXAStatefulConnection xasc =
(StandardXAStatefulConnection) enumeration.nextElement();
try {
log.debug(
"StandardXADataSource:resetCache closing Connection:"
+ xasc.con);
xasc.con.close();
} catch (SQLException e) {
log.error(
"StandardXADataSource:resetCache Error closing connection:"
+ xasc.con);
}
freeConnections.removeElement(xasc);
}
}
/**
* Called when an XA connection gets closed. When they have all
* been closed then any remaining physical connections are also
* closed.
*/
synchronized void connectionClosed() throws SQLException {
log.debug("StandardXADataSource:connectionClosed");
connectionCount--; // one more connection closed
if (connectionCount == 0) { // if no connections left
// Close any connections still associated with XIDs.
Enumeration cons = xidConnections.keys();
// used to iterate through the used connections
while (cons.hasMoreElements()) {
// while there are more connections
Object key = cons.nextElement(); // get the next connection
StandardXAStatefulConnection cur =
(StandardXAStatefulConnection)xidConnections.remove(key);
if(cur != null)
{
cur.con.close(); // close the physical connection
}
// cast to something more convenient
log.debug(
"StandardXADataSource:connectionClosed close physical connection");
}
Iterator connIterator = freeConnections.iterator();
while (connIterator.hasNext())
{
StandardXAStatefulConnection cur =(StandardXAStatefulConnection)connIterator.next();
cur.con.close();
connIterator.remove();
log.debug(
"StandardXADataSource:connectionClosed close any free connections");
}
}
}
/**
* Returns the number of connections that are either
* prepared or heuristically completed.
*/
public int getXidCount() {
int count = 0; // the return value
Enumeration cons = xidConnections.elements();
// used to iterate through the used connections
while (cons.hasMoreElements()) { // while there are more connections
Object o = cons.nextElement(); // get the next connection
StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
if ((cur.getState() == Status.STATUS_PREPARED)
|| // if prepared
(
cur.getState() == Status.STATUS_PREPARING)) {
// ...or heuristically committed
count++; // one more connection with a valid xid
}
}
log.debug(
"StandardXADataSource:getXidCount return XidCount=<" + count + ">");
return count;
}
/**
* Constructs a list of all prepared connections' xids.
*/
Xid[] recover() {
int nodeCount = getXidCount();
// get number of connections in transactions
Xid[] xids = new Xid[nodeCount]; // create the return array
int i = 0; // used as xids index
Enumeration cons = xidConnections.elements();
// used to iterate through the used connections
while (cons.hasMoreElements()) { // while there are more connections
Object o = cons.nextElement(); // get the next connection
StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
if ((cur.getState() == Status.STATUS_PREPARED)
|| // if prepared
(
cur.getState() == Status.STATUS_PREPARING)) {
// ...or heuristically committed
xids[i++] = cur.xid; // save in list
}
}
return xids;
}
/**
* Frees a connection to make it eligible for reuse. The free list
* is normally a last in, first out list (LIFO). This is efficient.
* However, timed out connections are nice to hang onto for error
* reporting, so they can be placed at the start. This is less
* efficient, but hopefully is a rare occurence.
*
* Here, no need to verify the number of connections, we remove an
* object from the xidConnections to put it in th freeConnections
*
*/
public synchronized void freeConnection(Xid id, boolean placeAtStart) {
log.debug("StandardXADataSource:freeConnection");
Object o = xidConnections.get(id); // lookup the connection by XID
StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
xidConnections.remove(id); // remove connection from in use list
log.debug(
"StandardXADataSource:freeConnection remove id from xidConnections");
if (!deadConnections.containsKey(id)) {
// if this isn't to be discarded
/*
try {
log.debug("StandardXADataSource:freeConnection setAutoCommit(true):" + cur.id);
log.debug("con='"+cur.con.toString()+"'");
cur.con.setAutoCommit(acommit);
} catch(SQLException e) {
log.error("ERROR: Failed while autocommiting a connection: "+e);
}
*/
cur.setState(Status.STATUS_NO_TRANSACTION);
// set its new internal state
if (!freeConnections.contains(cur)) {
if (placeAtStart) { // if we want to keep for as long as possible
freeConnections.insertElementAt(cur, 0);
// then place it at the start of the list
} else {
freeConnections.addElement(cur);
// otherwise it's a LIFO list
}
}
} else {
deadConnections.remove(id);
try {
cur.con.close();
} catch (SQLException e) {
//ignore
}
}
notify();
}
/**
* Invoked by the timer thread to check all transactions
* for timeouts. Returns the time of the next timeout event
* after current timeouts have expired.
*/
synchronized long checkTimeouts(long curTime) throws SQLException {
//log.debug("StandardXADataSource:checkTimeouts");
long nextTimeout = 0; // the earliest non-expired timeout in the list
Enumeration cons = xidConnections.elements();
// used to iterate through the used connections
while (cons.hasMoreElements()) { // while there are more connections
Object o = cons.nextElement(); // get the next connection
StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
if ((cur.timeout != 0)
&& // if connection has a timeout
(
curTime > cur.timeout)) {
// ...and transaction has timed out
//log.debug("StandardXADataSource:checkTimeouts connection timeout");
cur.con.rollback();
// undo everything to do with this transaction
cur.timedOut = true; // flag that it has timed out
//log.debug(cur.toString()+" timed out");
freeConnection(cur.xid, true);
// make the connection eligible for reuse
// The timed out connection is eligible for reuse. The Xid and timedOut
// flag will nevertheless remain valid until it is reallocated to another
// global transaction. This gives the TM a *chance* to get a timeout
// exception, but we won't hang on to it forever.
} else { // transaction has not timed out
if (cur.timeout != 0) { // but it has a timeout scheduled
if ((cur.timeout < nextTimeout)
|| // and it's the next timeout to expire
(
nextTimeout == 0)) {
// ...or first timeout we've found
nextTimeout = cur.timeout; // set up next timeout
}
}
}
}
return nextTimeout;
}
/**
* Checks the start of the free list to see if the connection
* previously associated with the supplied Xid has timed out.
* <P>
* Note that this can be an expensive operation as it has to
* scan all free connections. so it should only be called in
* the event of an error.
*/
synchronized private void checkTimeouts(Xid xid) throws XAException {
log.debug("StandardXADataSource:checkTimeouts");
for (int i = 0;
i < freeConnections.size();
i++) { // check each free connection
Object o = freeConnections.elementAt(i); // get next connection
StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
if (!cur.timedOut) { // if it hasn't timed out
continue; // skip it
}
log.debug(
"StandardXADataSource:checkTimeouts ("
+ i
+ "/"
+ freeConnections.size()
+ ") xid = "
+ xid);
log.debug(
"StandardXADataSource:checkTimeouts cur.xid = " + cur.xid);
if (xid.equals(cur.xid)) { // if we've found our xid
cur.timedOut = false; // cancel time out
throw new XAException(XAException.XA_RBTIMEOUT);
}
}
}
/**
* Returns the connection associated with a given XID.
* is reached, the Xid is found or an exception is thrown.
*/
synchronized StandardXAStatefulConnection getConnection(
Xid xid,
boolean mustFind)
throws XAException {
log.debug(
"StandardXADataSource:getConnection (xid="
+ xid
+ ", mustFind="
+ mustFind
+ ")");
Object o = xidConnections.get(xid); // lookup the connection by XID
log.debug("XID: " + o);
StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
if (mustFind) { // if we expected to find the connection
if (cur == null) { // and we didn't
log.debug(
"StandardXADataSource:getConnection (StatefulConnection is null)");
checkTimeouts(xid); // see if it's been freed during a timeout
throw new XAException(XAException.XAER_NOTA);
// not a valid XID
}
} else { // didn't expect to find the connection
if (cur != null) { // but we found it anyway
throw new XAException(XAException.XAER_DUPID); // duplicate XID
}
}
log.debug(
"StandardXADataSource:getConnection return connection associated with a given XID");
return cur;
}
/**
* Returns a connection from the free list, removing it
* in the process. If none area available then a new
* connection is created.
*/
synchronized StandardXAStatefulConnection getFreeConnection()
throws SQLException {
log.debug("StandardXADataSource:getFreeConnection");
StandardXAStatefulConnection cur = null;
// this will be the return value
int freeCount = freeConnections.size();
// get number of free connections
if (freeCount == 0) { // if there are no free connections
log.debug(
"StandardXADataSource:getFreeConnection there are no free connections, get a new database connection");
Connection con = super.getConnection(user, password);
// get a new database connection
cur = new StandardXAStatefulConnection(this, con);
// make the connection stateful
} else {
Object o = freeConnections.lastElement(); // get the last element
cur = (StandardXAStatefulConnection) o;
// cast to something more convenient
freeConnections.removeElementAt(freeCount - 1);
// remove from free list
cur.timeout = 0; // no timeout until start() called
cur.timedOut = false; // cancel any time old out
}
log.debug(
"StandardXADataSource:getFreeConnection return a connection from the free list");
try {
log.debug(
"StandardXADataSource:getFreeConnection setAutoCommit(true)");
// changed by cney - was false
cur.con.setAutoCommit(true);
} catch (SQLException e) {
log.error(
"StandardXADataSource:getFreeConnection ERROR: Failed while autocommiting a connection: "
+ e);
}
return cur;
}
public void closeFreeConnection() {
log.debug("StandardXADataSource:closeFreeConnection empty method TBD");
}
public void setMinCon(int min) {
this.minCon = min;
}
public void setMaxCon(int max) {
this.maxCon = max;
}
public void setDeadLockMaxWait(long deadLock) {
this.deadLockMaxWait = deadLock;
}
public int getMinCon() {
return this.minCon;
}
public int getMaxCon() {
return this.maxCon;
}
public long getDeadLockMaxWait() {
return this.deadLockMaxWait;
}
public int getAllConnections() {
return xidConnections.size() + freeConnections.size();
}
public synchronized void processToWait() throws Exception {
log.debug("StandardXADataSource:processToWait");
int currentWait = 0;
if (maxCon != 0) {
while ((getAllConnections() >= maxCon)
&& (currentWait < getDeadLockMaxWait())) {
dump();
try {
synchronized (this) {
wait(getDeadLockRetryWait());
}
} catch (InterruptedException e) {
log.error(
"StandardXADataSource:processToWait ERROR: Failed while waiting for an object: "
+ e);
}
currentWait += getDeadLockRetryWait();
}
if (getAllConnections() >= getMaxCon())
throw new Exception("StandardXADataSource:processToWait ERROR : impossible to obtain a new xa connection");
}
}
public void dump() {
for (int i = 0; i < freeConnections.size(); i++) {
log.debug(
"freeConnection:<"
+ freeConnections.elementAt(i).toString()
+ ">");
}
for (Enumeration enumeration = xidConnections.elements();
enumeration.hasMoreElements();
) {
log.debug("xidConnection:<" + enumeration.nextElement().toString() + ">");
}
}
public void setDeadLockRetryWait(long deadLockRetryWait) {
this.deadLockRetryWait = deadLockRetryWait;
}
public long getDeadLockRetryWait() {
return this.deadLockRetryWait;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("StandardXADataSource:\n");
sb.append(" connection count=<"+this.connectionCount+">\n");
if (deadConnections != null)
sb.append(" number of dead connection=<"+this.deadConnections.size()+">\n");
sb.append(" dead lock max wait=<"+this.deadLockMaxWait+">\n");
sb.append(" dead lock retry wait=<"+this.deadLockRetryWait+">\n");
if (driver != null)
sb.append(" driver=<"+this.driver.toString()+">\n");
sb.append(" driver name=<"+this.driverName+">\n");
if (freeConnections != null)
sb.append(" number of *free* connections=<"+this.freeConnections.size()+">\n");
sb.append(" max con=<"+this.maxCon+">\n");
sb.append(" min con=<"+this.minCon+">\n");
sb.append(" prepared stmt cache size=<"+this.preparedStmtCacheSize+">\n");
sb.append(" transaction manager=<"+this.transactionManager+">\n");
sb.append(" xid connection size=<"+this.xidConnections.size()+">\n");
sb.append(super.toString());
return sb.toString();
}
public Reference getReference() throws NamingException {
log.debug("StandardXADataSource:getReference return a reference of the object");
Reference ref = super.getReference();
ref.add(new StringRefAddr("transactionManagerName", this.transactionManagerName));
return ref;
}
public Object getObjectInstance(
Object refObj,
Name name,
Context nameCtx,
Hashtable env)
throws Exception {
super.getObjectInstance(refObj, name, nameCtx, env);
Reference ref = (Reference) refObj;
InitialContext ictx = new InitialContext(env);
this.setTransactionManagerName((String) ref.get("transactionManagerName").getContent());
if (this.transactionManagerName != null) {
try {
this.setTransactionManager(
(TransactionManager) ictx.lookup(this.transactionManagerName));
} catch (NamingException e) {
// ignore, TransactionManager might be set later enlisting the XAResouce on the Transaction
}
}
log.debug("StandardXADataSource:getObjectInstance: instance created");
return this;
}
}