Coverage Report - org.apache.ojb.odmg.TransactionImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
TransactionImpl
N/A
N/A
3.293
TransactionImpl$CascadeSettingException
N/A
N/A
3.293
 
 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  
 }