From b1036c09a7e9548cdfd629dc56e04c03d4dadf0f Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 7 Mar 2011 08:53:30 -0800 Subject: [PATCH] HHH-5983 : Actions have non-transient references that cause inconsistencies after deserialization --- .../hibernate/action/CollectionAction.java | 27 +++++++++----- .../org/hibernate/action/EntityAction.java | 35 +++++++++++++------ .../action/EntityIdentityInsertAction.java | 28 ++++++++++++--- .../org/hibernate/engine/ActionQueue.java | 26 ++++++++++---- .../test/nonflushedchanges/CreateTest.java | 3 ++ 5 files changed, 90 insertions(+), 29 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/action/CollectionAction.java b/hibernate-core/src/main/java/org/hibernate/action/CollectionAction.java index 77cd2af224..889e2be96a 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/CollectionAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/CollectionAction.java @@ -44,11 +44,12 @@ import java.io.Serializable; */ public abstract class CollectionAction implements Executable, Serializable, Comparable { private transient CollectionPersister persister; - private final Serializable key; - private final SessionImplementor session; - private final String collectionRole; + private transient SessionImplementor session; private final PersistentCollection collection; + private final Serializable key; + private final String collectionRole; + public CollectionAction( final CollectionPersister persister, final PersistentCollection collection, @@ -65,9 +66,19 @@ public abstract class CollectionAction implements Executable, Serializable, Comp return collection; } - private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - ois.defaultReadObject(); - persister = session.getFactory().getCollectionPersister( collectionRole ); + /** + * Reconnect to session after deserialization... + */ + public void afterDeserialize(SessionImplementor session) { + if ( this.session != null || this.persister != null ) { + throw new IllegalStateException( "already attached to a session." ); + } + // IMPL NOTE: non-flushed changes code calls this method with session == null... + // guard against NullPointerException + if ( session != null ) { + this.session = session; + this.persister = session.getFactory().getCollectionPersister( collectionRole ); + } } public final void beforeExecutions() throws CacheException { @@ -132,8 +143,8 @@ public abstract class CollectionAction implements Executable, Serializable, Comp key, persister.getKeyType(), persister.getRole(), - session.getEntityMode(), - session.getFactory() + session.getEntityMode(), + session.getFactory() ); persister.getCacheAccessStrategy().remove( ck ); } diff --git a/hibernate-core/src/main/java/org/hibernate/action/EntityAction.java b/hibernate-core/src/main/java/org/hibernate/action/EntityAction.java index 901651cffc..ab798f298b 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/EntityAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/EntityAction.java @@ -25,6 +25,8 @@ package org.hibernate.action; import org.hibernate.AssertionFailure; +import org.hibernate.engine.EntityEntry; +import org.hibernate.engine.EntityKey; import org.hibernate.engine.SessionImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; @@ -45,9 +47,9 @@ public abstract class EntityAction private final String entityName; private final Serializable id; - private final Object instance; - private final SessionImplementor session; + private transient Object instance; + private transient SessionImplementor session; private transient EntityPersister persister; /** @@ -98,11 +100,18 @@ public abstract class EntityAction */ public final Serializable getId() { if ( id instanceof DelayedPostInsertIdentifier ) { - return session.getPersistenceContext().getEntry( instance ).getId(); + Serializable eeId = session.getPersistenceContext().getEntry( instance ).getId(); + return eeId instanceof DelayedPostInsertIdentifier ? null : eeId; } return id; } + public final DelayedPostInsertIdentifier getDelayedId() { + return DelayedPostInsertIdentifier.class.isInstance( id ) ? + DelayedPostInsertIdentifier.class.cast( id ) : + null; + } + /** * entity instance accessor * @@ -156,15 +165,19 @@ public abstract class EntityAction } /** - * Serialization... - * - * @param ois Thed object stream - * @throws IOException Problem performing the default stream reading - * @throws ClassNotFoundException Problem performing the default stream reading + * Reconnect to session after deserialization... */ - private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - ois.defaultReadObject(); - persister = session.getFactory().getEntityPersister( entityName ); + public void afterDeserialize(SessionImplementor session) { + if ( this.session != null || this.persister != null ) { + throw new IllegalStateException( "already attached to a session." ); + } + // IMPL NOTE: non-flushed changes code calls this method with session == null... + // guard against NullPointerException + if ( session != null ) { + this.session = session; + this.persister = session.getFactory().getEntityPersister( entityName ); + this.instance = session.getPersistenceContext().getEntity( new EntityKey( id, persister, session.getEntityMode() ) ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/action/EntityIdentityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/EntityIdentityInsertAction.java index d9ab6cd914..549cb5cba6 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/EntityIdentityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/EntityIdentityInsertAction.java @@ -28,6 +28,7 @@ import java.io.Serializable; import org.hibernate.HibernateException; import org.hibernate.AssertionFailure; +import org.hibernate.engine.EntityEntry; import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.EntityKey; import org.hibernate.event.PostInsertEvent; @@ -39,7 +40,7 @@ import org.hibernate.persister.entity.EntityPersister; public final class EntityIdentityInsertAction extends EntityAction { - private final Object[] state; + private transient Object[] state; private final boolean isDelayed; private final EntityKey delayedEntityKey; //private CacheEntry cacheEntry; @@ -51,7 +52,12 @@ public final class EntityIdentityInsertAction extends EntityAction { EntityPersister persister, SessionImplementor session, boolean isDelayed) throws HibernateException { - super( session, null, instance, persister ); + super( + session, + ( isDelayed ? generateDelayedPostInsertIdentifier() : null ), + instance, + persister + ); this.state = state; this.isDelayed = isDelayed; this.delayedEntityKey = isDelayed ? generateDelayedEntityKey() : null; @@ -171,10 +177,24 @@ public final class EntityIdentityInsertAction extends EntityAction { return delayedEntityKey; } - private synchronized EntityKey generateDelayedEntityKey() { + private synchronized static DelayedPostInsertIdentifier generateDelayedPostInsertIdentifier() { + return new DelayedPostInsertIdentifier(); + } + + private EntityKey generateDelayedEntityKey() { if ( !isDelayed ) { throw new AssertionFailure( "cannot request delayed entity-key for non-delayed post-insert-id generation" ); } - return new EntityKey( new DelayedPostInsertIdentifier(), getPersister(), getSession().getEntityMode() ); + return new EntityKey( getDelayedId(), getPersister(), getSession().getEntityMode() ); + } + + public void afterDeserialize(SessionImplementor session) { + super.afterDeserialize( session ); + // IMPL NOTE: non-flushed changes code calls this method with session == null... + // guard against NullPointerException + if ( session != null ) { + EntityEntry entityEntry = session.getPersistenceContext().getEntry( getInstance() ); + this.state = entityEntry.getLoadedState(); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/ActionQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/ActionQueue.java index 92c8709fa5..2205a2a7b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/ActionQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/ActionQueue.java @@ -42,9 +42,11 @@ import org.hibernate.HibernateException; import org.hibernate.action.AfterTransactionCompletionProcess; import org.hibernate.action.BeforeTransactionCompletionProcess; import org.hibernate.action.BulkOperationCleanupAction; +import org.hibernate.action.CollectionAction; import org.hibernate.action.CollectionRecreateAction; import org.hibernate.action.CollectionRemoveAction; import org.hibernate.action.CollectionUpdateAction; +import org.hibernate.action.EntityAction; import org.hibernate.action.EntityDeleteAction; import org.hibernate.action.EntityIdentityInsertAction; import org.hibernate.action.EntityInsertAction; @@ -480,42 +482,54 @@ public class ActionQueue { log.trace( "starting deserialization of [" + queueSize + "] insertions entries" ); rtn.insertions = new ArrayList( queueSize ); for ( int i = 0; i < queueSize; i++ ) { - rtn.insertions.add( ois.readObject() ); + EntityAction action = ( EntityAction ) ois.readObject(); + action.afterDeserialize( session ); + rtn.insertions.add( action ); } queueSize = ois.readInt(); log.trace( "starting deserialization of [" + queueSize + "] deletions entries" ); rtn.deletions = new ArrayList( queueSize ); for ( int i = 0; i < queueSize; i++ ) { - rtn.deletions.add( ois.readObject() ); + EntityAction action = ( EntityAction ) ois.readObject(); + action.afterDeserialize( session ); + rtn.deletions.add( action ); } queueSize = ois.readInt(); log.trace( "starting deserialization of [" + queueSize + "] updates entries" ); rtn.updates = new ArrayList( queueSize ); for ( int i = 0; i < queueSize; i++ ) { - rtn.updates.add( ois.readObject() ); + EntityAction action = ( EntityAction ) ois.readObject(); + action.afterDeserialize( session ); + rtn.updates.add( action ); } queueSize = ois.readInt(); log.trace( "starting deserialization of [" + queueSize + "] collectionUpdates entries" ); rtn.collectionUpdates = new ArrayList( queueSize ); for ( int i = 0; i < queueSize; i++ ) { - rtn.collectionUpdates.add( ois.readObject() ); + CollectionAction action = ( CollectionAction ) ois.readObject(); + action.afterDeserialize( session ); + rtn.collectionUpdates.add( action ); } queueSize = ois.readInt(); log.trace( "starting deserialization of [" + queueSize + "] collectionRemovals entries" ); rtn.collectionRemovals = new ArrayList( queueSize ); for ( int i = 0; i < queueSize; i++ ) { - rtn.collectionRemovals.add( ois.readObject() ); + CollectionAction action = ( CollectionAction ) ois.readObject(); + action.afterDeserialize( session ); + rtn.collectionRemovals.add( action ); } queueSize = ois.readInt(); log.trace( "starting deserialization of [" + queueSize + "] collectionCreations entries" ); rtn.collectionCreations = new ArrayList( queueSize ); for ( int i = 0; i < queueSize; i++ ) { - rtn.collectionCreations.add( ois.readObject() ); + CollectionAction action = ( CollectionAction ) ois.readObject(); + action.afterDeserialize( session ); + rtn.collectionCreations.add( action ); } return rtn; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/nonflushedchanges/CreateTest.java b/hibernate-core/src/test/java/org/hibernate/test/nonflushedchanges/CreateTest.java index 10f85f02bf..cac073ba85 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/nonflushedchanges/CreateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/nonflushedchanges/CreateTest.java @@ -6,6 +6,7 @@ import java.util.Collection; import junit.framework.Test; +import org.hibernate.FlushMode; import org.hibernate.Hibernate; import org.hibernate.PersistentObjectException; import org.hibernate.Session; @@ -104,6 +105,7 @@ public class CreateTest extends AbstractOperationTestCase { s = applyNonFlushedChangesToNewSessionCloseOldSession( s ); s.persist( root ); s = applyNonFlushedChangesToNewSessionCloseOldSession( s ); + root = ( NumberedNode ) getOldToNewEntityRefMap().get( root ); SimpleJtaTransactionManagerImpl.getInstance().commit(); assertInsertCount( 2 ); @@ -178,6 +180,7 @@ public class CreateTest extends AbstractOperationTestCase { dupe = ( NumberedNode ) getOldToNewEntityRefMap().get( dupe ); s.persist( dupe ); s = applyNonFlushedChangesToNewSessionCloseOldSession( s ); + dupe = ( NumberedNode ) getOldToNewEntityRefMap().get( dupe ); SimpleJtaTransactionManagerImpl.getInstance().commit(); SimpleJtaTransactionManagerImpl.getInstance().begin();