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: *
019: * @author <a href="mailto:thma@apache.org">Thomas Mahler</a>
020: * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
021: *
022: */
023:
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.Map;
027: import java.util.List;
028: import java.util.ArrayList;
029:
030: import org.apache.commons.lang.builder.ToStringBuilder;
031: import org.apache.ojb.broker.Identity;
032: import org.apache.ojb.broker.PersistenceBroker;
033: import org.apache.ojb.broker.PersistenceBrokerException;
034: import org.apache.ojb.broker.OJBRuntimeException;
035: import org.apache.ojb.broker.PersistenceBrokerInternal;
036: import org.apache.ojb.broker.core.proxy.IndirectionHandler;
037: import org.apache.ojb.broker.core.proxy.ProxyHelper;
038: import org.apache.ojb.broker.metadata.ClassDescriptor;
039: import org.apache.ojb.broker.metadata.CollectionDescriptor;
040: import org.apache.ojb.broker.metadata.FieldDescriptor;
041: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
042: import org.apache.ojb.broker.util.BrokerHelper;
043: import org.apache.ojb.broker.util.ObjectModification;
044: import org.apache.ojb.broker.util.logging.Logger;
045: import org.apache.ojb.broker.util.logging.LoggerFactory;
046: import org.apache.ojb.odmg.states.ModificationState;
047: import org.apache.ojb.odmg.states.StateNewDirty;
048: import org.apache.ojb.odmg.states.StateOldClean;
049: import org.apache.ojb.odmg.states.StateOldDirty;
050: import org.apache.ojb.odmg.link.LinkEntry;
051: import org.apache.ojb.odmg.link.LinkEntryOneToOne;
052: import org.apache.ojb.odmg.link.LinkEntryOneToN;
053:
054: /**
055: * ObjectEnvelope is used during ODMG transactions as a wrapper for a
056: * persistent objects declaration
057: *
058: */
059: public class ObjectEnvelope implements ObjectModification,
060: Image.ImageListener {
061: private Logger log = LoggerFactory.getLogger(ObjectEnvelope.class);
062:
063: static final long serialVersionUID = -829177767933340522L;
064:
065: static final int IS_MATERIALIZED_OBJECT = 11;
066: static final int IS_MATERIALIZED_PROXY = 13;
067: static final int IS_UNMATERIALIZED_PROXY = 17;
068:
069: /**
070: * The objects modification state, e.g. Old and Clean
071: */
072: private ModificationState modificationState = null;
073: private Identity oid;
074: private Boolean hasChanged;
075: private boolean writeLocked;
076:
077: /**
078: * myObj holds the object we are wrapping.
079: */
080: private Object myObj;
081:
082: /**
083: * beforeImage holds a mapping between field
084: * names and values at the start of the transaction.
085: * currentImage holds the mapping at the
086: * end of the transaction.
087: */
088: private Map beforeImage;
089: private Map currentImage;
090: private ObjectEnvelopeTable buffer;
091: // list of all LinkEntry's
092: private List linkEntryList;
093:
094: /**
095: * Create a wrapper by providing an Object.
096: */
097: public ObjectEnvelope(ObjectEnvelopeTable buffer, Identity oid,
098: Object obj, boolean isNewObject) {
099: this .linkEntryList = new ArrayList();
100: this .buffer = buffer;
101: this .oid = oid;
102: // TODO: do we really need to materialize??
103: myObj = ProxyHelper.getRealObject(obj);
104: prepareInitialState(isNewObject);
105: /*
106: TODO: is it possible to improve this? Take care that "new"
107: objects should support "persistence by reachability" too
108: (detection of new/persistent reference objects after maon object lock)
109: */
110: beforeImage = buildObjectImage(getBroker());
111: }
112:
113: public PersistenceBrokerInternal getBroker() {
114: return buffer.getTransaction().getBrokerInternal();
115: }
116:
117: TransactionImpl getTx() {
118: return buffer.getTransaction();
119: }
120:
121: ObjectEnvelopeTable getEnvelopeTable() {
122: return buffer;
123: }
124:
125: public Map getBeforeImage() {
126: if (beforeImage == null) {
127: beforeImage = buildObjectImage(getBroker());
128: }
129: return beforeImage;
130: }
131:
132: public Map getCurrentImage() {
133: if (currentImage == null) {
134: currentImage = buildObjectImage(getBroker());
135: }
136: return currentImage;
137: }
138:
139: /**
140: * This method should be called before transaction ends
141: * to allow cleanup of used resources, e.g. remove proxy listener objects
142: * to avoid invoke of registered objects after tx end.
143: */
144: public void cleanup(boolean reuse, boolean wasInsert) {
145: if (currentImage != null) {
146: performImageCleanup(currentImage, reuse);
147: }
148: if (beforeImage != null) {
149: // we always free all resources of the old image
150: performImageCleanup(beforeImage, false);
151: }
152: if (reuse) {
153: refreshObjectImage(wasInsert);
154: } else {
155: myObj = null;
156: }
157: }
158:
159: private void performImageCleanup(Map imageMap, boolean reuse) {
160: Iterator iterator = imageMap.values().iterator();
161: while (iterator.hasNext()) {
162: Image base = (Image) iterator.next();
163: if (base != null)
164: base.cleanup(reuse);
165: }
166: }
167:
168: private void refreshObjectImage(boolean wasInsert) {
169: try {
170: // if an image already exists we
171: // replace the Identity too, maybe a temporary
172: // used PK value was replaced by the real one,
173: // see in docs SequenceManagerNativeImpl
174: if (getIdentity().isTransient()) {
175: refreshIdentity();
176: }
177: if (currentImage != null) {
178: beforeImage = currentImage;
179: } else {
180: if (beforeImage == null) {
181: beforeImage = buildObjectImage(getBroker());
182: }
183: }
184: currentImage = null;
185: hasChanged = null;
186: if (wasInsert) {
187: /*
188: on insert we have to replace the PK fields and the version fields, because
189: they populated after the object was written to DB, thus replace all field image values
190: */
191: refreshPKFields();
192: }
193: // TODO: How to handle version fields incremented by the DB?
194: // always refresh the version fields, because these fields will change when written to DB
195: refreshLockingFields();
196: } catch (PersistenceBrokerException e) {
197: beforeImage = null;
198: currentImage = null;
199: hasChanged = null;
200: log.error(
201: "Can't refresh object image for " + getIdentity(),
202: e);
203: throw e;
204: }
205: }
206:
207: private void refreshPKFields() {
208: FieldDescriptor[] flds = getClassDescriptor().getPkFields();
209: for (int i = 0; i < flds.length; i++) {
210: FieldDescriptor fld = flds[i];
211: addFieldImage(beforeImage, fld);
212: }
213: }
214:
215: private void refreshLockingFields() {
216: if (getClassDescriptor().isLocking()) {
217: FieldDescriptor[] flds = getClassDescriptor()
218: .getLockingFields();
219: for (int i = 0; i < flds.length; i++) {
220: FieldDescriptor fld = flds[i];
221: addFieldImage(beforeImage, fld);
222: }
223: }
224: }
225:
226: /**
227: * Replace the current with a new generated identity object and
228: * returns the old one.
229: */
230: public Identity refreshIdentity() {
231: Identity oldOid = getIdentity();
232: this .oid = getBroker().serviceIdentity().buildIdentity(myObj);
233: return oldOid;
234: }
235:
236: public Identity getIdentity() {
237: if (oid == null) {
238: oid = getBroker().serviceIdentity().buildIdentity(
239: getObject());
240: }
241: return oid;
242: }
243:
244: /**
245: * Returns the managed materialized object.
246: */
247: public Object getObject() {
248: return myObj;
249: }
250:
251: public Object getRealObject() {
252: return ProxyHelper.getRealObject(getObject());
253: }
254:
255: public void refreshObjectIfNeeded(Object obj) {
256: if (this .myObj != obj) {
257: this .myObj = obj;
258: }
259: }
260:
261: /**
262: * We need to implement the Two-Phase Commit
263: * protocol.
264: *
265: * beginCommit is where we say if we can or cannot
266: * commit the transaction. At the begining however,
267: * we need to attain the after image so we can isolate
268: * everything.
269: *
270: * We should issue the call against the database
271: * at this point. If we get a SQL Exception, we
272: * should throw the org.odmg.TransactionAbortedException.
273: *
274: * We should also check to see if the object is
275: * TransactionAware. If so, we should give it a chance
276: * to kill the transaction before we toss it to the
277: * database.
278: */
279: public void beforeCommit() {
280: if (myObj instanceof TransactionAware) {
281: TransactionAware ta = (TransactionAware) myObj;
282: ta.beforeCommit();
283: }
284: }
285:
286: /**
287: * Method declaration
288: */
289: public void afterCommit() {
290: if (myObj instanceof TransactionAware) {
291: TransactionAware ta = (TransactionAware) myObj;
292: ta.afterCommit();
293: }
294: }
295:
296: /**
297: * Method declaration
298: */
299: public void beforeAbort() {
300: if (myObj instanceof TransactionAware) {
301: TransactionAware ta = (TransactionAware) myObj;
302: ta.beforeAbort();
303: }
304: }
305:
306: /**
307: * Method declaration
308: */
309: public void afterAbort() {
310: if (myObj instanceof TransactionAware) {
311: TransactionAware ta = (TransactionAware) myObj;
312: ta.afterAbort();
313: }
314: }
315:
316: /**
317: * buildObjectImage() will return the image of the Object.
318: */
319: private Map buildObjectImage(PersistenceBroker broker)
320: throws PersistenceBrokerException {
321: Map imageMap = new HashMap();
322: ClassDescriptor cld = broker.getClassDescriptor(getObject()
323: .getClass());
324: //System.out.println("++++ build image: " + getObject());
325: // register 1:1 references in image
326: buildImageForSingleReferences(imageMap, cld);
327: // put object values to image map
328: buildImageForFields(imageMap, cld);
329: // register 1:n and m:n references in image
330: buildImageForCollectionReferences(imageMap, cld);
331: return imageMap;
332: }
333:
334: private void buildImageForSingleReferences(Map imageMap,
335: ClassDescriptor cld) {
336: // register all 1:1 references
337: Iterator iter = cld.getObjectReferenceDescriptors(true)
338: .iterator();
339: ObjectReferenceDescriptor rds;
340: while (iter.hasNext()) {
341: rds = (ObjectReferenceDescriptor) iter.next();
342: /*
343: arminw:
344: if a "super-reference" is matched (a 1:1 reference used to represent a super class)
345: we don't handle it, because this will be done by the PB-api and will never be change
346: */
347: if (!rds.isSuperReferenceDescriptor()) {
348: Object referenceObject = rds.getPersistentField().get(
349: myObj);
350:
351: IndirectionHandler handler = ProxyHelper
352: .getIndirectionHandler(referenceObject);
353: /*
354: arminw:
355: if object was serialized and anonymous FK are used in the main object, the FK
356: values are null, we have to refresh (re-assign) these values before building field images.
357: This will not touch the main object itself, because we only reassign anonymous FK fields.
358: */
359: if (handler == null
360: && referenceObject != null
361: && BrokerHelper.hasAnonymousKeyReference(rds
362: .getClassDescriptor(), rds)) {
363: getBroker().serviceBrokerHelper().link(myObj, rds,
364: false);
365: }
366: Image.SingleRef singleRef = new Image.SingleRef(this ,
367: rds, referenceObject);
368: imageMap.put(rds, singleRef);
369: }
370: }
371: }
372:
373: private void buildImageForFields(Map imageMap, ClassDescriptor cld) {
374: // register all non reference fields of object (with inherited fields)
375: FieldDescriptor[] fieldDescs = cld.getFieldDescriptor(true);
376: for (int i = 0; i < fieldDescs.length; i++) {
377: addFieldImage(imageMap, fieldDescs[i]);
378: }
379: }
380:
381: private void addFieldImage(Map imageMap, FieldDescriptor fld) {
382: // register copies of all field values
383: Object value = fld.getPersistentField().get(myObj);
384: // get the real sql type value
385: value = fld.getFieldConversion().javaToSql(value);
386: // make copy of the sql type value
387: value = fld.getJdbcType().getFieldType().copy(value);
388: // buffer in image the field name and the sql type value
389: // wrapped by a helper class
390: imageMap
391: .put(fld.getPersistentField().getName(),
392: new Image.Field(fld.getJdbcType()
393: .getFieldType(), value));
394: }
395:
396: private void buildImageForCollectionReferences(Map imageMap,
397: ClassDescriptor cld) {
398: // register the 1:n and m:n references
399: Iterator collections = cld.getCollectionDescriptors(true)
400: .iterator();
401: CollectionDescriptor cds;
402: while (collections.hasNext()) {
403: cds = (CollectionDescriptor) collections.next();
404: Object collectionOrArray = cds.getPersistentField().get(
405: myObj);
406: Image.MultipleRef colRef = new Image.MultipleRef(this , cds,
407: collectionOrArray);
408: imageMap.put(cds, colRef);
409: }
410: }
411:
412: /**
413: * Returns the Modification-state.
414: * @return org.apache.ojb.server.states.ModificationState
415: */
416: public ModificationState getModificationState() {
417: return modificationState;
418: }
419:
420: /**
421: * Returns true if the underlying Object needs an INSERT statement, else returns false.
422: */
423: public boolean needsInsert() {
424: return this .getModificationState().needsInsert();
425: }
426:
427: /**
428: * Returns true if the underlying Object needs an UPDATE statement, else returns false.
429: */
430: public boolean needsUpdate() {
431: return this .getModificationState().needsUpdate();
432: }
433:
434: /**
435: * Returns true if the underlying Object needs an UPDATE statement, else returns false.
436: */
437: public boolean needsDelete() {
438: return this .getModificationState().needsDelete();
439: }
440:
441: /**
442: * Sets the initial MoificationState of the wrapped object myObj. The initial state will be StateNewDirty if myObj
443: * is not persisten already. The state will be set to StateOldClean if the object is already persistent.
444: */
445: private void prepareInitialState(boolean isNewObject) {
446: // determine appropriate modification state
447: ModificationState initialState;
448: if (isNewObject) {
449: // if object is not already persistent it must be marked as new
450: // it must be marked as dirty because it must be stored even if it will not modified during tx
451: initialState = StateNewDirty.getInstance();
452: } else if (isDeleted(oid)) {
453: // if object is already persistent it will be marked as old.
454: // it is marked as dirty as it has been deleted during tx and now it is inserted again,
455: // possibly with new field values.
456: initialState = StateOldDirty.getInstance();
457: } else {
458: // if object is already persistent it will be marked as old.
459: // it is marked as clean as it has not been modified during tx already
460: initialState = StateOldClean.getInstance();
461: }
462: // remember it:
463: modificationState = initialState;
464: }
465:
466: /**
467: * Checks if the object with the given identity has been deleted
468: * within the transaction.
469: * @param id The identity
470: * @return true if the object has been deleted
471: * @throws PersistenceBrokerException
472: */
473: public boolean isDeleted(Identity id) {
474: ObjectEnvelope envelope = buffer.getByIdentity(id);
475:
476: return (envelope != null && envelope.needsDelete());
477: }
478:
479: /**
480: * set the Modification state to a new value. Used during state transitions.
481: * @param newModificationState org.apache.ojb.server.states.ModificationState
482: */
483: public void setModificationState(
484: ModificationState newModificationState) {
485: if (newModificationState != modificationState) {
486: if (log.isDebugEnabled()) {
487: log.debug("object state transition for object "
488: + this .oid + " (" + modificationState + " --> "
489: + newModificationState + ")");
490: // try{throw new Exception();}catch(Exception e)
491: // {
492: // e.printStackTrace();
493: // }
494: }
495: modificationState = newModificationState;
496: }
497: }
498:
499: /**
500: * returns a String representation.
501: * @return java.lang.String
502: */
503: public String toString() {
504: ToStringBuilder buf = new ToStringBuilder(this );
505: buf.append("Identity", oid).append("ModificationState",
506: modificationState.toString());
507: return buf.toString();
508: }
509:
510: /**
511: * For internal use only! Only call immediately before commit to guarantee
512: * that all changes can be detected (because this method cache the detected "change state"
513: * thus on eager call changes could be ignored). Checks whether object and internal clone
514: * differ and returns <em>true</em> if so, returns <em>false</em> else.
515: *
516: * @return boolean The result.
517: */
518: public boolean hasChanged(PersistenceBroker broker) {
519: if (hasChanged == null) {
520: Map current = null;
521: try {
522: current = getCurrentImage();
523: } catch (Exception e) {
524: log.warn(
525: "Could not verify object changes, mark dirty: "
526: + getIdentity(), e);
527: }
528: if (beforeImage != null && current != null) {
529: Iterator it = beforeImage.entrySet().iterator();
530: hasChanged = Boolean.FALSE;
531: while (it.hasNext()) {
532: Map.Entry entry = (Map.Entry) it.next();
533: Image imageBefore = (Image) entry.getValue();
534: Image imageCurrent = (Image) current.get(entry
535: .getKey());
536: if (imageBefore.modified(imageCurrent)) {
537: hasChanged = Boolean.TRUE;
538: break;
539: }
540: }
541: } else {
542: hasChanged = Boolean.TRUE;
543: }
544: if (log.isDebugEnabled()) {
545: log.debug("State detection for "
546: + getIdentity()
547: + " --> object "
548: + (hasChanged.booleanValue() ? "has changed"
549: : "unchanged"));
550: }
551: }
552: return hasChanged.booleanValue();
553: }
554:
555: /**
556: * Mark new or deleted reference elements
557: * @param broker
558: */
559: void markReferenceElements(PersistenceBroker broker) {
560: // these cases will be handled by ObjectEnvelopeTable#cascadingDependents()
561: // if(getModificationState().needsInsert() || getModificationState().needsDelete()) return;
562:
563: Map oldImage = getBeforeImage();
564: Map newImage = getCurrentImage();
565:
566: Iterator iter = newImage.entrySet().iterator();
567: while (iter.hasNext()) {
568: Map.Entry entry = (Map.Entry) iter.next();
569: Object key = entry.getKey();
570: // we only interested in references
571: if (key instanceof ObjectReferenceDescriptor) {
572: Image oldRefImage = (Image) oldImage.get(key);
573: Image newRefImage = (Image) entry.getValue();
574: newRefImage.performReferenceDetection(oldRefImage);
575: }
576: }
577: }
578:
579: public void doUpdate() {
580: if (log.isDebugEnabled())
581: log.debug("Start UPDATE action for " + getIdentity());
582: performLinkEntries();
583: getBroker().store(getObject(), getIdentity(),
584: getClassDescriptor(), false, true);
585: }
586:
587: public void doInsert() {
588: if (log.isDebugEnabled())
589: log.debug("Start INSERT action for " + getIdentity());
590: performLinkEntries();
591: getBroker().store(getObject(), getIdentity(),
592: getClassDescriptor(), true, true);
593: Identity oldOid = refreshIdentity();
594: buffer.replaceRegisteredIdentity(getIdentity(), oldOid);
595: }
596:
597: public void doDelete() {
598: if (log.isDebugEnabled())
599: log.debug("Start DELETE action for " + getIdentity());
600: getBroker().delete(getObject(), true);
601: }
602:
603: public void doEvictFromCache() {
604: if (log.isDebugEnabled())
605: log.debug("Remove from cache " + getIdentity());
606: getBroker().removeFromCache(getIdentity());
607: }
608:
609: public boolean isWriteLocked() {
610: return writeLocked;
611: }
612:
613: public void setWriteLocked(boolean writeLocked) {
614: this .writeLocked = writeLocked;
615: }
616:
617: ClassDescriptor getClassDescriptor() {
618: return getBroker().getClassDescriptor(
619: ProxyHelper.getRealClass(getObject()));
620: }
621:
622: void addLinkOneToOne(ObjectReferenceDescriptor ord, boolean unlink) {
623: LinkEntry entry = new LinkEntryOneToOne(ord, getObject(),
624: unlink);
625: linkEntryList.add(entry);
626: }
627:
628: void addLinkOneToN(CollectionDescriptor col, Object source,
629: boolean unlink) {
630: if (col.isMtoNRelation())
631: throw new OJBRuntimeException(
632: "Expected an 1:n relation, but specified a m:n");
633: LinkEntry entry = new LinkEntryOneToN(source, col, getObject(),
634: unlink);
635: linkEntryList.add(entry);
636: }
637:
638: private void performLinkEntries() {
639: PersistenceBroker broker = getBroker();
640: for (int i = 0; i < linkEntryList.size(); i++) {
641: LinkEntry linkEntry = (LinkEntry) linkEntryList.get(i);
642: linkEntry.execute(broker);
643: }
644: }
645:
646: public void addedOneToOne(ObjectReferenceDescriptor ord,
647: Object refObjOrProxy, Identity oid) {
648: // the main objects needs link/unlink of the FK to 1:1 reference,
649: // so mark this dirty
650: setModificationState(getModificationState().markDirty());
651: // if the object is already registered, OJB knows about
652: // else lock and register object, get read lock, because we
653: // don't know if the object is new or moved from an existing other object
654: ObjectEnvelope oe = buffer.getByIdentity(oid);
655: if (oe == null) {
656: RuntimeObject rt = new RuntimeObject(refObjOrProxy, getTx());
657: // we don't use cascade locking, because new reference object
658: // will be detected by ObjectEnvelopeTable#cascadeMarkedForInsert()
659: getTx().lockAndRegister(rt, TransactionExt.READ, false,
660: getTx().getRegistrationList());
661: }
662: // in any case we need to link the main object
663: addLinkOneToOne(ord, false);
664: }
665:
666: public void deletedOneToOne(ObjectReferenceDescriptor ord,
667: Object refObjOrProxy, Identity oid, boolean needsUnlink) {
668: // the main objects needs link/unlink of the FK to 1:1 reference,
669: // so mark this dirty
670: setModificationState(getModificationState().markDirty());
671: ObjectEnvelope oldRefMod = buffer.getByIdentity(oid);
672: // only delete when the reference wasn't assigned with another object
673: if (!buffer.isNewAssociatedObject(oid)) {
674: // if cascading delete is enabled, remove the 1:1 reference
675: // because it was removed from the main object
676: if (buffer.getTransaction().cascadeDeleteFor(ord)) {
677: oldRefMod.setModificationState(oldRefMod
678: .getModificationState().markDelete());
679: }
680: // unlink the main object
681: if (needsUnlink)
682: addLinkOneToOne(ord, true);
683: }
684: }
685:
686: public void addedXToN(CollectionDescriptor cod,
687: Object refObjOrProxy, Identity oid) {
688: ObjectEnvelope mod = buffer.getByIdentity(oid);
689: // if the object isn't registered already, it can be 'new' or already 'persistent'
690: if (mod == null) {
691: boolean isNew = getTx().isTransient(null, refObjOrProxy,
692: oid);
693: mod = buffer.get(oid, refObjOrProxy, isNew);
694: }
695: // if the object was deleted in an previous action, mark as new
696: // to avoid deletion, else mark object as dirty to assign the FK of
697: // the main object
698: if (mod.needsDelete()) {
699: mod.setModificationState(mod.getModificationState()
700: .markNew());
701: } else {
702: /*
703: arminw: if the reference is a m:n relation and the object state is
704: old clean, no need to update the reference.
705: */
706: if (!(cod.isMtoNRelation() && mod.getModificationState()
707: .equals(StateOldClean.getInstance()))) {
708: mod.setModificationState(mod.getModificationState()
709: .markDirty());
710: }
711: }
712: // buffer this object as "new" in a list to prevent deletion
713: // when object was moved from one collection to another
714: buffer.addNewAssociatedIdentity(oid);
715: // new referenced object found, so register all m:n relation for "linking"
716: if (cod.isMtoNRelation()) {
717: buffer.addM2NLinkEntry(cod, getObject(), refObjOrProxy);
718: } else {
719: // we have to link the new object
720: mod.addLinkOneToN(cod, getObject(), false);
721: }
722: if (mod.needsInsert()) {
723: buffer.addForInsertDependent(mod);
724: }
725: }
726:
727: public void deletedXToN(CollectionDescriptor cod,
728: Object refObjOrProxy, Identity oid) {
729: ObjectEnvelope mod = buffer.getByIdentity(oid);
730: // if this object is associated with another object it's
731: // not allowed to remove it, thus nothing will change
732: if (!buffer.isNewAssociatedObject(oid)) {
733: if (mod != null) {
734: boolean cascade = buffer.getTransaction()
735: .cascadeDeleteFor(cod);
736: if (cascade) {
737: mod.setModificationState(mod.getModificationState()
738: .markDelete());
739: buffer.addForDeletionDependent(mod);
740: }
741: if (cod.isMtoNRelation()) {
742: buffer.addM2NUnlinkEntry(cod, getObject(),
743: refObjOrProxy);
744: } else {
745: // when cascade 'true' we remove all dependent objects, so no need
746: // to unlink, else we have to unlink all referenced objects of this
747: // object
748: if (!cascade) {
749: mod.setModificationState(mod
750: .getModificationState().markDirty());
751: mod.addLinkOneToN(cod, getObject(), true);
752: }
753: }
754: } else {
755: throw new Image.ImageException(
756: "Unexpected behaviour, unregistered object to delete: "
757: + oid + ", main object is "
758: + getIdentity()
759: + ", envelope object is "
760: + this.toString());
761: }
762: }
763: }
764: }
|