001 package org.apache.ojb.odmg;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import javax.transaction.Status;
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.Enumeration;
022 import java.util.HashMap;
023 import java.util.Iterator;
024 import java.util.List;
025
026 import org.apache.commons.lang.SystemUtils;
027 import org.apache.ojb.broker.Identity;
028 import org.apache.ojb.broker.OJBRuntimeException;
029 import org.apache.ojb.broker.PBFactoryException;
030 import org.apache.ojb.broker.PersistenceBroker;
031 import org.apache.ojb.broker.PersistenceBrokerException;
032 import org.apache.ojb.broker.PersistenceBrokerInternal;
033 import org.apache.ojb.broker.core.PersistenceBrokerFactoryFactory;
034 import org.apache.ojb.broker.core.proxy.CollectionProxy;
035 import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
036 import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
037 import org.apache.ojb.broker.core.proxy.IndirectionHandler;
038 import org.apache.ojb.broker.core.proxy.MaterializationListener;
039 import org.apache.ojb.broker.core.proxy.ProxyHelper;
040 import org.apache.ojb.broker.metadata.ClassDescriptor;
041 import org.apache.ojb.broker.metadata.CollectionDescriptor;
042 import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
043 import org.apache.ojb.broker.util.BrokerHelper;
044 import org.apache.ojb.broker.util.GUID;
045 import org.apache.ojb.broker.util.configuration.Configurable;
046 import org.apache.ojb.broker.util.configuration.Configuration;
047 import org.apache.ojb.broker.util.configuration.ConfigurationException;
048 import org.apache.ojb.broker.util.logging.Logger;
049 import org.apache.ojb.broker.util.logging.LoggerFactory;
050 import org.apache.ojb.odmg.locking.LockManager;
051 import org.odmg.DatabaseClosedException;
052 import org.odmg.LockNotGrantedException;
053 import org.odmg.ODMGRuntimeException;
054 import org.odmg.Transaction;
055 import org.odmg.TransactionAbortedException;
056 import org.odmg.TransactionNotInProgressException;
057
058 /**
059 *
060 * Implementation of Transaction for org.odmg.Transaction.
061 *
062 * @author Thomas Mahler & David Dixon-Peugh
063 * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
064 * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
065 * @author <a href="mailto:brianm@apache.org">Brian McCallister</a>
066 * @version $Id: TransactionImpl.java,v 1.1 2007-08-24 22:17:37 ewestfal Exp $
067 *
068 */
069 public class TransactionImpl
070 implements Transaction, MaterializationListener, Configurable, CollectionProxyListener, TransactionExt
071 {
072 private Logger log = LoggerFactory.getLogger(TransactionImpl.class);
073 private boolean impliciteWriteLocks;
074 private boolean implicitLocking;
075 private boolean ordering;
076
077 private String txGUID;
078 protected PersistenceBrokerInternal broker = null;
079 private ArrayList registrationList = new ArrayList();
080 private ImplementationImpl implementation;
081 private NamedRootsMap namedRootsMap;
082
083 /**
084 * The status of the current transaction, as specified by the
085 * javax.transaction package.
086 * See {@link javax.transaction.Status} for list of valid values.
087 */
088 private int txStatus = Status.STATUS_NO_TRANSACTION;
089
090 /**
091 * the internal table containing all Objects "touched" by this tx and their
092 * respective transactional state
093 */
094 protected ObjectEnvelopeTable objectEnvelopeTable = null;
095
096 /**
097 * reference to the currently opened database
098 */
099 private DatabaseImpl curDB;
100 /**
101 * The tx may me listening to a number of IndirectionHandlers.
102 * on abort or commit these Handlers must be informed to remove
103 * tx from their List of Listeners.
104 */
105 private ArrayList registeredIndirectionHandlers = new ArrayList();
106
107 /**
108 * Unloaded collection proxies which will be registered with tx when
109 * collection is loaded
110 */
111 private ArrayList registeredCollectionProxies = new ArrayList();
112
113 /**
114 * list of proxy objects that were locked, but haven't been materialized yet.
115 * This is necessary so the locks can be released on closing the transaction
116 */
117 private ArrayList unmaterializedLocks = new ArrayList();
118
119 /**
120 * Creates new Transaction
121 * @param implementation The odmg Implementation class
122 */
123 public TransactionImpl(ImplementationImpl implementation)
124 {
125 this.implementation = implementation;
126 this.impliciteWriteLocks = implementation.isImpliciteWriteLocks();
127 this.implicitLocking = implementation.isImplicitLocking();
128 this.ordering = implementation.isOrdering();
129 //this.noteUserOrdering = implementation.isNoteUserOrder();
130
131 // assign a globally uniqe id to this tx
132 txGUID = new GUID().toString();
133 curDB = implementation.getCurrentDatabase();
134 namedRootsMap = new NamedRootsMap(this);
135 }
136
137 public ImplementationImpl getImplementation()
138 {
139 return implementation;
140 }
141
142 public NamedRootsMap getNamedRootsMap()
143 {
144 return namedRootsMap;
145 }
146
147 /**
148 * Returns the associated database
149 */
150 public DatabaseImpl getAssociatedDatabase()
151 {
152 return this.curDB;
153 }
154
155 protected int getStatus()
156 {
157 return txStatus;
158 }
159
160 protected void setStatus(int status)
161 {
162 this.txStatus = status;
163 }
164
165 private void checkForDB()
166 {
167 if (curDB == null || !curDB.isOpen())
168 {
169 log.error("Transaction without a associated open Database.");
170 throw new TransactionAbortedExceptionOJB(
171 "No open database found. Open the database before handling transactions");
172 }
173 }
174
175 /**
176 * Determine whether the transaction is open or not. A transaction is open if
177 * a call has been made to <code>begin</code> , but a subsequent call to
178 * either <code>commit</code> or <code>abort</code> has not been made.
179 * @return True if the transaction is open, otherwise false.
180 */
181 public boolean isOpen()
182 {
183 return (getStatus() == Status.STATUS_ACTIVE ||
184 getStatus() == Status.STATUS_MARKED_ROLLBACK ||
185 getStatus() == Status.STATUS_PREPARED ||
186 getStatus() == Status.STATUS_PREPARING ||
187 getStatus() == Status.STATUS_COMMITTING);
188 }
189
190 private void checkOpen()
191 {
192 if (!isOpen())
193 {
194 throw new TransactionNotInProgressException(
195 "Transaction was not open, call tx.begin() before perform action, current status is: " +
196 TxUtil.getStatusString(getStatus()));
197 }
198 }
199
200
201 /**
202 * Attach the caller's thread to this <code>Transaction</code> and detach the
203 * thread from any former <code>Transaction</code> the thread may have been
204 * associated with.
205 */
206 public void join()
207 {
208 checkOpen();
209 implementation.getTxManager().deregisterTx(this);
210 implementation.getTxManager().registerTx(this);
211 }
212
213 /**
214 * Upgrade the lock on the given object to the given lock mode. The call has
215 * no effect if the object's current lock is already at or above that level of
216 * lock mode.
217 *
218 * @param obj object to acquire a lock on.
219 * @param lockMode lock mode to acquire. The lock modes
220 * are <code>READ</code> , <code>UPGRADE</code> , and <code>WRITE</code> .
221 *
222 * @exception LockNotGrantedException Description of Exception
223 */
224 public void lock(Object obj, int lockMode) throws LockNotGrantedException
225 {
226 if (log.isDebugEnabled()) log.debug("lock object was called on tx " + this + ", object is " + obj.toString());
227 checkOpen();
228 RuntimeObject rtObject = new RuntimeObject(obj, this);
229 lockAndRegister(rtObject, lockMode, isImplicitLocking(), getRegistrationList());
230 // if(isImplicitLocking()) moveToLastInOrderList(rtObject.getIdentity());
231 }
232
233 /**
234 * Returns an empty List for registration of processed object Identity.
235 */
236 public ArrayList getRegistrationList()
237 {
238 clearRegistrationList();
239 return registrationList;
240 }
241
242 /**
243 * Clears the list of processed object Identity.
244 */
245 public void clearRegistrationList()
246 {
247 registrationList.clear();
248 }
249
250 /**
251 * Lock and register the specified object, make sure that when cascading locking and register
252 * is enabled to specify a List to register the already processed object Identiy.
253 */
254 public void lockAndRegister(RuntimeObject rtObject, int lockMode, List registeredObjects)
255 {
256 lockAndRegister(rtObject, lockMode, isImplicitLocking(), registeredObjects);
257 }
258
259 /**
260 * Lock and register the specified object, make sure that when cascading locking and register
261 * is enabled to specify a List to register the already processed object Identiy.
262 */
263 public synchronized void lockAndRegister(RuntimeObject rtObject, int lockMode, boolean cascade, List registeredObjects)
264 {
265 if(log.isDebugEnabled()) log.debug("Lock and register called for " + rtObject.getIdentity());
266 // if current object was already locked, do nothing
267 // avoid endless loops when circular object references are used
268 if(!registeredObjects.contains(rtObject.getIdentity()))
269 {
270 if(cascade)
271 {
272 // if implicite locking is enabled, first add the current object to
273 // list of registered objects to avoid endless loops on circular objects
274 registeredObjects.add(rtObject.getIdentity());
275 // lock and register 1:1 references first
276 //
277 // If implicit locking is used, we have materialize the main object
278 // to lock the referenced objects too
279 lockAndRegisterReferences(rtObject.getCld(), rtObject.getObjMaterialized(), lockMode, registeredObjects);
280 }
281 try
282 {
283 // perform the lock on the object
284 // we don't need to lock new objects
285 if(!rtObject.isNew())
286 {
287 doSingleLock(rtObject.getCld(), rtObject.getObj(), rtObject.getIdentity(), lockMode);
288 }
289 // after we locked the object, register it to detect status and changes while tx
290 doSingleRegister(rtObject, lockMode);
291 }
292 catch (Throwable t)
293 {
294 //log.error("Locking of obj " + rtObject.getIdentity() + " failed", t);
295 // if registering of object fails release lock on object, because later we don't
296 // get a change to do this.
297 implementation.getLockManager().releaseLock(this, rtObject.getIdentity(), rtObject.getObj());
298 if(t instanceof LockNotGrantedException)
299 {
300 throw (LockNotGrantedException) t;
301 }
302 else
303 {
304 log.error("Unexpected failure while locking", t);
305 throw new LockNotGrantedException("Locking failed for "
306 + rtObject.getIdentity()+ ", nested exception is: [" + t.getClass().getName()
307 + ": " + t.getMessage() + "]");
308 }
309 }
310 if(cascade)
311 {
312 // perform locks and register 1:n and m:n references
313 // If implicit locking is used, we have materialize the main object
314 // to lock the referenced objects too
315 lockAndRegisterCollections(rtObject.getCld(), rtObject.getObjMaterialized(), lockMode, registeredObjects);
316 }
317 }
318 }
319
320 /**
321 * Only lock the specified object, represented by
322 * the {@link RuntimeObject} instance.
323 *
324 * @param cld The {@link org.apache.ojb.broker.metadata.ClassDescriptor}
325 * of the object to acquire a lock on.
326 * @param oid The {@link org.apache.ojb.broker.Identity} of the object to lock.
327 * @param lockMode lock mode to acquire. The lock modes
328 * are <code>READ</code> , <code>UPGRADE</code> , and <code>WRITE</code>.
329 *
330 * @exception LockNotGrantedException Description of Exception
331 */
332 void doSingleLock(ClassDescriptor cld, Object obj, Identity oid, int lockMode) throws LockNotGrantedException
333 {
334 LockManager lm = implementation.getLockManager();
335 if (cld.isAcceptLocks())
336 {
337 if (lockMode == Transaction.READ)
338 {
339 if (log.isDebugEnabled()) log.debug("Do READ lock on object: " + oid);
340 if(!lm.readLock(this, oid, obj))
341 {
342 throw new LockNotGrantedException("Can not lock for READ: " + oid);
343 }
344 }
345 else if (lockMode == Transaction.WRITE)
346 {
347 if (log.isDebugEnabled()) log.debug("Do WRITE lock on object: " + oid);
348 if(!lm.writeLock(this, oid, obj))
349 {
350 throw new LockNotGrantedException("Can not lock for WRITE: " + oid);
351 }
352 }
353 else if (lockMode == Transaction.UPGRADE)
354 {
355 if (log.isDebugEnabled()) log.debug("Do UPGRADE lock on object: " + oid);
356 if(!lm.upgradeLock(this, oid, obj))
357 {
358 throw new LockNotGrantedException("Can not lock for UPGRADE: " + oid);
359 }
360 }
361 }
362 else
363 {
364 if (log.isDebugEnabled())
365 {
366 log.debug("Class '" + cld.getClassNameOfObject() + "' doesn't accept locks" +
367 " (accept-locks=false) when implicite locked, so OJB skip this object: " + oid);
368 }
369 }
370 }
371
372 /**
373 * Detach the caller's thread from this <code>Transaction</code> , but do not
374 * attach the thread to another <code>Transaction</code> .
375 */
376 public void leave()
377 {
378 checkOpen();
379 implementation.getTxManager().deregisterTx(this);
380 }
381
382 /**
383 * Write objects to data store, but don't release the locks.
384 * I don't know what we should do if we are in a checkpoint and
385 * we need to abort.
386 */
387 protected synchronized void doWriteObjects(boolean isFlush) throws TransactionAbortedException, LockNotGrantedException
388 {
389 /*
390 arminw:
391 if broker isn't in PB-tx, start tx
392 */
393 if (!getBroker().isInTransaction())
394 {
395 if (log.isDebugEnabled()) log.debug("call beginTransaction() on PB instance");
396 broker.beginTransaction();
397 }
398
399 // Notify objects of impending commits.
400 performTransactionAwareBeforeCommit();
401
402 // Now perfom the real work
403 objectEnvelopeTable.writeObjects(isFlush);
404 // now we have to perform the named objects
405 namedRootsMap.performDeletion();
406 namedRootsMap.performInsert();
407 namedRootsMap.afterWriteCleanup();
408 }
409
410 /**
411 * Do the Aborts, but don't release the locks.
412 * Do the aborts on the NamedRootsMap first, then
413 * abort the other stuff.
414 */
415 protected synchronized void doAbort()
416 {
417 // Notify objects of impending aborts.
418 performTransactionAwareBeforeRollback();
419
420 // Now, we abort everything. . .
421 objectEnvelopeTable.rollback();
422
423 // Now, we notify everything the abort is done.
424 performTransactionAwareAfterRollback();
425 }
426
427 /**
428 * Close a transaction and do all the cleanup associated with it.
429 */
430 protected synchronized void doClose()
431 {
432 try
433 {
434 LockManager lm = getImplementation().getLockManager();
435 Enumeration en = objectEnvelopeTable.elements();
436 while (en.hasMoreElements())
437 {
438 ObjectEnvelope oe = (ObjectEnvelope) en.nextElement();
439 lm.releaseLock(this, oe.getIdentity(), oe.getObject());
440 }
441
442 //remove locks for objects which haven't been materialized yet
443 for (Iterator it = unmaterializedLocks.iterator(); it.hasNext();)
444 {
445 lm.releaseLock(this, it.next());
446 }
447
448 // this tx is no longer interested in materialization callbacks
449 unRegisterFromAllIndirectionHandlers();
450 unRegisterFromAllCollectionProxies();
451 }
452 finally
453 {
454 /**
455 * MBAIRD: Be nice and close the table to release all refs
456 */
457 if (log.isDebugEnabled())
458 log.debug("Close Transaction and release current PB " + broker + " on tx " + this);
459 // remove current thread from LocalTxManager
460 // to avoid problems for succeeding calls of the same thread
461 implementation.getTxManager().deregisterTx(this);
462 // now cleanup and prepare for reuse
463 refresh();
464 }
465 }
466
467 /**
468 * cleanup tx and prepare for reuse
469 */
470 protected void refresh()
471 {
472 if (log.isDebugEnabled())
473 log.debug("Refresh this transaction for reuse: " + this);
474 try
475 {
476 // we reuse ObjectEnvelopeTable instance
477 objectEnvelopeTable.refresh();
478 }
479 catch (Exception e)
480 {
481 if (log.isDebugEnabled())
482 {
483 log.debug("error closing object envelope table : " + e.getMessage());
484 e.printStackTrace();
485 }
486 }
487 cleanupBroker();
488 // clear the temporary used named roots map
489 // we should do that, because same tx instance
490 // could be used several times
491 broker = null;
492 clearRegistrationList();
493 unmaterializedLocks.clear();
494 txStatus = Status.STATUS_NO_TRANSACTION;
495 }
496
497 /**
498 * Commit the transaction, but reopen the transaction, retaining all locks.
499 * Calling <code>checkpoint</code> commits persistent object modifications
500 * made within the transaction since the last checkpoint to the database. The
501 * transaction retains all locks it held on those objects at the time the
502 * checkpoint was invoked.
503 */
504 public void checkpoint()
505 {
506 if (log.isDebugEnabled()) log.debug("Checkpoint was called, commit changes hold locks on tx " + this);
507 try
508 {
509 checkOpen();
510 doWriteObjects(true);
511 // do commit on PB
512 if (hasBroker() && broker.isInTransaction()) broker.commitTransaction();
513 }
514 catch (Throwable t)
515 {
516 log.error("Checkpoint call failed, do abort transaction", t);
517 txStatus = Status.STATUS_MARKED_ROLLBACK;
518 abort();
519 if(!(t instanceof ODMGRuntimeException))
520 {
521 throw new TransactionAbortedExceptionOJB("Can't tx.checkpoint() objects: " + t.getMessage(), t);
522 }
523 else
524 {
525 throw (ODMGRuntimeException) t;
526 }
527 }
528 }
529
530 /**
531 * @see org.apache.ojb.odmg.TransactionExt#flush
532 */
533 public void flush()
534 {
535 if (log.isDebugEnabled())
536 {
537 log.debug("Flush was called - write changes to database, do not commit, hold locks on tx " + this);
538 }
539
540 try
541 {
542 checkOpen();
543 doWriteObjects(true);
544 }
545 catch (Throwable t)
546 {
547 log.error("Calling method 'tx.flush()' failed", t);
548 txStatus = Status.STATUS_MARKED_ROLLBACK;
549 abort();
550 if(!(t instanceof ODMGRuntimeException))
551 {
552 throw new TransactionAbortedExceptionOJB("Can't tx.flush() objects: " + t.getMessage(), t);
553 }
554 else
555 {
556 throw (ODMGRuntimeException) t;
557 }
558 }
559 }
560
561 /**
562 * @see org.apache.ojb.odmg.TransactionExt#markDelete
563 */
564 public void markDelete(Object anObject)
565 {
566 ObjectEnvelope otw = objectEnvelopeTable.get(anObject, false);
567 // not needed on delete - or?
568 //otw.refreshObjectIfNeeded(anObject);
569 otw.setModificationState(otw.getModificationState().markDelete());
570 }
571
572 public void deletePersistent(RuntimeObject rt)
573 {
574 // if(rt.isNew())
575 // {
576 // throw new ObjectNotPersistentException("Object " + rt.getIdentity() + " is not yet persistent");
577 // }
578 if(rt.isProxy())
579 {
580 Object realObj = rt.getHandler().getRealSubject();
581 rt = new RuntimeObject(realObj, rt.getIdentity(), this, false);
582 }
583 lockAndRegister(rt, Transaction.WRITE, getRegistrationList());
584 ObjectEnvelope oe = objectEnvelopeTable.getByIdentity(rt.getIdentity());
585 // TODO: not needed on delete - or? When optimistic locking is used we should always use the
586 // specified object instance to use the last version of the object
587 oe.refreshObjectIfNeeded(rt.getObj());
588 oe.setModificationState(oe.getModificationState().markDelete());
589 }
590
591 /**
592 * @see org.apache.ojb.odmg.TransactionExt#markDirty
593 */
594 public void markDirty(Object anObject)
595 {
596 ObjectEnvelope otw = objectEnvelopeTable.get(anObject, false);
597 otw.refreshObjectIfNeeded(anObject);
598 otw.setModificationState(otw.getModificationState().markDirty());
599 }
600
601 void markDirty(RuntimeObject rt)
602 {
603 ObjectEnvelope otw = objectEnvelopeTable.get(rt.getIdentity(), rt.getObj(), rt.isNew());
604 otw.refreshObjectIfNeeded(rt.getObj());
605 otw.setModificationState(otw.getModificationState().markDirty());
606 }
607
608 void markPersistent(RuntimeObject rtObj)
609 {
610 ObjectEnvelope oe = objectEnvelopeTable.getByIdentity(rtObj.getIdentity());
611 if(oe == null)
612 {
613 oe = objectEnvelopeTable.get(rtObj.getIdentity(), rtObj.getObj(), rtObj.isNew());
614 }
615 if(oe.needsDelete())
616 {
617 oe.setModificationState(oe.getModificationState().markNew());
618 }
619 else
620 {
621 oe.setModificationState(oe.getModificationState().markDirty());
622 }
623 oe.refreshObjectIfNeeded(rtObj.getObj());
624 }
625
626 void makePersistent(RuntimeObject rt)
627 {
628 try
629 {
630 lockAndRegister(rt, Transaction.WRITE, getRegistrationList());
631 markPersistent(rt);
632 }
633 catch (org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException ex)
634 {
635 log.error("Can't persist object: " + rt.getIdentity(), ex);
636 throw new org.odmg.ClassNotPersistenceCapableException(ex.getMessage());
637 }
638 }
639
640 /**
641 * @see org.apache.ojb.odmg.TransactionExt#isDeleted(org.apache.ojb.broker.Identity)
642 */
643 public boolean isDeleted(Identity id)
644 {
645 ObjectEnvelope envelope = objectEnvelopeTable.getByIdentity(id);
646 return (envelope != null && envelope.needsDelete());
647 }
648
649 /**
650 * Upgrade the lock on the given object to the given lock mode. Method <code>
651 * tryLock</code> is the same as <code>lock</code> except it returns a boolean
652 * indicating whether the lock was granted instead of generating an exception.
653 * @param obj Description of Parameter
654 * @param lockMode Description of Parameter
655 * @return Description of the Returned Value
656 * </code>, <code>UPGRADE</code> , and <code>WRITE</code> .
657 * @return true if the lock has been acquired, otherwise false.
658 */
659 public boolean tryLock(Object obj, int lockMode)
660 {
661 if (log.isDebugEnabled()) log.debug("Try to lock object was called on tx " + this);
662 checkOpen();
663 try
664 {
665 lock(obj, lockMode);
666 return true;
667 }
668 catch (LockNotGrantedException ex)
669 {
670 return false;
671 }
672 }
673
674 /**
675 * Commit and close the transaction. Calling <code>commit</code> commits to
676 * the database all persistent object modifications within the transaction and
677 * releases any locks held by the transaction. A persistent object
678 * modification is an update of any field of an existing persistent object, or
679 * an update or creation of a new named object in the database. If a
680 * persistent object modification results in a reference from an existing
681 * persistent object to a transient object, the transient object is moved to
682 * the database, and all references to it updated accordingly. Note that the
683 * act of moving a transient object to the database may create still more
684 * persistent references to transient objects, so its referents must be
685 * examined and moved as well. This process continues until the database
686 * contains no references to transient objects, a condition that is guaranteed
687 * as part of transaction commit. Committing a transaction does not remove
688 * from memory transient objects created during the transaction.
689 *
690 * The updateObjectList contains a list of all objects for which this transaction
691 * has write privledge to. We need to update these objects.
692 */
693 public void commit()
694 {
695 checkOpen();
696 try
697 {
698 prepareCommit();
699 checkForCommit();
700
701 txStatus = Status.STATUS_COMMITTING;
702 if (log.isDebugEnabled()) log.debug("Commit transaction " + this);
703 // now do real commit on broker
704 if(hasBroker()) getBroker().commitTransaction();
705
706 // Now, we notify everything the commit is done.
707 performTransactionAwareAfterCommit();
708
709 doClose();
710 txStatus = Status.STATUS_COMMITTED;
711 }
712 catch(Exception ex)
713 {
714 log.error("Error while commit objects, do abort tx " + this + ", " + ex.getMessage(), ex);
715 txStatus = Status.STATUS_MARKED_ROLLBACK;
716 abort();
717 if(!(ex instanceof ODMGRuntimeException))
718 {
719 throw new TransactionAbortedExceptionOJB("Can't commit objects: " + ex.getMessage(), ex);
720 }
721 else
722 {
723 throw (ODMGRuntimeException) ex;
724 }
725 }
726 }
727
728 protected void checkForCommit()
729 {
730 // Never commit transaction that has been marked for rollback
731 if (txStatus == Status.STATUS_MARKED_ROLLBACK)
732 throw new TransactionAbortedExceptionOJB("Illegal tx-status: tx is already markedRollback");
733 // Don't commit if not prepared
734 if (txStatus != Status.STATUS_PREPARED)
735 throw new IllegalStateException("Illegal tx-status: Do prepare commit before commit");
736 }
737
738 /**
739 * Prepare does the actual work of moving the changes at the object level
740 * into storage (the underlying rdbms for instance). prepare Can be called multiple times, and
741 * does not release locks.
742 *
743 * @throws TransactionAbortedException if the transaction has been aborted
744 * for any reason.
745 * @throws IllegalStateException Method called if transaction is
746 * not in the proper state to perform this operation
747 * @throws TransactionNotInProgressException if the transaction is closed.
748 */
749 protected void prepareCommit() throws TransactionAbortedException, LockNotGrantedException
750 {
751 if (txStatus == Status.STATUS_MARKED_ROLLBACK)
752 {
753 throw new TransactionAbortedExceptionOJB("Prepare Transaction: tx already marked for rollback");
754 }
755 if (txStatus != Status.STATUS_ACTIVE)
756 {
757 throw new IllegalStateException("Prepare Transaction: tx status is not 'active', status is " + TxUtil.getStatusString(txStatus));
758 }
759 try
760 {
761 txStatus = Status.STATUS_PREPARING;
762 doWriteObjects(false);
763 txStatus = Status.STATUS_PREPARED;
764 }
765 catch (RuntimeException e)
766 {
767 log.error("Could not prepare for commit", e);
768 txStatus = Status.STATUS_MARKED_ROLLBACK;
769 throw e;
770 }
771 }
772
773 /**
774 * Abort and close the transaction. Calling abort abandons all persistent
775 * object modifications and releases the associated locks. Aborting a
776 * transaction does not restore the state of modified transient objects
777 */
778 public void abort()
779 {
780 /*
781 do nothing if already rolledback
782 */
783 if (txStatus == Status.STATUS_NO_TRANSACTION
784 || txStatus == Status.STATUS_UNKNOWN
785 || txStatus == Status.STATUS_ROLLEDBACK)
786 {
787 log.info("Nothing to abort, tx is not active - status is " + TxUtil.getStatusString(txStatus));
788 return;
789 }
790 // check status of tx
791 if (txStatus != Status.STATUS_ACTIVE && txStatus != Status.STATUS_PREPARED &&
792 txStatus != Status.STATUS_MARKED_ROLLBACK)
793 {
794 throw new IllegalStateException("Illegal state for abort call, state was '" + TxUtil.getStatusString(txStatus) + "'");
795 }
796 if(log.isEnabledFor(Logger.INFO))
797 {
798 log.info("Abort transaction was called on tx " + this);
799 }
800 try
801 {
802 try
803 {
804 doAbort();
805 }
806 catch(Exception e)
807 {
808 log.error("Error while abort transaction, will be skipped", e);
809 }
810
811 // used in managed environments, ignored in non-managed
812 this.implementation.getTxManager().abortExternalTx(this);
813
814 try
815 {
816 if(hasBroker() && getBroker().isInTransaction())
817 {
818 getBroker().abortTransaction();
819 }
820 }
821 catch(Exception e)
822 {
823 log.error("Error while do abort used broker instance, will be skipped", e);
824 }
825 }
826 finally
827 {
828 txStatus = Status.STATUS_ROLLEDBACK;
829 // cleanup things, e.g. release all locks
830 doClose();
831 }
832 }
833
834 /**
835 * Start a transaction. Calling <code>begin</code> multiple times on the same
836 * transaction object, without an intervening call to <code>commit</code> or
837 * <code>abort</code> , causes the exception <code>
838 * TransactionInProgressException</code> to be thrown on the second and
839 * subsequent calls. Operations executed before a transaction has been opened,
840 * or before reopening after a transaction is aborted or committed, have
841 * undefined results; these may throw a <code>
842 * TransactionNotInProgressException</code> exception.
843 */
844 public synchronized void begin()
845 {
846 checkForBegin();
847 if (log.isDebugEnabled()) log.debug("Begin transaction was called on tx " + this);
848 // initialize the ObjectEnvelope table
849 objectEnvelopeTable = new ObjectEnvelopeTable(this);
850 // register transaction
851 implementation.getTxManager().registerTx(this);
852 // mark tx as active (open)
853 txStatus = Status.STATUS_ACTIVE;
854 }
855
856 protected void checkForBegin()
857 {
858 /**
859 * Is the associated database non-null and open? ODMG 3.0 says it must be.
860 */
861 if ((curDB == null) || !curDB.isOpen())
862 {
863 throw new DatabaseClosedException("Database is not open. Must have an open DB to begin the Tx.");
864 }
865 if (isOpen())
866 {
867 log.error("Transaction is already open");
868 throw new org.odmg.TransactionInProgressException("Impossible to call begin on already opened tx");
869 }
870 }
871
872 public String getGUID()
873 {
874 return txGUID;
875 }
876
877 /**
878 * Get object by identity. First lookup among objects registered in the
879 * transaction, then in persistent storage.
880 * @param id The identity
881 * @return The object
882 * @throws PersistenceBrokerException
883 */
884 public Object getObjectByIdentity(Identity id)
885 throws PersistenceBrokerException
886 {
887 checkOpen();
888 ObjectEnvelope envelope = objectEnvelopeTable.getByIdentity(id);
889 if (envelope != null)
890 {
891 return (envelope.needsDelete() ? null : envelope.getObject());
892 }
893 else
894 {
895 return getBroker().getObjectByIdentity(id);
896 }
897 }
898
899 /**
900 * Registers the object (without locking) with this transaction. This method
901 * expects that the object was already locked, no check is done!!!
902 */
903 void doSingleRegister(RuntimeObject rtObject, int lockMode)
904 throws LockNotGrantedException, PersistenceBrokerException
905 {
906 if(log.isDebugEnabled()) log.debug("Register object " + rtObject.getIdentity());
907 Object objectToRegister = rtObject.getObj();
908 /*
909 if the object is a Proxy there are two options:
910 1. The proxies real subject has already been materialized:
911 we take this real subject as the object to register and proceed
912 as if it were a ordinary object.
913 2. The real subject has not been materialized: Then there is nothing
914 to be registered now!
915 Of course we might just materialize the real subject to have something
916 to register. But this would make proxies useless for ODMG as proxies would
917 get materialized even if their real subjects were not used by the
918 client app.
919 Thus we register the current transaction as a Listener to the IndirectionHandler
920 of the Proxy.
921 Only when the IndirectionHandler performs the materialization of the real subject
922 at some later point in time it invokes callbacks on all it's listeners.
923 Using this callback we can defer the registering until it's really needed.
924 */
925 if(rtObject.isProxy())
926 {
927 IndirectionHandler handler = rtObject.getHandler();
928 if(handler == null)
929 {
930 throw new OJBRuntimeException("Unexpected error, expect an proxy object as indicated: " + rtObject);
931 }
932 if (handler.alreadyMaterialized())
933 {
934 objectToRegister = handler.getRealSubject();
935 }
936 else
937 {
938 registerToIndirectionHandler(handler);
939 registerUnmaterializedLocks(rtObject.getObj());
940 // all work is done, so set to null
941 objectToRegister = null;
942 }
943 }
944 // no Proxy and is not null, register real object
945 if (objectToRegister != null)
946 {
947 ObjectEnvelope envelope = objectEnvelopeTable.getByIdentity(rtObject.getIdentity());
948 // if we found an envelope, object is already registered --> we do nothing
949 // than refreshing the object!
950 if ((envelope == null))
951 {
952 // register object itself
953 envelope = objectEnvelopeTable.get(rtObject.getIdentity(), objectToRegister, rtObject.isNew());
954 }
955 else
956 {
957 /*
958 arminw:
959 if an different instance of the same object was locked again
960 we should replace the old instance with new one to make
961 accessible the changed fields
962 */
963 envelope.refreshObjectIfNeeded(objectToRegister);
964 }
965 /*
966 arminw:
967 For better performance we check if this object has already a write lock
968 in this case we don't need to acquire a write lock on commit
969 */
970 if(lockMode == Transaction.WRITE)
971 {
972 // signal ObjectEnvelope that a WRITE lock is already acquired
973 envelope.setWriteLocked(true);
974 }
975 }
976 }
977
978 /**
979 * we only use the registrationList map if the object is not a proxy. During the
980 * reference locking, we will materialize objects and they will enter the registered for
981 * lock map.
982 */
983 private void lockAndRegisterReferences(ClassDescriptor cld, Object sourceObject, int lockMode, List registeredObjects) throws LockNotGrantedException
984 {
985 if (implicitLocking)
986 {
987 Iterator i = cld.getObjectReferenceDescriptors(true).iterator();
988 while (i.hasNext())
989 {
990 ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
991 Object refObj = rds.getPersistentField().get(sourceObject);
992 if (refObj != null)
993 {
994 boolean isProxy = ProxyHelper.isProxy(refObj);
995 RuntimeObject rt = isProxy ? new RuntimeObject(refObj, this, false) : new RuntimeObject(refObj, this);
996 if (!registrationList.contains(rt.getIdentity()))
997 {
998 lockAndRegister(rt, lockMode, registeredObjects);
999 }
1000 }
1001 }
1002 }
1003 }
1004
1005 private void lockAndRegisterCollections(ClassDescriptor cld, Object sourceObject, int lockMode, List registeredObjects) throws LockNotGrantedException
1006 {
1007 if (implicitLocking)
1008 {
1009 Iterator i = cld.getCollectionDescriptors(true).iterator();
1010 while (i.hasNext())
1011 {
1012 CollectionDescriptor cds = (CollectionDescriptor) i.next();
1013 Object col = cds.getPersistentField().get(sourceObject);
1014 if (col != null)
1015 {
1016 CollectionProxy proxy = ProxyHelper.getCollectionProxy(col);
1017 if (proxy != null)
1018 {
1019 if (!proxy.isLoaded())
1020 {
1021 if (log.isDebugEnabled()) log.debug("adding self as listener to collection proxy");
1022 proxy.addListener(this);
1023 registeredCollectionProxies.add(proxy);
1024 continue;
1025 }
1026 }
1027 Iterator colIterator = BrokerHelper.getCollectionIterator(col);
1028 Object item = null;
1029 try
1030 {
1031 while (colIterator.hasNext())
1032 {
1033 item = colIterator.next();
1034 RuntimeObject rt = new RuntimeObject(item, this);
1035 if (rt.isProxy())
1036 {
1037 IndirectionHandler handler = ProxyHelper.getIndirectionHandler(item);
1038 if (!handler.alreadyMaterialized())
1039 {
1040 registerToIndirectionHandler(handler);
1041 continue;
1042 }
1043 else
1044 {
1045 // @todo consider registering to hear when this is
1046 // derefernced instead of just loading here -bmc
1047 item = handler.getRealSubject();
1048 }
1049 }
1050 if (!registrationList.contains(rt.getIdentity()))
1051 {
1052 lockAndRegister(rt, lockMode, registeredObjects);
1053 }
1054 }
1055 }
1056 catch (LockNotGrantedException e)
1057 {
1058 String eol = SystemUtils.LINE_SEPARATOR;
1059 log.error("Lock not granted, while lock collection references[" +
1060 eol + "current reference descriptor:" +
1061 eol + cds.toXML() +
1062 eol + "object to lock: " + item +
1063 eol + "main object class: " + sourceObject.getClass().getName() +
1064 eol + "]", e);
1065 throw e;
1066 }
1067 }
1068 }
1069 }
1070 }
1071
1072 /**
1073 * this callback is invoked before an Object is materialized
1074 * within an IndirectionHandler.
1075 * @param handler the invoking handler
1076 * @param oid the identity of the object to be materialized
1077 */
1078 public void beforeMaterialization(IndirectionHandler handler, Identity oid)
1079 {
1080 //noop
1081 }
1082
1083 /**
1084 * this callback is invoked after an Object is materialized
1085 * within an IndirectionHandler.
1086 * this callback allows to defer registration of objects until
1087 * it's really neccessary.
1088 * @param handler the invoking handler
1089 * @param materializedObject the materialized Object
1090 */
1091 public void afterMaterialization(IndirectionHandler handler, Object materializedObject)
1092 {
1093 try
1094 {
1095 Identity oid = handler.getIdentity();
1096 if (log.isDebugEnabled())
1097 log.debug("deferred registration: " + oid);
1098 if(!isOpen())
1099 {
1100 log.error("Proxy object materialization outside of a running tx, obj=" + oid);
1101 try{throw new Exception("Proxy object materialization outside of a running tx, obj=" + oid);}catch(Exception e)
1102 {
1103 e.printStackTrace();
1104 }
1105 }
1106 ClassDescriptor cld = getBroker().getClassDescriptor(materializedObject.getClass());
1107 RuntimeObject rt = new RuntimeObject(materializedObject, oid, cld, false, false);
1108 lockAndRegister(rt, Transaction.READ, isImplicitLocking(), getRegistrationList());
1109 }
1110 catch (Throwable t)
1111 {
1112 log.error("Register materialized object with this tx failed", t);
1113 throw new LockNotGrantedException(t.getMessage());
1114 }
1115 unregisterFromIndirectionHandler(handler);
1116 }
1117
1118 protected synchronized void unRegisterFromAllIndirectionHandlers()
1119 {
1120 // unregistering manipulates the registeredIndirectionHandlers vector
1121 // we have to loop through this vector to avoid index proplems.
1122 for (int i = registeredIndirectionHandlers.size() - 1; i >= 0; i--)
1123 {
1124 unregisterFromIndirectionHandler((IndirectionHandler) registeredIndirectionHandlers.get(i));
1125 }
1126 }
1127
1128 protected synchronized void unRegisterFromAllCollectionProxies()
1129 {
1130 for (int i = registeredCollectionProxies.size() - 1; i >= 0; i--)
1131 {
1132 unregisterFromCollectionProxy((CollectionProxy) registeredCollectionProxies.get(i));
1133 }
1134 }
1135
1136 protected synchronized void unregisterFromCollectionProxy(CollectionProxy handler)
1137 {
1138 handler.removeListener(this);
1139 registeredCollectionProxies.remove(handler);
1140 }
1141
1142 protected synchronized void unregisterFromIndirectionHandler(IndirectionHandler handler)
1143 {
1144 handler.removeListener(this);
1145 registeredIndirectionHandlers.remove(handler);
1146 }
1147
1148 protected synchronized void registerToIndirectionHandler(IndirectionHandler handler)
1149 {
1150 handler.addListener(this);
1151 registeredIndirectionHandlers.add(handler);
1152 }
1153
1154 /**
1155 * register proxy objects that were locked but haven't been materialized yet
1156 * so they can be unlocked when closing the transaction
1157 */
1158 protected void registerUnmaterializedLocks(Object obj)
1159 {
1160 unmaterializedLocks.add(obj);
1161 }
1162
1163 /**
1164 * Gets the broker associated with the transaction.
1165 * MBAIRD: only return the associated broker if the transaction is open,
1166 * if it's closed, throw a TransactionNotInProgressException. If we allow
1167 * brokers to be reaquired by an already closed transaction, there is a
1168 * very good chance the broker will be leaked as the doClose() method of
1169 * transactionImpl will never be called and thus the broker will never
1170 * be closed and returned to the pool.
1171 * @return Returns a PersistenceBroker
1172 * @throws TransactionNotInProgressException is the transaction is not open;
1173 */
1174 public PersistenceBrokerInternal getBrokerInternal()
1175 {
1176 if (broker == null || broker.isClosed())
1177 {
1178 checkOpen();
1179 try
1180 {
1181 checkForDB();
1182 broker = PersistenceBrokerFactoryFactory.instance().createPersistenceBroker(curDB.getPBKey());
1183 }
1184 catch (PBFactoryException e)
1185 {
1186 log.error("Cannot obtain PersistenceBroker from PersistenceBrokerFactory, " +
1187 "found PBKey was " + curDB.getPBKey(), e);
1188 throw new PersistenceBrokerException(e);
1189 }
1190 }
1191 return broker;
1192 }
1193
1194 public PersistenceBroker getBroker()
1195 {
1196 return getBrokerInternal();
1197 }
1198
1199 /**
1200 * Returns true if an {@link org.apache.ojb.broker.PersistenceBroker} was associated with this
1201 * tx instance.
1202 */
1203 protected boolean hasBroker()
1204 {
1205 return broker != null && !broker.isClosed();
1206 }
1207
1208 protected void cleanupBroker()
1209 {
1210 if(hasBroker())
1211 {
1212 try
1213 {
1214 if(broker.isInTransaction())
1215 {
1216 broker.abortTransaction();
1217 }
1218 }
1219 finally
1220 {
1221 broker.close();
1222 broker = null;
1223 }
1224 }
1225 }
1226
1227 /*
1228 * @see Configurable#configure(Configuration)
1229 */
1230 public void configure(Configuration config) throws ConfigurationException
1231 {
1232 }
1233
1234 /**
1235 * @see org.apache.ojb.odmg.TransactionExt#setImplicitLocking(boolean)
1236 */
1237 public synchronized void setImplicitLocking(boolean value)
1238 {
1239 implicitLocking = value;
1240 }
1241
1242 public boolean isImplicitLocking()
1243 {
1244 return implicitLocking;
1245 }
1246
1247 /**
1248 * noop -- here for interface
1249 */
1250 public void beforeLoading(CollectionProxyDefaultImpl colProxy)
1251 {
1252 // noop
1253 }
1254
1255 /**
1256 * Remove colProxy from list of pending collections and
1257 * register its contents with the transaction.
1258 */
1259 public void afterLoading(CollectionProxyDefaultImpl colProxy)
1260 {
1261 if (log.isDebugEnabled()) log.debug("loading a proxied collection a collection: " + colProxy);
1262 Collection data = colProxy.getData();
1263 for (Iterator iterator = data.iterator(); iterator.hasNext();)
1264 {
1265 Object o = iterator.next();
1266 if(!isOpen())
1267 {
1268 log.error("Collection proxy materialization outside of a running tx, obj=" + o);
1269 try{throw new Exception("Collection proxy materialization outside of a running tx, obj=" + o);}
1270 catch(Exception e)
1271 {e.printStackTrace();}
1272 }
1273 else
1274 {
1275 Identity oid = getBroker().serviceIdentity().buildIdentity(o);
1276 ClassDescriptor cld = getBroker().getClassDescriptor(ProxyHelper.getRealClass(o));
1277 RuntimeObject rt = new RuntimeObject(o, oid, cld, false, ProxyHelper.isProxy(o));
1278 lockAndRegister(rt, Transaction.READ, isImplicitLocking(), getRegistrationList());
1279 }
1280 }
1281 unregisterFromCollectionProxy(colProxy);
1282 }
1283
1284 protected void performTransactionAwareBeforeCommit()
1285 {
1286 Enumeration en = objectEnvelopeTable.elements();
1287 while (en.hasMoreElements())
1288 {
1289 ((ObjectEnvelope) en.nextElement()).beforeCommit();
1290 }
1291 }
1292 protected void performTransactionAwareAfterCommit()
1293 {
1294 Enumeration en = objectEnvelopeTable.elements();
1295 try
1296 {
1297 while (en.hasMoreElements())
1298 {
1299 ((ObjectEnvelope) en.nextElement()).afterCommit();
1300 }
1301 }
1302 catch(Exception e)
1303 {
1304 log.error("Unexpected error while perform 'TransactionAware#afterCommit()' listener after commit of objects," +
1305 " after commit you can't rollback - exception will be skipped.", e);
1306 }
1307 }
1308 protected void performTransactionAwareBeforeRollback()
1309 {
1310 Enumeration en = objectEnvelopeTable.elements();
1311 while (en.hasMoreElements())
1312 {
1313 try
1314 {
1315 ((ObjectEnvelope) en.nextElement()).beforeAbort();
1316 }
1317 catch(Exception e)
1318 {
1319 log.error("Unexpected error while perform 'TransactionAware#beforeAbort()' listener before rollback of objects" +
1320 " - exception will be skipped to complete rollback.", e);
1321 }
1322 }
1323 }
1324 protected void performTransactionAwareAfterRollback()
1325 {
1326 Enumeration en = objectEnvelopeTable.elements();
1327 try
1328 {
1329 while (en.hasMoreElements())
1330 {
1331 ((ObjectEnvelope) en.nextElement()).afterAbort();
1332 }
1333 }
1334 catch(Exception e)
1335 {
1336 log.error("Unexpected error while perform 'TransactionAware#afterAbort()' listener after rollback of objects" +
1337 " - exception will be skipped.", e);
1338 }
1339 }
1340
1341 /**
1342 * Detect new objects.
1343 */
1344 protected boolean isTransient(ClassDescriptor cld, Object obj, Identity oid)
1345 {
1346 // if the Identity is transient we assume a non-persistent object
1347 boolean isNew = oid != null && oid.isTransient();
1348 /*
1349 detection of new objects is costly (select of ID in DB to check if object
1350 already exists) we do:
1351 a. check if the object has nullified PK field
1352 b. check if the object is already registered
1353 c. lookup from cache and if not found, last option select on DB
1354 */
1355 if(!isNew)
1356 {
1357 final PersistenceBroker pb = getBroker();
1358 if(cld == null)
1359 {
1360 cld = pb.getClassDescriptor(obj.getClass());
1361 }
1362 isNew = pb.serviceBrokerHelper().hasNullPKField(cld, obj);
1363 if(!isNew)
1364 {
1365 if(oid == null)
1366 {
1367 oid = pb.serviceIdentity().buildIdentity(cld, obj);
1368 }
1369 final ObjectEnvelope mod = objectEnvelopeTable.getByIdentity(oid);
1370 if(mod != null)
1371 {
1372 // already registered object, use current state
1373 isNew = mod.needsInsert();
1374 }
1375 else
1376 {
1377 // if object was found cache, assume it's old
1378 // else make costly check against the DB
1379 isNew = pb.serviceObjectCache().lookup(oid) == null
1380 && !pb.serviceBrokerHelper().doesExist(cld, oid, obj);
1381 }
1382 }
1383 }
1384 return isNew;
1385 }
1386
1387 /**
1388 * Allows to change the <em>cascading delete</em> behavior of the specified reference
1389 * of the target class while this transaction is in use.
1390 *
1391 * @param target The class to change cascading delete behavior of the references.
1392 * @param referenceField The field name of the 1:1, 1:n or 1:n reference.
1393 * @param doCascade If <em>true</em> cascading delete is enabled, <em>false</em> disabled.
1394 */
1395 public void setCascadingDelete(Class target, String referenceField, boolean doCascade)
1396 {
1397 ClassDescriptor cld = getBroker().getClassDescriptor(target);
1398 ObjectReferenceDescriptor ord = cld.getObjectReferenceDescriptorByName(referenceField);
1399 if(ord == null)
1400 {
1401 ord = cld.getCollectionDescriptorByName(referenceField);
1402 }
1403 if(ord == null)
1404 {
1405 throw new CascadeSettingException("Invalid reference field name '" + referenceField
1406 + "', can't find 1:1, 1:n or m:n relation with that name in " + target);
1407 }
1408 runtimeCascadeDeleteMap.put(ord, (doCascade ? Boolean.TRUE : Boolean.FALSE));
1409 }
1410
1411 /**
1412 * Allows to change the <em>cascading delete</em> behavior of all references of the
1413 * specified class while this transaction is in use - if the specified class is an
1414 * interface, abstract class or class with "extent" classes the cascading flag will
1415 * be propagated.
1416 *
1417 * @param target The class to change cascading delete behavior of all references.
1418 * @param doCascade If <em>true</em> cascading delete is enabled, <em>false</em> disabled.
1419 */
1420 public void setCascadingDelete(Class target, boolean doCascade)
1421 {
1422 ClassDescriptor cld = getBroker().getClassDescriptor(target);
1423 List extents = cld.getExtentClasses();
1424 Boolean result = doCascade ? Boolean.TRUE : Boolean.FALSE;
1425 setCascadingDelete(cld, result);
1426 if(extents != null && extents.size() > 0)
1427 {
1428 for(int i = 0; i < extents.size(); i++)
1429 {
1430 Class extent = (Class) extents.get(i);
1431 ClassDescriptor tmp = getBroker().getClassDescriptor(extent);
1432 setCascadingDelete(tmp, result);
1433 }
1434 }
1435 }
1436
1437 private void setCascadingDelete(ClassDescriptor cld, Boolean cascade)
1438 {
1439 List singleRefs = cld.getObjectReferenceDescriptors(true);
1440 for(int i = 0; i < singleRefs.size(); i++)
1441 {
1442 Object o = singleRefs.get(i);
1443 runtimeCascadeDeleteMap.put(o, cascade);
1444 }
1445 List collectionRefs = cld.getCollectionDescriptors(true);
1446 for(int i = 0; i < collectionRefs.size(); i++)
1447 {
1448 Object o = collectionRefs.get(i);
1449 runtimeCascadeDeleteMap.put(o, cascade);
1450 }
1451 }
1452
1453 private HashMap runtimeCascadeDeleteMap = new HashMap();
1454 /**
1455 * Returns <em>true</em> if cascading delete is enabled for the specified
1456 * single or collection descriptor.
1457 */
1458 protected boolean cascadeDeleteFor(ObjectReferenceDescriptor ord)
1459 {
1460 boolean result;
1461 Boolean runtimeSetting = (Boolean) runtimeCascadeDeleteMap.get(ord);
1462 if(runtimeSetting == null)
1463 {
1464 /*
1465 arminw: Here we use the auto-delete flag defined in metadata
1466 */
1467 result = ord.getCascadingDelete() == ObjectReferenceDescriptor.CASCADE_OBJECT;
1468 }
1469 else
1470 {
1471 result = runtimeSetting.booleanValue();
1472 }
1473 return result;
1474 }
1475
1476 int getImpliciteLockType(int parentLockMode)
1477 {
1478 return (parentLockMode == Transaction.WRITE && impliciteWriteLocks) ? Transaction.WRITE : Transaction.READ;
1479 }
1480
1481 /**
1482 * Return <em>true</em> if the OJB ordering algorithm is enabled.
1483 * @see #setOrdering(boolean)
1484 */
1485 public boolean isOrdering()
1486 {
1487 return ordering;
1488 }
1489
1490 /**
1491 * Allows to enable/disable the OJB persistent object ordering algorithm. If
1492 * <em>true</em> OJB try to order the modified/new/deleted objects in a correct order
1493 * (based on a algorithm) before the objects are written to the persistent storage.
1494 * <br/>
1495 * If <em>false</em> the order of the objects rely on the order specified by
1496 * the user and on settings like {@link #setImplicitLocking(boolean)}.
1497 *
1498 * @param ordering Set <em>true</em> to enable object ordering on commit.
1499 */
1500 public void setOrdering(boolean ordering)
1501 {
1502 this.ordering = ordering;
1503 }
1504
1505
1506 //============================================================
1507 // inner class
1508 //============================================================
1509 /**
1510 * This was thrown when something wrong with the cascading delete setting.
1511 */
1512 static class CascadeSettingException extends OJBRuntimeException
1513 {
1514 public CascadeSettingException()
1515 {
1516 }
1517
1518 public CascadeSettingException(String msg)
1519 {
1520 super(msg);
1521 }
1522
1523 public CascadeSettingException(Throwable cause)
1524 {
1525 super(cause);
1526 }
1527
1528 public CascadeSettingException(String msg, Throwable cause)
1529 {
1530 super(msg, cause);
1531 }
1532 }
1533 }