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    }