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 }