001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/TransactionImpl.java,v 1.36 2004/05/17 22:55:47 wbiggs Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm;
021:
022: import java.util.ArrayList;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.TreeSet;
026:
027: import javax.transaction.Status;
028: import javax.transaction.Synchronization;
029:
030: import javax.jdo.InstanceCallbacks;
031: import javax.jdo.Transaction;
032: import javax.jdo.PersistenceManager;
033: import javax.jdo.JDODataStoreException;
034: import javax.jdo.JDOFatalDataStoreException;
035: import javax.jdo.JDOUserException;
036:
037: import org.xorm.datastore.DatastoreDriver;
038: import org.xorm.datastore.DriverException;
039: import org.xorm.datastore.Row;
040:
041: /**
042: * A transaction with ACID properties, to which may be attached
043: * any number of objects.
044: */
045: public class TransactionImpl implements Transaction, I15d {
046: private int status = Status.STATUS_NO_TRANSACTION;
047: private Synchronization synchronization;
048:
049: // Objects in hash are InterfaceInvocationHandlers
050: private HashSet objects = new HashSet();
051: // and here, RelationshipProxies
052: private HashSet relationships = new HashSet();
053:
054: private InterfaceManager mgr;
055: private DatastoreDriver driver;
056: private Options jdoOptions;
057:
058: public TransactionImpl(InterfaceManager mgr,
059: DatastoreDriver driver, Options jdoOptions) {
060: this .mgr = mgr;
061: this .driver = driver;
062: this .jdoOptions = jdoOptions;
063: }
064:
065: public PersistenceManager getPersistenceManager() {
066: return mgr;
067: }
068:
069: InterfaceManager getInterfaceManager() {
070: return mgr;
071: }
072:
073: public DatastoreDriver getDriver() {
074: return driver;
075: }
076:
077: // Adds a proxy object to the transaction
078: // Assumes the object is marked as transactional
079: public void attach(Object o) {
080: InterfaceInvocationHandler handler = InterfaceInvocationHandler
081: .getHandler(o);
082: objects.add(handler);
083: }
084:
085: void attachRelationship(RelationshipProxy rp) {
086: relationships.add(rp);
087: }
088:
089: public boolean contains(Object o) {
090: InterfaceInvocationHandler handler = InterfaceInvocationHandler
091: .getHandler(o);
092: return objects.contains(handler);
093: }
094:
095: public Object get(Class clazz, Object primaryKey) {
096: // TODO change implementation for performance
097: Iterator i = objects.iterator();
098: while (i.hasNext()) {
099: InterfaceInvocationHandler handler = (InterfaceInvocationHandler) i
100: .next();
101: if (handler.getClassMapping().getMappedClass()
102: .equals(clazz)
103: && handler.getObjectId().equals(primaryKey)) {
104: return handler.getProxy();
105: }
106: }
107: return null;
108: }
109:
110: public void detach(Object o) {
111: InterfaceInvocationHandler handler = InterfaceInvocationHandler
112: .getHandler(o);
113: objects.remove(handler);
114: }
115:
116: public void detachRelationship(RelationshipProxy rp) {
117: relationships.remove(rp);
118: }
119:
120: public void begin() {
121: begin(false);
122: }
123:
124: public void begin(boolean readOnly) {
125: // Avoid race conditions here
126: synchronized (this ) {
127: if (isActive()) {
128: throw new JDOUserException(I18N
129: .msg("E_begin_active_txn"));
130: }
131: status = Status.STATUS_ACTIVE;
132: }
133: try {
134: driver.begin(readOnly);
135: // } catch (DriverException e) {
136: } catch (Throwable e) {
137: e.printStackTrace();
138: status = Status.STATUS_NO_TRANSACTION;
139: throw new JDODataStoreException("Cannot begin transaction",
140: e);
141: }
142: }
143:
144: private void notifyExit(boolean commit) {
145: // Now tell everything the transaction's over.
146: Iterator i = objects.iterator();
147: while (i.hasNext()) {
148: if (((InterfaceInvocationHandler) i.next())
149: .exitTransaction(commit)) {
150: i.remove();
151: }
152: }
153: i = relationships.iterator();
154: while (i.hasNext()) {
155: ((RelationshipProxy) i.next()).exitTransaction(commit);
156: }
157: }
158:
159: public void rollback() {
160: if (status == Status.STATUS_NO_TRANSACTION) {
161: throw new JDOUserException(I18N.msg("E_rollback_no_txn"));
162: }
163:
164: status = Status.STATUS_COMMITTING; // Is this correct?
165:
166: try {
167: driver.rollback();
168: //} catch (DriverException e) {
169: } catch (Throwable e) {
170: e.printStackTrace();
171: throw new JDOFatalDataStoreException("Rollback failed", e);
172: }
173: notifyExit(false);
174: reset();
175: if (synchronization != null) {
176: synchronization.afterCompletion(Status.STATUS_ROLLEDBACK);
177: }
178: }
179:
180: public void commit() {
181: if (status == Status.STATUS_NO_TRANSACTION) {
182: throw new JDOUserException(I18N.msg("E_commit_no_txn"));
183: }
184:
185: status = Status.STATUS_COMMITTING;
186: ArrayList driverExceptions = new ArrayList();
187:
188: if (synchronization != null) {
189: synchronization.beforeCompletion();
190: }
191:
192: // Iterate over objects and build the list of those which
193: // need to be inserted/updated
194: TreeSet tree = new TreeSet(new DependencyComparator());
195: Iterator i = objects.iterator();
196: while (i.hasNext()) {
197: InterfaceInvocationHandler handler = (InterfaceInvocationHandler) i
198: .next();
199: if (handler.isPersistent()) {
200: if (handler.isDirty() || handler.isNew()
201: || handler.isDeleted()) {
202: tree.add(handler);
203: }
204: }
205: }
206:
207: // Remove deleted many-to-many relationship rows
208: i = relationships.iterator();
209: while (i.hasNext()) {
210: RelationshipProxy rp = (RelationshipProxy) i.next();
211: if (rp.getRelationshipMapping().isMToN()) {
212: try {
213: Iterator j = rp.getDeletedRows().iterator();
214: while (j.hasNext()) {
215: driver.delete((Row) j.next());
216: j.remove();
217: }
218: } catch (DriverException e) {
219: driverExceptions.add(e);
220: }
221: }
222: }
223:
224: // Now walk the list and take appropriate actions
225: i = tree.iterator();
226: while (i.hasNext()) {
227: InterfaceInvocationHandler handler = (InterfaceInvocationHandler) i
228: .next();
229: try {
230: if (handler.isNew()) {
231: if (!handler.isDeleted()) {
232: if (handler.getProxy() instanceof InstanceCallbacks) {
233: ((InstanceCallbacks) handler.getProxy())
234: .jdoPreStore();
235: }
236: Object oldID = handler.getObjectId();
237: driver.create(handler.getRow());
238: handler.refreshObjectId();
239: Object newID = handler.getObjectId();
240: notifyIDChangedAll(tree, oldID, newID);
241:
242: // Create the 2nd level cache entry
243: mgr.addToCache(handler.getRow());
244: }
245: } else if (handler.isDeleted()) {
246: if (handler.getProxy() instanceof InstanceCallbacks) {
247: ((InstanceCallbacks) handler.getProxy())
248: .jdoPreDelete();
249: }
250: driver.delete(handler.getRow());
251:
252: // Remove from the 2nd level cache
253: mgr.removeFromCache(handler.getRow());
254: } else if (handler.getRow().isDirty()) {
255: if (handler.getProxy() instanceof InstanceCallbacks) {
256: ((InstanceCallbacks) handler.getProxy())
257: .jdoPreStore();
258: }
259: driver.update(handler.getRow());
260:
261: // Replace the 2nd level cache entry
262: mgr.addToCache(handler.getRow());
263: }
264: } catch (Throwable e) {
265: e.printStackTrace();
266: driverExceptions.add(e);
267: }
268: }
269:
270: // After data objects are inserted, insert new relationship rows
271: i = relationships.iterator();
272: while (i.hasNext()) {
273: RelationshipProxy rp = (RelationshipProxy) i.next();
274: if (rp.getRelationshipMapping().isMToN()) {
275: try {
276: Iterator j = rp.getNewRows().iterator();
277: while (j.hasNext()) {
278: driver.create((Row) j.next());
279: j.remove();
280: }
281: } catch (Throwable e) {
282: e.printStackTrace();
283: driverExceptions.add(e);
284: }
285: }
286: }
287:
288: if (!driverExceptions.isEmpty()) {
289: // An error occurred
290: rollback();
291: throw new JDODataStoreException(
292: "Errors writing to datastore, transaction rolled back",
293: (Throwable[]) driverExceptions
294: .toArray(new Throwable[0]));
295: }
296:
297: try {
298: driver.commit();
299: status = Status.STATUS_COMMITTED;
300: } catch (Throwable e) {
301: e.printStackTrace();
302: try {
303: rollback();
304: throw new JDODataStoreException(
305: "Commit failed, rolled back instead", e);
306: } catch (JDOFatalDataStoreException e2) {
307: throw new JDOFatalDataStoreException(
308: "Commit failed, could not rollback", e);
309: }
310: }
311:
312: // Tell the contained objects to perform state transitions
313: notifyExit(true);
314:
315: if (synchronization != null) {
316: synchronization.afterCompletion(status);
317: }
318: reset();
319: }
320:
321: private void notifyIDChangedAll(TreeSet tree, Object oldID,
322: Object newID) {
323: Iterator i = tree.iterator();
324: while (i.hasNext()) {
325: InterfaceInvocationHandler handler = (InterfaceInvocationHandler) i
326: .next();
327: handler.notifyIDChanged(oldID, newID);
328: }
329: i = relationships.iterator();
330: while (i.hasNext()) {
331: RelationshipProxy rp = (RelationshipProxy) i.next();
332: rp.notifyIDChanged(oldID, newID);
333: }
334: }
335:
336: private void reset() {
337: status = Status.STATUS_NO_TRANSACTION;
338: objects = new HashSet();
339: relationships = new HashSet();
340: }
341:
342: public boolean isActive() {
343: return status != Status.STATUS_NO_TRANSACTION;
344: }
345:
346: public void setSynchronization(Synchronization synchronization) {
347: // Per JDO Spec 13.4.3, throw exception if called from callback
348: if (status == Status.STATUS_COMMITTING) {
349: throw new JDOUserException(I18N.msg("E_txn_committing"));
350: }
351: this .synchronization = synchronization;
352: }
353:
354: public Synchronization getSynchronization() {
355: return synchronization;
356: }
357:
358: void refreshAll() {
359: // Reloads all objects in the scope of the current transaction
360: Iterator i = objects.iterator();
361: while (i.hasNext()) {
362: InterfaceInvocationHandler handler = (InterfaceInvocationHandler) i
363: .next();
364: mgr.refresh(handler.getProxy());
365: }
366: }
367:
368: // The JDO options get/set methods are wrapper pattern.
369:
370: public void setNontransactionalRead(boolean value) {
371: jdoOptions.setNontransactionalRead(value);
372: }
373:
374: public boolean getNontransactionalRead() {
375: return jdoOptions.getNontransactionalRead();
376: }
377:
378: public void setNontransactionalWrite(boolean value) {
379: jdoOptions.setNontransactionalWrite(value);
380: }
381:
382: public boolean getNontransactionalWrite() {
383: return jdoOptions.getNontransactionalWrite();
384: }
385:
386: public void setOptimistic(boolean value) {
387: jdoOptions.setOptimistic(value);
388: }
389:
390: public boolean getOptimistic() {
391: return jdoOptions.getOptimistic();
392: }
393:
394: public void setRetainValues(boolean value) {
395: jdoOptions.setRetainValues(value);
396: }
397:
398: public boolean getRetainValues() {
399: return jdoOptions.getRetainValues();
400: }
401:
402: public void setRestoreValues(boolean restoreValues) {
403: jdoOptions.setRestoreValues(restoreValues);
404: }
405:
406: public boolean getRestoreValues() {
407: return jdoOptions.getRestoreValues();
408: }
409:
410: protected void finalize() throws Throwable {
411: if (isActive()) {
412: closeImpl();
413: }
414: }
415:
416: /**
417: * Releases active transactional resources by rolling back the
418: * datastore transaction if necessary.
419: */
420: void closeImpl() {
421: try {
422: rollback();
423: System.err.println(I18N.msg("W_finalize_active_txn"));
424: } catch (Throwable e) {
425: System.err.println(I18N.msg("W_finalize_active_txn_fail"));
426: //no logger, and throwing an exception in finalize is bad.
427: //Oh well, just System.err it.
428: e.printStackTrace();
429: status = Status.STATUS_NO_TRANSACTION;
430: }
431: }
432: }
|