1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.engine;
26
27 import java.io.IOException;
28 import java.io.InvalidObjectException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.io.Serializable;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38
39 import org.apache.commons.collections.map.ReferenceMap;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.hibernate.AssertionFailure;
43 import org.hibernate.Hibernate;
44 import org.hibernate.HibernateException;
45 import org.hibernate.LockMode;
46 import org.hibernate.MappingException;
47 import org.hibernate.NonUniqueObjectException;
48 import org.hibernate.PersistentObjectException;
49 import org.hibernate.TransientObjectException;
50 import org.hibernate.engine.loading.LoadContexts;
51 import org.hibernate.pretty.MessageHelper;
52 import org.hibernate.collection.PersistentCollection;
53 import org.hibernate.persister.collection.CollectionPersister;
54 import org.hibernate.persister.entity.EntityPersister;
55 import org.hibernate.proxy.HibernateProxy;
56 import org.hibernate.proxy.LazyInitializer;
57 import org.hibernate.tuple.ElementWrapper;
58 import org.hibernate.util.IdentityMap;
59 import org.hibernate.util.MarkerObject;
60
61 /**
62 * A <tt>PersistenceContext</tt> represents the state of persistent "stuff" which
63 * Hibernate is tracking. This includes persistent entities, collections,
64 * as well as proxies generated.
65 * </p>
66 * There is meant to be a one-to-one correspondence between a SessionImpl and
67 * a PersistentContext. The SessionImpl uses the PersistentContext to track
68 * the current state of its context. Event-listeners then use the
69 * PersistentContext to drive their processing.
70 *
71 * @author Steve Ebersole
72 */
73 public class StatefulPersistenceContext implements PersistenceContext {
74
75 public static final Object NO_ROW = new MarkerObject( "NO_ROW" );
76
77 private static final Logger log = LoggerFactory.getLogger( StatefulPersistenceContext.class );
78 private static final Logger PROXY_WARN_LOG = LoggerFactory.getLogger( StatefulPersistenceContext.class.getName() + ".ProxyWarnLog" );
79 private static final int INIT_COLL_SIZE = 8;
80
81 private SessionImplementor session;
82
83 // Loaded entity instances, by EntityKey
84 private Map entitiesByKey;
85
86 // Loaded entity instances, by EntityUniqueKey
87 private Map entitiesByUniqueKey;
88
89 // Identity map of EntityEntry instances, by the entity instance
90 private Map entityEntries;
91
92 // Entity proxies, by EntityKey
93 private Map proxiesByKey;
94
95 // Snapshots of current database state for entities
96 // that have *not* been loaded
97 private Map entitySnapshotsByKey;
98
99 // Identity map of array holder ArrayHolder instances, by the array instance
100 private Map arrayHolders;
101
102 // Identity map of CollectionEntry instances, by the collection wrapper
103 private Map collectionEntries;
104
105 // Collection wrappers, by the CollectionKey
106 private Map collectionsByKey; //key=CollectionKey, value=PersistentCollection
107
108 // Set of EntityKeys of deleted objects
109 private HashSet nullifiableEntityKeys;
110
111 // properties that we have tried to load, and not found in the database
112 private HashSet nullAssociations;
113
114 // A list of collection wrappers that were instantiating during result set
115 // processing, that we will need to initialize at the end of the query
116 private List nonlazyCollections;
117
118 // A container for collections we load up when the owning entity is not
119 // yet loaded ... for now, this is purely transient!
120 private Map unownedCollections;
121
122 private int cascading = 0;
123 private int loadCounter = 0;
124 private boolean flushing = false;
125
126 private boolean hasNonReadOnlyEntities = false;
127
128 private LoadContexts loadContexts;
129 private BatchFetchQueue batchFetchQueue;
130
131
132
133 /**
134 * Constructs a PersistentContext, bound to the given session.
135 *
136 * @param session The session "owning" this context.
137 */
138 public StatefulPersistenceContext(SessionImplementor session) {
139 this.session = session;
140
141 entitiesByKey = new HashMap( INIT_COLL_SIZE );
142 entitiesByUniqueKey = new HashMap( INIT_COLL_SIZE );
143 proxiesByKey = new ReferenceMap( ReferenceMap.HARD, ReferenceMap.WEAK );
144 entitySnapshotsByKey = new HashMap( INIT_COLL_SIZE );
145
146 entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
147 collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
148 collectionsByKey = new HashMap( INIT_COLL_SIZE );
149 arrayHolders = IdentityMap.instantiate( INIT_COLL_SIZE );
150
151 nullifiableEntityKeys = new HashSet();
152
153 initTransientState();
154 }
155
156 private void initTransientState() {
157 nullAssociations = new HashSet( INIT_COLL_SIZE );
158 nonlazyCollections = new ArrayList( INIT_COLL_SIZE );
159 }
160
161 public boolean isStateless() {
162 return false;
163 }
164
165 public SessionImplementor getSession() {
166 return session;
167 }
168
169 public LoadContexts getLoadContexts() {
170 if ( loadContexts == null ) {
171 loadContexts = new LoadContexts( this );
172 }
173 return loadContexts;
174 }
175
176 public void addUnownedCollection(CollectionKey key, PersistentCollection collection) {
177 if (unownedCollections==null) {
178 unownedCollections = new HashMap(8);
179 }
180 unownedCollections.put(key, collection);
181 }
182
183 public PersistentCollection useUnownedCollection(CollectionKey key) {
184 if (unownedCollections==null) {
185 return null;
186 }
187 else {
188 return (PersistentCollection) unownedCollections.remove(key);
189 }
190 }
191
192 /**
193 * Get the <tt>BatchFetchQueue</tt>, instantiating one if
194 * necessary.
195 */
196 public BatchFetchQueue getBatchFetchQueue() {
197 if (batchFetchQueue==null) {
198 batchFetchQueue = new BatchFetchQueue(this);
199 }
200 return batchFetchQueue;
201 }
202
203 public void clear() {
204 Iterator itr = proxiesByKey.values().iterator();
205 while ( itr.hasNext() ) {
206 final LazyInitializer li = ( ( HibernateProxy ) itr.next() ).getHibernateLazyInitializer();
207 li.setSession( null );
208 }
209 Map.Entry[] collectionEntryArray = IdentityMap.concurrentEntries( collectionEntries );
210 for ( int i = 0; i < collectionEntryArray.length; i++ ) {
211 ( ( PersistentCollection ) collectionEntryArray[i].getKey() ).unsetSession( getSession() );
212 }
213 arrayHolders.clear();
214 entitiesByKey.clear();
215 entitiesByUniqueKey.clear();
216 entityEntries.clear();
217 entitySnapshotsByKey.clear();
218 collectionsByKey.clear();
219 collectionEntries.clear();
220 if ( unownedCollections != null ) {
221 unownedCollections.clear();
222 }
223 proxiesByKey.clear();
224 nullifiableEntityKeys.clear();
225 if ( batchFetchQueue != null ) {
226 batchFetchQueue.clear();
227 }
228 hasNonReadOnlyEntities = false;
229 if ( loadContexts != null ) {
230 loadContexts.cleanup();
231 }
232 }
233
234 public boolean hasNonReadOnlyEntities() {
235 return hasNonReadOnlyEntities;
236 }
237
238 public void setEntryStatus(EntityEntry entry, Status status) {
239 entry.setStatus(status);
240 setHasNonReadOnlyEnties(status);
241 }
242
243 private void setHasNonReadOnlyEnties(Status status) {
244 if ( status==Status.DELETED || status==Status.MANAGED || status==Status.SAVING ) {
245 hasNonReadOnlyEntities = true;
246 }
247 }
248
249 public void afterTransactionCompletion() {
250 // Downgrade locks
251 Iterator iter = entityEntries.values().iterator();
252 while ( iter.hasNext() ) {
253 ( (EntityEntry) iter.next() ).setLockMode(LockMode.NONE);
254 }
255 }
256
257 /**
258 * Get the current state of the entity as known to the underlying
259 * database, or null if there is no corresponding row
260 */
261 public Object[] getDatabaseSnapshot(Serializable id, EntityPersister persister)
262 throws HibernateException {
263 EntityKey key = new EntityKey( id, persister, session.getEntityMode() );
264 Object cached = entitySnapshotsByKey.get(key);
265 if (cached!=null) {
266 return cached==NO_ROW ? null : (Object[]) cached;
267 }
268 else {
269 Object[] snapshot = persister.getDatabaseSnapshot( id, session );
270 entitySnapshotsByKey.put( key, snapshot==null ? NO_ROW : snapshot );
271 return snapshot;
272 }
273 }
274
275 public Object[] getNaturalIdSnapshot(Serializable id, EntityPersister persister)
276 throws HibernateException {
277 if ( !persister.hasNaturalIdentifier() ) {
278 return null;
279 }
280
281 // if the natural-id is marked as non-mutable, it is not retrieved during a
282 // normal database-snapshot operation...
283 int[] props = persister.getNaturalIdentifierProperties();
284 boolean[] updateable = persister.getPropertyUpdateability();
285 boolean allNatualIdPropsAreUpdateable = true;
286 for ( int i = 0; i < props.length; i++ ) {
287 if ( !updateable[ props[i] ] ) {
288 allNatualIdPropsAreUpdateable = false;
289 break;
290 }
291 }
292
293 if ( allNatualIdPropsAreUpdateable ) {
294 // do this when all the properties are updateable since there is
295 // a certain likelihood that the information will already be
296 // snapshot-cached.
297 Object[] entitySnapshot = getDatabaseSnapshot( id, persister );
298 if ( entitySnapshot == NO_ROW ) {
299 return null;
300 }
301 Object[] naturalIdSnapshot = new Object[ props.length ];
302 for ( int i = 0; i < props.length; i++ ) {
303 naturalIdSnapshot[i] = entitySnapshot[ props[i] ];
304 }
305 return naturalIdSnapshot;
306 }
307 else {
308 return persister.getNaturalIdentifierSnapshot( id, session );
309 }
310 }
311
312 /**
313 * Retrieve the cached database snapshot for the requested entity key.
314 * <p/>
315 * This differs from {@link #getDatabaseSnapshot} is two important respects:<ol>
316 * <li>no snapshot is obtained from the database if not already cached</li>
317 * <li>an entry of {@link #NO_ROW} here is interpretet as an exception</li>
318 * </ol>
319 * @param key The entity key for which to retrieve the cached snapshot
320 * @return The cached snapshot
321 * @throws IllegalStateException if the cached snapshot was == {@link #NO_ROW}.
322 */
323 public Object[] getCachedDatabaseSnapshot(EntityKey key) {
324 Object snapshot = entitySnapshotsByKey.get( key );
325 if ( snapshot == NO_ROW ) {
326 throw new IllegalStateException( "persistence context reported no row snapshot for " + MessageHelper.infoString( key.getEntityName(), key.getIdentifier() ) );
327 }
328 return ( Object[] ) snapshot;
329 }
330
331 /*public void removeDatabaseSnapshot(EntityKey key) {
332 entitySnapshotsByKey.remove(key);
333 }*/
334
335 public void addEntity(EntityKey key, Object entity) {
336 entitiesByKey.put(key, entity);
337 getBatchFetchQueue().removeBatchLoadableEntityKey(key);
338 }
339
340 /**
341 * Get the entity instance associated with the given
342 * <tt>EntityKey</tt>
343 */
344 public Object getEntity(EntityKey key) {
345 return entitiesByKey.get(key);
346 }
347
348 public boolean containsEntity(EntityKey key) {
349 return entitiesByKey.containsKey(key);
350 }
351
352 /**
353 * Remove an entity from the session cache, also clear
354 * up other state associated with the entity, all except
355 * for the <tt>EntityEntry</tt>
356 */
357 public Object removeEntity(EntityKey key) {
358 Object entity = entitiesByKey.remove(key);
359 Iterator iter = entitiesByUniqueKey.values().iterator();
360 while ( iter.hasNext() ) {
361 if ( iter.next()==entity ) iter.remove();
362 }
363 entitySnapshotsByKey.remove(key);
364 nullifiableEntityKeys.remove(key);
365 getBatchFetchQueue().removeBatchLoadableEntityKey(key);
366 getBatchFetchQueue().removeSubselect(key);
367 return entity;
368 }
369
370 /**
371 * Get an entity cached by unique key
372 */
373 public Object getEntity(EntityUniqueKey euk) {
374 return entitiesByUniqueKey.get(euk);
375 }
376
377 /**
378 * Add an entity to the cache by unique key
379 */
380 public void addEntity(EntityUniqueKey euk, Object entity) {
381 entitiesByUniqueKey.put(euk, entity);
382 }
383
384 /**
385 * Retreive the EntityEntry representation of the given entity.
386 *
387 * @param entity The entity for which to locate the EntityEntry.
388 * @return The EntityEntry for the given entity.
389 */
390 public EntityEntry getEntry(Object entity) {
391 return (EntityEntry) entityEntries.get(entity);
392 }
393
394 /**
395 * Remove an entity entry from the session cache
396 */
397 public EntityEntry removeEntry(Object entity) {
398 return (EntityEntry) entityEntries.remove(entity);
399 }
400
401 /**
402 * Is there an EntityEntry for this instance?
403 */
404 public boolean isEntryFor(Object entity) {
405 return entityEntries.containsKey(entity);
406 }
407
408 /**
409 * Get the collection entry for a persistent collection
410 */
411 public CollectionEntry getCollectionEntry(PersistentCollection coll) {
412 return (CollectionEntry) collectionEntries.get(coll);
413 }
414
415 /**
416 * Adds an entity to the internal caches.
417 */
418 public EntityEntry addEntity(
419 final Object entity,
420 final Status status,
421 final Object[] loadedState,
422 final EntityKey entityKey,
423 final Object version,
424 final LockMode lockMode,
425 final boolean existsInDatabase,
426 final EntityPersister persister,
427 final boolean disableVersionIncrement,
428 boolean lazyPropertiesAreUnfetched
429 ) {
430
431 addEntity( entityKey, entity );
432
433 return addEntry(
434 entity,
435 status,
436 loadedState,
437 null,
438 entityKey.getIdentifier(),
439 version,
440 lockMode,
441 existsInDatabase,
442 persister,
443 disableVersionIncrement,
444 lazyPropertiesAreUnfetched
445 );
446 }
447
448
449 /**
450 * Generates an appropriate EntityEntry instance and adds it
451 * to the event source's internal caches.
452 */
453 public EntityEntry addEntry(
454 final Object entity,
455 final Status status,
456 final Object[] loadedState,
457 final Object rowId,
458 final Serializable id,
459 final Object version,
460 final LockMode lockMode,
461 final boolean existsInDatabase,
462 final EntityPersister persister,
463 final boolean disableVersionIncrement,
464 boolean lazyPropertiesAreUnfetched) {
465
466 EntityEntry e = new EntityEntry(
467 status,
468 loadedState,
469 rowId,
470 id,
471 version,
472 lockMode,
473 existsInDatabase,
474 persister,
475 session.getEntityMode(),
476 disableVersionIncrement,
477 lazyPropertiesAreUnfetched
478 );
479 entityEntries.put(entity, e);
480
481 setHasNonReadOnlyEnties(status);
482 return e;
483 }
484
485 public boolean containsCollection(PersistentCollection collection) {
486 return collectionEntries.containsKey(collection);
487 }
488
489 public boolean containsProxy(Object entity) {
490 return proxiesByKey.containsValue( entity );
491 }
492
493 /**
494 * Takes the given object and, if it represents a proxy, reassociates it with this event source.
495 *
496 * @param value The possible proxy to be reassociated.
497 * @return Whether the passed value represented an actual proxy which got initialized.
498 * @throws MappingException
499 */
500 public boolean reassociateIfUninitializedProxy(Object value) throws MappingException {
501 if ( value instanceof ElementWrapper ) {
502 value = ( (ElementWrapper) value ).getElement();
503 }
504
505 if ( !Hibernate.isInitialized(value) ) {
506 HibernateProxy proxy = (HibernateProxy) value;
507 LazyInitializer li = proxy.getHibernateLazyInitializer();
508 reassociateProxy(li, proxy);
509 return true;
510 }
511 else {
512 return false;
513 }
514 }
515
516 /**
517 * If a deleted entity instance is re-saved, and it has a proxy, we need to
518 * reset the identifier of the proxy
519 */
520 public void reassociateProxy(Object value, Serializable id) throws MappingException {
521 if ( value instanceof ElementWrapper ) {
522 value = ( (ElementWrapper) value ).getElement();
523 }
524
525 if ( value instanceof HibernateProxy ) {
526 if ( log.isDebugEnabled() ) log.debug("setting proxy identifier: " + id);
527 HibernateProxy proxy = (HibernateProxy) value;
528 LazyInitializer li = proxy.getHibernateLazyInitializer();
529 li.setIdentifier(id);
530 reassociateProxy(li, proxy);
531 }
532 }
533
534 /**
535 * Associate a proxy that was instantiated by another session with this session
536 *
537 * @param li The proxy initializer.
538 * @param proxy The proxy to reassociate.
539 */
540 private void reassociateProxy(LazyInitializer li, HibernateProxy proxy) {
541 if ( li.getSession() != this.getSession() ) {
542 EntityPersister persister = session.getFactory().getEntityPersister( li.getEntityName() );
543 EntityKey key = new EntityKey( li.getIdentifier(), persister, session.getEntityMode() );
544 // any earlier proxy takes precedence
545 if ( !proxiesByKey.containsKey( key ) ) {
546 proxiesByKey.put( key, proxy );
547 }
548 proxy.getHibernateLazyInitializer().setSession( session );
549 }
550 }
551
552 /**
553 * Get the entity instance underlying the given proxy, throwing
554 * an exception if the proxy is uninitialized. If the given object
555 * is not a proxy, simply return the argument.
556 */
557 public Object unproxy(Object maybeProxy) throws HibernateException {
558 if ( maybeProxy instanceof ElementWrapper ) {
559 maybeProxy = ( (ElementWrapper) maybeProxy ).getElement();
560 }
561
562 if ( maybeProxy instanceof HibernateProxy ) {
563 HibernateProxy proxy = (HibernateProxy) maybeProxy;
564 LazyInitializer li = proxy.getHibernateLazyInitializer();
565 if ( li.isUninitialized() ) {
566 throw new PersistentObjectException(
567 "object was an uninitialized proxy for " +
568 li.getEntityName()
569 );
570 }
571 return li.getImplementation(); //unwrap the object
572 }
573 else {
574 return maybeProxy;
575 }
576 }
577
578 /**
579 * Possibly unproxy the given reference and reassociate it with the current session.
580 *
581 * @param maybeProxy The reference to be unproxied if it currently represents a proxy.
582 * @return The unproxied instance.
583 * @throws HibernateException
584 */
585 public Object unproxyAndReassociate(Object maybeProxy) throws HibernateException {
586 if ( maybeProxy instanceof ElementWrapper ) {
587 maybeProxy = ( (ElementWrapper) maybeProxy ).getElement();
588 }
589
590 if ( maybeProxy instanceof HibernateProxy ) {
591 HibernateProxy proxy = (HibernateProxy) maybeProxy;
592 LazyInitializer li = proxy.getHibernateLazyInitializer();
593 reassociateProxy(li, proxy);
594 return li.getImplementation(); //initialize + unwrap the object
595 }
596 else {
597 return maybeProxy;
598 }
599 }
600
601 /**
602 * Attempts to check whether the given key represents an entity already loaded within the
603 * current session.
604 * @param object The entity reference against which to perform the uniqueness check.
605 * @throws HibernateException
606 */
607 public void checkUniqueness(EntityKey key, Object object) throws HibernateException {
608 Object entity = getEntity(key);
609 if ( entity == object ) {
610 throw new AssertionFailure( "object already associated, but no entry was found" );
611 }
612 if ( entity != null ) {
613 throw new NonUniqueObjectException( key.getIdentifier(), key.getEntityName() );
614 }
615 }
616
617 /**
618 * If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy
619 * and overwrite the registration of the old one. This breaks == and occurs only for
620 * "class" proxies rather than "interface" proxies. Also init the proxy to point to
621 * the given target implementation if necessary.
622 *
623 * @param proxy The proxy instance to be narrowed.
624 * @param persister The persister for the proxied entity.
625 * @param key The internal cache key for the proxied entity.
626 * @param object (optional) the actual proxied entity instance.
627 * @return An appropriately narrowed instance.
628 * @throws HibernateException
629 */
630 public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object)
631 throws HibernateException {
632
633 boolean alreadyNarrow = persister.getConcreteProxyClass( session.getEntityMode() )
634 .isAssignableFrom( proxy.getClass() );
635
636 if ( !alreadyNarrow ) {
637 if ( PROXY_WARN_LOG.isWarnEnabled() ) {
638 PROXY_WARN_LOG.warn(
639 "Narrowing proxy to " +
640 persister.getConcreteProxyClass( session.getEntityMode() ) +
641 " - this operation breaks =="
642 );
643 }
644
645 if ( object != null ) {
646 proxiesByKey.remove(key);
647 return object; //return the proxied object
648 }
649 else {
650 proxy = persister.createProxy( key.getIdentifier(), session );
651 proxiesByKey.put(key, proxy); //overwrite old proxy
652 return proxy;
653 }
654
655 }
656 else {
657
658 if ( object != null ) {
659 LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
660 li.setImplementation(object);
661 }
662
663 return proxy;
664
665 }
666
667 }
668
669 /**
670 * Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
671 * third argument (the entity associated with the key) if no proxy exists. Init
672 * the proxy to the target implementation, if necessary.
673 */
674 public Object proxyFor(EntityPersister persister, EntityKey key, Object impl)
675 throws HibernateException {
676 if ( !persister.hasProxy() ) return impl;
677 Object proxy = proxiesByKey.get(key);
678 if ( proxy != null ) {
679 return narrowProxy(proxy, persister, key, impl);
680 }
681 else {
682 return impl;
683 }
684 }
685
686 /**
687 * Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
688 * argument (the entity associated with the key) if no proxy exists.
689 * (slower than the form above)
690 */
691 public Object proxyFor(Object impl) throws HibernateException {
692 EntityEntry e = getEntry(impl);
693 EntityPersister p = e.getPersister();
694 return proxyFor( p, new EntityKey( e.getId(), p, session.getEntityMode() ), impl );
695 }
696
697 /**
698 * Get the entity that owns this persistent collection
699 */
700 public Object getCollectionOwner(Serializable key, CollectionPersister collectionPersister) throws MappingException {
701 return getEntity( new EntityKey( key, collectionPersister.getOwnerEntityPersister(), session.getEntityMode() ) );
702 }
703
704 /**
705 * Get the entity that owned this persistent collection when it was loaded
706 *
707 * @param collection The persistent collection
708 * @return the owner, if its entity ID is available from the collection's loaded key
709 * and the owner entity is in the persistence context; otherwise, returns null
710 */
711 public Object getLoadedCollectionOwnerOrNull(PersistentCollection collection) {
712 CollectionEntry ce = getCollectionEntry( collection );
713 if ( ce.getLoadedPersister() == null ) {
714 return null; // early exit...
715 }
716 Object loadedOwner = null;
717 // TODO: an alternative is to check if the owner has changed; if it hasn't then
718 // return collection.getOwner()
719 Serializable entityId = getLoadedCollectionOwnerIdOrNull( ce );
720 if ( entityId != null ) {
721 loadedOwner = getCollectionOwner( entityId, ce.getLoadedPersister() );
722 }
723 return loadedOwner;
724 }
725
726 /**
727 * Get the ID for the entity that owned this persistent collection when it was loaded
728 *
729 * @param collection The persistent collection
730 * @return the owner ID if available from the collection's loaded key; otherwise, returns null
731 */
732 public Serializable getLoadedCollectionOwnerIdOrNull(PersistentCollection collection) {
733 return getLoadedCollectionOwnerIdOrNull( getCollectionEntry( collection ) );
734 }
735
736 /**
737 * Get the ID for the entity that owned this persistent collection when it was loaded
738 *
739 * @param ce The collection entry
740 * @return the owner ID if available from the collection's loaded key; otherwise, returns null
741 */
742 private Serializable getLoadedCollectionOwnerIdOrNull(CollectionEntry ce) {
743 if ( ce == null || ce.getLoadedKey() == null || ce.getLoadedPersister() == null ) {
744 return null;
745 }
746 // TODO: an alternative is to check if the owner has changed; if it hasn't then
747 // get the ID from collection.getOwner()
748 return ce.getLoadedPersister().getCollectionType().getIdOfOwnerOrNull( ce.getLoadedKey(), session );
749 }
750
751 /**
752 * add a collection we just loaded up (still needs initializing)
753 */
754 public void addUninitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id) {
755 CollectionEntry ce = new CollectionEntry(collection, persister, id, flushing);
756 addCollection(collection, ce, id);
757 }
758
759 /**
760 * add a detached uninitialized collection
761 */
762 public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection collection) {
763 CollectionEntry ce = new CollectionEntry( persister, collection.getKey() );
764 addCollection( collection, ce, collection.getKey() );
765 }
766
767 /**
768 * Add a new collection (ie. a newly created one, just instantiated by the
769 * application, with no database state or snapshot)
770 * @param collection The collection to be associated with the persistence context
771 */
772 public void addNewCollection(CollectionPersister persister, PersistentCollection collection)
773 throws HibernateException {
774 addCollection(collection, persister);
775 }
776
777 /**
778 * Add an collection to the cache, with a given collection entry.
779 *
780 * @param coll The collection for which we are adding an entry.
781 * @param entry The entry representing the collection.
782 * @param key The key of the collection's entry.
783 */
784 private void addCollection(PersistentCollection coll, CollectionEntry entry, Serializable key) {
785 collectionEntries.put( coll, entry );
786 CollectionKey collectionKey = new CollectionKey( entry.getLoadedPersister(), key, session.getEntityMode() );
787 PersistentCollection old = ( PersistentCollection ) collectionsByKey.put( collectionKey, coll );
788 if ( old != null ) {
789 if ( old == coll ) {
790 throw new AssertionFailure("bug adding collection twice");
791 }
792 // or should it actually throw an exception?
793 old.unsetSession( session );
794 collectionEntries.remove( old );
795 // watch out for a case where old is still referenced
796 // somewhere in the object graph! (which is a user error)
797 }
798 }
799
800 /**
801 * Add a collection to the cache, creating a new collection entry for it
802 *
803 * @param collection The collection for which we are adding an entry.
804 * @param persister The collection persister
805 */
806 private void addCollection(PersistentCollection collection, CollectionPersister persister) {
807 CollectionEntry ce = new CollectionEntry( persister, collection );
808 collectionEntries.put( collection, ce );
809 }
810
811 /**
812 * add an (initialized) collection that was created by another session and passed
813 * into update() (ie. one with a snapshot and existing state on the database)
814 */
815 public void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection collection)
816 throws HibernateException {
817 if ( collection.isUnreferenced() ) {
818 //treat it just like a new collection
819 addCollection( collection, collectionPersister );
820 }
821 else {
822 CollectionEntry ce = new CollectionEntry( collection, session.getFactory() );
823 addCollection( collection, ce, collection.getKey() );
824 }
825 }
826
827 /**
828 * add a collection we just pulled out of the cache (does not need initializing)
829 */
830 public CollectionEntry addInitializedCollection(CollectionPersister persister, PersistentCollection collection, Serializable id)
831 throws HibernateException {
832 CollectionEntry ce = new CollectionEntry(collection, persister, id, flushing);
833 ce.postInitialize(collection);
834 addCollection(collection, ce, id);
835 return ce;
836 }
837
838 /**
839 * Get the collection instance associated with the <tt>CollectionKey</tt>
840 */
841 public PersistentCollection getCollection(CollectionKey collectionKey) {
842 return (PersistentCollection) collectionsByKey.get(collectionKey);
843 }
844
845 /**
846 * Register a collection for non-lazy loading at the end of the
847 * two-phase load
848 */
849 public void addNonLazyCollection(PersistentCollection collection) {
850 nonlazyCollections.add(collection);
851 }
852
853 /**
854 * Force initialization of all non-lazy collections encountered during
855 * the current two-phase load (actually, this is a no-op, unless this
856 * is the "outermost" load)
857 */
858 public void initializeNonLazyCollections() throws HibernateException {
859 if ( loadCounter == 0 ) {
860 log.debug( "initializing non-lazy collections" );
861 //do this work only at the very highest level of the load
862 loadCounter++; //don't let this method be called recursively
863 try {
864 int size;
865 while ( ( size = nonlazyCollections.size() ) > 0 ) {
866 //note that each iteration of the loop may add new elements
867 ( (PersistentCollection) nonlazyCollections.remove( size - 1 ) ).forceInitialization();
868 }
869 }
870 finally {
871 loadCounter--;
872 clearNullProperties();
873 }
874 }
875 }
876
877
878 /**
879 * Get the <tt>PersistentCollection</tt> object for an array
880 */
881 public PersistentCollection getCollectionHolder(Object array) {
882 return (PersistentCollection) arrayHolders.get(array);
883 }
884
885 /**
886 * Register a <tt>PersistentCollection</tt> object for an array.
887 * Associates a holder with an array - MUST be called after loading
888 * array, since the array instance is not created until endLoad().
889 */
890 public void addCollectionHolder(PersistentCollection holder) {
891 //TODO:refactor + make this method private
892 arrayHolders.put( holder.getValue(), holder );
893 }
894
895 public PersistentCollection removeCollectionHolder(Object array) {
896 return (PersistentCollection) arrayHolders.remove(array);
897 }
898
899 /**
900 * Get the snapshot of the pre-flush collection state
901 */
902 public Serializable getSnapshot(PersistentCollection coll) {
903 return getCollectionEntry(coll).getSnapshot();
904 }
905
906 /**
907 * Get the collection entry for a collection passed to filter,
908 * which might be a collection wrapper, an array, or an unwrapped
909 * collection. Return null if there is no entry.
910 */
911 public CollectionEntry getCollectionEntryOrNull(Object collection) {
912 PersistentCollection coll;
913 if ( collection instanceof PersistentCollection ) {
914 coll = (PersistentCollection) collection;
915 //if (collection==null) throw new TransientObjectException("Collection was not yet persistent");
916 }
917 else {
918 coll = getCollectionHolder(collection);
919 if ( coll == null ) {
920 //it might be an unwrapped collection reference!
921 //try to find a wrapper (slowish)
922 Iterator wrappers = IdentityMap.keyIterator(collectionEntries);
923 while ( wrappers.hasNext() ) {
924 PersistentCollection pc = (PersistentCollection) wrappers.next();
925 if ( pc.isWrapper(collection) ) {
926 coll = pc;
927 break;
928 }
929 }
930 }
931 }
932
933 return (coll == null) ? null : getCollectionEntry(coll);
934 }
935
936 /**
937 * Get an existing proxy by key
938 */
939 public Object getProxy(EntityKey key) {
940 return proxiesByKey.get(key);
941 }
942
943 /**
944 * Add a proxy to the session cache
945 */
946 public void addProxy(EntityKey key, Object proxy) {
947 proxiesByKey.put(key, proxy);
948 }
949
950 /**
951 * Remove a proxy from the session cache.
952 * <p/>
953 * Additionally, ensure that any load optimization references
954 * such as batch or subselect loading get cleaned up as well.
955 *
956 * @param key The key of the entity proxy to be removed
957 * @return The proxy reference.
958 */
959 public Object removeProxy(EntityKey key) {
960 if ( batchFetchQueue != null ) {
961 batchFetchQueue.removeBatchLoadableEntityKey( key );
962 batchFetchQueue.removeSubselect( key );
963 }
964 return proxiesByKey.remove( key );
965 }
966
967 /**
968 * Record the fact that an entity does not exist in the database
969 *
970 * @param key the primary key of the entity
971 */
972 /*public void addNonExistantEntityKey(EntityKey key) {
973 nonExistantEntityKeys.add(key);
974 }*/
975
976 /**
977 * Record the fact that an entity does not exist in the database
978 *
979 * @param key a unique key of the entity
980 */
981 /*public void addNonExistantEntityUniqueKey(EntityUniqueKey key) {
982 nonExistentEntityUniqueKeys.add(key);
983 }*/
984
985 /*public void removeNonExist(EntityKey key) {
986 nonExistantEntityKeys.remove(key);
987 }*/
988
989 /**
990 * Retrieve the set of EntityKeys representing nullifiable references
991 */
992 public HashSet getNullifiableEntityKeys() {
993 return nullifiableEntityKeys;
994 }
995
996 public Map getEntitiesByKey() {
997 return entitiesByKey;
998 }
999
1000 public Map getEntityEntries() {
1001 return entityEntries;
1002 }
1003
1004 public Map getCollectionEntries() {
1005 return collectionEntries;
1006 }
1007
1008 public Map getCollectionsByKey() {
1009 return collectionsByKey;
1010 }
1011
1012 /**
1013 * Do we already know that the entity does not exist in the
1014 * database?
1015 */
1016 /*public boolean isNonExistant(EntityKey key) {
1017 return nonExistantEntityKeys.contains(key);
1018 }*/
1019
1020 /**
1021 * Do we already know that the entity does not exist in the
1022 * database?
1023 */
1024 /*public boolean isNonExistant(EntityUniqueKey key) {
1025 return nonExistentEntityUniqueKeys.contains(key);
1026 }*/
1027
1028 public int getCascadeLevel() {
1029 return cascading;
1030 }
1031
1032 public int incrementCascadeLevel() {
1033 return ++cascading;
1034 }
1035
1036 public int decrementCascadeLevel() {
1037 return --cascading;
1038 }
1039
1040 public boolean isFlushing() {
1041 return flushing;
1042 }
1043
1044 public void setFlushing(boolean flushing) {
1045 this.flushing = flushing;
1046 }
1047
1048 /**
1049 * Call this before begining a two-phase load
1050 */
1051 public void beforeLoad() {
1052 loadCounter++;
1053 }
1054
1055 /**
1056 * Call this after finishing a two-phase load
1057 */
1058 public void afterLoad() {
1059 loadCounter--;
1060 }
1061
1062 /**
1063 * Returns a string representation of the object.
1064 *
1065 * @return a string representation of the object.
1066 */
1067 public String toString() {
1068 return new StringBuffer()
1069 .append("PersistenceContext[entityKeys=")
1070 .append(entitiesByKey.keySet())
1071 .append(",collectionKeys=")
1072 .append(collectionsByKey.keySet())
1073 .append("]")
1074 .toString();
1075 }
1076
1077 /**
1078 * Search <tt>this</tt> persistence context for an associated entity instance which is considered the "owner" of
1079 * the given <tt>childEntity</tt>, and return that owner's id value. This is performed in the scenario of a
1080 * uni-directional, non-inverse one-to-many collection (which means that the collection elements do not maintain
1081 * a direct reference to the owner).
1082 * <p/>
1083 * As such, the processing here is basically to loop over every entity currently associated with this persistence
1084 * context and for those of the correct entity (sub) type to extract its collection role property value and see
1085 * if the child is contained within that collection. If so, we have found the owner; if not, we go on.
1086 * <p/>
1087 * Also need to account for <tt>mergeMap</tt> which acts as a local copy cache managed for the duration of a merge
1088 * operation. It represents a map of the detached entity instances pointing to the corresponding managed instance.
1089 *
1090 * @param entityName The entity name for the entity type which would own the child
1091 * @param propertyName The name of the property on the owning entity type which would name this child association.
1092 * @param childEntity The child entity instance for which to locate the owner instance id.
1093 * @param mergeMap A map of non-persistent instances from an on-going merge operation (possibly null).
1094 *
1095 * @return The id of the entityName instance which is said to own the child; null if an appropriate owner not
1096 * located.
1097 */
1098 public Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) {
1099 final String collectionRole = entityName + '.' + propertyName;
1100 final EntityPersister persister = session.getFactory().getEntityPersister( entityName );
1101 final CollectionPersister collectionPersister = session.getFactory().getCollectionPersister( collectionRole );
1102
1103 // iterate all the entities currently associated with the persistence context.
1104 Iterator entities = entityEntries.entrySet().iterator();
1105 while ( entities.hasNext() ) {
1106 final Map.Entry me = ( Map.Entry ) entities.next();
1107 final EntityEntry entityEntry = ( EntityEntry ) me.getValue();
1108 // does this entity entry pertain to the entity persister in which we are interested (owner)?
1109 if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) {
1110 final Object entityEntryInstance = me.getKey();
1111
1112 //check if the managed object is the parent
1113 boolean found = isFoundInParent(
1114 propertyName,
1115 childEntity,
1116 persister,
1117 collectionPersister,
1118 entityEntryInstance
1119 );
1120
1121 if ( !found && mergeMap != null ) {
1122 //check if the detached object being merged is the parent
1123 Object unmergedInstance = mergeMap.get( entityEntryInstance );
1124 Object unmergedChild = mergeMap.get( childEntity );
1125 if ( unmergedInstance != null && unmergedChild != null ) {
1126 found = isFoundInParent(
1127 propertyName,
1128 unmergedChild,
1129 persister,
1130 collectionPersister,
1131 unmergedInstance
1132 );
1133 }
1134 }
1135
1136 if ( found ) {
1137 return entityEntry.getId();
1138 }
1139
1140 }
1141 }
1142
1143 // if we get here, it is possible that we have a proxy 'in the way' of the merge map resolution...
1144 // NOTE: decided to put this here rather than in the above loop as I was nervous about the performance
1145 // of the loop-in-loop especially considering this is far more likely the 'edge case'
1146 if ( mergeMap != null ) {
1147 Iterator mergeMapItr = mergeMap.entrySet().iterator();
1148 while ( mergeMapItr.hasNext() ) {
1149 final Map.Entry mergeMapEntry = ( Map.Entry ) mergeMapItr.next();
1150 if ( mergeMapEntry.getKey() instanceof HibernateProxy ) {
1151 final HibernateProxy proxy = ( HibernateProxy ) mergeMapEntry.getKey();
1152 if ( persister.isSubclassEntityName( proxy.getHibernateLazyInitializer().getEntityName() ) ) {
1153 boolean found = isFoundInParent(
1154 propertyName,
1155 childEntity,
1156 persister,
1157 collectionPersister,
1158 mergeMap.get( proxy )
1159 );
1160 if ( !found ) {
1161 found = isFoundInParent(
1162 propertyName,
1163 mergeMap.get( childEntity ),
1164 persister,
1165 collectionPersister,
1166 mergeMap.get( proxy )
1167 );
1168 }
1169 if ( found ) {
1170 return proxy.getHibernateLazyInitializer().getIdentifier();
1171 }
1172 }
1173 }
1174 }
1175 }
1176
1177 return null;
1178 }
1179
1180 private boolean isFoundInParent(
1181 String property,
1182 Object childEntity,
1183 EntityPersister persister,
1184 CollectionPersister collectionPersister,
1185 Object potentialParent) {
1186 Object collection = persister.getPropertyValue(
1187 potentialParent,
1188 property,
1189 session.getEntityMode()
1190 );
1191 return collection != null
1192 && Hibernate.isInitialized( collection )
1193 && collectionPersister.getCollectionType().contains( collection, childEntity, session );
1194 }
1195
1196 /**
1197 * Search the persistence context for an index of the child object,
1198 * given a collection role
1199 */
1200 public Object getIndexInOwner(String entity, String property, Object childEntity, Map mergeMap) {
1201
1202 EntityPersister persister = session.getFactory()
1203 .getEntityPersister(entity);
1204 CollectionPersister cp = session.getFactory()
1205 .getCollectionPersister(entity + '.' + property);
1206 Iterator entities = entityEntries.entrySet().iterator();
1207 while ( entities.hasNext() ) {
1208 Map.Entry me = (Map.Entry) entities.next();
1209 EntityEntry ee = (EntityEntry) me.getValue();
1210 if ( persister.isSubclassEntityName( ee.getEntityName() ) ) {
1211 Object instance = me.getKey();
1212
1213 Object index = getIndexInParent(property, childEntity, persister, cp, instance);
1214
1215 if (index==null && mergeMap!=null) {
1216 Object unmergedInstance = mergeMap.get(instance);
1217 Object unmergedChild = mergeMap.get(childEntity);
1218 if ( unmergedInstance!=null && unmergedChild!=null ) {
1219 index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance);
1220 }
1221 }
1222
1223 if (index!=null) return index;
1224 }
1225 }
1226 return null;
1227 }
1228
1229 private Object getIndexInParent(
1230 String property,
1231 Object childEntity,
1232 EntityPersister persister,
1233 CollectionPersister collectionPersister,
1234 Object potentialParent
1235 ){
1236 Object collection = persister.getPropertyValue( potentialParent, property, session.getEntityMode() );
1237 if ( collection!=null && Hibernate.isInitialized(collection) ) {
1238 return collectionPersister.getCollectionType().indexOf(collection, childEntity);
1239 }
1240 else {
1241 return null;
1242 }
1243 }
1244
1245 /**
1246 * Record the fact that the association belonging to the keyed
1247 * entity is null.
1248 */
1249 public void addNullProperty(EntityKey ownerKey, String propertyName) {
1250 nullAssociations.add( new AssociationKey(ownerKey, propertyName) );
1251 }
1252
1253 /**
1254 * Is the association property belonging to the keyed entity null?
1255 */
1256 public boolean isPropertyNull(EntityKey ownerKey, String propertyName) {
1257 return nullAssociations.contains( new AssociationKey(ownerKey, propertyName) );
1258 }
1259
1260 private void clearNullProperties() {
1261 nullAssociations.clear();
1262 }
1263
1264 public void setReadOnly(Object entity, boolean readOnly) {
1265 EntityEntry entry = getEntry(entity);
1266 if (entry==null) {
1267 throw new TransientObjectException("Instance was not associated with the session");
1268 }
1269 entry.setReadOnly(readOnly, entity);
1270 hasNonReadOnlyEntities = hasNonReadOnlyEntities || !readOnly;
1271 }
1272
1273 public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId) {
1274 Object entity = entitiesByKey.remove( oldKey );
1275 EntityEntry oldEntry = ( EntityEntry ) entityEntries.remove( entity );
1276
1277 EntityKey newKey = new EntityKey( generatedId, oldEntry.getPersister(), getSession().getEntityMode() );
1278 addEntity( newKey, entity );
1279 addEntry(
1280 entity,
1281 oldEntry.getStatus(),
1282 oldEntry.getLoadedState(),
1283 oldEntry.getRowId(),
1284 generatedId,
1285 oldEntry.getVersion(),
1286 oldEntry.getLockMode(),
1287 oldEntry.isExistsInDatabase(),
1288 oldEntry.getPersister(),
1289 oldEntry.isBeingReplicated(),
1290 oldEntry.isLoadedWithLazyPropertiesUnfetched()
1291 );
1292 }
1293
1294 /**
1295 * Used by the owning session to explicitly control serialization of the
1296 * persistence context.
1297 *
1298 * @param oos The stream to which the persistence context should get written
1299 * @throws IOException serialization errors.
1300 */
1301 public void serialize(ObjectOutputStream oos) throws IOException {
1302 log.trace( "serializing persistent-context" );
1303
1304 oos.writeBoolean( hasNonReadOnlyEntities );
1305
1306 oos.writeInt( entitiesByKey.size() );
1307 log.trace( "starting serialization of [" + entitiesByKey.size() + "] entitiesByKey entries" );
1308 Iterator itr = entitiesByKey.entrySet().iterator();
1309 while ( itr.hasNext() ) {
1310 Map.Entry entry = ( Map.Entry ) itr.next();
1311 ( ( EntityKey ) entry.getKey() ).serialize( oos );
1312 oos.writeObject( entry.getValue() );
1313 }
1314
1315 oos.writeInt( entitiesByUniqueKey.size() );
1316 log.trace( "starting serialization of [" + entitiesByUniqueKey.size() + "] entitiesByUniqueKey entries" );
1317 itr = entitiesByUniqueKey.entrySet().iterator();
1318 while ( itr.hasNext() ) {
1319 Map.Entry entry = ( Map.Entry ) itr.next();
1320 ( ( EntityUniqueKey ) entry.getKey() ).serialize( oos );
1321 oos.writeObject( entry.getValue() );
1322 }
1323
1324 oos.writeInt( proxiesByKey.size() );
1325 log.trace( "starting serialization of [" + proxiesByKey.size() + "] proxiesByKey entries" );
1326 itr = proxiesByKey.entrySet().iterator();
1327 while ( itr.hasNext() ) {
1328 Map.Entry entry = ( Map.Entry ) itr.next();
1329 ( ( EntityKey ) entry.getKey() ).serialize( oos );
1330 oos.writeObject( entry.getValue() );
1331 }
1332
1333 oos.writeInt( entitySnapshotsByKey.size() );
1334 log.trace( "starting serialization of [" + entitySnapshotsByKey.size() + "] entitySnapshotsByKey entries" );
1335 itr = entitySnapshotsByKey.entrySet().iterator();
1336 while ( itr.hasNext() ) {
1337 Map.Entry entry = ( Map.Entry ) itr.next();
1338 ( ( EntityKey ) entry.getKey() ).serialize( oos );
1339 oos.writeObject( entry.getValue() );
1340 }
1341
1342 oos.writeInt( entityEntries.size() );
1343 log.trace( "starting serialization of [" + entityEntries.size() + "] entityEntries entries" );
1344 itr = entityEntries.entrySet().iterator();
1345 while ( itr.hasNext() ) {
1346 Map.Entry entry = ( Map.Entry ) itr.next();
1347 oos.writeObject( entry.getKey() );
1348 ( ( EntityEntry ) entry.getValue() ).serialize( oos );
1349 }
1350
1351 oos.writeInt( collectionsByKey.size() );
1352 log.trace( "starting serialization of [" + collectionsByKey.size() + "] collectionsByKey entries" );
1353 itr = collectionsByKey.entrySet().iterator();
1354 while ( itr.hasNext() ) {
1355 Map.Entry entry = ( Map.Entry ) itr.next();
1356 ( ( CollectionKey ) entry.getKey() ).serialize( oos );
1357 oos.writeObject( entry.getValue() );
1358 }
1359
1360 oos.writeInt( collectionEntries.size() );
1361 log.trace( "starting serialization of [" + collectionEntries.size() + "] collectionEntries entries" );
1362 itr = collectionEntries.entrySet().iterator();
1363 while ( itr.hasNext() ) {
1364 Map.Entry entry = ( Map.Entry ) itr.next();
1365 oos.writeObject( entry.getKey() );
1366 ( ( CollectionEntry ) entry.getValue() ).serialize( oos );
1367 }
1368
1369 oos.writeInt( arrayHolders.size() );
1370 log.trace( "starting serialization of [" + arrayHolders.size() + "] arrayHolders entries" );
1371 itr = arrayHolders.entrySet().iterator();
1372 while ( itr.hasNext() ) {
1373 Map.Entry entry = ( Map.Entry ) itr.next();
1374 oos.writeObject( entry.getKey() );
1375 oos.writeObject( entry.getValue() );
1376 }
1377
1378 oos.writeInt( nullifiableEntityKeys.size() );
1379 log.trace( "starting serialization of [" + nullifiableEntityKeys.size() + "] nullifiableEntityKeys entries" );
1380 itr = nullifiableEntityKeys.iterator();
1381 while ( itr.hasNext() ) {
1382 EntityKey entry = ( EntityKey ) itr.next();
1383 entry.serialize( oos );
1384 }
1385 }
1386
1387 public static StatefulPersistenceContext deserialize(
1388 ObjectInputStream ois,
1389 SessionImplementor session) throws IOException, ClassNotFoundException {
1390 log.trace( "deserializing persistent-context" );
1391 StatefulPersistenceContext rtn = new StatefulPersistenceContext( session );
1392
1393 // during deserialization, we need to reconnect all proxies and
1394 // collections to this session, as well as the EntityEntry and
1395 // CollectionEntry instances; these associations are transient
1396 // because serialization is used for different things.
1397
1398 try {
1399 // todo : we can actually just determine this from the incoming EntityEntry-s
1400 rtn.hasNonReadOnlyEntities = ois.readBoolean();
1401
1402 int count = ois.readInt();
1403 log.trace( "staring deserialization of [" + count + "] entitiesByKey entries" );
1404 rtn.entitiesByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1405 for ( int i = 0; i < count; i++ ) {
1406 rtn.entitiesByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() );
1407 }
1408
1409 count = ois.readInt();
1410 log.trace( "staring deserialization of [" + count + "] entitiesByUniqueKey entries" );
1411 rtn.entitiesByUniqueKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1412 for ( int i = 0; i < count; i++ ) {
1413 rtn.entitiesByUniqueKey.put( EntityUniqueKey.deserialize( ois, session ), ois.readObject() );
1414 }
1415
1416 count = ois.readInt();
1417 log.trace( "staring deserialization of [" + count + "] proxiesByKey entries" );
1418 rtn.proxiesByKey = new ReferenceMap( ReferenceMap.HARD, ReferenceMap.WEAK, count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count, .75f );
1419 for ( int i = 0; i < count; i++ ) {
1420 EntityKey ek = EntityKey.deserialize( ois, session );
1421 Object proxy = ois.readObject();
1422 if ( proxy instanceof HibernateProxy ) {
1423 ( ( HibernateProxy ) proxy ).getHibernateLazyInitializer().setSession( session );
1424 rtn.proxiesByKey.put( ek, proxy );
1425 }
1426 else {
1427 log.trace( "encountered prunded proxy" );
1428 }
1429 // otherwise, the proxy was pruned during the serialization process
1430 }
1431
1432 count = ois.readInt();
1433 log.trace( "staring deserialization of [" + count + "] entitySnapshotsByKey entries" );
1434 rtn.entitySnapshotsByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1435 for ( int i = 0; i < count; i++ ) {
1436 rtn.entitySnapshotsByKey.put( EntityKey.deserialize( ois, session ), ois.readObject() );
1437 }
1438
1439 count = ois.readInt();
1440 log.trace( "staring deserialization of [" + count + "] entityEntries entries" );
1441 rtn.entityEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1442 for ( int i = 0; i < count; i++ ) {
1443 Object entity = ois.readObject();
1444 EntityEntry entry = EntityEntry.deserialize( ois, session );
1445 rtn.entityEntries.put( entity, entry );
1446 }
1447
1448 count = ois.readInt();
1449 log.trace( "staring deserialization of [" + count + "] collectionsByKey entries" );
1450 rtn.collectionsByKey = new HashMap( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1451 for ( int i = 0; i < count; i++ ) {
1452 rtn.collectionsByKey.put( CollectionKey.deserialize( ois, session ), ois.readObject() );
1453 }
1454
1455 count = ois.readInt();
1456 log.trace( "staring deserialization of [" + count + "] collectionEntries entries" );
1457 rtn.collectionEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1458 for ( int i = 0; i < count; i++ ) {
1459 final PersistentCollection pc = ( PersistentCollection ) ois.readObject();
1460 final CollectionEntry ce = CollectionEntry.deserialize( ois, session );
1461 pc.setCurrentSession( session );
1462 rtn.collectionEntries.put( pc, ce );
1463 }
1464
1465 count = ois.readInt();
1466 log.trace( "staring deserialization of [" + count + "] arrayHolders entries" );
1467 rtn.arrayHolders = IdentityMap.instantiate( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count );
1468 for ( int i = 0; i < count; i++ ) {
1469 rtn.arrayHolders.put( ois.readObject(), ois.readObject() );
1470 }
1471
1472 count = ois.readInt();
1473 log.trace( "staring deserialization of [" + count + "] nullifiableEntityKeys entries" );
1474 rtn.nullifiableEntityKeys = new HashSet();
1475 for ( int i = 0; i < count; i++ ) {
1476 rtn.nullifiableEntityKeys.add( EntityKey.deserialize( ois, session ) );
1477 }
1478
1479 }
1480 catch ( HibernateException he ) {
1481 throw new InvalidObjectException( he.getMessage() );
1482 }
1483
1484 return rtn;
1485 }
1486 }