View Javadoc

1   package org.apache.ojb.odmg;
2   
3   /* Copyright 2002-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import 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 }