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