001    package org.apache.ojb.odmg;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.util.ArrayList;
019    import java.util.Enumeration;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.lang.builder.ToStringBuilder;
026    import org.apache.commons.lang.builder.ToStringStyle;
027    import org.apache.commons.lang.SystemUtils;
028    import org.apache.ojb.broker.Identity;
029    import org.apache.ojb.broker.OJBRuntimeException;
030    import org.apache.ojb.broker.OptimisticLockException;
031    import org.apache.ojb.broker.PersistenceBroker;
032    import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
033    import org.apache.ojb.broker.core.proxy.CollectionProxy;
034    import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
035    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
036    import org.apache.ojb.broker.core.proxy.ProxyHelper;
037    import org.apache.ojb.broker.metadata.ClassDescriptor;
038    import org.apache.ojb.broker.metadata.CollectionDescriptor;
039    import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
040    import org.apache.ojb.broker.util.BrokerHelper;
041    import org.apache.ojb.broker.util.logging.Logger;
042    import org.apache.ojb.broker.util.logging.LoggerFactory;
043    import org.apache.ojb.odmg.link.LinkEntry;
044    import org.apache.ojb.odmg.link.LinkEntryMtoN;
045    import org.apache.ojb.odmg.states.StateOldClean;
046    import org.odmg.LockNotGrantedException;
047    import org.odmg.ODMGRuntimeException;
048    import org.odmg.Transaction;
049    import org.odmg.TransactionAbortedException;
050    
051    /**
052     * manages all ObjectEnvelopes included by a transaction.
053     * Performs commit, and rollack operations on all included Envelopes.
054     *
055     * @author Thomas Mahler
056     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
057     *
058     *         MBAIRD: added explicit closing and de-referencing to prevent any
059     *         GC issues.
060     */
061    public class ObjectEnvelopeTable
062    {
063        private Logger log = LoggerFactory.getLogger(ObjectEnvelopeTable.class);
064        private TransactionImpl transaction;
065    
066        /**
067         * A list of {@link org.apache.ojb.broker.Identity} objects which are
068         * new associated with an object and should be protected from being marked
069         * as "delete". E.g. if a collection reference C is moved from object A1 to A2,
070         * then A1 wants to "delete" C and A2 wants to mark the new C object as "new".
071         */
072        private List newAssociatedIdentites = new ArrayList();
073        private List m2nLinkList = new ArrayList();
074        private List m2nUnlinkList = new ArrayList();
075        private List markedForDeletionList = new ArrayList();
076        private List markedForInsertList = new ArrayList();
077    
078        /** the internal table mapping Objects to their ObjectTransactionWrappers */
079        private Map mhtObjectEnvelopes = new HashMap();
080    
081        /**
082         * a vector containing the ObjectEnvelope objects representing modifications
083         * in the order they were added. If an ObjectEnvelope is added twice, only
084         * the the second addition is ignored.
085         */
086        private ArrayList mvOrderOfIds = new ArrayList();
087    
088        /** marker used to avoid superfluous reordering and commiting */
089        private boolean needsCommit = false;
090    
091        /** Creates new ObjectEnvelopeTable */
092        public ObjectEnvelopeTable(TransactionImpl myTransaction)
093        {
094            transaction = myTransaction;
095        }
096    
097        TransactionImpl getTransaction()
098        {
099            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    }