001: /*
002: * XAPool: Open Source XA JDBC Pool
003: * Copyright (C) 2003 Objectweb.org
004: * Initial Developer: Lutris Technologies Inc.
005: * Contact: xapool-public@lists.debian-sf.objectweb.org
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
020: * USA
021: */
022: package org.enhydra.jdbc.standard;
023:
024: import org.enhydra.jdbc.util.Logger;
025: import org.apache.commons.logging.LogFactory;
026:
027: import java.sql.Connection;
028: import java.sql.SQLException;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031: import java.util.Vector;
032: import java.util.Iterator;
033:
034: import javax.naming.Context;
035: import javax.naming.InitialContext;
036: import javax.naming.Name;
037: import javax.naming.NamingException;
038: import javax.naming.Reference;
039: import javax.naming.StringRefAddr;
040:
041: import javax.sql.XAConnection;
042: import javax.sql.XADataSource;
043: import javax.transaction.Status;
044: import javax.transaction.xa.XAException;
045: import javax.transaction.xa.Xid;
046: import javax.transaction.TransactionManager;
047:
048: /**
049: * Data source for creating StandardXAConnections.
050: */
051: public class StandardXADataSource extends
052: StandardConnectionPoolDataSource implements XADataSource {
053:
054: public int minCon; // minimum number of connections
055: public int maxCon; // maximum number of connections
056: public long deadLockMaxWait;
057: // time (in ms) to wait before return an exception
058: Vector freeConnections; // connections not currently associated with an XID
059: Hashtable xidConnections; // connections currently associated with an XID
060: Hashtable deadConnections;
061: // connections which should be discarded when the transaction finishes
062: public int connectionCount = 0; // total number of connections created
063: public long deadLockRetryWait; // time to wait before 2 try of loop
064: transient public TransactionManager transactionManager;
065: private String transactionManagerName;
066:
067: public static final int DEFAULT_MIN_CON = 50;
068: // minimum number of connections
069: public static final int DEFAULT_MAX_CON = 0;
070: // maximum number of connections
071: public static final long DEFAULT_DEADLOCKMAXWAIT = 300000; // 5 minutes
072: public static final int DEFAULT_DEADLOCKRETRYWAIT = 10000; // 10 seconds
073:
074: /**
075: * Constructor
076: */
077: public StandardXADataSource() {
078: super ();
079: minCon = DEFAULT_MIN_CON;
080: maxCon = DEFAULT_MAX_CON;
081: deadLockMaxWait = DEFAULT_DEADLOCKMAXWAIT;
082: deadLockRetryWait = DEFAULT_DEADLOCKRETRYWAIT;
083: freeConnections = new Vector(minCon, 1);
084: // allow a reasonable size for free connections
085: xidConnections = new Hashtable(minCon * 2, 0.5f);
086: // ...and same for used connections
087:
088: log = new Logger(LogFactory.getLog("org.enhydra.jdbc.xapool"));
089: log.debug("StandardXADataSource is created");
090: }
091:
092: public int getConnectionCount() {
093: return connectionCount;
094: }
095:
096: public Hashtable getXidConnections() {
097: return xidConnections;
098: }
099:
100: /**
101: * Creates an XA connection using the default username and password.
102: */
103: public XAConnection getXAConnection() throws SQLException {
104: log
105: .debug("StandardXADataSource:getXAConnection(0) XA connection returned");
106: return getXAConnection(user, password);
107: }
108:
109: /**
110: * Creates an XA connection using the supplied username and password.
111: */
112: public synchronized XAConnection getXAConnection(String user,
113: String password) throws SQLException {
114: log
115: .debug("StandardXADataSource:getXAConnection(user, password)");
116: StandardXAConnection xac = new StandardXAConnection(this , user,
117: password);
118: xac.setTransactionManager(transactionManager);
119: xac.setLogger(log);
120: connectionCount++;
121: return xac;
122: }
123:
124: public void setTransactionManager(TransactionManager tm) {
125: log.debug("StandardXADataSource:setTransactionManager");
126: this .transactionManager = tm;
127: }
128:
129: public TransactionManager getTransactionManager() {
130: return transactionManager;
131: }
132:
133: public void setTransactionManagerName(String tmName) {
134: log.debug("StandardXADataSource:setTransactionManagerName");
135: transactionManagerName = tmName;
136: }
137:
138: public void setUser(String user) {
139: log.debug("StandardXADataSource:setUser");
140: if (((user == null) || (getUser() == null)) ? user != getUser()
141: : !user.equals(getUser())) {
142: super .setUser(user);
143: resetCache();
144: }
145: }
146:
147: public void setPassword(String password) {
148: log.debug("StandardXADataSource:setPassword");
149: if (((password == null) || (getPassword() == null)) ? password != getPassword()
150: : !password.equals(getPassword())) {
151: super .setPassword(password);
152: resetCache();
153: }
154: }
155:
156: public void setUrl(String url) {
157: if (((url == null) || (getUrl() == null)) ? url != getUrl()
158: : !url.equals(getUrl())) {
159: super .setUrl(url);
160: resetCache();
161: }
162: }
163:
164: public void setDriverName(String driverName) throws SQLException {
165: if ((driverName == null && getDriverName() != null)
166: || (!driverName.equals(getDriverName()))) {
167: super .setDriverName(driverName);
168: resetCache();
169: }
170: }
171:
172: private synchronized void resetCache() {
173: log.debug("StandardXADataSource:resetCache");
174: // deadConnections will temporarily hold pointers to the
175: // current ongoing transactions. These will be discarded when
176: // freed
177: deadConnections = (Hashtable) xidConnections.clone();
178: deadConnections.putAll(xidConnections);
179:
180: // now we'll just clear out the freeConnections
181: Enumeration enumeration = freeConnections.elements();
182: while (enumeration.hasMoreElements()) {
183: StandardXAStatefulConnection xasc = (StandardXAStatefulConnection) enumeration
184: .nextElement();
185: try {
186: log
187: .debug("StandardXADataSource:resetCache closing Connection:"
188: + xasc.con);
189: xasc.con.close();
190: } catch (SQLException e) {
191: log
192: .error("StandardXADataSource:resetCache Error closing connection:"
193: + xasc.con);
194: }
195: freeConnections.removeElement(xasc);
196: }
197: }
198:
199: /**
200: * Called when an XA connection gets closed. When they have all
201: * been closed then any remaining physical connections are also
202: * closed.
203: */
204: synchronized void connectionClosed() throws SQLException {
205: log.debug("StandardXADataSource:connectionClosed");
206: connectionCount--; // one more connection closed
207: if (connectionCount == 0) { // if no connections left
208:
209: // Close any connections still associated with XIDs.
210: Enumeration cons = xidConnections.keys();
211: // used to iterate through the used connections
212: while (cons.hasMoreElements()) {
213: // while there are more connections
214: Object key = cons.nextElement(); // get the next connection
215: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) xidConnections
216: .remove(key);
217: if (cur != null) {
218:
219: cur.con.close(); // close the physical connection
220: }
221: // cast to something more convenient
222:
223: log
224: .debug("StandardXADataSource:connectionClosed close physical connection");
225: }
226:
227: Iterator connIterator = freeConnections.iterator();
228: while (connIterator.hasNext()) {
229: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) connIterator
230: .next();
231: cur.con.close();
232: connIterator.remove();
233: log
234: .debug("StandardXADataSource:connectionClosed close any free connections");
235: }
236: }
237: }
238:
239: /**
240: * Returns the number of connections that are either
241: * prepared or heuristically completed.
242: */
243: public int getXidCount() {
244: int count = 0; // the return value
245: Enumeration cons = xidConnections.elements();
246: // used to iterate through the used connections
247: while (cons.hasMoreElements()) { // while there are more connections
248: Object o = cons.nextElement(); // get the next connection
249: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
250: // cast to something more convenient
251: if ((cur.getState() == Status.STATUS_PREPARED) || // if prepared
252: (cur.getState() == Status.STATUS_PREPARING)) {
253: // ...or heuristically committed
254: count++; // one more connection with a valid xid
255: }
256: }
257: log.debug("StandardXADataSource:getXidCount return XidCount=<"
258: + count + ">");
259: return count;
260: }
261:
262: /**
263: * Constructs a list of all prepared connections' xids.
264: */
265: Xid[] recover() {
266: int nodeCount = getXidCount();
267: // get number of connections in transactions
268: Xid[] xids = new Xid[nodeCount]; // create the return array
269: int i = 0; // used as xids index
270: Enumeration cons = xidConnections.elements();
271: // used to iterate through the used connections
272: while (cons.hasMoreElements()) { // while there are more connections
273: Object o = cons.nextElement(); // get the next connection
274: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
275: // cast to something more convenient
276: if ((cur.getState() == Status.STATUS_PREPARED) || // if prepared
277: (cur.getState() == Status.STATUS_PREPARING)) {
278: // ...or heuristically committed
279: xids[i++] = cur.xid; // save in list
280: }
281: }
282: return xids;
283: }
284:
285: /**
286: * Frees a connection to make it eligible for reuse. The free list
287: * is normally a last in, first out list (LIFO). This is efficient.
288: * However, timed out connections are nice to hang onto for error
289: * reporting, so they can be placed at the start. This is less
290: * efficient, but hopefully is a rare occurence.
291: *
292: * Here, no need to verify the number of connections, we remove an
293: * object from the xidConnections to put it in th freeConnections
294: *
295: */
296: public synchronized void freeConnection(Xid id, boolean placeAtStart) {
297: log.debug("StandardXADataSource:freeConnection");
298: Object o = xidConnections.get(id); // lookup the connection by XID
299: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
300: // cast to something more convenient
301: xidConnections.remove(id); // remove connection from in use list
302: log
303: .debug("StandardXADataSource:freeConnection remove id from xidConnections");
304:
305: if (!deadConnections.containsKey(id)) {
306: // if this isn't to be discarded
307: /*
308: try {
309: log.debug("StandardXADataSource:freeConnection setAutoCommit(true):" + cur.id);
310: log.debug("con='"+cur.con.toString()+"'");
311: cur.con.setAutoCommit(acommit);
312: } catch(SQLException e) {
313: log.error("ERROR: Failed while autocommiting a connection: "+e);
314: }
315: */
316: cur.setState(Status.STATUS_NO_TRANSACTION);
317: // set its new internal state
318: if (!freeConnections.contains(cur)) {
319: if (placeAtStart) { // if we want to keep for as long as possible
320: freeConnections.insertElementAt(cur, 0);
321: // then place it at the start of the list
322: } else {
323: freeConnections.addElement(cur);
324: // otherwise it's a LIFO list
325: }
326: }
327: } else {
328: deadConnections.remove(id);
329: try {
330: cur.con.close();
331: } catch (SQLException e) {
332: //ignore
333: }
334: }
335: notify();
336:
337: }
338:
339: /**
340: * Invoked by the timer thread to check all transactions
341: * for timeouts. Returns the time of the next timeout event
342: * after current timeouts have expired.
343: */
344: synchronized long checkTimeouts(long curTime) throws SQLException {
345: //log.debug("StandardXADataSource:checkTimeouts");
346: long nextTimeout = 0; // the earliest non-expired timeout in the list
347: Enumeration cons = xidConnections.elements();
348: // used to iterate through the used connections
349: while (cons.hasMoreElements()) { // while there are more connections
350: Object o = cons.nextElement(); // get the next connection
351: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
352: // cast to something more convenient
353: if ((cur.timeout != 0) && // if connection has a timeout
354: (curTime > cur.timeout)) {
355: // ...and transaction has timed out
356: //log.debug("StandardXADataSource:checkTimeouts connection timeout");
357: cur.con.rollback();
358: // undo everything to do with this transaction
359: cur.timedOut = true; // flag that it has timed out
360: //log.debug(cur.toString()+" timed out");
361: freeConnection(cur.xid, true);
362: // make the connection eligible for reuse
363: // The timed out connection is eligible for reuse. The Xid and timedOut
364: // flag will nevertheless remain valid until it is reallocated to another
365: // global transaction. This gives the TM a *chance* to get a timeout
366: // exception, but we won't hang on to it forever.
367: } else { // transaction has not timed out
368: if (cur.timeout != 0) { // but it has a timeout scheduled
369: if ((cur.timeout < nextTimeout) || // and it's the next timeout to expire
370: (nextTimeout == 0)) {
371: // ...or first timeout we've found
372: nextTimeout = cur.timeout; // set up next timeout
373: }
374: }
375: }
376: }
377: return nextTimeout;
378: }
379:
380: /**
381: * Checks the start of the free list to see if the connection
382: * previously associated with the supplied Xid has timed out.
383: * <P>
384: * Note that this can be an expensive operation as it has to
385: * scan all free connections. so it should only be called in
386: * the event of an error.
387: */
388: synchronized private void checkTimeouts(Xid xid) throws XAException {
389: log.debug("StandardXADataSource:checkTimeouts");
390: for (int i = 0; i < freeConnections.size(); i++) { // check each free connection
391: Object o = freeConnections.elementAt(i); // get next connection
392: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
393: // cast to something more convenient
394: if (!cur.timedOut) { // if it hasn't timed out
395: continue; // skip it
396: }
397: log.debug("StandardXADataSource:checkTimeouts (" + i + "/"
398: + freeConnections.size() + ") xid = " + xid);
399: log.debug("StandardXADataSource:checkTimeouts cur.xid = "
400: + cur.xid);
401: if (xid.equals(cur.xid)) { // if we've found our xid
402: cur.timedOut = false; // cancel time out
403: throw new XAException(XAException.XA_RBTIMEOUT);
404: }
405: }
406: }
407:
408: /**
409: * Returns the connection associated with a given XID.
410: * is reached, the Xid is found or an exception is thrown.
411: */
412: synchronized StandardXAStatefulConnection getConnection(Xid xid,
413: boolean mustFind) throws XAException {
414: log.debug("StandardXADataSource:getConnection (xid=" + xid
415: + ", mustFind=" + mustFind + ")");
416: Object o = xidConnections.get(xid); // lookup the connection by XID
417: log.debug("XID: " + o);
418: StandardXAStatefulConnection cur = (StandardXAStatefulConnection) o;
419: // cast to something more convenient
420: if (mustFind) { // if we expected to find the connection
421: if (cur == null) { // and we didn't
422: log
423: .debug("StandardXADataSource:getConnection (StatefulConnection is null)");
424: checkTimeouts(xid); // see if it's been freed during a timeout
425: throw new XAException(XAException.XAER_NOTA);
426: // not a valid XID
427: }
428: } else { // didn't expect to find the connection
429: if (cur != null) { // but we found it anyway
430: throw new XAException(XAException.XAER_DUPID); // duplicate XID
431: }
432: }
433: log
434: .debug("StandardXADataSource:getConnection return connection associated with a given XID");
435: return cur;
436: }
437:
438: /**
439: * Returns a connection from the free list, removing it
440: * in the process. If none area available then a new
441: * connection is created.
442: */
443: synchronized StandardXAStatefulConnection getFreeConnection()
444: throws SQLException {
445: log.debug("StandardXADataSource:getFreeConnection");
446: StandardXAStatefulConnection cur = null;
447: // this will be the return value
448: int freeCount = freeConnections.size();
449: // get number of free connections
450: if (freeCount == 0) { // if there are no free connections
451: log
452: .debug("StandardXADataSource:getFreeConnection there are no free connections, get a new database connection");
453: Connection con = super .getConnection(user, password);
454: // get a new database connection
455: cur = new StandardXAStatefulConnection(this , con);
456: // make the connection stateful
457: } else {
458: Object o = freeConnections.lastElement(); // get the last element
459: cur = (StandardXAStatefulConnection) o;
460: // cast to something more convenient
461: freeConnections.removeElementAt(freeCount - 1);
462: // remove from free list
463: cur.timeout = 0; // no timeout until start() called
464: cur.timedOut = false; // cancel any time old out
465: }
466: log
467: .debug("StandardXADataSource:getFreeConnection return a connection from the free list");
468:
469: try {
470: log
471: .debug("StandardXADataSource:getFreeConnection setAutoCommit(true)");
472: // changed by cney - was false
473: cur.con.setAutoCommit(true);
474: } catch (SQLException e) {
475: log
476: .error("StandardXADataSource:getFreeConnection ERROR: Failed while autocommiting a connection: "
477: + e);
478: }
479:
480: return cur;
481: }
482:
483: public void closeFreeConnection() {
484: log
485: .debug("StandardXADataSource:closeFreeConnection empty method TBD");
486: }
487:
488: public void setMinCon(int min) {
489: this .minCon = min;
490: }
491:
492: public void setMaxCon(int max) {
493: this .maxCon = max;
494: }
495:
496: public void setDeadLockMaxWait(long deadLock) {
497: this .deadLockMaxWait = deadLock;
498: }
499:
500: public int getMinCon() {
501: return this .minCon;
502: }
503:
504: public int getMaxCon() {
505: return this .maxCon;
506: }
507:
508: public long getDeadLockMaxWait() {
509: return this .deadLockMaxWait;
510: }
511:
512: public int getAllConnections() {
513: return xidConnections.size() + freeConnections.size();
514: }
515:
516: public synchronized void processToWait() throws Exception {
517: log.debug("StandardXADataSource:processToWait");
518: int currentWait = 0;
519:
520: if (maxCon != 0) {
521: while ((getAllConnections() >= maxCon)
522: && (currentWait < getDeadLockMaxWait())) {
523: dump();
524: try {
525: synchronized (this ) {
526: wait(getDeadLockRetryWait());
527: }
528: } catch (InterruptedException e) {
529: log
530: .error("StandardXADataSource:processToWait ERROR: Failed while waiting for an object: "
531: + e);
532: }
533: currentWait += getDeadLockRetryWait();
534: }
535: if (getAllConnections() >= getMaxCon())
536: throw new Exception(
537: "StandardXADataSource:processToWait ERROR : impossible to obtain a new xa connection");
538: }
539: }
540:
541: public void dump() {
542: for (int i = 0; i < freeConnections.size(); i++) {
543: log.debug("freeConnection:<"
544: + freeConnections.elementAt(i).toString() + ">");
545: }
546: for (Enumeration enumeration = xidConnections.elements(); enumeration
547: .hasMoreElements();) {
548: log.debug("xidConnection:<"
549: + enumeration.nextElement().toString() + ">");
550: }
551:
552: }
553:
554: public void setDeadLockRetryWait(long deadLockRetryWait) {
555: this .deadLockRetryWait = deadLockRetryWait;
556: }
557:
558: public long getDeadLockRetryWait() {
559: return this .deadLockRetryWait;
560: }
561:
562: public String toString() {
563: StringBuffer sb = new StringBuffer();
564: sb.append("StandardXADataSource:\n");
565: sb.append(" connection count=<" + this .connectionCount
566: + ">\n");
567: if (deadConnections != null)
568: sb.append(" number of dead connection=<"
569: + this .deadConnections.size() + ">\n");
570: sb.append(" dead lock max wait=<" + this .deadLockMaxWait
571: + ">\n");
572: sb.append(" dead lock retry wait=<"
573: + this .deadLockRetryWait + ">\n");
574: if (driver != null)
575: sb.append(" driver=<" + this .driver.toString() + ">\n");
576: sb.append(" driver name=<" + this .driverName + ">\n");
577: if (freeConnections != null)
578: sb.append(" number of *free* connections=<"
579: + this .freeConnections.size() + ">\n");
580: sb.append(" max con=<" + this .maxCon + ">\n");
581: sb.append(" min con=<" + this .minCon + ">\n");
582: sb.append(" prepared stmt cache size=<"
583: + this .preparedStmtCacheSize + ">\n");
584: sb.append(" transaction manager=<"
585: + this .transactionManager + ">\n");
586: sb.append(" xid connection size=<"
587: + this .xidConnections.size() + ">\n");
588: sb.append(super .toString());
589: return sb.toString();
590: }
591:
592: public Reference getReference() throws NamingException {
593: log
594: .debug("StandardXADataSource:getReference return a reference of the object");
595: Reference ref = super .getReference();
596: ref.add(new StringRefAddr("transactionManagerName",
597: this .transactionManagerName));
598: return ref;
599: }
600:
601: public Object getObjectInstance(Object refObj, Name name,
602: Context nameCtx, Hashtable env) throws Exception {
603:
604: super .getObjectInstance(refObj, name, nameCtx, env);
605: Reference ref = (Reference) refObj;
606: InitialContext ictx = new InitialContext(env);
607: this .setTransactionManagerName((String) ref.get(
608: "transactionManagerName").getContent());
609: if (this .transactionManagerName != null) {
610: try {
611: this .setTransactionManager((TransactionManager) ictx
612: .lookup(this .transactionManagerName));
613: } catch (NamingException e) {
614: // ignore, TransactionManager might be set later enlisting the XAResouce on the Transaction
615: }
616: }
617: log
618: .debug("StandardXADataSource:getObjectInstance: instance created");
619: return this;
620: }
621:
622: }
|