View Javadoc

1   package org.apache.ojb.odmg;
2   
3   /* Copyright 2002-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import javax.transaction.Status;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Enumeration;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import org.apache.commons.lang.SystemUtils;
27  import org.apache.ojb.broker.Identity;
28  import org.apache.ojb.broker.OJBRuntimeException;
29  import org.apache.ojb.broker.PBFactoryException;
30  import org.apache.ojb.broker.PersistenceBroker;
31  import org.apache.ojb.broker.PersistenceBrokerException;
32  import org.apache.ojb.broker.PersistenceBrokerInternal;
33  import org.apache.ojb.broker.core.PersistenceBrokerFactoryFactory;
34  import org.apache.ojb.broker.core.proxy.CollectionProxy;
35  import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
36  import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
37  import org.apache.ojb.broker.core.proxy.IndirectionHandler;
38  import org.apache.ojb.broker.core.proxy.MaterializationListener;
39  import org.apache.ojb.broker.core.proxy.ProxyHelper;
40  import org.apache.ojb.broker.metadata.ClassDescriptor;
41  import org.apache.ojb.broker.metadata.CollectionDescriptor;
42  import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
43  import org.apache.ojb.broker.util.BrokerHelper;
44  import org.apache.ojb.broker.util.GUID;
45  import org.apache.ojb.broker.util.configuration.Configurable;
46  import org.apache.ojb.broker.util.configuration.Configuration;
47  import org.apache.ojb.broker.util.configuration.ConfigurationException;
48  import org.apache.ojb.broker.util.logging.Logger;
49  import org.apache.ojb.broker.util.logging.LoggerFactory;
50  import org.apache.ojb.odmg.locking.LockManager;
51  import org.odmg.DatabaseClosedException;
52  import org.odmg.LockNotGrantedException;
53  import org.odmg.ODMGRuntimeException;
54  import org.odmg.Transaction;
55  import org.odmg.TransactionAbortedException;
56  import org.odmg.TransactionNotInProgressException;
57  
58  /**
59   *
60   * Implementation of Transaction for org.odmg.Transaction.
61   *
62   * @author     Thomas Mahler & David Dixon-Peugh
63   * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
64   * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
65   * @author <a href="mailto:brianm@apache.org">Brian McCallister</a>
66   * @version $Id: TransactionImpl.java,v 1.1 2007-08-24 22:17:37 ewestfal Exp $
67   *
68   */
69  public class TransactionImpl
70          implements Transaction, MaterializationListener, Configurable, CollectionProxyListener, TransactionExt
71  {
72      private Logger log = LoggerFactory.getLogger(TransactionImpl.class);
73      private boolean impliciteWriteLocks;
74      private boolean implicitLocking;
75      private boolean ordering;
76  
77      private String txGUID;
78      protected PersistenceBrokerInternal broker = null;
79      private ArrayList registrationList = new ArrayList();
80      private ImplementationImpl implementation;
81      private NamedRootsMap namedRootsMap;
82  
83      /**
84       * The status of the current transaction, as specified by the
85       * javax.transaction package.
86       * See {@link javax.transaction.Status} for list of valid values.
87       */
88      private int txStatus = Status.STATUS_NO_TRANSACTION;
89  
90      /**
91       * the internal table containing all Objects "touched" by this tx and their
92       * respective transactional state
93       */
94      protected ObjectEnvelopeTable objectEnvelopeTable = null;
95  
96      /**
97       * reference to the currently opened database
98       */
99      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 }