--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.jdbc.XATransactionState\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.jdbc;\r
+\r
+\r
+import java.sql.SQLException;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.timer.TimerFactory;\r
+import org.apache.derby.impl.jdbc.EmbedConnection;\r
+import javax.transaction.xa.XAResource;\r
+import org.apache.derby.iapi.services.context.ContextImpl;\r
+import org.apache.derby.iapi.services.context.ContextManager;\r
+import org.apache.derby.iapi.error.ExceptionSeverity;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.store.access.xa.XAXactId;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import java.util.HashMap;\r
+import javax.transaction.xa.XAException;\r
+\r
+/** \r
+*/\r
+final class XATransactionState extends ContextImpl {\r
+\r
+ /** Rollback-only due to timeout */\r
+ final static int TRO_TIMEOUT = -3;\r
+ /** Rollback-only due to deadlock */\r
+ final static int TRO_DEADLOCK = -2;\r
+ /** Rollback-only due to end(TMFAIL) */\r
+ final static int TRO_FAIL = -1;\r
+ final static int T0_NOT_ASSOCIATED = 0;\r
+ final static int T1_ASSOCIATED = 1;\r
+ // final static int T2_ASSOCIATION_SUSPENDED = 2;\r
+ final static int TC_COMPLETED = 3; // rollback/commit called\r
+\r
+ final EmbedConnection conn;\r
+ final EmbedXAResource creatingResource;\r
+ // owning XAResource\r
+ private EmbedXAResource associatedResource; \r
+ final XAXactId xid; \r
+ /**\r
+ When an XAResource suspends a transaction (end(TMSUSPEND)) it must be resumed\r
+ using the same XAConnection. This has been the traditional Cloudscape/Derby behaviour,\r
+ though there does not seem to be a specific reference to this behaviour in\r
+ the JTA spec. Note that while the transaction is suspended by this XAResource,\r
+ another XAResource may join the transaction and suspend it after the join.\r
+ */\r
+ HashMap suspendedList;\r
+\r
+\r
+ /**\r
+ Association state of the transaction.\r
+ */\r
+ int associationState;\r
+\r
+ int rollbackOnlyCode;\r
+\r
+\r
+ /**\r
+ has this transaction been prepared.\r
+ */\r
+ boolean isPrepared;\r
+\r
+ /** Has this transaction been finished (committed\r
+ * or rolled back)? */\r
+ boolean isFinished;\r
+\r
+ /** A timer task scheduled for the time when the transaction will timeout. */\r
+ CancelXATransactionTask timeoutTask = null;\r
+\r
+\r
+ /** The implementation of TimerTask to cancel a global transaction. */\r
+ private class CancelXATransactionTask extends TimerTask {\r
+\r
+ /** Creates the cancelation object to be passed to a timer. */\r
+ public CancelXATransactionTask() {\r
+ XATransactionState.this.timeoutTask = this;\r
+ }\r
+\r
+ /** Runs the cancel task of the global transaction */\r
+ public void run() {\r
+ try {\r
+ XATransactionState.this.cancel();\r
+ } catch (XAException ex) {\r
+ Monitor.logThrowable(ex);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+\r
+ XATransactionState(ContextManager cm, EmbedConnection conn, \r
+ EmbedXAResource resource, XAXactId xid) {\r
+\r
+ super(cm, "XATransactionState");\r
+ this.conn = conn;\r
+ this.associatedResource = resource;\r
+ this.creatingResource = resource;\r
+ this.associationState = XATransactionState.T1_ASSOCIATED;\r
+ this.xid = xid;\r
+ this.isFinished = false;\r
+\r
+ }\r
+\r
+ public void cleanupOnError(Throwable t) {\r
+\r
+ if (t instanceof StandardException) {\r
+\r
+ StandardException se = (StandardException) t;\r
+ \r
+ if (se.getSeverity() >= ExceptionSeverity.SESSION_SEVERITY) {\r
+ popMe();\r
+ return;\r
+ }\r
+\r
+ if (se.getSeverity() == ExceptionSeverity.TRANSACTION_SEVERITY) {\r
+\r
+ synchronized (this) {\r
+ // disable use of the connection until it is cleaned up.\r
+ conn.setApplicationConnection(null);\r
+ notifyAll();\r
+ associationState = TRO_FAIL;\r
+ if (SQLState.DEADLOCK.equals(se.getMessageId()))\r
+ rollbackOnlyCode = XAException.XA_RBDEADLOCK;\r
+ else if (SQLState.LOCK_TIMEOUT.equals(se.getMessageId()))\r
+ rollbackOnlyCode = XAException.XA_RBTIMEOUT; \r
+ else\r
+ rollbackOnlyCode = XAException.XA_RBOTHER;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void start(EmbedXAResource resource, int flags) throws XAException {\r
+\r
+ synchronized (this) {\r
+ if (associationState == XATransactionState.TRO_FAIL)\r
+ throw new XAException(rollbackOnlyCode);\r
+\r
+ boolean isSuspendedByResource = (suspendedList != null) && (suspendedList.get(resource) != null);\r
+\r
+ if (flags == XAResource.TMRESUME) {\r
+ if (!isSuspendedByResource)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+\r
+ } else {\r
+ // cannot join a transaction we have suspended.\r
+ if (isSuspendedByResource)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+ }\r
+\r
+ while (associationState == XATransactionState.T1_ASSOCIATED) {\r
+ \r
+ try {\r
+ wait();\r
+ } catch (InterruptedException ie) {\r
+ throw new XAException(XAException.XA_RETRY);\r
+ }\r
+ }\r
+\r
+\r
+ switch (associationState) {\r
+ case XATransactionState.T0_NOT_ASSOCIATED:\r
+ break;\r
+\r
+ case XATransactionState.TRO_DEADLOCK:\r
+ case XATransactionState.TRO_TIMEOUT:\r
+ case XATransactionState.TRO_FAIL:\r
+ throw new XAException(rollbackOnlyCode);\r
+\r
+ default:\r
+ throw new XAException(XAException.XAER_NOTA);\r
+ }\r
+\r
+ if (isPrepared)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+\r
+ if (isSuspendedByResource) {\r
+ suspendedList.remove(resource);\r
+ }\r
+\r
+ associationState = XATransactionState.T1_ASSOCIATED;\r
+ associatedResource = resource;\r
+\r
+ }\r
+ }\r
+\r
+ boolean end(EmbedXAResource resource, int flags, \r
+ boolean endingCurrentXid) throws XAException {\r
+\r
+ boolean rollbackOnly = false;\r
+ synchronized (this) {\r
+\r
+\r
+ boolean isSuspendedByResource = (suspendedList != null) && (suspendedList.get(resource) != null);\r
+\r
+ if (!endingCurrentXid) {\r
+ while (associationState == XATransactionState.T1_ASSOCIATED) {\r
+ \r
+ try {\r
+ wait();\r
+ } catch (InterruptedException ie) {\r
+ throw new XAException(XAException.XA_RETRY);\r
+ }\r
+ }\r
+ }\r
+\r
+ switch (associationState) {\r
+ case XATransactionState.TC_COMPLETED:\r
+ throw new XAException(XAException.XAER_NOTA);\r
+ case XATransactionState.TRO_FAIL:\r
+ if (endingCurrentXid)\r
+ flags = XAResource.TMFAIL;\r
+ else\r
+ throw new XAException(rollbackOnlyCode);\r
+ }\r
+\r
+ boolean notify = false;\r
+ switch (flags) {\r
+ case XAResource.TMSUCCESS:\r
+ if (isSuspendedByResource) {\r
+ suspendedList.remove(resource);\r
+ }\r
+ else {\r
+ if (resource != associatedResource)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+\r
+ associationState = XATransactionState.T0_NOT_ASSOCIATED;\r
+ associatedResource = null;\r
+ notify = true;\r
+ }\r
+\r
+ conn.setApplicationConnection(null);\r
+ break;\r
+\r
+ case XAResource.TMFAIL:\r
+\r
+ if (isSuspendedByResource) {\r
+ suspendedList.remove(resource);\r
+ } else {\r
+ if (resource != associatedResource)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+ associatedResource = null;\r
+ }\r
+ \r
+ if (associationState != XATransactionState.TRO_FAIL) {\r
+ associationState = XATransactionState.TRO_FAIL;\r
+ rollbackOnlyCode = XAException.XA_RBROLLBACK;\r
+ }\r
+ conn.setApplicationConnection(null);\r
+ notify = true;\r
+ rollbackOnly = true;\r
+ break;\r
+\r
+ case XAResource.TMSUSPEND:\r
+ if (isSuspendedByResource)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+ \r
+ if (resource != associatedResource)\r
+ throw new XAException(XAException.XAER_PROTO);\r
+\r
+ if (suspendedList == null)\r
+ suspendedList = new HashMap();\r
+ suspendedList.put(resource, this);\r
+\r
+ associationState = XATransactionState.T0_NOT_ASSOCIATED;\r
+ associatedResource = null;\r
+ conn.setApplicationConnection(null);\r
+ notify = true;\r
+\r
+ break;\r
+\r
+ default:\r
+ throw new XAException(XAException.XAER_INVAL);\r
+ }\r
+\r
+ if (notify)\r
+ notifyAll();\r
+\r
+ return rollbackOnly;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Schedule a timeout task wich will rollback the global transaction\r
+ * after the specified time will elapse.\r
+ *\r
+ * @param timeoutMillis The number of milliseconds to be elapsed before\r
+ * the transaction will be rolled back.\r
+ */\r
+ synchronized void scheduleTimeoutTask(long timeoutMillis) {\r
+ // schedule a time out task if the timeout was specified\r
+ if (timeoutMillis > 0) {\r
+ // take care of the transaction timeout\r
+ TimerTask cancelTask = new CancelXATransactionTask();\r
+ TimerFactory timerFactory = Monitor.getMonitor().getTimerFactory();\r
+ Timer timer = timerFactory.getCancellationTimer();\r
+ timer.schedule(cancelTask, timeoutMillis);\r
+ } else {\r
+ timeoutTask = null;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Rollback the global transaction and cancel the timeout task.\r
+ */\r
+ synchronized void xa_rollback() throws SQLException {\r
+ conn.xa_rollback();\r
+ xa_finalize();\r
+ }\r
+\r
+ /**\r
+ * Commit the global transaction and cancel the timeout task.\r
+ * @param onePhase Indicates whether to use one phase commit protocol.\r
+ * Otherwise two phase commit protocol will be used.\r
+ */\r
+ synchronized void xa_commit(boolean onePhase) throws SQLException {\r
+ conn.xa_commit(onePhase);\r
+ xa_finalize();\r
+ }\r
+\r
+ /**\r
+ * Prepare the global transaction for commit.\r
+ */\r
+ synchronized int xa_prepare() throws SQLException {\r
+ int retVal = conn.xa_prepare();\r
+ return retVal;\r
+ }\r
+\r
+ /** This method cancels timeoutTask and marks the transaction\r
+ * as finished by assigning 'isFinished = true'.\r
+ */\r
+ synchronized void xa_finalize() {\r
+ if (timeoutTask != null) {\r
+ timeoutTask.cancel();\r
+ }\r
+ isFinished = true;\r
+ }\r
+\r
+ /**\r
+ * This function is called from the timer task when the transaction\r
+ * times out.\r
+ *\r
+ * @see CancelXATransactionTask\r
+ */\r
+ private synchronized void cancel() throws XAException {\r
+ // Check isFinished just to be sure that\r
+ // the cancellation task was not started\r
+ // just before the xa_commit/rollback\r
+ // obtained this object's monitor.\r
+ if (!isFinished) {\r
+ // Check whether the transaction is associated\r
+ // with any EmbedXAResource instance.\r
+ if (associationState == XATransactionState.T1_ASSOCIATED) {\r
+ conn.cancelRunningStatement();\r
+ EmbedXAResource assocRes = associatedResource;\r
+ end(assocRes, XAResource.TMFAIL, true);\r
+ }\r
+\r
+ // Rollback the global transaction\r
+ try {\r
+ conn.xa_rollback();\r
+ } catch (SQLException sqle) {\r
+ XAException ex = new XAException(XAException.XAER_RMERR);\r
+ ex.initCause(sqle);\r
+ throw ex;\r
+ }\r
+\r
+ // Do the cleanup on the resource\r
+ creatingResource.returnConnectionToResource(this, xid);\r
+ }\r
+ }\r
+}\r