001    package org.apache.ojb.otm.core;
002    
003    /* Copyright 2003-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.lang.reflect.Array;
019    import java.math.BigDecimal;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.Collection;
023    import java.util.Comparator;
024    import java.util.Iterator;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.Date;
028    import java.util.List;
029    import java.util.Set;
030    import java.util.Stack;
031    
032    import org.apache.commons.collections.iterators.ArrayIterator;
033    import org.apache.ojb.broker.Identity;
034    import org.apache.ojb.broker.OJBRuntimeException;
035    import org.apache.ojb.broker.PBKey;
036    import org.apache.ojb.broker.PersistenceBroker;
037    import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
038    import org.apache.ojb.broker.cache.ObjectCache;
039    import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
040    import org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl;
041    import org.apache.ojb.broker.core.proxy.SetProxyDefaultImpl;
042    import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
043    import org.apache.ojb.broker.core.proxy.IndirectionHandler;
044    import org.apache.ojb.broker.core.proxy.MaterializationListener;
045    import org.apache.ojb.broker.core.proxy.ProxyHelper;
046    import org.apache.ojb.broker.metadata.ClassDescriptor;
047    import org.apache.ojb.broker.metadata.CollectionDescriptor;
048    import org.apache.ojb.broker.metadata.FieldDescriptor;
049    import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
050    import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
051    
052    import org.apache.ojb.otm.EditingContext;
053    import org.apache.ojb.otm.OTMKit;
054    import org.apache.ojb.otm.copy.ObjectCopyStrategy;
055    import org.apache.ojb.otm.lock.LockManager;
056    import org.apache.ojb.otm.lock.LockType;
057    import org.apache.ojb.otm.lock.LockingException;
058    import org.apache.ojb.otm.states.State;
059    import org.apache.ojb.otm.swizzle.Swizzling;
060    
061    /**
062     *
063     * Concrete implementation of EditingContext.
064     *
065     * @author  <a href="mailto:rraghuram@hotmail.com">Raghu Rajah</a>
066     * @see     org.apache.ojb.otm.EditingContext
067     *
068     */
069    public class ConcreteEditingContext
070            implements EditingContext, MaterializationListener, ObjectCache
071    {
072        // for hasBidirectionalAssociation method
073        // Maps PBKeys to the sets of classes with/without
074        // bidirectional associations
075        private static HashMap _withBidirAsscMap = new HashMap();
076        private static HashMap _withoutBidirAsscMap = new HashMap();
077    
078        private HashSet _withBidirAssc;
079        private HashSet _withoutBidirAssc;
080    
081        private HashMap _objects;
082        private ArrayList _order;
083        private Transaction _tx;
084        private PersistenceBroker _pb;
085        private HashMap _original;
086        private HashMap _checkpointed;
087        private HashMap _colProxyListeners;
088    
089        //////////////////////////////////////////
090        // Constructor
091        //////////////////////////////////////////
092    
093        public ConcreteEditingContext(Transaction tx, PersistenceBroker pb)
094        {
095            PBKey pbkey;
096    
097            _tx = tx;
098            _pb = pb;
099            _objects = new HashMap();
100            _order = new ArrayList();
101            _original = new HashMap();
102            _checkpointed = _original;
103            pbkey = _pb.getPBKey();
104            _withoutBidirAssc = (HashSet) _withoutBidirAsscMap.get(pbkey);
105            if (_withoutBidirAssc != null)
106            {
107                _withBidirAssc = (HashSet) _withBidirAsscMap.get(pbkey);
108            }
109            else
110            {
111                _withoutBidirAssc = new HashSet();
112                _withoutBidirAsscMap.put(pbkey, _withoutBidirAssc);
113                _withBidirAssc = new HashSet();
114                _withBidirAsscMap.put(pbkey, _withBidirAssc);
115            }
116        }
117    
118        //////////////////////////////////////////
119        // EditingContext operations
120        //////////////////////////////////////////
121    
122        /**
123         * @see org.apache.ojb.otm.EditingContext#insert(Identity, Object, int)
124         */
125        public void insert(Identity oid, Object userObject, int lock)
126                throws LockingException
127        {
128            ContextEntry entry;
129    
130            entry = insertInternal(oid, userObject, lock, true, null, new Stack());
131            if ((entry != null) && entry.state.needsDelete()) {
132                // Undelete it
133                entry.state = State.PERSISTENT_CLEAN;
134            }
135        }
136    
137        private ContextEntry insertInternal(Identity oid, Object userObject,
138                int lock, boolean canCreate, Identity insertBeforeThis, Stack stack)
139                throws LockingException
140        {
141            ContextEntry entry;
142            LockManager lockManager;
143            Swizzling swizzlingStrategy;
144            IndirectionHandler handler = null;
145            OTMKit kit = _tx.getKit();
146            // Are we building object's relations for the userObject in the transaction?
147            // Otherwise we just get data from the "userObject" and put it into
148            // the previously loaded/created object in the transaction
149            boolean buildingObject = false;
150            boolean lazySwizzle = false;
151    
152            if (lock == LockType.NO_LOCK)
153            {
154                return null;
155            }
156    
157            entry = (ContextEntry) _objects.get(oid);
158    
159            if (userObject == null)
160            {
161                // invalidating object...
162                _original.remove(oid);
163                _checkpointed.remove(oid);
164                if (entry != null)
165                {
166                    entry.userObject = null;
167                    entry.cacheObject = null;
168                }
169                return entry;
170            }
171    
172            lockManager = LockManager.getInstance();
173            swizzlingStrategy = kit.getSwizzlingStrategy();
174    
175            handler = ProxyHelper.getIndirectionHandler(userObject);
176            if ((handler != null) && handler.alreadyMaterialized())
177            {
178                userObject = handler.getRealSubject();
179                handler = null;
180            }
181    
182            if ((entry == null) || (entry.userObject == null))
183            {
184                // first insertion of the userObject into editing context
185                Object swizzledObject = swizzlingStrategy.swizzle(userObject, null, _pb, this);
186                entry = new ContextEntry(swizzledObject);
187                if (entry.handler != null)
188                {
189                    ObjectCopyStrategy copyStrategy = _tx.getKit().getCopyStrategy(oid);
190                    entry.cacheObject = copyStrategy.copy(userObject, _pb);
191                    // Assume that object exists, otherwise were the proxy came from?
192                    _objects.put(oid, entry);
193                    lockManager.ensureLock(oid, _tx, lock, _pb); // lock after _objects.put to avoid hanged locks
194                    entry.handler.addListener(this);
195                }
196                else
197                {
198                    Object origCacheObj = _pb.getObjectByIdentity(oid);
199    
200                    if ((origCacheObj == null) && !canCreate)
201                    {
202                        // we don't create the objects by reachability
203                        throw new IllegalStateException("Related object is neither persistent, nor otm-depentent: " + oid);
204                    }
205                    if (origCacheObj != null)
206                    {
207                        entry.cacheObject = origCacheObj;
208                    }
209                    buildingObject = true;
210                    _objects.put(oid, entry);
211                    lockManager.ensureLock(oid, _tx, lock, _pb); // lock after _objects.put to avoid hanged locks
212    
213                    if (userObject != null)
214                    {
215                        if ((origCacheObj == null) && canCreate)
216                        {
217                            ObjectCopyStrategy copyStrategy = _tx.getKit().getCopyStrategy(oid);
218                            entry.cacheObject = copyStrategy.copy(userObject, _pb);
219                            entry.state = State.PERSISTENT_NEW;
220                            if (kit.isEagerInsert(userObject)
221                                    || hasBidirectionalAssociation(userObject.getClass()))
222                            {
223                                _pb.store(entry.cacheObject, entry.state);
224                                entry.state = State.PERSISTENT_CLEAN;
225                                origCacheObj = entry.cacheObject;
226                            }
227                        }
228    
229                        if (origCacheObj != null)
230                        {
231                            _original.put(oid, getFields(userObject, false, true));
232                        }
233                    }
234                }
235                if (insertBeforeThis != null)
236                {
237                    int insertIndex = _order.indexOf(insertBeforeThis);
238                    _order.add(insertIndex, oid);
239                }
240                else
241                {
242                    _order.add(oid);
243                }
244            }
245            else
246            {
247                // The object in context is the same object attempted an insert on
248                // Ensure we have the correct lock level
249                lockManager.ensureLock(oid, _tx, lock, _pb);
250    
251                if (handler == null)
252                {
253                    if (!swizzlingStrategy.isSameInstance(entry.userObject, userObject))
254                    {
255                        // the new object contains data to deal with
256                        if (entry.handler != null)
257                        {
258                            // materialize old object even if it is not
259                            // materialized yet, because we need a place
260                            // to copy the data from the new object
261                            entry.userObject = entry.handler.getRealSubject();
262                            entry.handler = null;
263                        }
264                        // swizzle after lockReachableObjects(), when all related objects
265                        // will be in the editing context
266                        lazySwizzle = true;
267                    }
268                }
269            }
270    
271            // perform automatic read lock for all reachable objects
272            // if the inserted object is materialized
273            if ((handler == null) && !stack.contains(userObject))
274            {
275                stack.push(userObject);
276                lockReachableObjects(oid, userObject, entry.cacheObject, lock, stack, buildingObject);
277                stack.pop();
278                if (lazySwizzle)
279                {
280                    entry.userObject = swizzlingStrategy.swizzle(userObject, entry.userObject, _pb, this);
281                }
282            }
283    
284            return entry;
285        }
286    
287        /**
288         * Lock all objects reachable via 1:N and N:1 relations,
289         * @param lock The lock type to use
290         */
291        private void lockReachableObjects(Identity oid, Object userObject,
292                Object cacheObject, int lock, Stack stack, boolean buildingObject)
293                throws LockingException
294        {
295            ContextEntry entry;
296            boolean onlyDependants = !_tx.getKit().isImplicitLockingUsed();
297            ClassDescriptor mif = _pb.getClassDescriptor(userObject.getClass());
298    
299            // N:1 relations
300            Iterator iter = mif.getObjectReferenceDescriptors().iterator();
301            ObjectReferenceDescriptor rds = null;
302            PersistentField f;
303            Object relUserObj;
304            Identity relOid;
305            boolean isDependent;
306    
307            while (iter.hasNext())
308            {
309                rds = (ObjectReferenceDescriptor) iter.next();
310                isDependent = rds.getOtmDependent();
311                if (onlyDependants && !isDependent)
312                {
313                    continue;
314                }
315                f = rds.getPersistentField();
316                relUserObj = f.get(userObject);
317                if (relUserObj != null)
318                {
319                    relOid = new Identity(relUserObj, _pb);
320                    entry = (ContextEntry) _objects.get(relOid);
321                    if ((entry == null) || (entry.userObject != relUserObj))
322                    {
323                        entry = insertInternal(relOid, relUserObj, lock, isDependent,
324                                               oid, stack);
325                        if (buildingObject && (entry != null))
326                        {
327                            f.set(userObject, entry.userObject);
328                            f.set(cacheObject, entry.cacheObject);
329                        }
330                    }
331                }
332            }
333    
334            // 1:N relations
335            Iterator collections = mif.getCollectionDescriptors().iterator();
336            CollectionDescriptor cds;
337            Object userCol;
338            Iterator userColIterator;
339            Class type;
340            ArrayList newUserCol = null;
341            ArrayList newCacheCol = null;
342    
343            while (collections.hasNext())
344            {
345                cds = (CollectionDescriptor) collections.next();
346                f = cds.getPersistentField();
347                type = f.getType();
348                isDependent = cds.getOtmDependent();
349                if (onlyDependants && !isDependent)
350                {
351                    continue;
352                }
353                userCol = f.get(userObject);
354                if (userCol != null)
355                {
356                    if ((userCol instanceof CollectionProxyDefaultImpl)
357                            && !((CollectionProxyDefaultImpl) userCol).isLoaded())
358                    {
359                        continue;
360                    }
361    
362                    if (buildingObject)
363                    {
364                        newUserCol = new ArrayList();
365                        newCacheCol = new ArrayList();
366                    }
367    
368                    if (Collection.class.isAssignableFrom(type))
369                    {
370                        userColIterator = ((Collection) userCol).iterator();
371                    }
372                    else if (type.isArray())
373                    {
374                        userColIterator = new ArrayIterator(userCol);
375                    }
376                    else
377                    {
378                        throw new OJBRuntimeException(
379                            userCol.getClass()
380                                + " can not be managed by OJB OTM, use Array or Collection instead !");
381                    }
382    
383                    while (userColIterator.hasNext())
384                    {
385                        relUserObj = userColIterator.next();
386                        relOid = new Identity(relUserObj, _pb);
387                        entry = (ContextEntry) _objects.get(relOid);
388                        if ((entry == null) || (entry.userObject != relUserObj))
389                        {
390                            entry = insertInternal(relOid, relUserObj, lock,
391                                                   isDependent, null, stack);
392                        }
393                        if (buildingObject && (entry != null))
394                        {
395                            newUserCol.add(entry.userObject);
396                            newCacheCol.add(entry.cacheObject);
397                        }
398                    }
399                    if (buildingObject)
400                    {
401                        setCollectionField(userObject, f, newUserCol);
402                        setCollectionField(cacheObject, f, newCacheCol);
403                    }
404                }
405            }
406        }
407    
408        /**
409         * @see org.apache.ojb.otm.EditingContext#remove(Identity)
410         */
411        public void remove(Identity oid)
412        {
413            _objects.remove(oid);
414            _order.remove(oid);
415            LockManager.getInstance().releaseLock(oid, _tx);
416        }
417    
418    
419        public void deletePersistent(Identity oid, Object userObject)
420                throws LockingException
421        {
422            ContextEntry entry;
423    
424            entry = insertInternal(oid, userObject, LockType.WRITE_LOCK, true, null,
425                                   new Stack());
426            if (entry != null)
427            {
428                entry.state = entry.state.deletePersistent();
429            }
430            _order.remove(oid);
431            _order.add(oid);
432        }
433    
434        /**
435         * @see org.apache.ojb.otm.EditingContext#lookup(Identity)
436         */
437        public Object lookup(Identity oid)
438        {
439            ContextEntry entry = (ContextEntry) _objects.get(oid);
440            return (entry == null ? null : entry.userObject);
441        }
442    
443        public boolean contains(Identity oid)
444        {
445            return lookup(oid) != null;
446        }
447    
448        /**
449         * @see org.apache.ojb.otm.EditingContext#lookupState(Identity)
450         */
451        public State lookupState(Identity oid)
452                throws LockingException
453        {
454            State retval = null;
455            ContextEntry entry = (ContextEntry) _objects.get(oid);
456            if (entry != null)
457            {
458                /**
459                 * possibly return a clone so we don't allow people to tweak states.
460                 */
461                retval = entry.state;
462            }
463            return retval;
464        }
465    
466        /**
467         * @see org.apache.ojb.otm.EditingContext#setState(Identity, State)
468         */
469        public void setState(Identity oid, State state)
470        {
471            ContextEntry entry = (ContextEntry) _objects.get(oid);
472            entry.state = state;
473        }
474    
475        public Collection getAllObjectsInContext()
476        {
477            return _objects.values();
478        }
479    
480        //////////////////////////////////////////
481        // MaterializationListener interface
482        //////////////////////////////////////////
483    
484        public void beforeMaterialization(IndirectionHandler handler, Identity oid)
485        {
486            //noop
487        }
488    
489        public void afterMaterialization(IndirectionHandler handler, Object cacheObject)
490        {
491            Identity oid = handler.getIdentity();
492            ContextEntry entry = (ContextEntry) _objects.get(oid);
493    
494            if (entry == null)
495            {
496                return;
497            }
498    
499            int lock = LockManager.getInstance().getLockHeld(oid, _tx);
500            ObjectCopyStrategy copyStrategy = _tx.getKit().getCopyStrategy(oid);
501            Object userObject = copyStrategy.copy(cacheObject, _pb);
502            handler.setRealSubject(userObject);
503            _original.put(oid, getFields(userObject, false, true));
504    
505            // replace the proxy object with the real one
506            entry.userObject = userObject;
507            entry.cacheObject = cacheObject;
508            entry.handler.removeListener(this);
509            entry.handler = null;
510    
511            // perform automatic lock for all reachable objects
512            // if the inserted object is materialized
513            try
514            {
515                lockReachableObjects(oid, userObject, cacheObject, lock, new Stack(), true);
516            }
517            catch (LockingException ex)
518            {
519                throw new LockingPassthruException(ex);
520            }
521        }
522    
523    
524    
525        //////////////////////////////////////////
526        // Other operations
527        //////////////////////////////////////////
528    
529        /**
530         *
531         *  Commit this context into the persistent store.
532         *  The EditingContext is not usable after a commit.
533         *
534         */
535        public void commit() throws TransactionAbortedException
536        {
537            checkpointInternal(true);
538            releaseLocksAndClear();
539        }
540    
541        private void releaseLocksAndClear()
542        {
543            releaseLocks();
544            removeMaterializationListener();
545            _objects.clear();
546            _order.clear();
547            _original.clear();
548            if (_checkpointed != _original)
549            {
550                _checkpointed.clear();
551            }
552        }
553    
554        /**
555         *
556         *  Writes all changes in this context into the persistent store.
557         *
558         */
559        public void checkpoint() throws TransactionAbortedException
560        {
561            checkpointInternal(false);
562            _checkpointed = new HashMap();
563            for (Iterator iterator = _order.iterator(); iterator.hasNext();)
564            {
565                Identity oid = (Identity) iterator.next();
566                ContextEntry entry = (ContextEntry) _objects.get(oid);
567                if (entry.handler == null)
568                {
569                    _checkpointed.put(oid, getFields(entry.userObject, false, true));
570                }
571            }
572        }
573    
574        /**
575         *
576         *  Writes all changes in this context into the persistent store.
577         *
578         */
579        private void checkpointInternal(boolean isCommit)
580                throws TransactionAbortedException
581        {
582            if (_order.size() == 0)
583            {
584                return;
585            }
586    
587            removeCollectionProxyListeners();
588    
589            ConnectionManagerIF connMan = _pb.serviceConnectionManager();
590            boolean saveBatchMode = connMan.isBatchMode();
591            Swizzling swizzlingStrategy = _tx.getKit().getSwizzlingStrategy();
592            LockManager lockManager = LockManager.getInstance();
593            Identity[] lockOrder = (Identity[]) _order.toArray(new Identity[_order.size()]);
594            ObjectCache cache = _pb.serviceObjectCache();
595            boolean isInsertVerified = _tx.getKit().isInsertVerified();
596            ArrayList changedCollections = new ArrayList();
597    
598            // sort objects in the order of oid.hashCode to avoid deadlocks
599            Arrays.sort(lockOrder, new Comparator()
600            {
601                public int compare(Object o1, Object o2)
602                {
603                    return o1.hashCode() - o2.hashCode();
604                }
605    
606                public boolean equals(Object obj)
607                {
608                    return false;
609                }
610            });
611    
612            try {
613                // mark dirty objects and lock them for write
614                // also handle dependent objects and if there were inserted once,
615                // repeat this process for their dependants ("cascade create")
616                ArrayList newObjects = new ArrayList();
617                int countNewObjects;
618                do
619                {
620                    newObjects.clear();
621                    countNewObjects = 0;
622                    for (int i = 0; i < lockOrder.length; i++)
623                    {
624                        Identity oid = lockOrder[i];
625                        ContextEntry entry = (ContextEntry) _objects.get(oid);
626                        State state = entry.state;
627    
628                        if (entry.userObject == null) // invalidated
629                        {
630                            continue;
631                        }
632    
633                        if (entry.handler == null) // materialized
634                        {
635                            if (!state.isDeleted())
636                            {
637                                Object[][] origFields = (Object[][]) _checkpointed.get(oid);
638                                Object[][] newFields = getFields(entry.userObject, true, !isCommit);
639    
640                                if (origFields == null)
641                                {
642                                    entry.needsCacheSwizzle = true;
643                                    newObjects.addAll(
644                                            handleDependentReferences(oid, entry.userObject,
645                                            null, newFields[0], newFields[2]));
646                                    newObjects.addAll(
647                                            handleDependentCollections(oid, entry.userObject,
648                                            null, newFields[1], newFields[3]));
649                                }
650                                else
651                                {
652                                    if (isModified(origFields[0], newFields[0]))
653                                    {
654                                        entry.state = state.markDirty();
655                                        entry.needsCacheSwizzle = true;
656                                        lockManager.ensureLock(oid, _tx, LockType.WRITE_LOCK, _pb);
657                                        newObjects.addAll(
658                                                handleDependentReferences(oid, entry.userObject,
659                                                origFields[0], newFields[0], newFields[2]));
660                                    }
661    
662                                    if (isModified(origFields[1], newFields[1]))
663                                    {
664                                        // there are modified collections,
665                                        // so we need to lock the object and to swizzle it to cache
666                                        entry.needsCacheSwizzle = true;
667                                        lockManager.ensureLock(oid, _tx, LockType.WRITE_LOCK, _pb);
668                                        newObjects.addAll(
669                                                handleDependentCollections(oid, entry.userObject,
670                                                origFields[1], newFields[1], newFields[3]));
671                                        changedCollections.add(oid);
672                                    }
673                                }
674                            }
675                        }
676                    }
677                    countNewObjects = newObjects.size();
678                    if (countNewObjects > 0)
679                    {
680                        // new objects are not locked, so we don't need to ensure the order
681                        lockOrder = (Identity[]) newObjects.toArray(
682                                new Identity[countNewObjects]);
683                    }
684                }
685                while (countNewObjects > 0);
686    
687                // Swizzle the context objects and the cache objects
688                for (Iterator it = _order.iterator(); it.hasNext(); )
689                {
690                    Identity oid = (Identity) it.next();
691                    ContextEntry entry = (ContextEntry) _objects.get(oid);
692    
693                    if (entry.needsCacheSwizzle)
694                    {
695                        entry.userObject = swizzlingStrategy.getRealTarget(entry.userObject);
696                        entry.cacheObject = swizzlingStrategy.swizzle(
697                        // we create the special ObjectCache implememntation
698                                // that returns cacheObject, not userObject
699                                entry.userObject, entry.cacheObject, _pb, new ObjectCache()
700                                {
701                                    public Object lookup(Identity anOid)
702                                    {
703                                        ContextEntry ent = (ContextEntry) _objects.get(anOid);
704                                        return (ent == null ? null : ent.cacheObject);
705                                    }
706    
707                                    public boolean contains(Identity oid)
708                                    {
709                                        return lookup(oid) != null;
710                                    }
711    
712                                    public void cache(Identity anOid, Object obj)
713                                    {
714                                        // do nothing
715                                    }
716    
717                                    public boolean cacheIfNew(Identity oid, Object obj)
718                                    {
719                                        return false;
720                                    }
721    
722                                    public void clear()
723                                    {
724                                        // do nothing
725                                    }
726    
727                                    public void remove(Identity anOid)
728                                    {
729                                        // do nothing
730                                    }
731                                });
732                    }
733                }
734    
735                // Cascade delete for dependent objects
736                int countCascadeDeleted;
737                do
738                {
739                    countCascadeDeleted = 0;
740                    // Use intermediate new ArrayList(_order) because _order
741                    // may be changed during cascade delete
742                    for (Iterator it = (new ArrayList(_order)).iterator(); it.hasNext(); )
743                    {
744                        Identity oid = (Identity) it.next();
745                        ContextEntry entry = (ContextEntry) _objects.get(oid);
746    
747                        if (entry.state.isDeleted())
748                        {
749                            countCascadeDeleted += doCascadeDelete(oid, entry.userObject);
750                        }
751                    }
752                }
753                while (countCascadeDeleted > 0);
754    
755                // perform database operations
756                connMan.setBatchMode(true);
757                try
758                {
759                    for (Iterator it = _order.iterator(); it.hasNext(); )
760                    {
761                        Identity oid = (Identity) it.next();
762                        ContextEntry entry = (ContextEntry) _objects.get(oid);
763                        State state = entry.state;
764    
765                        if (!state.needsInsert() && !state.needsUpdate()
766                                && !state.needsDelete())
767                        {
768                            if (changedCollections.contains(oid)) {
769                                _pb.store(entry.cacheObject, state);
770                            }
771                            continue;
772                        }
773    
774                        if (state.needsInsert())
775                        {
776                            if (isInsertVerified)
777                            {
778                                // PB verifies object existence by default
779                                _pb.store(entry.cacheObject);
780                            }
781                            else
782                            {
783                                // PB migth already created the object by auto-update
784                                if (cache.lookup(oid) == null) {
785                                    _pb.store(entry.cacheObject, state);
786                                }
787                            }
788    
789                        }
790                        else if (state.needsUpdate())
791                        {
792                            _pb.store(entry.cacheObject, state);
793                        }
794                        else if (state.needsDelete())
795                        {
796                            _pb.delete(entry.cacheObject);
797                        }
798                        entry.state = state.commit();
799                    }
800                    connMan.executeBatch();
801                }
802                finally
803                {
804                    connMan.setBatchMode(saveBatchMode);
805                }
806            } catch (Throwable ex) {
807                ex.printStackTrace();
808                throw new TransactionAbortedException(ex);
809            }
810        }
811    
812        /**
813         *
814         *  Rollback all changes made during this transaction. The EditingContext is not usable after
815         *  a rollback.
816         *
817         */
818        public void rollback()
819        {
820            for (Iterator iterator = _order.iterator(); iterator.hasNext();)
821            {
822                Identity oid = (Identity) iterator.next();
823                ContextEntry entry = (ContextEntry) _objects.get(oid);
824                entry.state = entry.state.rollback();
825                Object[][] origFields = (Object[][]) _original.get(oid);
826                if (origFields != null)
827                {
828                    setFields(entry.userObject, origFields);
829                    setFields(entry.cacheObject, origFields);
830                }
831            }
832            releaseLocksAndClear();
833        }
834    
835        /**
836         *
837         *  Rollback all changes made during this transaction to the given object.
838         *
839         */
840        public void refresh(Identity oid, Object object)
841        {
842            ContextEntry entry = (ContextEntry) _objects.get(oid);
843            Object[][] origFields = (Object[][]) _original.get(oid);
844            if (origFields != null)
845            {
846                setFields(entry.userObject, origFields);
847                if (object != entry.userObject)
848                {
849                    setFields(object, origFields);
850                }
851            }
852            entry.state = entry.state.refresh();
853        }
854    
855        private void removeMaterializationListener()
856        {
857            for (Iterator it = _order.iterator(); it.hasNext();)
858            {
859                Identity oid = (Identity) it.next();
860                ContextEntry entry = (ContextEntry) _objects.get(oid);
861                if (entry.handler != null)
862                {
863                    entry.handler.removeListener(this);
864                }
865            }
866        }
867    
868        private void removeCollectionProxyListeners()
869        {
870            if (_colProxyListeners != null)
871            {
872                for (Iterator it = _colProxyListeners.keySet().iterator(); it.hasNext();)
873                {
874                    CollectionProxyListener listener = (CollectionProxyListener) it.next();
875                    CollectionProxyDefaultImpl colProxy = (CollectionProxyDefaultImpl) _colProxyListeners.get(listener);
876                    colProxy.removeListener(listener);
877                }
878                _colProxyListeners.clear();
879            }
880        }
881    
882        private void releaseLocks()
883        {
884            LockManager lockManager = LockManager.getInstance();
885    
886            for (Iterator it = _objects.keySet().iterator(); it.hasNext(); )
887            {
888                Identity oid = (Identity) it.next();
889                lockManager.releaseLock(oid, _tx);
890            }
891            _tx.getKit().getLockMap().gc();
892        }
893    
894        /**
895         * This method compared simple field values:
896         * there are some tricks...
897         */
898        private boolean isEqual(Object fld1, Object fld2)
899        {
900            if (fld1 == null || fld2 == null)
901            {
902                return (fld1 == fld2);
903            }
904            else if ((fld1 instanceof BigDecimal) && (fld2 instanceof BigDecimal))
905            {
906                return (((BigDecimal) fld1).compareTo((BigDecimal) fld2) == 0);
907            }
908            else if ((fld1 instanceof Date) && (fld2 instanceof Date))
909            {
910                return (((Date) fld1).getTime() == ((Date) fld2).getTime());
911            }
912            else
913            {
914                return fld1.equals(fld2);
915            }
916        }
917    
918        private boolean isModified(Object[] newFields, Object[] oldFields)
919        {
920            if (newFields.length != oldFields.length)
921            {
922                return true;
923            }
924    
925            for (int i = 0; i < newFields.length; i++)
926            {
927                if (!isEqual(newFields[i], oldFields[i]))
928                {
929                    return true;
930                }
931            }
932    
933            return false;
934        }
935    
936        /**
937         * @param addListeners Whether to add CollectionProxy listeners
938         * @return four arrays of field values:
939         * 1) The class, simple fields, identities of references
940         * 2) collections of identities
941         * 3) references (parallel to identities of references in 1)
942         * 4) collections of objects (parallel to collections of identities in 2)
943         * if "withObjects" parameter is "false", then returns nulls
944         * in places of 3) and 4)
945         */
946        private Object[][] getFields(Object obj, boolean withObjects, boolean addListeners)
947        {
948            ClassDescriptor mif = _pb.getClassDescriptor(obj.getClass());
949            FieldDescriptor[] fieldDescs = mif.getFieldDescriptions();
950            Collection refDescs = mif.getObjectReferenceDescriptors();
951            Collection colDescs = mif.getCollectionDescriptors();
952            int count = 0;
953            Object[] fields = new Object[1 + fieldDescs.length + refDescs.size()];
954            ArrayList[] collections = new ArrayList[colDescs.size()];
955            Object[] references = null;
956            ArrayList[] collectionsOfObjects = null;
957            int lockForListeners = LockType.NO_LOCK;
958    
959            if (withObjects)
960            {
961                references = new Object[refDescs.size()];
962                collectionsOfObjects = new ArrayList[colDescs.size()];
963            }
964    
965            if (addListeners)
966            {
967                lockForListeners = LockManager.getInstance().getLockHeld(
968                        new Identity(obj, _pb), _tx);
969            }
970    
971            fields[0] = obj.getClass(); // we must notice if the object class changes
972            count++;
973    
974            for (int i = 0; i < fieldDescs.length; i++)
975            {
976                FieldDescriptor fd = fieldDescs[i];
977                PersistentField f = fd.getPersistentField();
978                fields[count] = f.get(obj);
979                count++;
980            }
981    
982            int countRefs = 0;
983            for (Iterator it = refDescs.iterator(); it.hasNext(); count++, countRefs++)
984            {
985                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) it.next();
986                PersistentField f = rds.getPersistentField();
987                Object relObj = f.get(obj);
988                if (relObj != null)
989                {
990                    fields[count] = new Identity(relObj, _pb);
991                    if (withObjects)
992                    {
993                        references[countRefs] = relObj;
994                    }
995                }
996            }
997    
998            count = 0;
999            for (Iterator it = colDescs.iterator(); it.hasNext(); count++)
1000            {
1001                CollectionDescriptor cds = (CollectionDescriptor) it.next();
1002                PersistentField f = cds.getPersistentField();
1003                Class type = f.getType();
1004                Object col = f.get(obj);
1005    
1006                if ((col != null) && (col instanceof CollectionProxyDefaultImpl)
1007                        && !((CollectionProxyDefaultImpl) col).isLoaded())
1008                {
1009                    if (addListeners)
1010                    {
1011                        OTMCollectionProxyListener listener =
1012                                new OTMCollectionProxyListener(cds, collections,
1013                                                               count, lockForListeners);
1014    
1015                        ((CollectionProxyDefaultImpl) col).addListener(listener);
1016                        if (_colProxyListeners == null)
1017                        {
1018                            _colProxyListeners = new HashMap();
1019                        }
1020                        _colProxyListeners.put(listener, col);
1021                    }
1022                    continue;
1023                }
1024    
1025                if (col != null)
1026                {
1027                    ArrayList list = new ArrayList();
1028                    ArrayList listOfObjects = null;
1029                    Iterator colIterator;
1030    
1031                    collections[count] = list;
1032                    if (withObjects)
1033                    {
1034                        listOfObjects = new ArrayList();
1035                        collectionsOfObjects[count] = listOfObjects;
1036                    }
1037    
1038                    if (Collection.class.isAssignableFrom(type))
1039                    {
1040                        colIterator = ((Collection) col).iterator();
1041                    }
1042                    else if (type.isArray())
1043                    {
1044                        colIterator = new ArrayIterator(col);
1045                    }
1046                    else
1047                    {
1048                        continue;
1049                    }
1050    
1051                    while (colIterator.hasNext())
1052                    {
1053                        Object relObj = colIterator.next();
1054                        list.add(new Identity(relObj, _pb));
1055                        if (withObjects)
1056                        {
1057                            listOfObjects.add(relObj);
1058                        }
1059                    }
1060                }
1061            }
1062    
1063            return new Object[][] {fields, collections, references, collectionsOfObjects};
1064        }
1065    
1066        private void setFields(Object obj, Object[][] fieldsAndCollections)
1067        {
1068            ClassDescriptor mif = _pb.getClassDescriptor(obj.getClass());
1069            FieldDescriptor[] fieldDescs = mif.getFieldDescriptions();
1070            Collection refDescs = mif.getObjectReferenceDescriptors();
1071            Collection colDescs = mif.getCollectionDescriptors();
1072            Object[] fields = fieldsAndCollections[0];
1073            ArrayList[] collections = (ArrayList[]) fieldsAndCollections[1];
1074            int count = 0;
1075    
1076            if (!fields[0].equals(obj.getClass()))
1077            {
1078                System.err.println("Can't restore the object fields "
1079                        + "since its class changed during transaction from "
1080                        + fields[0] + " to " + obj.getClass());
1081                return;
1082            }
1083            count++;
1084    
1085            for (int i = 0; i < fieldDescs.length; i++)
1086            {
1087                FieldDescriptor fd = fieldDescs[i];
1088                PersistentField f = fd.getPersistentField();
1089                f.set(obj, fields[count]);
1090                count++;
1091            }
1092    
1093            for (Iterator it = refDescs.iterator(); it.hasNext(); count++)
1094            {
1095                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) it.next();
1096                PersistentField f = rds.getPersistentField();
1097                Identity oid = (Identity) fields[count];
1098                Object relObj;
1099                if (oid == null)
1100                {
1101                    relObj = null;
1102                }
1103                else
1104                {
1105                    relObj = _pb.getObjectByIdentity(oid);
1106                }
1107                f.set(obj, relObj);
1108            }
1109    
1110            count = 0;
1111            for (Iterator it = colDescs.iterator(); it.hasNext(); count++)
1112            {
1113                CollectionDescriptor cds = (CollectionDescriptor) it.next();
1114                PersistentField f = cds.getPersistentField();
1115                ArrayList list = collections[count];
1116                ArrayList newCol;
1117    
1118                if (list == null)
1119                {
1120                    f.set(obj, null);
1121                }
1122                else
1123                {
1124                    newCol = new ArrayList();
1125                    for (Iterator it2 = list.iterator(); it2.hasNext(); )
1126                    {
1127                        Identity relOid = (Identity) it2.next();
1128                        Object relObj = _pb.getObjectByIdentity(relOid);
1129    
1130                        if (relObj != null)
1131                        {
1132                            newCol.add(relObj);
1133                        }
1134                    }
1135                    setCollectionField(obj, f, newCol);
1136                }
1137            }
1138        }
1139    
1140        private void setCollectionField(Object obj, PersistentField f, List newCol)
1141        {
1142            Class type = f.getType();
1143    
1144            if (Collection.class.isAssignableFrom(type))
1145            {
1146                Collection col = (Collection) f.get(obj);
1147    
1148                if (col == null)
1149                {
1150                    if (type == List.class || type == Collection.class)
1151                    {
1152                        col = new ArrayList();
1153                    }
1154                    else if (type == Set.class)
1155                    {
1156                        col = new HashSet();
1157                    }
1158                    else
1159                    {
1160                        try
1161                        {
1162                            col = (Collection) type.newInstance();
1163                        }
1164                        catch (Throwable ex)
1165                        {
1166                            System.err.println("Cannot instantiate collection field: " + f);
1167                            ex.printStackTrace();
1168                            return;
1169                        }
1170                    }
1171                }
1172                else
1173                {
1174                    if (col instanceof CollectionProxyDefaultImpl)
1175                    {
1176                        CollectionProxyDefaultImpl cp = (CollectionProxyDefaultImpl) col;
1177                        if (col instanceof List)
1178                        {
1179                            col = new ListProxyDefaultImpl(_pb.getPBKey(), cp.getData().getClass(), null);
1180                        }
1181                        else if (col instanceof Set)
1182                        {
1183                            col = new SetProxyDefaultImpl(_pb.getPBKey(), cp.getData().getClass(), null);
1184                        }
1185                        else
1186                        {
1187                            col = new CollectionProxyDefaultImpl(_pb.getPBKey(), cp.getData().getClass(), null);
1188                        }
1189                        col.clear();
1190                    }
1191                    else
1192                    {
1193                        try
1194                        {
1195                            col = (Collection) col.getClass().newInstance();
1196                        }
1197                        catch (Exception ex)
1198                        {
1199                            System.err.println("Cannot instantiate collection field: " + f);
1200                            ex.printStackTrace();
1201                            return;
1202                        }
1203                    }
1204                }
1205                col.addAll(newCol);
1206                f.set(obj, col);
1207            }
1208            else if (type.isArray())
1209            {
1210                int length = newCol.size();
1211                Object array = Array.newInstance(type.getComponentType(), length);
1212    
1213                for (int i = 0; i < length; i++)
1214                {
1215                    Array.set(array, i, newCol.get(i));
1216                }
1217                f.set(obj, array);
1218            }
1219        }
1220    
1221        /**
1222         * Does the given class has bidirectional assiciation
1223         * with some other class?
1224         */
1225        private boolean hasBidirectionalAssociation(Class clazz)
1226        {
1227            ClassDescriptor cdesc;
1228            Collection refs;
1229            boolean hasBidirAssc;
1230    
1231            if (_withoutBidirAssc.contains(clazz))
1232            {
1233                return false;
1234            }
1235    
1236            if (_withBidirAssc.contains(clazz))
1237            {
1238                return true;
1239            }
1240    
1241            // first time we meet this class, let's look at metadata
1242            cdesc = _pb.getClassDescriptor(clazz);
1243            refs = cdesc.getObjectReferenceDescriptors();
1244            hasBidirAssc = false;
1245            REFS_CYCLE:
1246            for (Iterator it = refs.iterator(); it.hasNext(); )
1247            {
1248                ObjectReferenceDescriptor ord;
1249                ClassDescriptor relCDesc;
1250                Collection relRefs;
1251    
1252                ord = (ObjectReferenceDescriptor) it.next();
1253                relCDesc = _pb.getClassDescriptor(ord.getItemClass());
1254                relRefs = relCDesc.getObjectReferenceDescriptors();
1255                for (Iterator relIt = relRefs.iterator(); relIt.hasNext(); )
1256                {
1257                    ObjectReferenceDescriptor relOrd;
1258    
1259                    relOrd = (ObjectReferenceDescriptor) relIt.next();
1260                    if (relOrd.getItemClass().equals(clazz))
1261                    {
1262                        hasBidirAssc = true;
1263                        break REFS_CYCLE;
1264                    }
1265                }
1266            }
1267            if (hasBidirAssc)
1268            {
1269                _withBidirAssc.add(clazz);
1270            }
1271            else
1272            {
1273                _withoutBidirAssc.add(clazz);
1274            }
1275    
1276            return hasBidirAssc;
1277        }
1278    
1279        /**
1280         * @return number of deleted objects: 1 or 0 (if the object is already deleted)
1281         */
1282        private int markDelete(Identity oid, Identity mainOid, boolean isCollection)
1283        {
1284            ContextEntry entry = (ContextEntry) _objects.get(oid);
1285    
1286            if (entry == null)
1287            {
1288                throw new IllegalStateException("markDelete failed: the dependent object "
1289                        + oid + " is not in the editing context");
1290            }
1291    
1292            if (entry.state.isDeleted())
1293            {
1294                return 0;
1295            }
1296            else
1297            {
1298                entry.state = entry.state.deletePersistent();
1299                if (mainOid != null)
1300                {
1301                    int dependentIndex = _order.indexOf(oid);
1302                    int mainIndex = _order.indexOf(mainOid);
1303    
1304                    if (isCollection) // remove collection item before main obj
1305                    {
1306                        if (dependentIndex > mainIndex)
1307                        {
1308                            _order.remove(dependentIndex);
1309                            _order.add(mainIndex, oid);
1310                        }
1311                    }
1312                    else // remove reference after main obj
1313                    {
1314                        if (dependentIndex < mainIndex)
1315                        {
1316                            _order.remove(dependentIndex); // this causes mainIndex--
1317                            _order.add(mainIndex, oid);
1318                        }
1319                    }
1320    
1321                }
1322                return 1;
1323            }
1324        }
1325    
1326        /**
1327         * Mark for creation all newly introduced dependent references.
1328         * Mark for deletion all nullified dependent references.
1329         * @return the list of created objects
1330         */
1331        private ArrayList handleDependentReferences(Identity oid, Object userObject,
1332                Object[] origFields, Object[] newFields, Object[] newRefs)
1333                throws LockingException
1334        {
1335            ClassDescriptor mif = _pb.getClassDescriptor(userObject.getClass());
1336            FieldDescriptor[] fieldDescs = mif.getFieldDescriptions();
1337            Collection refDescs = mif.getObjectReferenceDescriptors();
1338            int count = 1 + fieldDescs.length;
1339            ArrayList newObjects = new ArrayList();
1340            int countRefs = 0;
1341    
1342            for (Iterator it = refDescs.iterator(); it.hasNext(); count++, countRefs++)
1343            {
1344                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) it.next();
1345                Identity origOid = (origFields == null ? null : (Identity) origFields[count]);
1346                Identity newOid = (Identity) newFields[count];
1347    
1348                if (rds.getOtmDependent())
1349                {
1350                    if ((origOid == null) && (newOid != null))
1351                    {
1352                        ContextEntry entry = (ContextEntry) _objects.get(newOid);
1353    
1354                        if (entry == null)
1355                        {
1356                            Object relObj = newRefs[countRefs];
1357                            insertInternal(newOid, relObj, LockType.WRITE_LOCK,
1358                                           true, oid, new Stack());
1359                            newObjects.add(newOid);
1360                        }
1361                    }
1362                    else if ((origOid != null) &&
1363                             ((newOid == null) || !newOid.equals(origOid)))
1364                    {
1365                        markDelete(origOid, oid, false);
1366                    }
1367                }
1368            }
1369    
1370            return newObjects;
1371        }
1372    
1373        /**
1374         * Mark for creation all objects that were included into dependent collections.
1375         * Mark for deletion all objects that were excluded from dependent collections.
1376         */
1377        private ArrayList handleDependentCollections(Identity oid, Object obj,
1378                Object[] origCollections, Object[] newCollections,
1379                Object[] newCollectionsOfObjects)
1380                throws LockingException
1381        {
1382            ClassDescriptor mif = _pb.getClassDescriptor(obj.getClass());
1383            Collection colDescs = mif.getCollectionDescriptors();
1384            ArrayList newObjects = new ArrayList();
1385            int count = 0;
1386    
1387            for (Iterator it = colDescs.iterator(); it.hasNext(); count++)
1388            {
1389                CollectionDescriptor cds = (CollectionDescriptor) it.next();
1390    
1391                if (cds.getOtmDependent())
1392                {
1393                    ArrayList origList = (origCollections == null ? null
1394                                            : (ArrayList) origCollections[count]);
1395                    ArrayList newList = (ArrayList) newCollections[count];
1396    
1397                    if (origList != null)
1398                    {
1399                        for (Iterator it2 = origList.iterator(); it2.hasNext(); )
1400                        {
1401                            Identity origOid = (Identity) it2.next();
1402    
1403                            if ((newList == null) || !newList.contains(origOid))
1404                            {
1405                                markDelete(origOid, oid, true);
1406                            }
1407                        }
1408                    }
1409    
1410                    if (newList != null)
1411                    {
1412                        int countElem = 0;
1413                        for (Iterator it2 = newList.iterator(); it2.hasNext(); countElem++)
1414                        {
1415                            Identity newOid = (Identity) it2.next();
1416    
1417                            if ((origList == null) || !origList.contains(newOid))
1418                            {
1419                                ContextEntry entry = (ContextEntry) _objects.get(newOid);
1420    
1421                                if (entry == null)
1422                                {
1423                                    ArrayList relCol = (ArrayList)
1424                                            newCollectionsOfObjects[count];
1425                                    Object relObj = relCol.get(countElem);
1426                                    insertInternal(newOid, relObj, LockType.WRITE_LOCK,
1427                                                   true, null, new Stack());
1428                                    newObjects.add(newOid);
1429                                }
1430                            }
1431                        }
1432                    }
1433                }
1434            }
1435    
1436            return newObjects;
1437        }
1438    
1439        /**
1440         * Mark for deletion all dependent objects (via references and collections).
1441         * @return the number of deleted objects
1442         */
1443        private int doCascadeDelete(Identity oid, Object obj)
1444        {
1445            ClassDescriptor mif = _pb.getClassDescriptor(ProxyHelper.getRealClass(obj));
1446            Collection refDescs = mif.getObjectReferenceDescriptors();
1447            Collection colDescs = mif.getCollectionDescriptors();
1448            int countCascadeDeleted = 0;
1449    
1450            for (Iterator it = refDescs.iterator(); it.hasNext(); )
1451            {
1452                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) it.next();
1453    
1454                if (rds.getOtmDependent())
1455                {
1456                    PersistentField f = rds.getPersistentField();
1457                    Object relObj = f.get(obj);
1458    
1459                    if (relObj != null)
1460                    {
1461                        countCascadeDeleted +=
1462                                markDelete(new Identity(relObj, _pb), oid, false);
1463                    }
1464                }
1465            }
1466    
1467            for (Iterator it = colDescs.iterator(); it.hasNext(); )
1468            {
1469                CollectionDescriptor cds = (CollectionDescriptor) it.next();
1470    
1471                if (cds.getOtmDependent())
1472                {
1473                    PersistentField f = cds.getPersistentField();
1474                    Class type = f.getType();
1475                    Object col = f.get(obj);
1476    
1477                    if (col != null)
1478                    {
1479                        Iterator colIterator;
1480    
1481                        if (Collection.class.isAssignableFrom(type))
1482                        {
1483                            colIterator = ((Collection) col).iterator();
1484                        }
1485                        else if (type.isArray())
1486                        {
1487                            colIterator = new ArrayIterator(col);
1488                        }
1489                        else
1490                        {
1491                            continue;
1492                        }
1493    
1494                        while (colIterator.hasNext())
1495                        {
1496    
1497                            countCascadeDeleted +=
1498                                    markDelete(new Identity(colIterator.next(), _pb), oid, true);
1499                        }
1500                    }
1501                }
1502            }
1503    
1504            return countCascadeDeleted;
1505        }
1506    
1507        /*
1508         * The rest of ObjectCache implementation for swizling
1509         * All methods except lookup() are never used by swizzling,
1510         * remove() appeared to already exist in this class
1511         * with the same signature as in ObjectCache interface,
1512         * other methods are unsupported.
1513         */
1514        public void cache(Identity oid, Object obj)
1515        {
1516            throw new UnsupportedOperationException();
1517        }
1518    
1519        public boolean cacheIfNew(Identity oid, Object obj)
1520        {
1521            // not implemented
1522            throw new UnsupportedOperationException("Not implemented");
1523        }
1524    
1525        public void clear()
1526        {
1527            throw new UnsupportedOperationException();
1528        }
1529    
1530    
1531        //////////////////////////////////////////
1532        // Inner classes
1533        //////////////////////////////////////////
1534    
1535        private static class ContextEntry
1536        {
1537            Object userObject;
1538            Object cacheObject;
1539            State state = State.PERSISTENT_CLEAN;
1540    
1541            /**
1542             * Handler the proxy object, null if the object is real
1543             */
1544            IndirectionHandler handler;
1545    
1546            /**
1547             * This flag is used during commit/checkpoint
1548             */
1549            boolean needsCacheSwizzle;
1550    
1551            ContextEntry(Object theUserObject)
1552            {
1553                userObject = theUserObject;
1554                if (userObject != null)
1555                {
1556                    handler = ProxyHelper.getIndirectionHandler(userObject);
1557                    if ((handler != null) && handler.alreadyMaterialized())
1558                    {
1559                        userObject = handler.getRealSubject();
1560                        handler = null;
1561                    }
1562                }
1563            }
1564        }
1565    
1566        private class OTMCollectionProxyListener implements CollectionProxyListener
1567        {
1568            private final CollectionDescriptor _cds;
1569            private final ArrayList[] _collections;
1570            private final int _index;
1571            private final int _lock;
1572    
1573            OTMCollectionProxyListener(CollectionDescriptor cds,
1574                    ArrayList[] collections, int index, int lock)
1575            {
1576                _cds = cds;
1577                _collections = collections;
1578                _index = index;
1579                _lock = lock;
1580            }
1581    
1582            public void beforeLoading(CollectionProxyDefaultImpl colProxy)
1583            {
1584                // do nothing
1585            }
1586    
1587            /**
1588             * The collection proxy contains PB cache objects. We have to replace it
1589             * with a collection of user objects.
1590             */
1591            public void afterLoading(CollectionProxyDefaultImpl colProxy)
1592            {
1593                ArrayList list = new ArrayList();
1594                ArrayList newUserCol = new ArrayList();
1595                LockManager lockManager = LockManager.getInstance();
1596                _collections[_index] = list;
1597    
1598                for (Iterator it = colProxy.iterator(); it.hasNext(); )
1599                {
1600                    Object relUserObj;
1601                    Object relCacheObj = it.next();
1602                    Identity relOid = new Identity(relCacheObj, _pb);
1603                    ContextEntry entry;
1604    
1605                    list.add(relOid);
1606                    entry = (ContextEntry) _objects.get(relOid);
1607                    if (entry != null)
1608                    {
1609                        relUserObj = entry.userObject;
1610                    }
1611                    else
1612                    {
1613                        ObjectCopyStrategy copyStrategy;
1614    
1615                        copyStrategy = _tx.getKit().getCopyStrategy(relOid);
1616                        relUserObj = copyStrategy.copy(relCacheObj, _pb);
1617                        try
1618                        {
1619                            entry = insertInternal(relOid, relUserObj, _lock,
1620                                    _cds.getOtmDependent(), null, new Stack());
1621                            if (entry != null)
1622                            {
1623                                relUserObj = entry.userObject;
1624                            }
1625                        }
1626                        catch (LockingException ex)
1627                        {
1628                            throw new LockingPassthruException(ex);
1629                        }
1630                    }
1631                    newUserCol.add(relUserObj);
1632                }
1633                colProxy.clear();
1634                colProxy.addAll(newUserCol);
1635            }
1636        }
1637    }