0001: /*
0002: * JBoss, Home of Professional Open Source.
0003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
0004: * as indicated by the @author tags. See the copyright.txt file in the
0005: * distribution for a full listing of individual contributors.
0006: *
0007: * This is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU Lesser General Public License as
0009: * published by the Free Software Foundation; either version 2.1 of
0010: * the License, or (at your option) any later version.
0011: *
0012: * This software is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this software; if not, write to the Free
0019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
0021: */
0022: package org.jboss.tm;
0023:
0024: import java.util.ArrayList;
0025: import java.util.Collections;
0026: import java.util.HashMap;
0027: import java.util.HashSet;
0028: import java.util.Iterator;
0029: import java.util.Map;
0030: import java.util.Set;
0031:
0032: import javax.resource.spi.work.Work;
0033: import javax.resource.spi.work.WorkCompletedException;
0034: import javax.resource.spi.work.WorkException;
0035: import javax.transaction.HeuristicMixedException;
0036: import javax.transaction.HeuristicRollbackException;
0037: import javax.transaction.RollbackException;
0038: import javax.transaction.Status;
0039: import javax.transaction.Synchronization;
0040: import javax.transaction.SystemException;
0041: import javax.transaction.Transaction;
0042: import javax.transaction.xa.XAException;
0043: import javax.transaction.xa.XAResource;
0044: import javax.transaction.xa.Xid;
0045:
0046: import org.jboss.logging.Logger;
0047: import org.jboss.tm.integrity.TransactionIntegrity;
0048: import org.jboss.util.timeout.Timeout;
0049: import org.jboss.util.timeout.TimeoutFactory;
0050: import org.jboss.util.timeout.TimeoutTarget;
0051:
0052: /**
0053: * Our <code>Transaction</code> implementation.
0054: *
0055: * @see TxManager
0056: *
0057: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Ãberg</a>
0058: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
0059: * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
0060: * @author <a href="mailto:toby.allsopp@peace.com">Toby Allsopp</a>
0061: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
0062: * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
0063: * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
0064: * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
0065: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
0066: * @version $Revision: 57208 $
0067: */
0068: public class TransactionImpl implements Transaction, TimeoutTarget {
0069: // Constants -----------------------------------------------------
0070:
0071: /**
0072: * Code meaning "no heuristics seen",
0073: * must not be XAException.XA_HEURxxx
0074: */
0075: private static final int HEUR_NONE = XAException.XA_RETRY;
0076:
0077: // Resource states
0078: private final static int RS_NEW = 0; // not yet enlisted
0079: private final static int RS_ENLISTED = 1; // enlisted
0080: private final static int RS_SUSPENDED = 2; // suspended
0081: private final static int RS_ENDED = 3; // not associated
0082: private final static int RS_VOTE_READONLY = 4; // voted read-only
0083: private final static int RS_VOTE_OK = 5; // voted ok
0084: private final static int RS_FORGOT = 6; // RM has forgotten
0085:
0086: // Attributes ----------------------------------------------------
0087:
0088: /** Class logger, we don't want a new logger with every transaction. */
0089: private static Logger log = Logger.getLogger(TransactionImpl.class);
0090:
0091: /** True if trace messages should be logged. */
0092: private boolean trace = log.isTraceEnabled();
0093:
0094: /** The ID of this transaction. */
0095: private XidImpl xid;
0096:
0097: /** The global id */
0098: private GlobalId gid;
0099:
0100: private HashSet threads = new HashSet(1);
0101:
0102: private Map transactionLocalMap = Collections
0103: .synchronizedMap(new HashMap());
0104:
0105: private Throwable cause;
0106:
0107: /**
0108: * The synchronizations to call back.
0109: */
0110: private Synchronization[] sync = new Synchronization[3];
0111:
0112: /**
0113: * Size of allocated synchronization array.
0114: */
0115: private int syncAllocSize = 3;
0116:
0117: /**
0118: * Count of synchronizations for this transaction.
0119: */
0120: private int syncCount = 0;
0121:
0122: /**
0123: * A list of the XAResources that have participated in this transaction.
0124: */
0125: private ArrayList resources = new ArrayList(3);
0126:
0127: /**
0128: * The XAResource used in the last resource gambit
0129: */
0130: private Resource lastResource;
0131:
0132: /**
0133: * Flags that it is too late to enlist new resources.
0134: */
0135: private boolean resourcesEnded = false;
0136:
0137: /**
0138: * Last branch id used.
0139: */
0140: private long lastBranchId = 0;
0141:
0142: /**
0143: * Status of this transaction.
0144: */
0145: private int status;
0146:
0147: /**
0148: * The heuristics status of this transaction.
0149: */
0150: private int heuristicCode = HEUR_NONE;
0151:
0152: /**
0153: * The time when this transaction was started.
0154: */
0155: private long start;
0156:
0157: /**
0158: * The timeout handle for this transaction.
0159: */
0160: private Timeout timeout;
0161:
0162: /**
0163: * Timeout in millisecs
0164: */
0165: private long timeoutPeriod;
0166:
0167: /**
0168: * Mutex for thread-safety. This should only be changed in the
0169: * <code>lock()</code> and <code>unlock()</code> methods.
0170: */
0171: private Thread locked = null;
0172:
0173: /**
0174: * The lock depth
0175: */
0176: private int lockDepth = 0;
0177:
0178: /** Any current work associated with the transaction */
0179: private Work work;
0180:
0181: /**
0182: * Flags that we are done with this transaction and that it can be reused.
0183: */
0184: private boolean done = false;
0185:
0186: // Static --------------------------------------------------------
0187:
0188: /**
0189: * Factory for Xid instances of specified class.
0190: * This is set from the <code>TransactionManagerService</code>
0191: * MBean.
0192: */
0193: static XidFactoryMBean xidFactory;
0194:
0195: static TransactionManagerService txManagerService;
0196:
0197: /** The timeout factory */
0198: static TimeoutFactory timeoutFactory = TimeoutFactory
0199: .getSingleton();
0200:
0201: /**
0202: * This static code is only present for testing purposes so a
0203: * tm can be usable without a lot of setup.
0204: */
0205: static void defaultXidFactory() {
0206: if (xidFactory == null)
0207: xidFactory = new XidFactory();
0208: }
0209:
0210: // Constructors --------------------------------------------------
0211:
0212: TransactionImpl(long timeout) {
0213: xid = xidFactory.newXid();
0214: gid = xid.getTrulyGlobalId();
0215:
0216: status = Status.STATUS_ACTIVE;
0217:
0218: start = System.currentTimeMillis();
0219: this .timeout = timeoutFactory.createTimeout(start + timeout,
0220: this );
0221: this .timeoutPeriod = timeout;
0222: if (trace)
0223: log.trace("Created new instance for tx=" + toString());
0224: }
0225:
0226: TransactionImpl(GlobalId gid, long timeout) {
0227: this .gid = gid;
0228: xid = xidFactory.newXid();
0229:
0230: status = Status.STATUS_ACTIVE;
0231:
0232: start = System.currentTimeMillis();
0233: this .timeout = timeoutFactory.createTimeout(start + timeout,
0234: this );
0235: this .timeoutPeriod = timeout;
0236: if (trace)
0237: log.trace("Created new instance for tx=" + toString());
0238: }
0239:
0240: // Implements TimeoutTarget --------------------------------------
0241:
0242: /**
0243: * Called when our timeout expires.
0244: */
0245: public void timedOut(Timeout timeout) {
0246: lock();
0247: try {
0248:
0249: log.warn("Transaction " + toString() + " timed out."
0250: + " status=" + getStringStatus(status));
0251:
0252: if (this .timeout == null)
0253: return; // Don't race with timeout cancellation.
0254: this .timeout = null;
0255:
0256: switch (status) {
0257: case Status.STATUS_ROLLEDBACK:
0258: case Status.STATUS_COMMITTED:
0259: case Status.STATUS_NO_TRANSACTION:
0260: return; // Transaction done.
0261:
0262: case Status.STATUS_ROLLING_BACK:
0263: return; // Will be done shortly.
0264:
0265: case Status.STATUS_COMMITTING:
0266: // This is _very_ bad:
0267: // We are in the second commit phase, and have decided
0268: // to commit, but now we get a timeout and should rollback.
0269: // So we end up with a mixed decision.
0270: gotHeuristic(null, XAException.XA_HEURMIX);
0271: status = Status.STATUS_MARKED_ROLLBACK;
0272: return; // commit will fail
0273:
0274: case Status.STATUS_PREPARED:
0275: // This is bad:
0276: // We are done with the first phase, and are persistifying
0277: // our decision. Fortunately this case is currently never
0278: // hit, as we do not release the lock between the two phases.
0279: case Status.STATUS_ACTIVE:
0280: status = Status.STATUS_MARKED_ROLLBACK;
0281: // fall through..
0282: case Status.STATUS_MARKED_ROLLBACK:
0283: // don't rollback for now, this messes up with the TxInterceptor.
0284: interruptThreads();
0285: return;
0286:
0287: case Status.STATUS_PREPARING:
0288: status = Status.STATUS_MARKED_ROLLBACK;
0289: return; // commit will fail
0290:
0291: default:
0292: log.warn("Unknown status at timeout, tx=" + toString());
0293: return;
0294: }
0295: } finally {
0296: unlock();
0297: }
0298: }
0299:
0300: // Implements Transaction ----------------------------------------
0301:
0302: public void commit() throws RollbackException,
0303: HeuristicMixedException, HeuristicRollbackException,
0304: java.lang.SecurityException,
0305: java.lang.IllegalStateException, SystemException {
0306: lock();
0307: try {
0308: if (trace)
0309: log.trace("Committing, tx=" + this + ", status="
0310: + getStringStatus(status));
0311:
0312: beforePrepare();
0313:
0314: if (status == Status.STATUS_ACTIVE) {
0315: switch (getCommitStrategy()) {
0316: case 0:
0317: // Zero phase commit is really fast ;-)
0318: if (trace)
0319: log.trace("Zero phase commit " + this
0320: + ": No resources.");
0321: status = Status.STATUS_COMMITTED;
0322: break;
0323: case 1:
0324: // One phase commit
0325: if (trace)
0326: log.trace("One phase commit " + this
0327: + ": One resource.");
0328: commitResources(true);
0329: break;
0330: default:
0331: // Two phase commit
0332: if (trace)
0333: log.trace("Two phase commit " + this
0334: + ": Many resources.");
0335:
0336: if (!prepareResources()) {
0337: boolean commitDecision = status == Status.STATUS_PREPARED
0338: && (heuristicCode == HEUR_NONE || heuristicCode == XAException.XA_HEURCOM);
0339:
0340: // TODO: Save decision to stable storage for recovery
0341: // after system crash.
0342:
0343: if (commitDecision)
0344: commitResources(false);
0345: } else
0346: status = Status.STATUS_COMMITTED; // all was read-only
0347: }
0348: }
0349:
0350: if (status != Status.STATUS_COMMITTED) {
0351: Throwable causedByThrowable = cause;
0352: rollbackResources();
0353: completeTransaction();
0354:
0355: // throw jboss rollback exception with the saved off cause
0356: throw new JBossRollbackException(
0357: "Unable to commit, tx=" + toString()
0358: + " status=" + getStringStatus(status),
0359: causedByThrowable);
0360: }
0361:
0362: completeTransaction();
0363: checkHeuristics();
0364:
0365: if (trace)
0366: log.trace("Committed OK, tx=" + this );
0367:
0368: } finally {
0369: unlock();
0370: }
0371: }
0372:
0373: public void rollback() throws java.lang.IllegalStateException,
0374: java.lang.SecurityException, SystemException {
0375: lock();
0376: try {
0377:
0378: if (trace)
0379: log.trace("rollback(): Entered, tx=" + toString()
0380: + " status=" + getStringStatus(status));
0381:
0382: checkWork();
0383:
0384: switch (status) {
0385: case Status.STATUS_ACTIVE:
0386: status = Status.STATUS_MARKED_ROLLBACK;
0387: // fall through..
0388: case Status.STATUS_MARKED_ROLLBACK:
0389: endResources();
0390: rollbackResources();
0391: completeTransaction();
0392: // Cannot throw heuristic exception, so we just have to
0393: // clear the heuristics without reporting.
0394: heuristicCode = HEUR_NONE;
0395: return;
0396: case Status.STATUS_PREPARING:
0397: // Set status to avoid race with prepareResources().
0398: status = Status.STATUS_MARKED_ROLLBACK;
0399: return; // commit() will do rollback.
0400: default:
0401: throw new IllegalStateException("Cannot rollback(), "
0402: + "tx=" + toString() + " status="
0403: + getStringStatus(status));
0404: }
0405: } finally {
0406: Thread.interrupted();// clear timeout that did an interrupt
0407: unlock();
0408: }
0409: }
0410:
0411: public boolean delistResource(XAResource xaRes, int flag)
0412: throws java.lang.IllegalStateException, SystemException {
0413: if (xaRes == null)
0414: throw new IllegalArgumentException("null xaRes tx=" + this );
0415: if (flag != XAResource.TMSUCCESS
0416: && flag != XAResource.TMSUSPEND
0417: && flag != XAResource.TMFAIL)
0418: throw new IllegalArgumentException("Bad flag: " + flag
0419: + " tx=" + this );
0420:
0421: lock();
0422: try {
0423: if (trace)
0424: log.trace("delistResource(): Entered, tx=" + toString()
0425: + " status=" + getStringStatus(status));
0426:
0427: Resource resource = findResource(xaRes);
0428: if (resource == null)
0429: throw new IllegalArgumentException(
0430: "xaRes not enlisted " + xaRes);
0431:
0432: switch (status) {
0433: case Status.STATUS_ACTIVE:
0434: case Status.STATUS_MARKED_ROLLBACK:
0435: break;
0436: case Status.STATUS_PREPARING:
0437: throw new IllegalStateException(
0438: "Already started preparing. " + this );
0439: case Status.STATUS_ROLLING_BACK:
0440: throw new IllegalStateException(
0441: "Already started rolling back. " + this );
0442: case Status.STATUS_PREPARED:
0443: throw new IllegalStateException("Already prepared. "
0444: + this );
0445: case Status.STATUS_COMMITTING:
0446: throw new IllegalStateException(
0447: "Already started committing. " + this );
0448: case Status.STATUS_COMMITTED:
0449: throw new IllegalStateException("Already committed. "
0450: + this );
0451: case Status.STATUS_ROLLEDBACK:
0452: throw new IllegalStateException("Already rolled back. "
0453: + this );
0454: case Status.STATUS_NO_TRANSACTION:
0455: throw new IllegalStateException("No transaction. "
0456: + this );
0457: case Status.STATUS_UNKNOWN:
0458: throw new IllegalStateException("Unknown state " + this );
0459: default:
0460: throw new IllegalStateException("Illegal status: "
0461: + getStringStatus(status) + " tx=" + this );
0462: }
0463:
0464: try {
0465: return resource.delistResource(xaRes, flag);
0466: } catch (XAException xae) {
0467: logXAException(xae);
0468: status = Status.STATUS_MARKED_ROLLBACK;
0469: cause = xae;
0470: return false;
0471: }
0472: } finally {
0473: unlock();
0474: }
0475: }
0476:
0477: public boolean enlistResource(XAResource xaRes)
0478: throws RollbackException, java.lang.IllegalStateException,
0479: SystemException {
0480: if (xaRes == null)
0481: throw new IllegalArgumentException("null xaRes tx=" + this );
0482:
0483: lock();
0484: try {
0485:
0486: if (trace)
0487: log.trace("enlistResource(): Entered, tx=" + toString()
0488: + " status=" + getStringStatus(status)
0489: + " xaRes=" + xaRes);
0490:
0491: switch (status) {
0492: case Status.STATUS_ACTIVE:
0493: case Status.STATUS_PREPARING:
0494: break;
0495: case Status.STATUS_PREPARED:
0496: throw new IllegalStateException("Already prepared. "
0497: + this );
0498: case Status.STATUS_COMMITTING:
0499: throw new IllegalStateException(
0500: "Already started committing. " + this );
0501: case Status.STATUS_COMMITTED:
0502: throw new IllegalStateException("Already committed. "
0503: + this );
0504: case Status.STATUS_MARKED_ROLLBACK:
0505: throw new RollbackException(
0506: "Already marked for rollback " + this );
0507: case Status.STATUS_ROLLING_BACK:
0508: throw new RollbackException(
0509: "Already started rolling back. " + this );
0510: case Status.STATUS_ROLLEDBACK:
0511: throw new RollbackException("Already rolled back. "
0512: + this );
0513: case Status.STATUS_NO_TRANSACTION:
0514: throw new IllegalStateException("No transaction. "
0515: + this );
0516: case Status.STATUS_UNKNOWN:
0517: throw new IllegalStateException("Unknown state " + this );
0518: default:
0519: throw new IllegalStateException("Illegal status: "
0520: + getStringStatus(status) + " tx=" + this );
0521: }
0522:
0523: if (resourcesEnded)
0524: throw new IllegalStateException(
0525: "Too late to enlist resources " + this );
0526:
0527: // Add resource
0528: try {
0529: Resource resource = findResource(xaRes);
0530:
0531: // Existing resource
0532: if (resource != null) {
0533: if (resource.isEnlisted()) {
0534: if (trace)
0535: log.trace("Already enlisted: tx="
0536: + toString() + " status="
0537: + getStringStatus(status)
0538: + " xaRes=" + xaRes);
0539: return true; // already enlisted
0540: }
0541: if (resource.isDelisted(xaRes))
0542: // this is a resource that returns false on all calls to
0543: // isSameRM. Further, the last resource enlisted has
0544: // already been delisted, so it is time to enlist it again.
0545: resource = null;
0546: else
0547: return resource.startResource();
0548: }
0549:
0550: resource = findResourceManager(xaRes);
0551: if (resource != null) {
0552: // The xaRes is new. We register the xaRes with the Xid
0553: // that the RM has previously seen from this transaction,
0554: // and note that it has the same RM.
0555: resource = addResource(xaRes, resource.getXid(),
0556: resource);
0557: return resource.startResource();
0558: }
0559:
0560: // New resource and new RM: Create a new transaction branch.
0561: resource = addResource(xaRes, createXidBranch(), null);
0562: return resource.startResource();
0563: } catch (XAException xae) {
0564: logXAException(xae);
0565: cause = xae;
0566: return false;
0567: }
0568: } finally {
0569: unlock();
0570: }
0571:
0572: }
0573:
0574: public int getStatus() throws SystemException {
0575: if (done)
0576: return Status.STATUS_NO_TRANSACTION;
0577: return status;
0578: }
0579:
0580: public void registerSynchronization(Synchronization s)
0581: throws RollbackException, java.lang.IllegalStateException,
0582: SystemException {
0583: if (s == null)
0584: throw new IllegalArgumentException("Null synchronization "
0585: + this );
0586:
0587: lock();
0588: try {
0589: if (trace) {
0590: log.trace("registerSynchronization(): Entered, "
0591: + "tx=" + toString() + " status="
0592: + getStringStatus(status));
0593: }
0594:
0595: switch (status) {
0596: case Status.STATUS_ACTIVE:
0597: case Status.STATUS_PREPARING:
0598: break;
0599: case Status.STATUS_PREPARED:
0600: throw new IllegalStateException("Already prepared. "
0601: + this );
0602: case Status.STATUS_COMMITTING:
0603: throw new IllegalStateException(
0604: "Already started committing. " + this );
0605: case Status.STATUS_COMMITTED:
0606: throw new IllegalStateException("Already committed. "
0607: + this );
0608: case Status.STATUS_MARKED_ROLLBACK:
0609: throw new RollbackException(
0610: "Already marked for rollback " + this );
0611: case Status.STATUS_ROLLING_BACK:
0612: throw new RollbackException(
0613: "Already started rolling back. " + this );
0614: case Status.STATUS_ROLLEDBACK:
0615: throw new RollbackException("Already rolled back. "
0616: + this );
0617: case Status.STATUS_NO_TRANSACTION:
0618: throw new IllegalStateException("No transaction. "
0619: + this );
0620: case Status.STATUS_UNKNOWN:
0621: throw new IllegalStateException("Unknown state " + this );
0622: default:
0623: throw new IllegalStateException("Illegal status: "
0624: + getStringStatus(status) + " tx=" + this );
0625: }
0626:
0627: if (syncCount == syncAllocSize) {
0628: // expand table
0629: syncAllocSize = 2 * syncAllocSize;
0630:
0631: Synchronization[] sy = new Synchronization[syncAllocSize];
0632: System.arraycopy(sync, 0, sy, 0, syncCount);
0633: sync = sy;
0634: }
0635: sync[syncCount++] = s;
0636: } finally {
0637: unlock();
0638: }
0639: }
0640:
0641: public void setRollbackOnly()
0642: throws java.lang.IllegalStateException, SystemException {
0643: lock();
0644: try {
0645: if (trace)
0646: log.trace("setRollbackOnly(): Entered, tx="
0647: + toString() + " status="
0648: + getStringStatus(status));
0649:
0650: switch (status) {
0651: case Status.STATUS_ACTIVE:
0652: case Status.STATUS_PREPARING:
0653: case Status.STATUS_PREPARED:
0654: status = Status.STATUS_MARKED_ROLLBACK;
0655: // fall through..
0656: case Status.STATUS_MARKED_ROLLBACK:
0657: case Status.STATUS_ROLLING_BACK:
0658: return;
0659: case Status.STATUS_COMMITTING:
0660: throw new IllegalStateException(
0661: "Already started committing. " + this );
0662: case Status.STATUS_COMMITTED:
0663: throw new IllegalStateException("Already committed. "
0664: + this );
0665: case Status.STATUS_ROLLEDBACK:
0666: throw new IllegalStateException("Already rolled back. "
0667: + this );
0668: case Status.STATUS_NO_TRANSACTION:
0669: throw new IllegalStateException("No transaction. "
0670: + this );
0671: case Status.STATUS_UNKNOWN:
0672: throw new IllegalStateException("Unknown state " + this );
0673: default:
0674: throw new IllegalStateException("Illegal status: "
0675: + getStringStatus(status) + " tx=" + this );
0676: }
0677: } finally {
0678: unlock();
0679: }
0680: }
0681:
0682: // Public --------------------------------------------------------
0683:
0684: public int getAssociatedThreadCount() {
0685: lock();
0686: try {
0687: return threads.size();
0688: } finally {
0689: unlock();
0690: }
0691: }
0692:
0693: public Set getAssociatedThreads() {
0694: lock();
0695: try {
0696: return Collections.unmodifiableSet(threads);
0697: } finally {
0698: unlock();
0699: }
0700: }
0701:
0702: public int hashCode() {
0703: return xid.hashCode();
0704: }
0705:
0706: public String toString() {
0707: return "TransactionImpl:" + xidFactory.toString(xid);
0708: }
0709:
0710: public boolean equals(Object obj) {
0711: if (obj != null && obj instanceof TransactionImpl)
0712: return getLocalIdValue() == (((TransactionImpl) obj)
0713: .getLocalIdValue());
0714: return false;
0715: }
0716:
0717: /**
0718: * Returns the local id of this transaction. The local id is used as
0719: * a transaction propagation context within the JBoss server, and
0720: * in the TxManager for mapping local transaction ids to transactions.
0721: */
0722: public long getLocalIdValue() {
0723: return xid.getLocalIdValue();
0724: }
0725:
0726: /**
0727: * Returns the local id of this transaction. The local id is used as
0728: * a transaction propagation context within the JBoss server, and
0729: * in the TxManager for mapping local transaction ids to transactions.
0730: */
0731: public LocalId getLocalId() {
0732: return xid.getLocalId();
0733: }
0734:
0735: /**
0736: * Returns the global id of this transaction. Ths global id is used in
0737: * the TxManager, which keeps a map from global ids to transactions.
0738: */
0739: public GlobalId getGlobalId() {
0740: return gid;
0741: }
0742:
0743: /**
0744: * Returns the xid of this transaction.
0745: */
0746: public XidImpl getXid() {
0747: return xid;
0748: }
0749:
0750: // Package protected ---------------------------------------------
0751:
0752: void associateCurrentThread() {
0753: Thread.interrupted();
0754: lock();
0755: try {
0756: threads.add(Thread.currentThread());
0757: } finally {
0758: unlock();
0759: }
0760: }
0761:
0762: void disassociateCurrentThread() {
0763: // Just a tidyup, no need to synchronize
0764: if (done) {
0765: threads.remove(Thread.currentThread());
0766: } else {
0767: // Removing the association for an active transaction
0768: lock();
0769: try {
0770: threads.remove(Thread.currentThread());
0771: } finally {
0772: unlock();
0773: }
0774: }
0775: Thread.interrupted();
0776: }
0777:
0778: /**
0779: * Lock this instance.
0780: */
0781: synchronized void lock() {
0782: if (done)
0783: throw new IllegalStateException(
0784: "Transaction has terminated " + this );
0785:
0786: Thread currentThread = Thread.currentThread();
0787: if (locked != null && locked != currentThread) {
0788: log.debug("Lock contention, tx=" + toString()
0789: + " otherThread=" + locked);
0790: //DEBUG Thread.currentThread().dumpStack();
0791:
0792: while (locked != null && locked != currentThread) {
0793: try {
0794: // Wakeup happens when:
0795: // - notify() is called from unlock()
0796: // - notifyAll is called from instanceDone()
0797: wait();
0798: } catch (InterruptedException ex) {
0799: // ignore
0800: }
0801:
0802: if (done)
0803: throw new IllegalStateException(
0804: "Transaction has now terminated " + this );
0805: }
0806: }
0807:
0808: locked = currentThread;
0809: ++lockDepth;
0810: }
0811:
0812: /**
0813: * Unlock this instance.
0814: */
0815: synchronized void unlock() {
0816: Thread currentThread = Thread.currentThread();
0817: if (locked == null || locked != currentThread) {
0818: log.warn("Unlocking, but not locked, tx=" + toString()
0819: + " otherThread=" + locked, new Throwable(
0820: "[Stack trace]"));
0821: } else {
0822: if (--lockDepth == 0) {
0823: locked = null;
0824: notify();
0825: }
0826: }
0827: }
0828:
0829: /**
0830: * Prepare an external transaction
0831: *
0832: * @return XAResource.XA_RDONLY or XAResource.XA_OK
0833: */
0834: int prepare() throws HeuristicMixedException,
0835: HeuristicRollbackException, RollbackException {
0836: lock();
0837: try {
0838: if (trace)
0839: log.trace("Preparing, tx=" + this + ", status="
0840: + getStringStatus(status));
0841:
0842: checkWork();
0843:
0844: beforePrepare();
0845:
0846: if (status == Status.STATUS_ACTIVE) {
0847: switch (getCommitStrategy()) {
0848: case 0: {
0849: // Nothing to do
0850: if (trace)
0851: log.trace("Prepare tx=" + this
0852: + ": No resources.");
0853: status = Status.STATUS_COMMITTED;
0854: completeTransaction();
0855: return XAResource.XA_RDONLY;
0856: }
0857: default: {
0858: // Two phase commit
0859: if (trace)
0860: log.trace("Prepare tx=" + this
0861: + ": Many resources.");
0862:
0863: if (!prepareResources()) {
0864: /*boolean commitDecision =
0865: status == Status.STATUS_PREPARED &&
0866: (heuristicCode == HEUR_NONE ||
0867: heuristicCode == XAException.XA_HEURCOM);*/
0868:
0869: // TODO: Save decision to stable storage for recovery
0870: // after system crash.
0871: } else {
0872: if (trace)
0873: log.trace("Prepared tx=" + this
0874: + ": All readonly.");
0875: status = Status.STATUS_COMMITTED;
0876: completeTransaction();
0877: return XAResource.XA_RDONLY;
0878: }
0879: }
0880: }
0881: }
0882:
0883: if (status != Status.STATUS_PREPARED) {
0884: // save off the cause throwable as Instance done resets it to null
0885: Throwable causedByThrowable = cause;
0886: rollbackResources();
0887: completeTransaction();
0888:
0889: // throw jboss rollback exception with the saved off cause
0890: throw new JBossRollbackException(
0891: "Unable to prepare, tx=" + toString()
0892: + " status=" + getStringStatus(status),
0893: causedByThrowable);
0894: }
0895:
0896: // We are ok to commit
0897: return XAResource.XA_OK;
0898: } finally {
0899: unlock();
0900: }
0901: }
0902:
0903: /**
0904: * Commit an external transaction
0905: *
0906: * @param onePhase whether the commit is one or two phase
0907: */
0908: void commit(boolean onePhase) throws RollbackException,
0909: HeuristicMixedException, HeuristicRollbackException,
0910: SystemException {
0911: checkWork();
0912:
0913: // One phase commit optimization
0914: if (onePhase) {
0915: commit();
0916: return;
0917: }
0918:
0919: // Two phase
0920: lock();
0921: try {
0922: if (trace)
0923: log.trace("Committing two phase, tx=" + this
0924: + ", status=" + getStringStatus(status));
0925:
0926: switch (status) {
0927: case Status.STATUS_PREPARING:
0928: throw new IllegalStateException("Still preparing. "
0929: + this );
0930: case Status.STATUS_ROLLING_BACK:
0931: throw new IllegalStateException(
0932: "Already started rolling back. " + this );
0933: case Status.STATUS_ROLLEDBACK:
0934: instanceDone();
0935: checkHeuristics();
0936: throw new IllegalStateException("Already rolled back. "
0937: + this );
0938: case Status.STATUS_COMMITTING:
0939: throw new IllegalStateException(
0940: "Already started committing. " + this );
0941: case Status.STATUS_COMMITTED:
0942: instanceDone();
0943: checkHeuristics();
0944: throw new IllegalStateException("Already committed. "
0945: + this );
0946: case Status.STATUS_NO_TRANSACTION:
0947: throw new IllegalStateException("No transaction. "
0948: + this );
0949: case Status.STATUS_UNKNOWN:
0950: throw new IllegalStateException("Unknown state " + this );
0951: case Status.STATUS_MARKED_ROLLBACK:
0952: endResources();
0953: rollbackResources();
0954: completeTransaction();
0955: checkHeuristics();
0956: throw new RollbackException(
0957: "Already marked for rollback " + this );
0958: case Status.STATUS_PREPARED:
0959: break;
0960: default:
0961: throw new IllegalStateException("Illegal status: "
0962: + getStringStatus(status) + " tx=" + this );
0963: }
0964:
0965: commitResources(false);
0966:
0967: if (status != Status.STATUS_COMMITTED) {
0968: Throwable causedByThrowable = cause;
0969: rollbackResources();
0970: completeTransaction();
0971:
0972: // throw jboss rollback exception with the saved off cause
0973: throw new JBossRollbackException(
0974: "Unable to commit, tx=" + toString()
0975: + " status=" + getStringStatus(status),
0976: causedByThrowable);
0977: }
0978:
0979: completeTransaction();
0980: checkHeuristics();
0981:
0982: if (trace)
0983: log.trace("Committed OK, tx=" + this );
0984:
0985: } finally {
0986: unlock();
0987: }
0988: }
0989:
0990: /**
0991: * Get the work
0992: *
0993: * @return the work
0994: */
0995: Work getWork() {
0996: return work;
0997: }
0998:
0999: /**
1000: * Set the work
1001: *
1002: * @param work the work
1003: * @throws WorkCompletedException with error code WorkException.TX_CONCURRENT_WORK_DISALLOWED
1004: * when work is already present for the xid or whose completion is in progress, only
1005: * the global part of the xid must be used for this check. Or with error code
1006: * WorkException.TX_RECREATE_FAILED if it is unable to recreate the transaction context
1007: */
1008: void setWork(Work work) throws WorkCompletedException {
1009: lock();
1010: try {
1011: if (work == null) {
1012: this .work = null;
1013: return;
1014: }
1015:
1016: if (status == Status.STATUS_NO_TRANSACTION
1017: || status == Status.STATUS_UNKNOWN)
1018: throw new WorkCompletedException(
1019: "The transaction is not active " + this + ": "
1020: + getStringStatus(status),
1021: WorkException.TX_RECREATE_FAILED);
1022: else if (status != Status.STATUS_ACTIVE)
1023: throw new WorkCompletedException(
1024: "Too late to start work " + this + ": "
1025: + getStringStatus(status),
1026: WorkException.TX_CONCURRENT_WORK_DISALLOWED);
1027: else if (this .work != null)
1028: throw new WorkCompletedException("Already have work "
1029: + this + ": " + this .work,
1030: WorkException.TX_CONCURRENT_WORK_DISALLOWED);
1031:
1032: this .work = work;
1033: } finally {
1034: unlock();
1035: }
1036: }
1037:
1038: /**
1039: * Getter for property done.
1040: */
1041: boolean isDone() {
1042: return done;
1043: }
1044:
1045: // Private -------------------------------------------------------
1046:
1047: /**
1048: * Before prepare
1049: */
1050: private void beforePrepare() throws HeuristicMixedException,
1051: HeuristicRollbackException, RollbackException {
1052: checkIntegrity();
1053:
1054: doBeforeCompletion();
1055:
1056: if (trace)
1057: log.trace("Before completion done, tx=" + this
1058: + ", status=" + getStringStatus(status));
1059:
1060: endResources();
1061: }
1062:
1063: /**
1064: * Check the integrity of the transaction
1065: */
1066: private void checkIntegrity() throws HeuristicMixedException,
1067: HeuristicRollbackException, RollbackException {
1068: // Spec defined checks for the transaction in a valid state
1069: checkBeforeStatus();
1070:
1071: TransactionIntegrity integrity = TxManager.getInstance()
1072: .getTransactionIntegrity();
1073: if (integrity != null) {
1074: // Extra integrity checks
1075: unlock();
1076: try {
1077: integrity.checkTransactionIntegrity(this );
1078: } finally {
1079: lock();
1080: }
1081:
1082: // Recheck the transaction state
1083: checkBeforeStatus();
1084: }
1085: }
1086:
1087: /**
1088: * Check the before status
1089: */
1090: private void checkBeforeStatus() throws HeuristicMixedException,
1091: HeuristicRollbackException, RollbackException {
1092: switch (status) {
1093: case Status.STATUS_PREPARING:
1094: throw new IllegalStateException(
1095: "Already started preparing. " + this );
1096: case Status.STATUS_PREPARED:
1097: throw new IllegalStateException("Already prepared. " + this );
1098: case Status.STATUS_ROLLING_BACK:
1099: throw new IllegalStateException(
1100: "Already started rolling back. " + this );
1101: case Status.STATUS_ROLLEDBACK:
1102: instanceDone();
1103: checkHeuristics();
1104: throw new IllegalStateException("Already rolled back."
1105: + this );
1106: case Status.STATUS_COMMITTING:
1107: throw new IllegalStateException(
1108: "Already started committing. " + this );
1109: case Status.STATUS_COMMITTED:
1110: instanceDone();
1111: checkHeuristics();
1112: throw new IllegalStateException("Already committed. "
1113: + this );
1114: case Status.STATUS_NO_TRANSACTION:
1115: throw new IllegalStateException("No transaction. " + this );
1116: case Status.STATUS_UNKNOWN:
1117: throw new IllegalStateException("Unknown state " + this );
1118: case Status.STATUS_MARKED_ROLLBACK:
1119: endResources();
1120: rollbackResources();
1121: completeTransaction();
1122: checkHeuristics();
1123: throw new RollbackException("Already marked for rollback "
1124: + this );
1125: case Status.STATUS_ACTIVE:
1126: break;
1127: default:
1128: throw new IllegalStateException("Illegal status: "
1129: + getStringStatus(status) + " tx=" + this );
1130: }
1131: }
1132:
1133: /**
1134: * Complete the transaction
1135: */
1136: private void completeTransaction() {
1137: cancelTimeout();
1138: doAfterCompletion();
1139: instanceDone();
1140: }
1141:
1142: /**
1143: * Interrupt all threads involved with transaction
1144: * This is called on timeout
1145: */
1146: private void interruptThreads() {
1147: TxManager manager = TxManager.getInstance();
1148: if (manager.isInterruptThreads()) {
1149: HashSet clone = (HashSet) threads.clone();
1150: threads.clear();
1151: for (Iterator i = clone.iterator(); i.hasNext();) {
1152: Thread thread = (Thread) i.next();
1153: try {
1154: thread.interrupt();
1155: } catch (Throwable ignored) {
1156: if (trace)
1157: log.trace("Ignored error interrupting thread: "
1158: + thread, ignored);
1159: }
1160: }
1161: }
1162: }
1163:
1164: /**
1165: * Return a string representation of the given status code.
1166: */
1167: private String getStringStatus(int status) {
1168: switch (status) {
1169: case Status.STATUS_PREPARING:
1170: return "STATUS_PREPARING";
1171: case Status.STATUS_PREPARED:
1172: return "STATUS_PREPARED";
1173: case Status.STATUS_ROLLING_BACK:
1174: return "STATUS_ROLLING_BACK";
1175: case Status.STATUS_ROLLEDBACK:
1176: return "STATUS_ROLLEDBACK";
1177: case Status.STATUS_COMMITTING:
1178: return "STATUS_COMMITING";
1179: case Status.STATUS_COMMITTED:
1180: return "STATUS_COMMITED";
1181: case Status.STATUS_NO_TRANSACTION:
1182: return "STATUS_NO_TRANSACTION";
1183: case Status.STATUS_UNKNOWN:
1184: return "STATUS_UNKNOWN";
1185: case Status.STATUS_MARKED_ROLLBACK:
1186: return "STATUS_MARKED_ROLLBACK";
1187: case Status.STATUS_ACTIVE:
1188: return "STATUS_ACTIVE";
1189:
1190: default:
1191: return "STATUS_UNKNOWN(" + status + ")";
1192: }
1193: }
1194:
1195: /**
1196: * Return a string representation of the given XA error code.
1197: */
1198: private String getStringXAErrorCode(int errorCode) {
1199: switch (errorCode) {
1200: case XAException.XA_HEURCOM:
1201: return "XA_HEURCOM";
1202: case XAException.XA_HEURHAZ:
1203: return "XA_HEURHAZ";
1204: case XAException.XA_HEURMIX:
1205: return "XA_HEURMIX";
1206: case XAException.XA_HEURRB:
1207: return "XA_HEURRB";
1208:
1209: case XAException.XA_NOMIGRATE:
1210: return "XA_NOMIGRATE";
1211:
1212: case XAException.XA_RBCOMMFAIL:
1213: return "XA_RBCOMMFAIL";
1214: case XAException.XA_RBDEADLOCK:
1215: return "XA_RBDEADLOCK";
1216: case XAException.XA_RBINTEGRITY:
1217: return "XA_RBINTEGRITY";
1218: case XAException.XA_RBOTHER:
1219: return "XA_RBOTHER";
1220: case XAException.XA_RBPROTO:
1221: return "XA_RBPROTO";
1222: case XAException.XA_RBROLLBACK:
1223: return "XA_RBROLLBACK";
1224: case XAException.XA_RBTIMEOUT:
1225: return "XA_RBTIMEOUT";
1226: case XAException.XA_RBTRANSIENT:
1227: return "XA_RBTRANSIENT";
1228:
1229: case XAException.XA_RDONLY:
1230: return "XA_RDONLY";
1231: case XAException.XA_RETRY:
1232: return "XA_RETRY";
1233:
1234: case XAException.XAER_ASYNC:
1235: return "XAER_ASYNC";
1236: case XAException.XAER_DUPID:
1237: return "XAER_DUPID";
1238: case XAException.XAER_INVAL:
1239: return "XAER_INVAL";
1240: case XAException.XAER_NOTA:
1241: return "XAER_NOTA";
1242: case XAException.XAER_OUTSIDE:
1243: return "XAER_OUTSIDE";
1244: case XAException.XAER_PROTO:
1245: return "XAER_PROTO";
1246: case XAException.XAER_RMERR:
1247: return "XAER_RMERR";
1248: case XAException.XAER_RMFAIL:
1249: return "XAER_RMFAIL";
1250:
1251: default:
1252: return "XA_UNKNOWN(" + errorCode + ")";
1253: }
1254: }
1255:
1256: private void logXAException(XAException xae) {
1257: log.warn("XAException: tx=" + toString() + " errorCode="
1258: + getStringXAErrorCode(xae.errorCode), xae);
1259: if (txManagerService != null)
1260: txManagerService.formatXAException(xae, log);
1261: }
1262:
1263: /**
1264: * Mark this transaction as non-existing.
1265: */
1266: private synchronized void instanceDone() {
1267: TxManager manager = TxManager.getInstance();
1268:
1269: if (status == Status.STATUS_COMMITTED)
1270: manager.incCommitCount();
1271: else
1272: manager.incRollbackCount();
1273:
1274: // Clear tables refering to external objects.
1275: // Even if a client holds on to this instance forever, the objects
1276: // that we have referenced may be garbage collected.
1277: sync = null;
1278: resources = null;
1279: transactionLocalMap.clear();
1280: threads.clear();
1281:
1282: // Garbage collection
1283: manager.releaseTransactionImpl(this );
1284:
1285: // Set the status
1286: status = Status.STATUS_NO_TRANSACTION;
1287:
1288: // Notify all threads waiting for the lock.
1289: notifyAll();
1290:
1291: // set the done flag
1292: done = true;
1293: }
1294:
1295: /**
1296: * Cancel the timeout.
1297: * This will release the lock while calling out.
1298: */
1299: private void cancelTimeout() {
1300: if (timeout != null) {
1301: unlock();
1302: try {
1303: timeout.cancel();
1304: } catch (Exception e) {
1305: if (trace)
1306: log.trace("failed to cancel timeout " + this , e);
1307: } finally {
1308: lock();
1309: }
1310: timeout = null;
1311: }
1312: }
1313:
1314: /**
1315: * Return the resource for the given XAResource
1316: */
1317: private Resource findResource(XAResource xaRes) {
1318: // A linear search may seem slow, but please note that
1319: // the number of XA resources registered with a transaction
1320: // are usually low.
1321: // Note: This searches backwards intentionally! It ensures that
1322: // if this resource was enlisted multiple times, then the last one
1323: // will be returned. All others should be in the state RS_ENDED.
1324: // This allows ResourceManagers that always return false from isSameRM
1325: // to be enlisted and delisted multiple times.
1326: for (int idx = resources.size() - 1; idx >= 0; --idx) {
1327: Resource resource = (Resource) resources.get(idx);
1328: if (xaRes == resource.getXAResource())
1329: return resource;
1330: }
1331:
1332: return null;
1333: }
1334:
1335: private Resource findResourceManager(XAResource xaRes)
1336: throws XAException {
1337: for (int i = 0; i < resources.size(); ++i) {
1338: Resource resource = (Resource) resources.get(i);
1339: if (resource.isResourceManager(xaRes))
1340: return resource;
1341: }
1342: return null;
1343: }
1344:
1345: /**
1346: * Add a resource, expanding tables if needed.
1347: *
1348: * @param xaRes The new XA resource to add. It is assumed that the
1349: * resource is not already in the table of XA resources.
1350: * @param branchXid The Xid for the transaction branch that is to
1351: * be used for associating with this resource.
1352: * @param sameRMResource The resource of the first
1353: * XA resource having the same resource manager as
1354: * <code>xaRes</code>, or <code>null</code> if <code>xaRes</code>
1355: * is the first resource seen with this resource manager.
1356: *
1357: * @return the new resource
1358: */
1359: private Resource addResource(XAResource xaRes, Xid branchXid,
1360: Resource sameRMResource) {
1361: Resource resource = new Resource(xaRes, branchXid,
1362: sameRMResource);
1363: resources.add(resource);
1364:
1365: // Remember the first resource that wants the last resource gambit
1366: if (lastResource == null && xaRes instanceof LastResource)
1367: lastResource = resource;
1368:
1369: return resource;
1370: }
1371:
1372: /**
1373: * End Tx association for all resources.
1374: */
1375: private void endResources() {
1376: for (int idx = 0; idx < resources.size(); ++idx) {
1377: Resource resource = (Resource) resources.get(idx);
1378: try {
1379: resource.endResource();
1380: } catch (XAException xae) {
1381: logXAException(xae);
1382: status = Status.STATUS_MARKED_ROLLBACK;
1383: cause = xae;
1384: }
1385: }
1386: resourcesEnded = true; // Too late to enlist new resources.
1387: }
1388:
1389: /**
1390: * Call synchronization <code>beforeCompletion()</code>.
1391: * This will release the lock while calling out.
1392: */
1393: private void doBeforeCompletion() {
1394: unlock();
1395: try {
1396: for (int i = 0; i < syncCount; i++) {
1397: try {
1398: if (trace)
1399: log.trace("calling sync " + i + ", " + sync[i]
1400: + " tx=" + this );
1401:
1402: sync[i].beforeCompletion();
1403: } catch (Throwable t) {
1404: if (trace)
1405: log.trace(
1406: "failed before completion " + sync[i],
1407: t);
1408:
1409: status = Status.STATUS_MARKED_ROLLBACK;
1410:
1411: // save the cause off so the user can inspect it
1412: cause = t;
1413: break;
1414: }
1415: }
1416: } finally {
1417: lock();
1418: }
1419: }
1420:
1421: /**
1422: * Call synchronization <code>afterCompletion()</code>.
1423: * This will release the lock while calling out.
1424: */
1425: private void doAfterCompletion() {
1426: // Assert: Status indicates: Too late to add new synchronizations.
1427: unlock();
1428: try {
1429: for (int i = 0; i < syncCount; i++) {
1430: try {
1431: sync[i].afterCompletion(status);
1432: } catch (Throwable t) {
1433: if (trace)
1434: log.trace("failed after completion " + sync[i],
1435: t);
1436: }
1437: }
1438: } finally {
1439: lock();
1440: }
1441: }
1442:
1443: /**
1444: * We got another heuristic.
1445: *
1446: * Promote <code>heuristicCode</code> if needed and tell
1447: * the resource to forget the heuristic.
1448: * This will release the lock while calling out.
1449: *
1450: * @param resource The resource of the XA resource that got a
1451: * heurictic in our internal tables, or <code>null</code>
1452: * if the heuristic came from here.
1453: * @param code The heuristic code, one of
1454: * <code>XAException.XA_HEURxxx</code>.
1455: */
1456: private void gotHeuristic(Resource resource, int code) {
1457: switch (code) {
1458: case XAException.XA_HEURMIX:
1459: heuristicCode = XAException.XA_HEURMIX;
1460: break;
1461: case XAException.XA_HEURRB:
1462: if (heuristicCode == HEUR_NONE)
1463: heuristicCode = XAException.XA_HEURRB;
1464: else if (heuristicCode == XAException.XA_HEURCOM
1465: || heuristicCode == XAException.XA_HEURHAZ)
1466: heuristicCode = XAException.XA_HEURMIX;
1467: break;
1468: case XAException.XA_HEURCOM:
1469: if (heuristicCode == HEUR_NONE)
1470: heuristicCode = XAException.XA_HEURCOM;
1471: else if (heuristicCode == XAException.XA_HEURRB
1472: || heuristicCode == XAException.XA_HEURHAZ)
1473: heuristicCode = XAException.XA_HEURMIX;
1474: break;
1475: case XAException.XA_HEURHAZ:
1476: if (heuristicCode == HEUR_NONE)
1477: heuristicCode = XAException.XA_HEURHAZ;
1478: else if (heuristicCode == XAException.XA_HEURCOM
1479: || heuristicCode == XAException.XA_HEURRB)
1480: heuristicCode = XAException.XA_HEURMIX;
1481: break;
1482: default:
1483: throw new IllegalArgumentException();
1484: }
1485:
1486: if (resource != null)
1487: resource.forget();
1488: }
1489:
1490: /**
1491: * Check for heuristics, clear and throw exception if any found.
1492: */
1493: private void checkHeuristics() throws HeuristicMixedException,
1494: HeuristicRollbackException {
1495: switch (heuristicCode) {
1496: case XAException.XA_HEURHAZ:
1497: case XAException.XA_HEURMIX:
1498: heuristicCode = HEUR_NONE;
1499: if (trace)
1500: log.trace("Throwing HeuristicMixedException, tx="
1501: + this + "status=" + getStringStatus(status));
1502: throw new HeuristicMixedException();
1503: case XAException.XA_HEURRB:
1504: heuristicCode = HEUR_NONE;
1505: if (trace)
1506: log.trace("Throwing HeuristicRollbackException, tx="
1507: + this + "status=" + getStringStatus(status));
1508: throw new HeuristicRollbackException();
1509: case XAException.XA_HEURCOM:
1510: heuristicCode = HEUR_NONE;
1511: // Why isn't HeuristicCommitException used in JTA ?
1512: // And why define something that is not used ?
1513: // For now we just have to ignore this failure, even if it happened
1514: // on rollback.
1515: if (trace)
1516: log.trace("NOT Throwing HeuristicCommitException, tx="
1517: + this + "status=" + getStringStatus(status));
1518: return;
1519: }
1520: }
1521:
1522: /**
1523: * Prepare all enlisted resources.
1524: * If the first phase of the commit process results in a decision
1525: * to commit the <code>status</code> will be
1526: * <code>Status.STATUS_PREPARED</code> on return.
1527: * Otherwise the <code>status</code> will be
1528: * <code>Status.STATUS_MARKED_ROLLBACK</code> on return.
1529: * This will release the lock while calling out.
1530: *
1531: * @return True iff all resources voted read-only.
1532: */
1533: private boolean prepareResources() {
1534: boolean readOnly = true;
1535:
1536: status = Status.STATUS_PREPARING;
1537:
1538: // Prepare te XAResources
1539: for (int i = 0; i < resources.size(); ++i) {
1540: // Abort prepare on state change.
1541: if (status != Status.STATUS_PREPARING)
1542: return false;
1543:
1544: Resource resource = (Resource) resources.get(i);
1545:
1546: if (resource.isResourceManager() == false)
1547: continue; // This RM already prepared.
1548:
1549: // Ignore the last resource it is done later
1550: if (resource == lastResource)
1551: continue;
1552:
1553: try {
1554: int vote = resource.prepare();
1555:
1556: if (vote == RS_VOTE_OK)
1557: readOnly = false;
1558: else if (vote != RS_VOTE_READONLY) {
1559: // Illegal vote: rollback.
1560: if (trace)
1561: log.trace(
1562: "illegal vote in prepare resources tx="
1563: + this + " resource="
1564: + resource, new Exception());
1565: status = Status.STATUS_MARKED_ROLLBACK;
1566: return false;
1567: }
1568: } catch (XAException e) {
1569: readOnly = false;
1570:
1571: logXAException(e);
1572:
1573: switch (e.errorCode) {
1574: case XAException.XA_HEURCOM:
1575: // Heuristic commit is not that bad when preparing.
1576: // But it means trouble if we have to rollback.
1577: gotHeuristic(resource, e.errorCode);
1578: break;
1579: case XAException.XA_HEURRB:
1580: case XAException.XA_HEURMIX:
1581: case XAException.XA_HEURHAZ:
1582: gotHeuristic(resource, e.errorCode);
1583: if (status == Status.STATUS_PREPARING)
1584: status = Status.STATUS_MARKED_ROLLBACK;
1585: break;
1586: default:
1587: cause = e;
1588: if (status == Status.STATUS_PREPARING)
1589: status = Status.STATUS_MARKED_ROLLBACK;
1590: break;
1591: }
1592: } catch (Throwable t) {
1593: if (trace)
1594: log.trace(
1595: "unhandled throwable in prepareResources "
1596: + this , t);
1597: if (status == Status.STATUS_PREPARING)
1598: status = Status.STATUS_MARKED_ROLLBACK;
1599: cause = t;
1600: }
1601: }
1602:
1603: // Abort prepare on state change.
1604: if (status != Status.STATUS_PREPARING)
1605: return false;
1606:
1607: // Are we doing the last resource gambit?
1608: if (lastResource != null) {
1609: try {
1610: lastResource.prepareLastResource();
1611: lastResource.commit(false);
1612: } catch (XAException e) {
1613: logXAException(e);
1614: switch (e.errorCode) {
1615: case XAException.XA_HEURRB:
1616: case XAException.XA_HEURCOM:
1617: case XAException.XA_HEURMIX:
1618: case XAException.XA_HEURHAZ:
1619: //usually throws an exception, but not for a couple of cases.
1620: gotHeuristic(lastResource, e.errorCode);
1621: if (status == Status.STATUS_PREPARING)
1622: status = Status.STATUS_MARKED_ROLLBACK;
1623: break;
1624: default:
1625: cause = e;
1626: if (status == Status.STATUS_PREPARING)
1627: status = Status.STATUS_MARKED_ROLLBACK;
1628: break;
1629: }
1630: } catch (Throwable t) {
1631: if (trace)
1632: log.trace(
1633: "unhandled throwable in prepareResources "
1634: + this , t);
1635: if (status == Status.STATUS_PREPARING)
1636: status = Status.STATUS_MARKED_ROLLBACK;
1637: cause = t;
1638: }
1639: }
1640:
1641: if (status == Status.STATUS_PREPARING)
1642: status = Status.STATUS_PREPARED;
1643: else
1644: return false;
1645:
1646: return readOnly;
1647: }
1648:
1649: /**
1650: * Commit all enlisted resources.
1651: * This will release the lock while calling out.
1652: */
1653: private void commitResources(boolean onePhase) {
1654: status = Status.STATUS_COMMITTING;
1655:
1656: for (int i = 0; i < resources.size(); ++i) {
1657:
1658: // Abort commit on state change.
1659: if (status != Status.STATUS_COMMITTING)
1660: return;
1661:
1662: Resource resource = (Resource) resources.get(i);
1663:
1664: // Ignore the last resource, it is already committed
1665: if (onePhase == false && lastResource == resource)
1666: continue;
1667:
1668: try {
1669: resource.commit(onePhase);
1670: } catch (XAException e) {
1671: logXAException(e);
1672: switch (e.errorCode) {
1673: case XAException.XA_HEURRB:
1674: case XAException.XA_HEURCOM:
1675: case XAException.XA_HEURMIX:
1676: case XAException.XA_HEURHAZ:
1677: //usually throws an exception, but not for a couple of cases.
1678: gotHeuristic(resource, e.errorCode);
1679: //May not be correct for HEURCOM
1680: //Two phase commit is committed after prepare is logged.
1681: if (onePhase)
1682: status = Status.STATUS_MARKED_ROLLBACK;
1683:
1684: break;
1685: default:
1686: cause = e;
1687: if (onePhase) {
1688: status = Status.STATUS_MARKED_ROLLBACK;
1689: break;
1690: }
1691: //Not much we can do if there is an RMERR in the
1692: //commit phase of 2pc. I guess we try the other rms.
1693: }
1694: } catch (Throwable t) {
1695: if (trace)
1696: log.trace("unhandled throwable in commitResources "
1697: + this , t);
1698: }
1699: }
1700:
1701: if (status == Status.STATUS_COMMITTING)
1702: status = Status.STATUS_COMMITTED;
1703: }
1704:
1705: /**
1706: * Rollback all enlisted resources.
1707: * This will release the lock while calling out.
1708: */
1709: private void rollbackResources() {
1710: status = Status.STATUS_ROLLING_BACK;
1711:
1712: for (int i = 0; i < resources.size(); ++i) {
1713: Resource resource = (Resource) resources.get(i);
1714: try {
1715: resource.rollback();
1716: } catch (XAException e) {
1717: logXAException(e);
1718: switch (e.errorCode) {
1719: case XAException.XA_HEURRB:
1720: // Heuristic rollback is not that bad when rolling back.
1721: gotHeuristic(resource, e.errorCode);
1722: continue;
1723: case XAException.XA_HEURCOM:
1724: case XAException.XA_HEURMIX:
1725: case XAException.XA_HEURHAZ:
1726: gotHeuristic(resource, e.errorCode);
1727: continue;
1728: default:
1729: cause = e;
1730: break;
1731: }
1732: } catch (Throwable t) {
1733: if (trace)
1734: log.trace(
1735: "unhandled throwable in rollbackResources "
1736: + this , t);
1737: }
1738: }
1739:
1740: status = Status.STATUS_ROLLEDBACK;
1741: }
1742:
1743: /**
1744: * Create an Xid representing a new branch of this transaction.
1745: */
1746: private Xid createXidBranch() {
1747: long branchId = ++lastBranchId;
1748:
1749: return xidFactory.newBranch(xid, branchId);
1750: }
1751:
1752: /**
1753: * Determine the commit strategy
1754: *
1755: * @return 0 for nothing to do, 1 for one phase and 2 for two phase
1756: */
1757: private int getCommitStrategy() {
1758: int resourceCount = resources.size();
1759:
1760: if (resourceCount == 0)
1761: return 0;
1762:
1763: if (resourceCount == 1)
1764: return 1;
1765:
1766: // first XAResource surely has -1, it's the first!
1767: for (int i = 1; i < resourceCount; ++i) {
1768: Resource resource = (Resource) resources.get(i);
1769: if (resource.isResourceManager()) {
1770: // this one is not the same rm as previous ones,
1771: // there must be at least 2
1772: return 2;
1773: }
1774:
1775: }
1776: // all rms are the same one, one phase commit is ok.
1777: return 1;
1778: }
1779:
1780: public long getTimeLeftBeforeTimeout(boolean errorRollback)
1781: throws RollbackException {
1782: if (errorRollback && status != Status.STATUS_ACTIVE)
1783: throw new RollbackException("Transaction is not active: "
1784: + TxUtils.getStatusAsString(status));
1785: return (start + timeoutPeriod) - System.currentTimeMillis();
1786: }
1787:
1788: Object getTransactionLocalValue(TransactionLocal tlocal) {
1789: return transactionLocalMap.get(tlocal);
1790: }
1791:
1792: void putTransactionLocalValue(TransactionLocal tlocal, Object value) {
1793: transactionLocalMap.put(tlocal, value);
1794: }
1795:
1796: boolean containsTransactionLocal(TransactionLocal tlocal) {
1797: return transactionLocalMap.containsKey(tlocal);
1798: }
1799:
1800: /**
1801: * Check we have no outstanding work
1802: *
1803: * @throws IllegalStateException when there is still work
1804: */
1805: private void checkWork() {
1806: if (work != null)
1807: throw new IllegalStateException("Work still outstanding "
1808: + work + " tx=" + this );
1809: }
1810:
1811: // Inner classes -------------------------------------------------
1812:
1813: /**
1814: * Represents a resource enlisted in the transaction
1815: */
1816: private class Resource {
1817: /** The XAResource */
1818: private XAResource xaResource;
1819:
1820: /** The state of the resources */
1821: private int resourceState;
1822:
1823: /** The related xa resource from the same resource manager */
1824: private Resource resourceSameRM;
1825:
1826: /** The Xid of this resource */
1827: private Xid resourceXid;
1828:
1829: /**
1830: * Create a new resource
1831: */
1832: public Resource(XAResource xaResource, Xid resourceXid,
1833: Resource resourceSameRM) {
1834: this .xaResource = xaResource;
1835: this .resourceXid = resourceXid;
1836: this .resourceSameRM = resourceSameRM;
1837: resourceState = RS_NEW;
1838: }
1839:
1840: /**
1841: * Get the XAResource for this resource
1842: */
1843: public XAResource getXAResource() {
1844: return xaResource;
1845: }
1846:
1847: /**
1848: * Get the Xid for this resource
1849: */
1850: public Xid getXid() {
1851: return resourceXid;
1852: }
1853:
1854: /**
1855: * Is the resource enlisted?
1856: */
1857: public boolean isEnlisted() {
1858: return resourceState == RS_ENLISTED;
1859: }
1860:
1861: /**
1862: * Is this a resource manager
1863: */
1864: public boolean isResourceManager() {
1865: return resourceSameRM == null;
1866: }
1867:
1868: /**
1869: * Is this the resource manager for the passed xa resource
1870: */
1871: public boolean isResourceManager(XAResource xaRes)
1872: throws XAException {
1873: return resourceSameRM == null && xaRes.isSameRM(xaResource);
1874: }
1875:
1876: /**
1877: * Is the resource delisted and the XAResource always returns false
1878: * for isSameRM
1879: */
1880: public boolean isDelisted(XAResource xaRes) throws XAException {
1881: return resourceState == RS_ENDED
1882: && xaResource.isSameRM(xaRes) == false;
1883: }
1884:
1885: /**
1886: * Call <code>start()</code> on a XAResource and update
1887: * internal state information.
1888: * This will release the lock while calling out.
1889: *
1890: * @return when started, false otherwise
1891: */
1892: public boolean startResource() throws XAException {
1893: int flags = XAResource.TMJOIN;
1894:
1895: if (resourceSameRM == null) {
1896: switch (resourceState) {
1897: case RS_NEW:
1898: flags = XAResource.TMNOFLAGS;
1899: break;
1900: case RS_SUSPENDED:
1901: flags = XAResource.TMRESUME;
1902: break;
1903:
1904: default:
1905: if (trace)
1906: log
1907: .trace("Unhandled resource state: "
1908: + resourceState
1909: + " (not RS_NEW or RS_SUSPENDED, using TMJOIN flags)");
1910: }
1911: }
1912:
1913: if (trace)
1914: log.trace("startResource("
1915: + xidFactory.toString(resourceXid)
1916: + ") entered: " + xaResource.toString()
1917: + " flags=" + flags);
1918:
1919: unlock();
1920: // OSH FIXME: resourceState could be incorrect during this callout.
1921: try {
1922: try {
1923: xaResource.start(resourceXid, flags);
1924: } catch (XAException e) {
1925: throw e;
1926: } catch (Throwable t) {
1927: if (trace)
1928: log
1929: .trace(
1930: "unhandled throwable error in startResource",
1931: t);
1932: status = Status.STATUS_MARKED_ROLLBACK;
1933: return false;
1934: }
1935:
1936: // Now the XA resource is associated with a transaction.
1937: resourceState = RS_ENLISTED;
1938: } finally {
1939: lock();
1940: if (trace)
1941: log.trace("startResource("
1942: + xidFactory.toString(resourceXid)
1943: + ") leaving: " + xaResource.toString()
1944: + " flags=" + flags);
1945: }
1946: return true;
1947: }
1948:
1949: /**
1950: * Delist the resource unless we already did it
1951: */
1952: public boolean delistResource(XAResource xaRes, int flag)
1953: throws XAException {
1954: if (isDelisted(xaRes)) {
1955: // This RM always returns false on isSameRM. Further,
1956: // the last resource has already been delisted.
1957: log.warn("Resource already delisted. tx="
1958: + this .toString());
1959: return false;
1960: }
1961: endResource(flag);
1962: return true;
1963: }
1964:
1965: /**
1966: * End the resource
1967: */
1968: public void endResource() throws XAException {
1969: if (resourceState == RS_ENLISTED
1970: || resourceState == RS_SUSPENDED) {
1971: if (trace)
1972: log.trace("endresources(" + xaResource
1973: + "): state=" + resourceState);
1974: endResource(XAResource.TMSUCCESS);
1975: }
1976: }
1977:
1978: /**
1979: * Call <code>end()</code> on the XAResource and update
1980: * internal state information.
1981: * This will release the lock while calling out.
1982: *
1983: * @param flag The flag argument for the end() call.
1984: */
1985: private void endResource(int flag) throws XAException {
1986: if (trace)
1987: log.trace("endResource("
1988: + xidFactory.toString(resourceXid)
1989: + ") entered: " + xaResource.toString()
1990: + " flag=" + flag);
1991:
1992: unlock();
1993: // OSH FIXME: resourceState could be incorrect during this callout.
1994: try {
1995: try {
1996: xaResource.end(resourceXid, flag);
1997: } catch (XAException e) {
1998: throw e;
1999: } catch (Throwable t) {
2000: if (trace)
2001: log
2002: .trace(
2003: "unhandled throwable error in endResource",
2004: t);
2005: status = Status.STATUS_MARKED_ROLLBACK;
2006: // Resource may or may not be ended after illegal exception.
2007: // We just assume it ended.
2008: resourceState = RS_ENDED;
2009: return;
2010: }
2011:
2012: // Update our internal state information
2013: if (flag == XAResource.TMSUSPEND)
2014: resourceState = RS_SUSPENDED;
2015: else {
2016: if (flag == XAResource.TMFAIL)
2017: status = Status.STATUS_MARKED_ROLLBACK;
2018: resourceState = RS_ENDED;
2019: }
2020: } finally {
2021: lock();
2022: if (trace)
2023: log.trace("endResource("
2024: + xidFactory.toString(resourceXid)
2025: + ") leaving: " + xaResource.toString()
2026: + " flag=" + flag);
2027: }
2028: }
2029:
2030: /**
2031: * Forget the resource
2032: */
2033: public void forget() {
2034: unlock();
2035: try {
2036: xaResource.forget(resourceXid);
2037: } catch (XAException xae) {
2038: logXAException(xae);
2039: cause = xae;
2040: } finally {
2041: lock();
2042: }
2043: resourceState = RS_FORGOT;
2044: }
2045:
2046: /**
2047: * Prepare the resource
2048: */
2049: public int prepare() throws XAException {
2050: int vote;
2051: unlock();
2052: try {
2053: vote = xaResource.prepare(resourceXid);
2054: } finally {
2055: lock();
2056: }
2057:
2058: if (vote == XAResource.XA_OK)
2059: resourceState = RS_VOTE_OK;
2060: else if (vote == XAResource.XA_RDONLY)
2061: resourceState = RS_VOTE_READONLY;
2062:
2063: return resourceState;
2064: }
2065:
2066: /**
2067: * Prepare the last resource
2068: */
2069: public void prepareLastResource() throws XAException {
2070: resourceState = RS_VOTE_OK;
2071: }
2072:
2073: /**
2074: * Commit the resource
2075: */
2076: public void commit(boolean onePhase) throws XAException {
2077: if (trace)
2078: log.trace("Committing resource " + xaResource
2079: + " state=" + resourceState);
2080:
2081: if (!onePhase && resourceState != RS_VOTE_OK)
2082: return; // Voted read-only at prepare phase.
2083:
2084: if (resourceSameRM != null)
2085: return; // This RM already committed.
2086:
2087: unlock();
2088: try {
2089: xaResource.commit(resourceXid, onePhase);
2090: } finally {
2091: lock();
2092: }
2093: }
2094:
2095: /**
2096: * Rollback the resource
2097: */
2098: public void rollback() throws XAException {
2099: if (resourceState == RS_VOTE_READONLY)
2100: return;
2101: // Already forgotten
2102: if (resourceState == RS_FORGOT)
2103: return;
2104: if (resourceSameRM != null)
2105: return; // This RM already rolled back.
2106:
2107: unlock();
2108: try {
2109: xaResource.rollback(resourceXid);
2110: } finally {
2111: lock();
2112: }
2113: }
2114: }
2115: }
|