001: /**
002: * Copyright (C) 2006 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import java.util.HashMap;
020: import java.util.LinkedList;
021: import java.sql.Connection;
022: import java.util.List;
023: import java.util.Collections;
024: import org.apache.log4j.Logger;
025: import java.util.Vector;
026: import java.util.HashSet;
027: import java.io.Serializable;
028:
029: /**
030: * This is a high-level transaction object used throughout the persistence
031: * layer. Also, it can store attributes, and register listeners.
032: * @author Brautigam Robert
033: * @version Revision: $Revision$
034: */
035: public class Transaction extends HashMap {
036: private static Logger logger = Logger.getLogger(Transaction.class);
037: private static Logger statsLogger = Logger
038: .getLogger(Transaction.class.getName() + ".stats");
039:
040: private TransactionTracker tracker;
041: private Connection connection;
042: private boolean rollbackOnly = false;
043: private int depth = 0;
044: private Long serial;
045: private Long endSerial;
046: private StoreException rollbackException = null;
047:
048: private LinkedList savedObjectList;
049: private LinkedList removedObjectList;
050: private HashSet saveTables;
051: private HashSet removeTables;
052:
053: /* Statistical attributes */
054: private DatabaseStatistics stats;
055: private Exception allocateTrace = null;
056: private String lastOperation;
057: private Thread starterThread;
058:
059: public Transaction(TransactionTracker tracker) {
060: super ();
061: this .tracker = tracker;
062: savedObjectList = new LinkedList();
063: removedObjectList = new LinkedList();
064: saveTables = new HashSet();
065: removeTables = new HashSet();
066: stats = new DatabaseStatistics();
067: starterThread = Thread.currentThread();
068: allocateTrace = new Exception("trace");
069: logger.debug("transaction created.");
070: }
071:
072: Thread getStarterThread() {
073: return starterThread;
074: }
075:
076: Exception getAllocateTrace() {
077: return allocateTrace;
078: }
079:
080: String getLastOperation() {
081: return lastOperation;
082: }
083:
084: void setLastOperation(String lastOperation) {
085: this .lastOperation = lastOperation;
086: }
087:
088: public int getTransactionIsolation() {
089: try {
090: return connection.getTransactionIsolation();
091: } catch (Exception e) {
092: throw new StoreException(
093: "unable to determine transaction isolation level.");
094: }
095: }
096:
097: public void setTransactionIsolation(int level) {
098: try {
099: connection.setTransactionIsolation(level);
100: } catch (Exception e) {
101: throw new StoreException(
102: "cannot set transaction isolation level: " + level,
103: e);
104: }
105: }
106:
107: TransactionTracker getTracker() {
108: return tracker;
109: }
110:
111: Connection getConnection() {
112: return connection;
113: }
114:
115: void setConnection(Connection connection) {
116: this .connection = connection;
117: }
118:
119: void addSavedObject(Object obj) {
120: savedObjectList.add(obj);
121: }
122:
123: /**
124: * Get the objects saved in this transaction as a list.
125: */
126: List getSavedObjects() {
127: return Collections.unmodifiableList(savedObjectList);
128: }
129:
130: void addRemovedObject(Object obj) {
131: removedObjectList.add(obj);
132: }
133:
134: /**
135: * Get the objects removed in this transaction as a list.
136: */
137: List getRemovedObjects() {
138: return Collections.unmodifiableList(removedObjectList);
139: }
140:
141: /**
142: * Start the transaction. A transaction must be always start with
143: * the call to <code>begin()</code>. All subsequent calls to this method
144: * will increase the transaction depth, and to commit the transaction
145: * exactly that many <code>commit()</code> and <code>rollback()</code>
146: * calls must occur.
147: */
148: public void begin() {
149: logger.debug("transaction begins at depth: " + depth);
150: depth++;
151: }
152:
153: /**
154: * Commit a transaction.
155: */
156: public void commit() {
157: if (logger.isDebugEnabled())
158: logger.debug("transaction " + this + " commits at depth: "
159: + depth);
160: if (statsLogger.isDebugEnabled())
161: statsLogger.debug("transaction stats in commit: " + stats);
162: depth--;
163: if (depth > 0)
164: return;
165: if ((rollbackOnly) || (rollbackException != null)) {
166: tracker.rollback(this );
167: // Throw the exception given if use didn't anticipate
168: // rollback.
169: if (!rollbackOnly)
170: throw rollbackException;
171: } else
172: tracker.commit(this );
173: }
174:
175: /**
176: * Rollback this transaction.
177: */
178: public void rollback() {
179: if (logger.isDebugEnabled())
180: logger.debug("transaction " + this
181: + " rolls back at depth: " + depth);
182: if (statsLogger.isDebugEnabled())
183: statsLogger
184: .debug("transaction stats in rollback: " + stats);
185: depth--;
186: if (depth > 0) {
187: markRollbackOnly();
188: return;
189: }
190: tracker.rollback(this );
191: }
192:
193: /**
194: * Mark this transaction as rollback only. This means the next call
195: * to either commit or rollback will rollback either way.
196: */
197: public void markRollbackOnly() {
198: rollbackOnly = true;
199: }
200:
201: /**
202: * Returns whether this transaction was marked "rollback only".
203: */
204: public boolean isRollbackOnly() {
205: return rollbackOnly;
206: }
207:
208: /**
209: * Get the serial of this transaction, and set with given serial
210: * if not yet given.
211: */
212: Long getSerial(Long serial) {
213: if ((this .serial == null) && (serial != null)) {
214: // Receive serial
215: this .serial = serial;
216: tracker.registerTransactionId(serial);
217: }
218: return this .serial;
219: }
220:
221: Long getSerial() {
222: return this .serial;
223: }
224:
225: void addSaveTable(String table) {
226: saveTables.add(table);
227: }
228:
229: void setSaveTables(List saveTables) {
230: this .saveTables = new HashSet(saveTables);
231: }
232:
233: List getSaveTables() {
234: return new Vector(saveTables);
235: }
236:
237: void addRemoveTable(String table) {
238: removeTables.add(table);
239: }
240:
241: void setRemoveTables(List removeTables) {
242: this .removeTables = new HashSet(removeTables);
243: }
244:
245: List getRemoveTables() {
246: return new Vector(removeTables);
247: }
248:
249: public DatabaseStatistics getStats() {
250: return stats;
251: }
252:
253: void addStats(DatabaseStatistics stats) {
254: this .stats.add(stats);
255: }
256:
257: public String toString() {
258: return "[Tx: " + serial + "]";
259: }
260:
261: public int hashCode() {
262: return System.identityHashCode(this );
263: }
264:
265: public boolean equals(Object obj) {
266: return this == obj;
267: }
268:
269: StoreException getRollbackException() {
270: return rollbackException;
271: }
272:
273: void setRollbackException(StoreException rollbackException) {
274: this .rollbackException = rollbackException;
275: }
276:
277: Long getEndSerial() {
278: return endSerial;
279: }
280:
281: void setEndSerial(Long endSerial) {
282: this.endSerial = endSerial;
283: }
284:
285: }
|