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