From 095fdc14f030acf61bee801562b8bdafb7d54e33 Mon Sep 17 00:00:00 2001
From: "Brian S. O'Neill" <bronee@gmail.com>
Date: Fri, 25 Jan 2008 19:56:16 +0000
Subject: Add transaction detach/attach methods.

---
 .../java/com/amazon/carbonado/Transaction.java     | 26 +++++++++
 .../carbonado/repo/logging/LoggingTransaction.java | 14 +++++
 .../amazon/carbonado/spi/TransactionManager.java   | 28 ++++++----
 .../com/amazon/carbonado/spi/TransactionPair.java  | 20 +++++++
 .../com/amazon/carbonado/spi/TransactionScope.java | 65 +++++++++++++---------
 5 files changed, 114 insertions(+), 39 deletions(-)

(limited to 'src/main/java/com/amazon/carbonado')

diff --git a/src/main/java/com/amazon/carbonado/Transaction.java b/src/main/java/com/amazon/carbonado/Transaction.java
index 5c8203f..1c2c06d 100644
--- a/src/main/java/com/amazon/carbonado/Transaction.java
+++ b/src/main/java/com/amazon/carbonado/Transaction.java
@@ -117,4 +117,30 @@ public interface Transaction {
      * Returns the isolation level of this transaction.
      */
     IsolationLevel getIsolationLevel();
+
+    /**
+     * Detaches this transaction from the current thread. It can be attached
+     * later, and to any thread which currently has no thread-local
+     * transaction.
+     *
+     * <p>Detaching a transaction also detaches any parent and nested child
+     * transactions. Attaching any of them achieves the same result as
+     * attaching this transaction.
+     *
+     * @throws IllegalStateException if transaction is attached to a different
+     * thread
+     * @since 1.2
+     */
+    void detach();
+
+    /**
+     * Attaches this transaction to the current thread, if it has been
+     * detached. Attaching a transaction also attaches any parent and nested
+     * child transactions.
+     *
+     * @throws IllegalStateException if current thread has a different
+     * transaction already attached
+     * @since 1.2
+     */
+    void attach();
 }
diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java
index fd88f35..dd2a4f5 100644
--- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java
+++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingTransaction.java
@@ -83,6 +83,20 @@ class LoggingTransaction implements Transaction {
         return mTxn.getIsolationLevel();
     }
 
