Coverage Report - org.apache.ojb.odmg.ObjectEnvelopeTable
 
Classes in this File Line Coverage Branch Coverage Complexity
ObjectEnvelopeTable
N/A
N/A
3.333
 
 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 java.util.ArrayList;
 19  
 import java.util.Enumeration;
 20  
 import java.util.HashMap;
 21  
 import java.util.Iterator;
 22  
 import java.util.List;
 23  
 import java.util.Map;
 24  
 
 25  
 import org.apache.commons.lang.builder.ToStringBuilder;
 26  
 import org.apache.commons.lang.builder.ToStringStyle;
 27  
 import org.apache.commons.lang.SystemUtils;
 28  
 import org.apache.ojb.broker.Identity;
 29  
 import org.apache.ojb.broker.OJBRuntimeException;
 30  
 import org.apache.ojb.broker.OptimisticLockException;
 31  
 import org.apache.ojb.broker.PersistenceBroker;
 32  
 import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
 33  
 import org.apache.ojb.broker.core.proxy.CollectionProxy;
 34  
 import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
 35  
 import org.apache.ojb.broker.core.proxy.IndirectionHandler;
 36  
 import org.apache.ojb.broker.core.proxy.ProxyHelper;
 37  
 import org.apache.ojb.broker.metadata.ClassDescriptor;
 38  
 import org.apache.ojb.broker.metadata.CollectionDescriptor;
 39  
 import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
 40  
 import org.apache.ojb.broker.util.BrokerHelper;
 41  
 import org.apache.ojb.broker.util.logging.Logger;
 42  
 import org.apache.ojb.broker.util.logging.LoggerFactory;
 43  
 import org.apache.ojb.odmg.link.LinkEntry;
 44  
 import org.apache.ojb.odmg.link.LinkEntryMtoN;
 45  
 import org.apache.ojb.odmg.states.StateOldClean;
 46  
 import org.odmg.LockNotGrantedException;
 47  
 import org.odmg.ODMGRuntimeException;
 48  
 import org.odmg.Transaction;
 49  
 import org.odmg.TransactionAbortedException;
 50  
 
 51  
 /**
 52  
  * manages all ObjectEnvelopes included by a transaction.
 53  
  * Performs commit, and rollack operations on all included Envelopes.
 54  
  *
 55  
  * @author Thomas Mahler
 56  
  * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
 57  
  *
 58  
  *         MBAIRD: added explicit closing and de-referencing to prevent any
 59  
  *         GC issues.
 60  
  */
 61  
 public class ObjectEnvelopeTable
 62  
 {
 63  
     private Logger log = LoggerFactory.getLogger(ObjectEnvelopeTable.class);
 64  
     private TransactionImpl transaction;
 65  
 
 66  
     /**
 67  
      * A list of {@link org.apache.ojb.broker.Identity} objects which are
 68  
      * new associated with an object and should be protected from being marked
 69  
      * as "delete". E.g. if a collection reference C is moved from object A1 to A2,
 70  
      * then A1 wants to "delete" C and A2 wants to mark the new C object as "new".
 71  
      */
 72  
     private List newAssociatedIdentites = new ArrayList();
 73  
     private List m2nLinkList = new ArrayList();
 74  
     private List m2nUnlinkList = new ArrayList();
 75  
     private List markedForDeletionList = new ArrayList();
 76  
     private List markedForInsertList = new ArrayList();
 77  
 
 78  
     /** the internal table mapping Objects to their ObjectTransactionWrappers */
 79  
     private Map mhtObjectEnvelopes = new HashMap();
 80  
 
 81  
     /**
 82  
      * a vector containing the ObjectEnvelope objects representing modifications
 83  
      * in the order they were added. If an ObjectEnvelope is added twice, only
 84  
      * the the second addition is ignored.
 85  
      */
 86  
     private ArrayList mvOrderOfIds = new ArrayList();
 87  
 
 88  
     /** marker used to avoid superfluous reordering and commiting */
 89  
     private boolean needsCommit = false;
 90  
 
 91  
     /** Creates new ObjectEnvelopeTable */
 92  
     public ObjectEnvelopeTable(TransactionImpl myTransaction)
 93  
     {
 94  
         transaction = myTransaction;
 95  
     }
 96  
 
 97  
     TransactionImpl getTransaction()
 98  
     {
 99  
         return transaction;
 100  
     }
 101  
 
 102  
     /** prepare this instance for reuse */
 103  
     public void refresh()
 104  
     {
 105  
         needsCommit = false;
 106  
         mhtObjectEnvelopes = new HashMap();
 107  
         mvOrderOfIds = new ArrayList();
 108  
         afterWriteCleanup();
 109  
     }
 110  
 
 111  
     void afterWriteCleanup()
 112  
     {
 113  
         m2nLinkList.clear();
 114  
         m2nUnlinkList.clear();
 115  
         newAssociatedIdentites.clear();
 116  
         markedForDeletionList.clear();
 117  
         markedForInsertList.clear();
 118  
     }
 119  
 
 120  
     /**
 121  
      * Perform write to DB on all registered object wrapper ({@link ObjectEnvelope})
 122  
      *
 123  
      * @param reuse When all registered objects be re-used after writing to
 124  
      * DB set <em>true</em>, else set <em>false</em> to improve performance.
 125  
      */
 126  
     public void writeObjects(boolean reuse) throws TransactionAbortedException, LockNotGrantedException
 127  
     {
 128  
         PersistenceBroker broker = transaction.getBroker();
 129  
         ConnectionManagerIF connMan = broker.serviceConnectionManager();
 130  
         boolean saveBatchMode = connMan.isBatchMode();
 131  
 
 132  
         try
 133  
         {
 134  
             if(log.isDebugEnabled())
 135  
             {
 136  
                 log.debug(
 137  
                         "PB is in internal tx: "
 138  
                                 + broker.isInTransaction()
 139  
                                 + "  broker was: "
 140  
                                 + broker);
 141  
             }
 142  
             // all neccessary db operations are executed within a PersistenceBroker transaction:
 143  
             if(!broker.isInTransaction())
 144  
             {
 145  
                 log.error("PB associated with current odmg-tx is not in tx");
 146  
                 throw new TransactionAbortedException("Underlying PB is not in tx, was begin call done before commit?");
 147  
             }
 148  
 
 149  
             // Committing has to be done in two phases. First implicitly upgrade to lock on all related
 150  
             // objects of objects in this transaction. Then the list of locked objects has to be
 151  
             // reordered to solve referential integrity dependencies, then the objects are
 152  
             // written into the database.
 153  
 
 154  
             // 0. turn on the batch mode
 155  
             connMan.setBatchMode(true);
 156  
 
 157  
             // 1. mark objects no longer available in collection
 158  
             // for delete and add new found objects
 159  
             checkAllEnvelopes(broker);
 160  
 
 161  
             // 2. mark all dependend objects for cascading insert/delete
 162  
             cascadingDependents();
 163  
 
 164  
             // 3. upgrade implicit locks.
 165  
             //upgradeImplicitLocksAndCheckIfCommitIsNeeded();
 166  
             upgradeLockIfNeeded();
 167  
 
 168  
             // 4. Reorder objects
 169  
             reorder();
 170  
 //            System.out.println("## ordering: ");
 171  
 //            for(int i = 0; i < mvOrderOfIds.size(); i++)
 172  
 //            {
 173  
 //                System.out.println("" + mvOrderOfIds.get(i));
 174  
 //            }
 175  
 //            System.out.println("## ordering end");
 176  
 
 177  
             // 5. write objects.
 178  
             writeAllEnvelopes(reuse);
 179  
 
 180  
             // 6. execute batch
 181  
             connMan.executeBatch();
 182  
 
 183  
             // 7. Update all Envelopes to new CleanState
 184  
             prepareForReuse(reuse);
 185  
 
 186  
             // 6. commit cleanup
 187  
             afterWriteCleanup();
 188  
 
 189  
         }
 190  
         catch(Exception e)
 191  
         {
 192  
             connMan.clearBatch();
 193  
             /*
 194  
             arminw:
 195  
             log only a warn message, because in top-level methods
 196  
             a error log will be done ditto
 197  
             */
 198  
             if(e instanceof OptimisticLockException)
 199  
             {
 200  
                 // make error log to show the full stack trace one time
 201  
                 log.error("Optimistic lock exception while write objects", e);
 202  
                 // PB OptimisticLockException should be clearly signalled to the user
 203  
                 Object sourceObject = ((OptimisticLockException) e).getSourceObject();
 204  
                 throw new LockNotGrantedException("Optimistic lock exception occur, source object was (" + sourceObject + ")," +
 205  
                         " message was (" + e.getMessage() + ")");
 206  
             }
 207  
             else if(!(e instanceof RuntimeException))
 208  
             {
 209  
                 log.warn("Error while write objects for tx " + transaction, e);
 210  
                 throw new ODMGRuntimeException("Unexpected error while write objects: " + e.getMessage());
 211  
             }
 212  
             else
 213  
             {
 214  
                 log.warn("Error while write objects for tx " + transaction, e);
 215  
                 throw (RuntimeException) e;
 216  
             }
 217  
         }
 218  
         finally
 219  
         {
 220  
             needsCommit = false;
 221  
             connMan.setBatchMode(saveBatchMode);
 222  
         }
 223  
     }
 224  
 
 225  
     /** commit all envelopes against the current broker */
 226  
     private void writeAllEnvelopes(boolean reuse)
 227  
     {
 228  
         // perform remove of m:n indirection table entries first
 229  
         performM2NUnlinkEntries();
 230  
 
 231  
         Iterator iter;
 232  
         // using clone to avoid ConcurentModificationException
 233  
         iter = ((List) mvOrderOfIds.clone()).iterator();
 234  
         while(iter.hasNext())
 235  
         {
 236  
             ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
 237  
             boolean insert = false;
 238  
             if(needsCommit)
 239  
             {
 240  
                 insert = mod.needsInsert();
 241  
                 mod.getModificationState().commit(mod);
 242  
                 if(reuse && insert)
 243  
                 {
 244  
                     getTransaction().doSingleLock(mod.getClassDescriptor(), mod.getObject(), mod.getIdentity(), Transaction.WRITE);
 245  
                 }
 246  
             }
 247  
             /*
 248  
             arminw: important to call this cleanup method for each registered
 249  
             ObjectEnvelope, because this method will e.g. remove proxy listener
 250  
             objects for registered objects.
 251  
             */
 252  
             mod.cleanup(reuse, insert);
 253  
         }
 254  
         // add m:n indirection table entries
 255  
         performM2NLinkEntries();
 256  
     }
 257  
 
 258  
     /**
 259  
      * Mark objects no longer available in collection for delete and new objects for insert.
 260  
      *
 261  
      * @param broker the PB to persist all objects
 262  
      */
 263  
     private void checkAllEnvelopes(PersistenceBroker broker)
 264  
     {
 265  
         Iterator iter = ((List) mvOrderOfIds.clone()).iterator();
 266  
         while(iter.hasNext())
 267  
         {
 268  
             ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
 269  
             // only non transient objects should be performed
 270  
             if(!mod.getModificationState().isTransient())
 271  
             {
 272  
                 mod.markReferenceElements(broker);
 273  
             }
 274  
         }
 275  
     }
 276  
 
 277  
     /**
 278  
      * This method have to be called to reuse all registered {@link ObjectEnvelope}
 279  
      * objects after transaction commit/flush/checkpoint call.
 280  
      */
 281  
     private void prepareForReuse(boolean reuse)
 282  
     {
 283  
         if(reuse)
 284  
         {
 285  
             // using clone to avoid ConcurentModificationException
 286  
             Iterator iter = ((List) mvOrderOfIds.clone()).iterator();
 287  
             while(iter.hasNext())
 288  
             {
 289  
                 ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
 290  
                 if(!needsCommit || (mod.getModificationState() == StateOldClean.getInstance()
 291  
                         || mod.getModificationState().isTransient()))
 292  
                 {
 293  
                     // nothing to do
 294  
                 }
 295  
                 else
 296  
                 {
 297  
                     mod.setModificationState(mod.getModificationState().markClean());
 298  
                 }
 299  
             }
 300  
         }
 301  
     }
 302  
 
 303  
     /**
 304  
      * Checks the status of all modified objects and
 305  
      * upgrade the lock if needed, cleanup the {@link ObjectEnvelope}
 306  
      * objects.
 307  
      */
 308  
     private void upgradeLockIfNeeded()
 309  
     {
 310  
         // using clone to avoid ConcurentModificationException
 311  
         Iterator iter = ((List) mvOrderOfIds.clone()).iterator();
 312  
         TransactionImpl tx = getTransaction();
 313  
         ObjectEnvelope mod;
 314  
         while(iter.hasNext())
 315  
         {
 316  
             mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
 317  
             // ignore transient objects
 318  
             if(!mod.getModificationState().isTransient())
 319  
             {
 320  
                 /*
 321  
                 now we check if all modified objects has a write lock. On insert of new
 322  
                 objects we don't need a write lock.
 323  
                 */
 324  
                 if(!mod.needsInsert())
 325  
                 {
 326  
                     if((mod.needsDelete() || mod.needsUpdate()
 327  
                             || mod.hasChanged(tx.getBroker())))
 328  
                     {
 329  
                         needsCommit = true;
 330  
                         // mark object dirty
 331  
                         mod.setModificationState(mod.getModificationState().markDirty());
 332  
                         ClassDescriptor cld = mod.getClassDescriptor();
 333  
                         // if the object isn't already locked, we will do it now
 334  
                         if(!mod.isWriteLocked())
 335  
                         {
 336  
                             tx.doSingleLock(cld, mod.getObject(), mod.getIdentity(), Transaction.WRITE);
 337  
                         }
 338  
                     }
 339  
                 }
 340  
                 else
 341  
                 {
 342  
                     needsCommit = true;
 343  
                 }
 344  
             }
 345  
         }
 346  
     }
 347  
 
 348  
     /** perform rollback on all tx-states */
 349  
     public void rollback()
 350  
     {
 351  
         try
 352  
         {
 353  
             Iterator iter = mvOrderOfIds.iterator();
 354  
             while(iter.hasNext())
 355  
             {
 356  
                 ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
 357  
                 if(log.isDebugEnabled())
 358  
                     log.debug("rollback: " + mod);
 359  
                 // if the Object has been modified by transaction, mark object as dirty
 360  
                 if(mod.hasChanged(transaction.getBroker()))
 361  
                 {
 362  
                     mod.setModificationState(mod.getModificationState().markDirty());
 363  
                 }
 364  
                 mod.getModificationState().rollback(mod);
 365  
             }
 366  
         }
 367  
         finally
 368  
         {
 369  
             needsCommit = false;
 370  
         }
 371  
         afterWriteCleanup();
 372  
     }
 373  
 
 374  
     /** remove an objects entry from the object registry */
 375  
     public void remove(Object pKey)
 376  
     {
 377  
         Identity id;
 378  
         if(pKey instanceof Identity)
 379  
         {
 380  
             id = (Identity) pKey;
 381  
         }
 382  
         else
 383  
         {
 384  
             id = transaction.getBroker().serviceIdentity().buildIdentity(pKey);
 385  
         }
 386  
         mhtObjectEnvelopes.remove(id);
 387  
         mvOrderOfIds.remove(id);
 388  
     }
 389  
 
 390  
     /**
 391  
      * Get an enumeration of all the elements in this ObjectEnvelopeTable
 392  
      * in random order.
 393  
      *
 394  
      * @return Enumeration an enumeration of all elements managed by this ObjectEnvelopeTable
 395  
      */
 396  
     public Enumeration elements()
 397  
     {
 398  
         return java.util.Collections.enumeration(mhtObjectEnvelopes.values());
 399  
     }
 400  
 
 401  
     /** retrieve an objects ObjectModification state from the hashtable */
 402  
     public ObjectEnvelope getByIdentity(Identity id)
 403  
     {
 404  
         return (ObjectEnvelope) mhtObjectEnvelopes.get(id);
 405  
     }
 406  
 
 407  
     /**
 408  
      * retrieve an objects ObjectEnvelope state from the hashtable.
 409  
      * If no ObjectEnvelope is found, a new one is created and returned.
 410  
      *
 411  
      * @return the resulting ObjectEnvelope
 412  
      */
 413  
     public ObjectEnvelope get(Object pKey, boolean isNew)
 414  
     {
 415  
         PersistenceBroker broker = transaction.getBroker();
 416  
         Identity oid = broker.serviceIdentity().buildIdentity(pKey);
 417  
         return get(oid, pKey, isNew);
 418  
     }
 419  
 
 420  
     /**
 421  
      * retrieve an objects ObjectEnvelope state from the hashtable.
 422  
      * If no ObjectEnvelope is found, a new one is created and returned.
 423  
      *
 424  
      * @return the resulting ObjectEnvelope
 425  
      */
 426  
     public ObjectEnvelope get(Identity oid, Object pKey, boolean isNew)
 427  
     {
 428  
         ObjectEnvelope result = getByIdentity(oid);
 429  
         if(result == null)
 430  
         {
 431  
             result = new ObjectEnvelope(this, oid, pKey, isNew);
 432  
             mhtObjectEnvelopes.put(oid, result);
 433  
             mvOrderOfIds.add(oid);
 434  
             if(log.isDebugEnabled())
 435  
                 log.debug("register: " + result);
 436  
         }
 437  
         return result;
 438  
     }
 439  
 
 440  
     /** Returns a String representation of this object */
 441  
     public String toString()
 442  
     {
 443  
         ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
 444  
         String eol = SystemUtils.LINE_SEPARATOR;
 445  
         buf.append("# ObjectEnvelopeTable dump:" + eol + "start[");
 446  
         Enumeration en = elements();
 447  
         while(en.hasMoreElements())
 448  
         {
 449  
             ObjectEnvelope mod = (ObjectEnvelope) en.nextElement();
 450  
             buf.append(mod.toString() + eol);
 451  
         }
 452  
         buf.append("]end");
 453  
         return buf.toString();
 454  
     }
 455  
 
 456  
     /** retrieve an objects ObjectModification state from the hashtable */
 457  
     public boolean contains(Identity oid)
 458  
     {
 459  
         //Integer keyInteger = new Integer(System.identityHashCode(key));
 460  
         return mhtObjectEnvelopes.containsKey(oid);
 461  
     }
 462  
 
 463  
     /** Reorder the objects in the table to resolve referential integrity dependencies. */
 464  
     private void reorder()
 465  
     {
 466  
         if(getTransaction().isOrdering() && needsCommit && mhtObjectEnvelopes.size() > 1)
 467  
         {
 468  
             ObjectEnvelopeOrdering ordering = new ObjectEnvelopeOrdering(mvOrderOfIds, mhtObjectEnvelopes);
 469  
             ordering.reorder();
 470  
             Identity[] newOrder = ordering.getOrdering();
 471  
 
 472  
             mvOrderOfIds.clear();
 473  
             for(int i = 0; i < newOrder.length; i++)
 474  
             {
 475  
                 mvOrderOfIds.add(newOrder[i]);
 476  
             }
 477  
         }
 478  
     }
 479  
 
 480  
     void cascadingDependents()
 481  
     {
 482  
         Iterator it = mhtObjectEnvelopes.values().iterator();
 483  
         ObjectEnvelope mod;
 484  
         // first we search for all deleted/insert objects
 485  
         while(it.hasNext())
 486  
         {
 487  
             mod = (ObjectEnvelope) it.next();
 488  
             if(mod.needsDelete())
 489  
             {
 490  
                 addForDeletionDependent(mod);
 491  
             }
 492  
             else if(mod.needsInsert())
 493  
             {
 494  
                 addForInsertDependent(mod);
 495  
             }
 496  
         }
 497  
         /*
 498  
         Now start cascade insert/delete work. The order of first delete
 499  
         then insert is mandatory, because the user could move unmaterialized
 500  
         collection proxy objects from one existing, which was deleted, to a new object. In this case
 501  
         the proxy was materialized on deletion of the main object, but on performing
 502  
         the cascading insert the collection objects will be found and assigned to the new object.
 503  
         */
 504  
         cascadeMarkedForDeletion();
 505  
         cascadeMarkedForInsert();
 506  
     }
 507  
 
 508  
     void addNewAssociatedIdentity(Identity oid)
 509  
     {
 510  
         newAssociatedIdentites.add(oid);
 511  
     }
 512  
 
 513  
     boolean isNewAssociatedObject(Identity oid)
 514  
     {
 515  
         return newAssociatedIdentites.contains(oid);
 516  
     }
 517  
 
 518  
     void addForInsertDependent(ObjectEnvelope mod)
 519  
     {
 520  
         markedForInsertList.add(mod);
 521  
     }
 522  
 
 523  
     /** Starts recursive insert on all insert objects object graph */
 524  
     private void cascadeMarkedForInsert()
 525  
     {
 526  
         // This list was used to avoid endless recursion on circular references
 527  
         List alreadyPrepared = new ArrayList();
 528  
         for(int i = 0; i < markedForInsertList.size(); i++)
 529  
         {
 530  
             ObjectEnvelope mod = (ObjectEnvelope) markedForInsertList.get(i);
 531  
             // only if a new object was found we cascade to register the dependent objects
 532  
             if(mod.needsInsert())
 533  
             {
 534  
                 cascadeInsertFor(mod, alreadyPrepared);
 535  
                 alreadyPrepared.clear();
 536  
             }
 537  
         }
 538  
         markedForInsertList.clear();
 539  
     }
 540  
 
 541  
     /**
 542  
      * Walk through the object graph of the specified insert object. Was used for
 543  
      * recursive object graph walk.
 544  
      */
 545  
     private void cascadeInsertFor(ObjectEnvelope mod, List alreadyPrepared)
 546  
     {
 547  
         // avoid endless recursion, so use List for registration
 548  
         if(alreadyPrepared.contains(mod.getIdentity())) return;
 549  
         alreadyPrepared.add(mod.getIdentity());
 550  
 
 551  
         ClassDescriptor cld = getTransaction().getBroker().getClassDescriptor(mod.getObject().getClass());
 552  
 
 553  
         List refs = cld.getObjectReferenceDescriptors(true);
 554  
         cascadeInsertSingleReferences(mod, refs, alreadyPrepared);
 555  
 
 556  
         List colls = cld.getCollectionDescriptors(true);
 557  
         cascadeInsertCollectionReferences(mod, colls, alreadyPrepared);
 558  
     }
 559  
 
 560  
     private void cascadeInsertSingleReferences(ObjectEnvelope source, List descriptor, List alreadyPrepared)
 561  
     {
 562  
         for(int i = 0; i < descriptor.size(); i++)
 563  
         {
 564  
             ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) descriptor.get(i);
 565  
             Object depObj = ord.getPersistentField().get(source.getObject());
 566  
 
 567  
             if(depObj != null)
 568  
             {
 569  
                 // in any case we have to link the source object when the object needs insert
 570  
                 source.addLinkOneToOne(ord, false);
 571  
 
 572  
                 IndirectionHandler handler = ProxyHelper.getIndirectionHandler(depObj);
 573  
                 // if the object is not materialized, nothing has changed
 574  
                 if(handler == null || handler.alreadyMaterialized())
 575  
                 {
 576  
                     RuntimeObject rt;
 577  
                     // if materialized
 578  
                     if(handler != null)
 579  
                     {
 580  
                         rt = new RuntimeObject(handler.getRealSubject(), getTransaction(), false);
 581  
                     }
 582  
                     else
 583  
                     {
 584  
                         rt = new RuntimeObject(depObj, getTransaction());
 585  
                     }
 586  
                     Identity oid = rt.getIdentity();
 587  
                     if(!alreadyPrepared.contains(oid))
 588  
                     {
 589  
                         ObjectEnvelope depMod = getByIdentity(oid);
 590  
                         // if the object isn't registered and is a new object, register it
 591  
                         // else we have nothing to do
 592  
                         if(depMod == null && rt.isNew())
 593  
                         {
 594  
                             getTransaction().lockAndRegister(rt, Transaction.WRITE, false, getTransaction().getRegistrationList());
 595  
                             depMod = getByIdentity(oid);
 596  
                             cascadeInsertFor(depMod, alreadyPrepared);
 597  
                         }
 598  
                     }
 599  
                 }
 600  
             }
 601  
         }
 602  
     }
 603  
 
 604  
     private void cascadeInsertCollectionReferences(ObjectEnvelope source, List descriptor, List alreadyPrepared)
 605  
     {
 606  
         // PersistenceBroker pb = getTransaction().getBroker();
 607  
         for(int i = 0; i < descriptor.size(); i++)
 608  
         {
 609  
             CollectionDescriptor col = (CollectionDescriptor) descriptor.get(i);
 610  
             Object collOrArray = col.getPersistentField().get(source.getObject());
 611  
             CollectionProxy proxy = ProxyHelper.getCollectionProxy(collOrArray);
 612  
             /*
 613  
             on insert we perform only materialized collection objects. This should be
 614  
             sufficient, because in method #cascadingDependents() we make sure that on
 615  
             move of unmaterialized collection objects the proxy was materialized if needed.
 616  
             */
 617  
             if(proxy == null && collOrArray != null)
 618  
             {
 619  
                 Iterator it = BrokerHelper.getCollectionIterator(collOrArray);
 620  
                 while(it.hasNext())
 621  
                 {
 622  
                     Object colObj = it.next();
 623  
                     if(colObj != null)
 624  
                     {
 625  
                         RuntimeObject rt = new RuntimeObject(colObj, getTransaction());
 626  
                         Identity oid = rt.getIdentity();
 627  
                         /*
 628  
                         arminw:
 629  
                         only when the main object need insert we start with FK assignment
 630  
                         of the 1:n and m:n relations. If the main objects need update (was already persisted)
 631  
                         it should be handled by the object state detection in ObjectEnvelope
 632  
                         */
 633  
                         if(source.needsInsert())
 634  
                         {
 635  
                             /*
 636  
                             arminw:
 637  
                             TODO: what is the valid way to go, when the collection object itself is
 638  
                             a unmaterialized proxy object? Think in this case we should materialize the
 639  
                             object when the main object needs insert, because we have to assign the FK values
 640  
                             to the main object
 641  
                             */
 642  
                             colObj = ProxyHelper.getRealObject(colObj);
 643  
                             ObjectEnvelope oe = getByIdentity(oid);
 644  
                             if(oe == null)
 645  
                             {
 646  
                                 getTransaction().lockAndRegister(rt, Transaction.WRITE, false, getTransaction().getRegistrationList());
 647  
                                 oe = getByIdentity(oid);
 648  
                             }
 649  
                             if(col.isMtoNRelation())
 650  
                             {
 651  
                                 // the main objects needs insert, thus add new m:n link
 652  
                                 addM2NLinkEntry(col, source.getObject(), colObj);
 653  
                             }
 654  
                             else
 655  
                             {
 656  
                                 // we mark collection reference for linking
 657  
                                 oe.addLinkOneToN(col, source.getObject(), false);
 658  
                                 /*
 659  
                                 arminw: The referenced object could be already persisted, so we have
 660  
                                 to dirty it to guarantee the setting of the FK (linking)
 661  
                                 */
 662  
                                 oe.setModificationState(oe.getModificationState().markDirty());
 663  
                             }
 664  
                             cascadeInsertFor(oe, alreadyPrepared);
 665  
                         }
 666  
                     }
 667  
                 }
 668  
             }
 669  
         }
 670  
     }
 671  
 
 672  
     void addForDeletionDependent(ObjectEnvelope mod)
 673  
     {
 674  
         markedForDeletionList.add(mod);
 675  
     }
 676  
 
 677  
     /** Starts recursive delete on all delete objects object graph */
 678  
     private void cascadeMarkedForDeletion()
 679  
     {
 680  
         List alreadyPrepared = new ArrayList();
 681  
         for(int i = 0; i < markedForDeletionList.size(); i++)
 682  
         {
 683  
             ObjectEnvelope mod = (ObjectEnvelope) markedForDeletionList.get(i);
 684  
             // if the object wasn't associated with another object, start cascade delete
 685  
             if(!isNewAssociatedObject(mod.getIdentity()))
 686  
             {
 687  
                 cascadeDeleteFor(mod, alreadyPrepared);
 688  
                 alreadyPrepared.clear();
 689  
             }
 690  
         }
 691  
         markedForDeletionList.clear();
 692  
     }
 693  
 
 694  
     /**
 695  
      * Walk through the object graph of the specified delete object. Was used for
 696  
      * recursive object graph walk.
 697  
      */
 698  
     private void cascadeDeleteFor(ObjectEnvelope mod, List alreadyPrepared)
 699  
     {
 700  
         // avoid endless recursion
 701  
         if(alreadyPrepared.contains(mod.getIdentity())) return;
 702  
 
 703  
         alreadyPrepared.add(mod.getIdentity());
 704  
 
 705  
         ClassDescriptor cld = getTransaction().getBroker().getClassDescriptor(mod.getObject().getClass());
 706  
 
 707  
         List refs = cld.getObjectReferenceDescriptors(true);
 708  
         cascadeDeleteSingleReferences(mod, refs, alreadyPrepared);
 709  
 
 710  
         List colls = cld.getCollectionDescriptors(true);
 711  
         cascadeDeleteCollectionReferences(mod, colls, alreadyPrepared);
 712  
     }
 713  
 
 714  
     private void cascadeDeleteSingleReferences(ObjectEnvelope source, List descriptor, List alreadyPrepared)
 715  
     {
 716  
         for(int i = 0; i < descriptor.size(); i++)
 717  
         {
 718  
             ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) descriptor.get(i);
 719  
             if(getTransaction().cascadeDeleteFor(ord))
 720  
             {
 721  
                 Object depObj = ord.getPersistentField().get(source.getObject());
 722  
                 if(depObj != null)
 723  
                 {
 724  
                     Identity oid = getTransaction().getBroker().serviceIdentity().buildIdentity(depObj);
 725  
                     // if(!isNewAssociatedObject(oid) && !alreadyPrepared.contains(oid))
 726  
                     // if the object has a new association with a different object, don't delete it
 727  
                     if(!isNewAssociatedObject(oid))
 728  
                     {
 729  
                         ObjectEnvelope depMod = get(oid, depObj, false);
 730  
                         depMod.setModificationState(depMod.getModificationState().markDelete());
 731  
                         cascadeDeleteFor(depMod, alreadyPrepared);
 732  
                     }
 733  
                 }
 734  
             }
 735  
         }
 736  
     }
 737  
 
 738  
     private void cascadeDeleteCollectionReferences(ObjectEnvelope source, List descriptor, List alreadyPrepared)
 739  
     {
 740  
         PersistenceBroker pb = getTransaction().getBroker();
 741  
         for(int i = 0; i < descriptor.size(); i++)
 742  
         {
 743  
             CollectionDescriptor col = (CollectionDescriptor) descriptor.get(i);
 744  
             boolean cascadeDelete = getTransaction().cascadeDeleteFor(col);
 745  
             Object collOrArray = col.getPersistentField().get(source.getObject());
 746  
             // TODO: remove cast
 747  
             CollectionProxyDefaultImpl proxy = (CollectionProxyDefaultImpl) ProxyHelper.getCollectionProxy(collOrArray);
 748  
             // on delete we have to materialize dependent objects
 749  
             if(proxy != null)
 750  
             {
 751  
                 collOrArray = proxy.getData();
 752  
             }
 753  
             if(collOrArray != null)
 754  
             {
 755  
                 Iterator it = BrokerHelper.getCollectionIterator(collOrArray);
 756  
                 while(it.hasNext())
 757  
                 {
 758  
                     Object colObj = ProxyHelper.getRealObject(it.next());
 759  
                     Identity oid = pb.serviceIdentity().buildIdentity(colObj);
 760  
                     ObjectEnvelope colMod = get(oid, colObj, false);
 761  
                     if(cascadeDelete)
 762  
                     {
 763  
                         colMod.setModificationState(colMod.getModificationState().markDelete());
 764  
                         cascadeDeleteFor(colMod, alreadyPrepared);
 765  
                     }
 766  
                     else
 767  
                     {
 768  
                         if(!col.isMtoNRelation())
 769  
                         {
 770  
                             colMod.addLinkOneToN(col, source.getObject(), true);
 771  
                             colMod.setModificationState(colMod.getModificationState().markDirty());
 772  
                         }
 773  
                     }
 774  
                     if(col.isMtoNRelation())
 775  
                     {
 776  
                         addM2NUnlinkEntry(col, source.getObject(), colObj);
 777  
                     }
 778  
                 }
 779  
             }
 780  
         }
 781  
     }
 782  
 
 783  
     void addM2NLinkEntry(CollectionDescriptor cod, Object leftSource, Object rightSource)
 784  
     {
 785  
         if(!cod.isMtoNRelation()) throw new OJBRuntimeException("Expect a m:n releation, but specified a 1:n");
 786  
         m2nLinkList.add(new LinkEntryMtoN(leftSource, cod, rightSource, false));
 787  
     }
 788  
 
 789  
     void performM2NLinkEntries()
 790  
     {
 791  
         PersistenceBroker broker = getTransaction().getBroker();
 792  
         LinkEntry entry;
 793  
         for(int i = 0; i < m2nLinkList.size(); i++)
 794  
         {
 795  
             entry = (LinkEntry) m2nLinkList.get(i);
 796  
             entry.execute(broker);
 797  
         }
 798  
     }
 799  
 
 800  
     void addM2NUnlinkEntry(CollectionDescriptor cod, Object leftSource, Object rightSource)
 801  
     {
 802  
         if(!cod.isMtoNRelation()) throw new OJBRuntimeException("Expect a m:n releation, but specified a 1:n");
 803  
         m2nUnlinkList.add(new LinkEntryMtoN(leftSource, cod, rightSource, true));
 804  
     }
 805  
 
 806  
     void performM2NUnlinkEntries()
 807  
     {
 808  
         PersistenceBroker broker = getTransaction().getBroker();
 809  
         LinkEntry entry;
 810  
         for(int i = 0; i < m2nUnlinkList.size(); i++)
 811  
         {
 812  
             entry = (LinkEntry) m2nUnlinkList.get(i);
 813  
             entry.execute(broker);
 814  
         }
 815  
     }
 816  
 
 817  
     /**
 818  
      * Replace the {@link org.apache.ojb.broker.Identity}
 819  
      * of a registered {@link ObjectEnvelope} object.
 820  
      *
 821  
      * @param newOid
 822  
      * @param oldOid
 823  
      * @return Returns <em>true</em> if successful.
 824  
      */
 825  
     boolean replaceRegisteredIdentity(Identity newOid, Identity oldOid)
 826  
     {
 827  
         /*
 828  
         TODO: Find a better solution
 829  
         */
 830  
         boolean result = false;
 831  
         Object oe = mhtObjectEnvelopes.remove(oldOid);
 832  
         if(oe != null)
 833  
         {
 834  
             mhtObjectEnvelopes.put(newOid, oe);
 835  
             int index = mvOrderOfIds.indexOf(oldOid);
 836  
             mvOrderOfIds.remove(index);
 837  
             mvOrderOfIds.add(index, newOid);
 838  
             result = true;
 839  
             if(log.isDebugEnabled()) log.debug("Replace identity: " + oldOid + " --replaced-by--> " + newOid);
 840  
         }
 841  
         else
 842  
         {
 843  
             log.warn("Can't replace unregistered object identity (" + oldOid + ") with new identity (" + newOid + ")");
 844  
         }
 845  
         return result;
 846  
     }
 847  
 }