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    }