+    public void detach() {
+        if (mLog.isEnabled()) {
+            mLog.write("Transaction.detach() on " + idChain());
+        }
+        mTxn.detach();
+    }
+
+    public void attach() {
+        if (mLog.isEnabled()) {
+            mLog.write("Transaction.attach() on " + idChain());
+        }
+        mTxn.attach();
+    }
+
     private String idChain() {
         if (mParent == null) {
             return String.valueOf(mID);
diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java
index 4a28d24..a2b1375 100644
--- a/src/main/java/com/amazon/carbonado/spi/TransactionManager.java
+++ b/src/main/java/com/amazon/carbonado/spi/TransactionManager.java
@@ -74,32 +74,36 @@ public abstract class TransactionManager<Txn> {
      * which does not currently have a TransactionScope.
      *
      * @return detached thread-local TransactionScope or null if none
+     * @since 1.2
      */
-    /*
     public TransactionScope<Txn> detachLocalScope() {
         TransactionScope<Txn> scope = mLocalScope.get();
         if (scope != null) {
-            if (!scope.detach()) {
-                scope = null;
-            } else {
-                mLocalScope.remove();
-            }
+            scope.markDetached();
+            mLocalScope.remove();
         }
         return scope;
     }
-    */
 
     // Called by TransactionScope.
-    /*
-    void attach(TransactionScope<Txn> scope) {
+    boolean removeLocalScope(TransactionScope<Txn> scope) {
+        TransactionScope<Txn> existing = mLocalScope.get();
+        if (existing == null || existing == scope) {
+            mLocalScope.remove();
+            return true;
+        }
+        return false;
+    }
+
+    // Called by TransactionScope.
+    boolean setLocalScope(TransactionScope<Txn> scope) {
         TransactionScope<Txn> existing = mLocalScope.get();
         if (existing == null || existing == scope) {
             mLocalScope.set(scope);
-        } else {
-            throw new IllegalStateException("Current thread already has a transaction scope");
+            return true;
         }
+        return false;
     }
-    */
 
     /**
      * Closes all transaction scopes. Should be called only when repository is
diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionPair.java b/src/main/java/com/amazon/carbonado/spi/TransactionPair.java
index d97aef4..ff07bc2 100644
--- a/src/main/java/com/amazon/carbonado/spi/TransactionPair.java
+++ b/src/main/java/com/amazon/carbonado/spi/TransactionPair.java
@@ -86,4 +86,24 @@ public class TransactionPair implements Transaction {
         return mPrimaryTransaction.getIsolationLevel()
             .lowestCommon(mSecondaryTransaction.getIsolationLevel());
     }
+
+    public void detach() {
+        mPrimaryTransaction.detach();
+        try {
+            mSecondaryTransaction.detach();
+        } catch (IllegalStateException e) {
+            mPrimaryTransaction.attach();
+            throw e;
+        }
+    }
+
+    public void attach() {
+        mPrimaryTransaction.attach();
+        try {
+            mSecondaryTransaction.attach();
+        } catch (IllegalStateException e) {
+            mPrimaryTransaction.detach();
+            throw e;
+        }
+    }
 }
diff --git a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java
index 0ac2cee..82a20eb 100644
--- a/src/main/java/com/amazon/carbonado/spi/TransactionScope.java
+++ b/src/main/java/com/amazon/carbonado/spi/TransactionScope.java
@@ -41,8 +41,6 @@ import com.amazon.carbonado.Transaction;
  * @see TransactionManager
  */
 public class TransactionScope<Txn> {
-    private static final int OPEN = 0, DETACHED = 1, CLOSED = 2;
-
     final TransactionManager<Txn> mTxnMgr;
     final Lock mLock;
 
@@ -51,12 +49,12 @@ public class TransactionScope<Txn> {
     // Tracks all registered cursors by storage type.
     private Map<Class<?>, CursorList<TransactionImpl<Txn>>> mCursors;
 
-    private int mState;
+    private boolean mClosed;
+    private boolean mDetached;
 
     TransactionScope(TransactionManager<Txn> txnMgr, boolean closed) {
         mTxnMgr = txnMgr;
         mLock = new ReentrantLock(true);
-        mState = closed ? CLOSED : OPEN;
     }
 
     /**
@@ -192,7 +190,7 @@ public class TransactionScope<Txn> {
     public boolean isForUpdate() {
         mLock.lock();
         try {
-            return (mState == CLOSED || mActive == null) ? false : mActive.isForUpdate();
+            return (mClosed || mActive == null) ? false : mActive.isForUpdate();
         } finally {
             mLock.unlock();
         }
@@ -205,7 +203,7 @@ public class TransactionScope<Txn> {
     public IsolationLevel getIsolationLevel() {
         mLock.lock();
         try {
-            return (mState == CLOSED || mActive == null) ? null : mActive.getIsolationLevel();
+            return (mClosed || mActive == null) ? null : mActive.getIsolationLevel();
         } finally {
             mLock.unlock();
         }
@@ -215,41 +213,46 @@ public class TransactionScope<Txn> {
      * Attach this scope to the current thread, if it has been {@link
      * TransactionManager#detachLocalScope detached}.
      *
-     * @throws IllegalStateException if scope is not detached or if current
-     * thread already has a transaction scope
+     * @throws IllegalStateException if current thread has a different
+     * transaction already attached
      */
-    /*
     public void attach() {
         mLock.lock();
         try {
-            if (mState != DETACHED) {
-                throw new IllegalStateException("Transaction scope is not detached");
+            if (mTxnMgr.setLocalScope(this)) {
+                mDetached = false;
+            } else {
+                throw new IllegalStateException
+                    ("Current thread has a different transaction already attached");
             }
-            mState = OPEN;
         } finally {
             mLock.unlock();
         }
-        mTxnMgr.attach(this);
     }
-    */
 
-    /**
-     * @return false if closed
-     */
-    /*
-    boolean detach() {
+    // Called by TransactionImpl.
+    void detach() {
         mLock.lock();
         try {
-            if (mState == CLOSED) {
-                return false;
+            if (mTxnMgr.removeLocalScope(this)) {
+                mDetached = true;
+            } else {
+                throw new IllegalStateException("Transaction is attached to a different thread");
             }
-            mState = DETACHED;
-            return true;
         } finally {
             mLock.unlock();
         }
     }
-    */
+
+    // Called by TransactionManager.
+    void markDetached() {
+        mLock.lock();
+        try {
+            mDetached = true;
+        } finally {
+            mLock.unlock();
+        }
+    }
 
     /**
      * Exits all transactions and closes all cursors. Should be called only
@@ -258,7 +261,7 @@ public class TransactionScope<Txn> {
     void close() throws RepositoryException {
         mLock.lock();
         try {
-            if (mState != CLOSED) {
+            if (!mClosed) {
                 while (mActive != null) {
                     mActive.exit();
                 }
@@ -269,7 +272,7 @@ public class TransactionScope<Txn> {
                 }
             }
         } finally {
-            mState = CLOSED;
+            mClosed = true;
             mLock.unlock();
         }
     }
@@ -278,7 +281,7 @@ public class TransactionScope<Txn> {
      * Caller must hold mLock.
      */
     private void checkClosed() {
-        if (mState == CLOSED) {
+        if (mClosed) {
             throw new IllegalStateException("Repository is closed");
         }
     }
@@ -409,6 +412,14 @@ public class TransactionScope<Txn> {
             return mLevel;
         }
 
+        public void detach() {
+            mScope.detach();
+        }
+
+        public void attach() {
+            mScope.attach();
+        }
+
         // Caller must hold mLock.
         <S extends Storable> void register(Cursor<S> cursor) {
             if (mCursorList == null) {
-- 
cgit v1.2.3