Java tutorial
/* * 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; } }