001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2000,2007 Oracle. All rights reserved.
005: *
006: * $Id: CurrentTransaction.java,v 1.46.2.1 2007/02/01 14:49:39 cwl Exp $
007: */
008:
009: package com.sleepycat.collections;
010:
011: import java.lang.ref.WeakReference;
012: import java.util.ArrayList;
013: import java.util.List;
014: import java.util.WeakHashMap;
015:
016: import com.sleepycat.compat.DbCompat;
017: import com.sleepycat.je.Cursor;
018: import com.sleepycat.je.CursorConfig;
019: import com.sleepycat.je.Database;
020: import com.sleepycat.je.DatabaseException;
021: import com.sleepycat.je.Environment;
022: import com.sleepycat.je.EnvironmentConfig;
023: import com.sleepycat.je.LockMode;
024: import com.sleepycat.je.Transaction;
025: import com.sleepycat.je.TransactionConfig;
026: import com.sleepycat.util.RuntimeExceptionWrapper;
027:
028: /**
029: * Provides access to the current transaction for the current thread within the
030: * context of a Berkeley DB environment. This class provides explicit
031: * transaction control beyond that provided by the {@link TransactionRunner}
032: * class. However, both methods of transaction control manage per-thread
033: * transactions.
034: *
035: * @author Mark Hayes
036: */
037: public class CurrentTransaction {
038:
039: /* For internal use, this class doubles as an Environment wrapper. */
040:
041: private static WeakHashMap envMap = new WeakHashMap();
042:
043: private LockMode writeLockMode;
044: private boolean cdbMode;
045: private boolean txnMode;
046: private boolean lockingMode;
047: private Environment env;
048: private ThreadLocal localTrans = new ThreadLocal();
049: private ThreadLocal localCdbCursors;
050:
051: /**
052: * Gets the CurrentTransaction accessor for a specified Berkeley DB
053: * environment. This method always returns the same reference when called
054: * more than once with the same environment parameter.
055: *
056: * @param env is an open Berkeley DB environment.
057: *
058: * @return the CurrentTransaction accessor for the given environment, or
059: * null if the environment is not transactional.
060: */
061: public static CurrentTransaction getInstance(Environment env) {
062:
063: CurrentTransaction currentTxn = getInstanceInternal(env);
064: return currentTxn.isTxnMode() ? currentTxn : null;
065: }
066:
067: /**
068: * Gets the CurrentTransaction accessor for a specified Berkeley DB
069: * environment. Unlike getInstance(), this method never returns null.
070: *
071: * @param env is an open Berkeley DB environment.
072: */
073: static CurrentTransaction getInstanceInternal(Environment env) {
074: synchronized (envMap) {
075: CurrentTransaction myEnv = null;
076: WeakReference myEnvRef = (WeakReference) envMap.get(env);
077: if (myEnvRef != null) {
078: myEnv = (CurrentTransaction) myEnvRef.get();
079: }
080: if (myEnv == null) {
081: myEnv = new CurrentTransaction(env);
082: envMap.put(env, new WeakReference(myEnv));
083: }
084: return myEnv;
085: }
086: }
087:
088: private CurrentTransaction(Environment env) {
089: this .env = env;
090: try {
091: EnvironmentConfig config = env.getConfig();
092: txnMode = config.getTransactional();
093: lockingMode = DbCompat.getInitializeLocking(config);
094: if (txnMode || lockingMode) {
095: writeLockMode = LockMode.RMW;
096: } else {
097: writeLockMode = LockMode.DEFAULT;
098: }
099: cdbMode = DbCompat.getInitializeCDB(config);
100: if (cdbMode) {
101: localCdbCursors = new ThreadLocal();
102: }
103: } catch (DatabaseException e) {
104: throw new RuntimeExceptionWrapper(e);
105: }
106: }
107:
108: /**
109: * Returns whether environment is configured for locking.
110: */
111: final boolean isLockingMode() {
112:
113: return lockingMode;
114: }
115:
116: /**
117: * Returns whether this is a transactional environment.
118: */
119: final boolean isTxnMode() {
120:
121: return txnMode;
122: }
123:
124: /**
125: * Returns whether this is a Concurrent Data Store environment.
126: */
127: final boolean isCdbMode() {
128:
129: return cdbMode;
130: }
131:
132: /**
133: * Return the LockMode.RMW or null, depending on whether locking is
134: * enabled. LockMode.RMW will cause an error if passed when locking
135: * is not enabled. Locking is enabled if locking or transactions were
136: * specified for this environment.
137: */
138: final LockMode getWriteLockMode() {
139:
140: return writeLockMode;
141: }
142:
143: /**
144: * Returns the underlying Berkeley DB environment.
145: */
146: public final Environment getEnvironment() {
147:
148: return env;
149: }
150:
151: /**
152: * Returns the transaction associated with the current thread for this
153: * environment, or null if no transaction is active.
154: */
155: public final Transaction getTransaction() {
156:
157: Trans trans = (Trans) localTrans.get();
158: return (trans != null) ? trans.txn : null;
159: }
160:
161: /**
162: * Returns whether auto-commit may be performed by the collections API.
163: * True is returned if no collections API transaction is currently active,
164: * and no XA transaction is currently active.
165: */
166: boolean isAutoCommitAllowed() throws DatabaseException {
167:
168: return getTransaction() == null
169: && DbCompat.getThreadTransaction(env) == null;
170: }
171:
172: /**
173: * Begins a new transaction for this environment and associates it with
174: * the current thread. If a transaction is already active for this
175: * environment and thread, a nested transaction will be created.
176: *
177: * @param config the transaction configuration used for calling
178: * {@link Environment#beginTransaction}, or null to use the default
179: * configuration.
180: *
181: * @return the new transaction.
182: *
183: * @throws DatabaseException if the transaction cannot be started, in which
184: * case any existing transaction is not affected.
185: *
186: * @throws IllegalStateException if a transaction is already active and
187: * nested transactions are not supported by the environment.
188: */
189: public final Transaction beginTransaction(TransactionConfig config)
190: throws DatabaseException {
191:
192: Trans trans = (Trans) localTrans.get();
193: if (trans != null) {
194: if (trans.txn != null) {
195: if (!DbCompat.NESTED_TRANSACTIONS) {
196: throw new IllegalStateException(
197: "Nested transactions are not supported");
198: }
199: Transaction parentTxn = trans.txn;
200: trans = new Trans(trans, config);
201: trans.txn = env.beginTransaction(parentTxn, config);
202: localTrans.set(trans);
203: } else {
204: trans.txn = env.beginTransaction(null, config);
205: trans.config = config;
206: }
207: } else {
208: trans = new Trans(null, config);
209: trans.txn = env.beginTransaction(null, config);
210: localTrans.set(trans);
211: }
212: return trans.txn;
213: }
214:
215: /**
216: * Commits the transaction that is active for the current thread for this
217: * environment and makes the parent transaction (if any) the current
218: * transaction.
219: *
220: * @return the parent transaction or null if the committed transaction was
221: * not nested.
222: *
223: * @throws DatabaseException if an error occurs committing the transaction.
224: * The transaction will still be closed and the parent transaction will
225: * become the current transaction.
226: *
227: * @throws IllegalStateException if no transaction is active for the
228: * current thread for this environment.
229: */
230: public final Transaction commitTransaction()
231: throws DatabaseException, IllegalStateException {
232:
233: Trans trans = (Trans) localTrans.get();
234: if (trans != null && trans.txn != null) {
235: Transaction parent = closeTxn(trans);
236: trans.txn.commit();
237: return parent;
238: } else {
239: throw new IllegalStateException("No transaction is active");
240: }
241: }
242:
243: /**
244: * Aborts the transaction that is active for the current thread for this
245: * environment and makes the parent transaction (if any) the current
246: * transaction.
247: *
248: * @return the parent transaction or null if the aborted transaction was
249: * not nested.
250: *
251: * @throws DatabaseException if an error occurs aborting the transaction.
252: * The transaction will still be closed and the parent transaction will
253: * become the current transaction.
254: *
255: * @throws IllegalStateException if no transaction is active for the
256: * current thread for this environment.
257: */
258: public final Transaction abortTransaction()
259: throws DatabaseException, IllegalStateException {
260:
261: Trans trans = (Trans) localTrans.get();
262: if (trans != null && trans.txn != null) {
263: Transaction parent = closeTxn(trans);
264: trans.txn.abort();
265: return parent;
266: } else {
267: throw new IllegalStateException("No transaction is active");
268: }
269: }
270:
271: /**
272: * Returns whether the current transaction is a readUncommitted
273: * transaction.
274: */
275: final boolean isReadUncommitted() {
276:
277: Trans trans = (Trans) localTrans.get();
278: if (trans != null && trans.config != null) {
279: return trans.config.getReadUncommitted();
280: } else {
281: return false;
282: }
283: }
284:
285: private Transaction closeTxn(Trans trans) {
286:
287: localTrans.set(trans.parent);
288: return (trans.parent != null) ? trans.parent.txn : null;
289: }
290:
291: private static class Trans {
292:
293: private Trans parent;
294: private Transaction txn;
295: private TransactionConfig config;
296:
297: private Trans(Trans parent, TransactionConfig config) {
298:
299: this .parent = parent;
300: this .config = config;
301: }
302: }
303:
304: /**
305: * Opens a cursor for a given database, dup'ing an existing CDB cursor if
306: * one is open for the current thread.
307: */
308: Cursor openCursor(Database db, CursorConfig cursorConfig,
309: boolean writeCursor, Transaction txn)
310: throws DatabaseException {
311:
312: if (cdbMode) {
313: CdbCursors cdbCursors = null;
314: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
315: .get();
316: if (cdbCursorsMap == null) {
317: cdbCursorsMap = new WeakHashMap();
318: localCdbCursors.set(cdbCursorsMap);
319: } else {
320: cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
321: }
322: if (cdbCursors == null) {
323: cdbCursors = new CdbCursors();
324: cdbCursorsMap.put(db, cdbCursors);
325: }
326:
327: /*
328: * In CDB mode the cursorConfig specified by the user is ignored
329: * and only the writeCursor parameter is honored. This is the only
330: * meaningful cursor attribute for CDB, and here we count on
331: * writeCursor flag being set correctly by the caller.
332: */
333: List cursors;
334: CursorConfig cdbConfig;
335: if (writeCursor) {
336: if (cdbCursors.readCursors.size() > 0) {
337:
338: /*
339: * Although CDB allows opening a write cursor when a read
340: * cursor is open, a self-deadlock will occur if a write is
341: * attempted for a record that is read-locked; we should
342: * avoid self-deadlocks at all costs
343: */
344: throw new IllegalStateException(
345: "cannot open CDB write cursor when read cursor is open");
346: }
347: cursors = cdbCursors.writeCursors;
348: cdbConfig = new CursorConfig();
349: DbCompat.setWriteCursor(cdbConfig, true);
350: } else {
351: cursors = cdbCursors.readCursors;
352: cdbConfig = null;
353: }
354: Cursor cursor;
355: if (cursors.size() > 0) {
356: Cursor other = ((Cursor) cursors.get(0));
357: cursor = other.dup(false);
358: } else {
359: cursor = db.openCursor(null, cdbConfig);
360: }
361: cursors.add(cursor);
362: return cursor;
363: } else {
364: return db.openCursor(txn, cursorConfig);
365: }
366: }
367:
368: /**
369: * Duplicates a cursor for a given database.
370: *
371: * @param writeCursor true to open a write cursor in a CDB environment, and
372: * ignored for other environments.
373: *
374: * @param samePosition is passed through to Cursor.dup().
375: *
376: * @return the open cursor.
377: *
378: * @throws DatabaseException if a database problem occurs.
379: */
380: Cursor dupCursor(Cursor cursor, boolean writeCursor,
381: boolean samePosition) throws DatabaseException {
382:
383: if (cdbMode) {
384: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
385: .get();
386: if (cdbCursorsMap != null) {
387: Database db = cursor.getDatabase();
388: CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap
389: .get(db);
390: if (cdbCursors != null) {
391: List cursors = writeCursor ? cdbCursors.writeCursors
392: : cdbCursors.readCursors;
393: if (cursors.contains(cursor)) {
394: Cursor newCursor = cursor.dup(samePosition);
395: cursors.add(newCursor);
396: return newCursor;
397: }
398: }
399: }
400: throw new IllegalStateException("cursor to dup not tracked");
401: } else {
402: return cursor.dup(samePosition);
403: }
404: }
405:
406: /**
407: * Closes a cursor.
408: *
409: * @param cursor the cursor to close.
410: *
411: * @throws DatabaseException if a database problem occurs.
412: */
413: void closeCursor(Cursor cursor) throws DatabaseException {
414:
415: if (cursor == null) {
416: return;
417: }
418: if (cdbMode) {
419: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
420: .get();
421: if (cdbCursorsMap != null) {
422: Database db = cursor.getDatabase();
423: CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap
424: .get(db);
425: if (cdbCursors != null) {
426: if (cdbCursors.readCursors.remove(cursor)
427: || cdbCursors.writeCursors.remove(cursor)) {
428: cursor.close();
429: return;
430: }
431: }
432: }
433: throw new IllegalStateException(
434: "closing CDB cursor that was not known to be open");
435: } else {
436: cursor.close();
437: }
438: }
439:
440: /**
441: * Returns true if a CDB cursor is open and therefore a Database write
442: * operation should not be attempted since a self-deadlock may result.
443: */
444: boolean isCDBCursorOpen(Database db) throws DatabaseException {
445:
446: if (cdbMode) {
447: WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors
448: .get();
449: if (cdbCursorsMap != null) {
450: CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap
451: .get(db);
452:
453: if (cdbCursors != null
454: && (cdbCursors.readCursors.size() > 0 || cdbCursors.writeCursors
455: .size() > 0)) {
456: return true;
457: }
458: }
459: }
460: return false;
461: }
462:
463: static final class CdbCursors {
464:
465: List writeCursors = new ArrayList();
466: List readCursors = new ArrayList();
467: }
468: }
|