diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index 7780186452..7b91a87b58 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -267,6 +267,8 @@ public final class CollectionEntry implements Serializable { return snapshot; } + private boolean fromMerge = false; + /** * Reset the stored snapshot for both the persistent collection and this collection entry. * Used during the merge of detached collections. @@ -276,9 +278,14 @@ public final class CollectionEntry implements Serializable { */ public void resetStoredSnapshot(PersistentCollection collection, Serializable storedSnapshot) { LOG.debugf("Reset storedSnapshot to %s for %s", storedSnapshot, this); - + + if ( fromMerge ) { + return; // EARLY EXIT! + } + snapshot = storedSnapshot; - collection.setSnapshot(loadedKey, role, snapshot); + collection.setSnapshot( loadedKey, role, snapshot ); + fromMerge = true; } private void setLoadedPersister(CollectionPersister persister) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 4950059ea2..a46e5cce36 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -526,15 +526,13 @@ public abstract class CollectionType extends AbstractType implements Association // on the target because we simply do not know... if ( original instanceof PersistentCollection ) { if ( result instanceof PersistentCollection ) { - if ( ! ( ( PersistentCollection ) original ).isDirty() ) { - ( ( PersistentCollection ) result ).clearDirty(); - } + final PersistentCollection originalPersistentCollection = (PersistentCollection) original; + final PersistentCollection resultPersistentCollection = (PersistentCollection) result; - if ( elemType instanceof AssociationType ) { - preserveSnapshot( (PersistentCollection) original, - (PersistentCollection) result, - (AssociationType) elemType, owner, copyCache, - session ); + preserveSnapshot( originalPersistentCollection, resultPersistentCollection, elemType, owner, copyCache, session ); + + if ( ! originalPersistentCollection.isDirty() ) { + resultPersistentCollection.clearDirty(); } } } @@ -542,9 +540,13 @@ public abstract class CollectionType extends AbstractType implements Association return result; } - private void preserveSnapshot(PersistentCollection original, - PersistentCollection result, AssociationType elemType, - Object owner, Map copyCache, SessionImplementor session) { + private void preserveSnapshot( + PersistentCollection original, + PersistentCollection result, + Type elemType, + Object owner, + Map copyCache, + SessionImplementor session) { Serializable originalSnapshot = original.getStoredSnapshot(); Serializable resultSnapshot = result.getStoredSnapshot(); Serializable targetSnapshot; @@ -553,39 +555,35 @@ public abstract class CollectionType extends AbstractType implements Association targetSnapshot = new ArrayList( ( (List) originalSnapshot ).size() ); for ( Object obj : (List) originalSnapshot ) { - ( (List) targetSnapshot ).add( elemType.replace( - obj, null, session, owner, copyCache ) ); + ( (List) targetSnapshot ).add( elemType.replace( obj, null, session, owner, copyCache ) ); } } else if ( originalSnapshot instanceof Map ) { if ( originalSnapshot instanceof SortedMap ) { - targetSnapshot = new TreeMap( - ( (SortedMap) originalSnapshot ).comparator() ); + targetSnapshot = new TreeMap( ( (SortedMap) originalSnapshot ).comparator() ); } else { targetSnapshot = new HashMap( - CollectionHelper.determineProperSizing( - ( (Map) originalSnapshot ).size() ), - CollectionHelper.LOAD_FACTOR ); + CollectionHelper.determineProperSizing( ( (Map) originalSnapshot ).size() ), + CollectionHelper.LOAD_FACTOR + ); } - for ( Map.Entry entry : ( - (Map) originalSnapshot ).entrySet() ) { + for ( Map.Entry entry : ( (Map) originalSnapshot ).entrySet() ) { Object key = entry.getKey(); Object value = entry.getValue(); - Object resultSnapshotValue = ( resultSnapshot == null ) ? null + Object resultSnapshotValue = ( resultSnapshot == null ) + ? null : ( (Map) resultSnapshot ).get( key ); + Object newValue = elemType.replace( value, resultSnapshotValue, session, owner, copyCache ); + if ( key == value ) { - Object newValue = elemType.replace( value, - resultSnapshotValue, session, owner, copyCache ); ( (Map) targetSnapshot ).put( newValue, newValue ); } else { - Object newValue = elemType.replace( value, - resultSnapshotValue, session, owner, copyCache ); ( (Map) targetSnapshot ).put( key, newValue ); } @@ -595,8 +593,7 @@ public abstract class CollectionType extends AbstractType implements Association else if ( originalSnapshot instanceof Object[] ) { Object[] arr = (Object[]) originalSnapshot; for ( int i = 0; i < arr.length; i++ ) { - arr[i] = elemType.replace( - arr[i], null, session, owner, copyCache ); + arr[i] = elemType.replace( arr[i], null, session, owner, copyCache ); } targetSnapshot = originalSnapshot; @@ -607,8 +604,7 @@ public abstract class CollectionType extends AbstractType implements Association } - CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( - result ); + CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( result ); if ( ce != null ) { ce.resetStoredSnapshot( result, targetSnapshot ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/AggregatedCollectionEventListener.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/AggregatedCollectionEventListener.java new file mode 100644 index 0000000000..1f2b2cd7e0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/AggregatedCollectionEventListener.java @@ -0,0 +1,202 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.AbstractCollectionEvent; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.InitializeCollectionEvent; +import org.hibernate.event.spi.InitializeCollectionEventListener; +import org.hibernate.event.spi.PostCollectionRecreateEvent; +import org.hibernate.event.spi.PostCollectionRecreateEventListener; +import org.hibernate.event.spi.PostCollectionRemoveEvent; +import org.hibernate.event.spi.PostCollectionRemoveEventListener; +import org.hibernate.event.spi.PostCollectionUpdateEvent; +import org.hibernate.event.spi.PostCollectionUpdateEventListener; +import org.hibernate.event.spi.PreCollectionRecreateEvent; +import org.hibernate.event.spi.PreCollectionRecreateEventListener; +import org.hibernate.event.spi.PreCollectionRemoveEvent; +import org.hibernate.event.spi.PreCollectionRemoveEventListener; +import org.hibernate.event.spi.PreCollectionUpdateEvent; +import org.hibernate.event.spi.PreCollectionUpdateEventListener; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.metamodel.source.MetadataImplementor; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; + +/** + * @author Steve Ebersole + * @author Gail Badner + */ +public class AggregatedCollectionEventListener + implements InitializeCollectionEventListener, + PreCollectionRecreateEventListener, + PostCollectionRecreateEventListener, + PreCollectionRemoveEventListener, + PostCollectionRemoveEventListener, + PreCollectionUpdateEventListener, + PostCollectionUpdateEventListener { + + private static final Logger log = Logger.getLogger( AggregatedCollectionEventListener.class ); + + private final List eventEntryList = new ArrayList(); + + public void reset() { + eventEntryList.clear(); + } + + public List getEventEntryList() { + return eventEntryList; + } + + @Override + public void onInitializeCollection(InitializeCollectionEvent event) throws HibernateException { + addEvent( event ); + } + + protected void addEvent(AbstractCollectionEvent event) { + log.debugf( "Added collection event : %s", event ); + eventEntryList.add( new EventEntry( event ) ); + } + + + // recreate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public void onPreRecreateCollection(PreCollectionRecreateEvent event) { + addEvent( event ); + } + + @Override + public void onPostRecreateCollection(PostCollectionRecreateEvent event) { + addEvent( event ); + } + + + // remove ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public void onPreRemoveCollection(PreCollectionRemoveEvent event) { + addEvent( event ); + } + + @Override + public void onPostRemoveCollection(PostCollectionRemoveEvent event) { + addEvent( event ); + } + + + // update ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public void onPreUpdateCollection(PreCollectionUpdateEvent event) { + addEvent( event ); + } + + @Override + public void onPostUpdateCollection(PostCollectionUpdateEvent event) { + addEvent( event ); + } + + public static class EventEntry { + private final AbstractCollectionEvent event; + private final Serializable snapshotAtTimeOfEventHandling; + + public EventEntry(AbstractCollectionEvent event) { + this.event = event; + // make a copy of the collection? + this.snapshotAtTimeOfEventHandling = event.getSession() + .getPersistenceContext() + .getCollectionEntry( event.getCollection() ) + .getSnapshot(); + } + + public AbstractCollectionEvent getEvent() { + return event; + } + + public Serializable getSnapshotAtTimeOfEventHandling() { + return snapshotAtTimeOfEventHandling; + } + } + + public static class IntegratorImpl implements Integrator { + private AggregatedCollectionEventListener listener; + + public AggregatedCollectionEventListener getListener() { + if ( listener == null ) { + throw new HibernateException( "Integrator not yet processed" ); + } + return listener; + } + + @Override + public void integrate( + Configuration configuration, + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + integrate( serviceRegistry ); + } + + protected void integrate(SessionFactoryServiceRegistry serviceRegistry) { + if ( listener != null ) { + log.warn( "integrate called second time on testing collection listener Integrator (could be result of rebuilding SF on test failure)" ); + } + listener = new AggregatedCollectionEventListener(); + + final EventListenerRegistry listenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); + listenerRegistry.appendListeners( EventType.INIT_COLLECTION, listener ); + listenerRegistry.appendListeners( EventType.PRE_COLLECTION_RECREATE, listener ); + listenerRegistry.appendListeners( EventType.POST_COLLECTION_RECREATE, listener ); + listenerRegistry.appendListeners( EventType.PRE_COLLECTION_REMOVE, listener ); + listenerRegistry.appendListeners( EventType.POST_COLLECTION_REMOVE, listener ); + listenerRegistry.appendListeners( EventType.PRE_COLLECTION_UPDATE, listener ); + listenerRegistry.appendListeners( EventType.POST_COLLECTION_UPDATE, listener ); + } + + + @Override + public void integrate( + MetadataImplementor metadata, + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + integrate( serviceRegistry ); + } + + @Override + public void disintegrate( + SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { + //To change body of implemented methods use File | Settings | File Templates. + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Alias.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Alias.java new file mode 100644 index 0000000000..ecab38368a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Alias.java @@ -0,0 +1,85 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Steve Ebersole + */ +@Entity +public class Alias implements Identifiable { + private Integer id; + private String alias; + private List characters = new ArrayList(); + + public Alias() { + } + + public Alias(Integer id, String alias) { + this.id = id; + this.alias = alias; + } + + @Id + @Override + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + @ManyToMany( cascade = CascadeType.ALL ) + @JoinTable( name = "CHARACTER_ALIAS" ) +// @JoinTable( +// name = "CHARACTER_ALIAS", +// joinColumns = @JoinColumn(name="ALIAS_ID", referencedColumnName="ID"), +// inverseJoinColumns = @JoinColumn(name="CHARACTER_ID", referencedColumnName="ID") +// ) + public List getCharacters() { + return characters; + } + + public void setCharacters(List characters) { + this.characters = characters; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java index 656a52e7fb..103ad483cf 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/BadMergeHandlingTest.java @@ -23,17 +23,6 @@ */ package org.hibernate.test.event.collection.detached; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import org.hibernate.Session; @@ -53,7 +42,7 @@ public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Customer.class, Alias.class, CreditCard.class }; + return new Class[] { Character.class, Alias.class }; } @Test @@ -61,10 +50,11 @@ public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { public void testMergeAndHold() { Session s = openSession(); s.beginTransaction(); - Customer paul = new Customer( 1, "Paul Atreides" ); + + Character paul = new Character( 1, "Paul Atreides" ); s.persist( paul ); - Customer duke = new Customer( 2, "Duke Leto" ); - s.persist( duke ); + Character paulo = new Character( 2, "Paulo Atreides" ); + s.persist( paulo ); Alias alias1 = new Alias( 1, "Paul Muad'Dib" ); s.persist( alias1 ); @@ -75,12 +65,6 @@ public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { Alias alias3 = new Alias( 3, "The Preacher" ); s.persist( alias3 ); - CreditCard cc1 = new CreditCard( 1 ); - s.persist( cc1 ); - - CreditCard cc2 = new CreditCard( 2 ); - s.persist( cc2 ); - s.getTransaction().commit(); s.close(); @@ -88,29 +72,24 @@ public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { s = openSession(); s.beginTransaction(); - // customer 1 - alias1.customers.add( paul ); + // customer 1 + alias1.getCharacters().add( paul ); s.merge( alias1 ); - alias2.customers.add( paul ); + alias2.getCharacters().add( paul ); s.merge( alias2 ); - alias3.customers.add( paul ); + alias3.getCharacters().add( paul ); s.merge( alias3 ); - s.flush(); + s.flush(); - // customer 2 - alias1.customers.add( duke ); - s.merge( alias1 ); - alias2.customers.add( duke ); - s.merge( alias2 ); - alias3.customers.add( duke ); - s.merge( alias3 ); - s.flush(); - - cc1.customer = paul; - s.merge( cc1 ); - cc2.customer = paul; - s.merge( cc2 ); + // customer 2 + alias1.getCharacters().add( paulo ); + s.merge( alias1 ); + alias2.getCharacters().add( paulo ); + s.merge( alias2 ); + alias3.getCharacters().add( paulo ); + s.merge( alias3 ); + s.flush(); s.getTransaction().commit(); s.close(); @@ -118,7 +97,7 @@ public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { // now try to read them back (I guess) s = openSession(); s.beginTransaction(); - List results = s.createQuery( "select c from Customer c join c.aliases a where a.alias = :aParam" ) + List results = s.createQuery( "select c from Character c join c.aliases a where a.alias = :aParam" ) .setParameter( "aParam", "Usul" ) .list(); assertEquals( 2, results.size() ); @@ -126,63 +105,4 @@ public class BadMergeHandlingTest extends BaseCoreFunctionalTestCase { s.close(); } - @Entity( name="Customer" ) - public static class Customer { - @Id - private Integer id; - private String name; - @ManyToMany( cascade= CascadeType.ALL, mappedBy="customers" ) - private Collection aliases = new ArrayList(); - @OneToMany(cascade=CascadeType.ALL, mappedBy="customer") - private Collection creditCards = new ArrayList(); - - public Customer() { - } - - public Customer(Integer id, String name) { - this.id = id; - this.name = name; - } - } - - @Entity( name="Alias" ) - public static class Alias { - @Id - private Integer id; - private String alias; - @ManyToMany(cascade=CascadeType.ALL) - @JoinTable(name="FKS_ALIAS_CUSTOMER", - joinColumns= - @JoinColumn( - name="FK_FOR_ALIAS_TABLE", referencedColumnName="ID"), - inverseJoinColumns= - @JoinColumn( - name="FK_FOR_CUSTOMER_TABLE", referencedColumnName="ID") - ) - private Collection customers = new ArrayList(); - - public Alias() { - } - - public Alias(Integer id, String alias) { - this.id = id; - this.alias = alias; - } - } - - @Entity( name="CreditCard" ) - public static class CreditCard { - @Id - private Integer id; - @ManyToOne(cascade=CascadeType.ALL) - @JoinColumn (name="FK3_FOR_CUSTOMER_TABLE") - private Customer customer; - - public CreditCard() { - } - - public CreditCard(Integer id) { - this.id = id; - } - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Character.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Character.java new file mode 100644 index 0000000000..8fb59524a7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Character.java @@ -0,0 +1,81 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Steve Ebersole + */ +@Entity +public class Character implements Identifiable { + private Integer id; + private String name; + private List aliases = new ArrayList(); + + public Character() { + } + + public Character(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + @Override + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToMany( cascade= CascadeType.ALL, mappedBy="characters" ) + public List getAliases() { + return aliases; + } + + public void setAliases(List aliases) { + this.aliases = aliases; + } + + public void associateAlias(Alias alias) { + alias.getCharacters().add( this ); + getAliases().add( alias ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Identifiable.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Identifiable.java new file mode 100644 index 0000000000..9db49a911e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/Identifiable.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +/** + * @author Steve Ebersole + */ +public interface Identifiable { + public Integer getId(); +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MergeCollectionEventTest.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MergeCollectionEventTest.java new file mode 100644 index 0000000000..55517d107a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MergeCollectionEventTest.java @@ -0,0 +1,251 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.event.collection.detached; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.event.spi.AbstractCollectionEvent; +import org.hibernate.event.spi.PostCollectionRecreateEvent; +import org.hibernate.event.spi.PostCollectionUpdateEvent; +import org.hibernate.event.spi.PreCollectionRecreateEvent; +import org.hibernate.event.spi.PreCollectionRemoveEvent; +import org.hibernate.event.spi.PreCollectionUpdateEvent; +import org.hibernate.service.BootstrapServiceRegistryBuilder; + +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ +@TestForIssue( jiraKey = "HHH-7928" ) +public class MergeCollectionEventTest extends BaseCoreFunctionalTestCase { + + private AggregatedCollectionEventListener.IntegratorImpl collectionListenerIntegrator = + new AggregatedCollectionEventListener.IntegratorImpl(); + + @Before + public void resetListener() { + collectionListenerIntegrator.getListener().reset(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Character.class, Alias.class }; + } + + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + super.prepareBootstrapRegistryBuilder( builder ); + builder.with( collectionListenerIntegrator ); + } + + @Override + protected void cleanupTestData() throws Exception { + Session s = openSession(); + s.beginTransaction(); + List aliases = s.createQuery( "from Alias" ).list(); + for ( Alias alias : aliases ) { + for ( Character character : alias.getCharacters() ) { + character.getAliases().clear(); + } + alias.getCharacters().clear(); + } + s.flush(); + s.createQuery( "delete Alias" ).executeUpdate(); + s.createQuery( "delete Character" ).executeUpdate(); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testCollectionEventHandlingOnMerge() { + final AggregatedCollectionEventListener listener = collectionListenerIntegrator.getListener(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // This first bit really is just preparing the entities. There is generally no collection + // events of real interest during this part + + Session s = openSession(); + s.beginTransaction(); + Character paul = new Character( 1, "Paul Atreides" ); + s.save( paul ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, paul, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, paul, Collections.EMPTY_LIST ); + + listener.reset(); + + s = openSession(); + s.beginTransaction(); + Character paulo = new Character( 2, "Paulo Atreides" ); + s.save( paulo ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, paulo, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, paulo, Collections.EMPTY_LIST ); + + listener.reset(); + + s = openSession(); + s.beginTransaction(); + Alias alias1 = new Alias( 1, "Paul Muad'Dib" ); + s.save( alias1 ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, alias1, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, alias1, Collections.EMPTY_LIST ); + + listener.reset(); + + s = openSession(); + s.beginTransaction(); + Alias alias2 = new Alias( 2, "Usul" ); + s.save( alias2 ); + s.getTransaction().commit(); + s.close(); + + assertEquals( 2, listener.getEventEntryList().size() ); + checkListener( 0, PreCollectionRecreateEvent.class, alias2, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionRecreateEvent.class, alias2, Collections.EMPTY_LIST ); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // at this point we can start setting up the associations and checking collection events + // of "real interest" + + listener.reset(); + + paul.associateAlias( alias1 ); + paul.associateAlias( alias2 ); + + paulo.associateAlias( alias1 ); + paulo.associateAlias( alias2 ); + + s = openSession(); + s.beginTransaction(); + s.merge( alias1 ); + + assertEquals( 0, listener.getEventEntryList().size() ); + + // this is where HHH-7928 (problem with HHH-6361 fix) shows up... + s.flush(); + + assertEquals( 8, listener.getEventEntryList().size() ); // 4 collections x 2 events per + checkListener( 0, PreCollectionUpdateEvent.class, alias1, Collections.EMPTY_LIST ); + checkListener( 1, PostCollectionUpdateEvent.class, alias1, alias1.getCharacters() ); + checkListener( 2, PreCollectionUpdateEvent.class, paul, Collections.EMPTY_LIST ); + checkListener( 3, PostCollectionUpdateEvent.class, paul, paul.getAliases() ); + checkListener( 4, PreCollectionUpdateEvent.class, alias2, Collections.EMPTY_LIST ); + checkListener( 5, PostCollectionUpdateEvent.class, alias2, alias2.getCharacters() ); + checkListener( 6, PreCollectionUpdateEvent.class, paulo, Collections.EMPTY_LIST ); + checkListener( 7, PostCollectionUpdateEvent.class, paulo, paul.getAliases() ); + + List alias1CharactersSnapshot = copy( alias1.getCharacters() ); + List alias2CharactersSnapshot = copy( alias2.getCharacters() ); + + listener.reset(); + + s.merge( alias2 ); + + assertEquals( 0, listener.getEventEntryList().size() ); + + s.flush(); + + assertEquals( 8, listener.getEventEntryList().size() ); // 4 collections x 2 events per + checkListener( 0, PreCollectionUpdateEvent.class, alias1, alias1CharactersSnapshot ); + checkListener( 1, PostCollectionUpdateEvent.class, alias1, alias1CharactersSnapshot ); +// checkListener( 2, PreCollectionUpdateEvent.class, paul, Collections.EMPTY_LIST ); +// checkListener( 3, PostCollectionUpdateEvent.class, paul, paul.getAliases() ); + checkListener( 4, PreCollectionUpdateEvent.class, alias2, alias2CharactersSnapshot ); + checkListener( 5, PostCollectionUpdateEvent.class, alias2, alias2.getCharacters() ); +// checkListener( 6, PreCollectionUpdateEvent.class, paulo, Collections.EMPTY_LIST ); +// checkListener( 7, PostCollectionUpdateEvent.class, paulo, paul.getAliases() ); + + s.getTransaction().commit(); + s.close(); + +// +// checkListener(listeners, listeners.getInitializeCollectionListener(), +// mce, null, eventCount++); +// checkListener(listeners, listeners.getPreCollectionUpdateListener(), +// mce, oldRefentities1, eventCount++); +// checkListener(listeners, listeners.getPostCollectionUpdateListener(), +// mce, mce.getRefEntities1(), eventCount++); + + } + + + protected void checkListener( + int eventIndex, + Class expectedEventType, + Identifiable expectedOwner, + List expectedCollectionEntrySnapshot) { + final AggregatedCollectionEventListener.EventEntry eventEntry + = collectionListenerIntegrator.getListener().getEventEntryList().get( eventIndex ); + final AbstractCollectionEvent event = eventEntry.getEvent(); + + assertTyping( expectedEventType, event ); + +// because of the merge graphs, the instances are likely different. just base check on type and id +// assertEquals( expectedOwner, event.getAffectedOwnerOrNull() ); + assertEquals( expectedOwner.getClass().getName(), event.getAffectedOwnerEntityName() ); + assertEquals( expectedOwner.getId(), event.getAffectedOwnerIdOrNull() ); + + if ( event instanceof PreCollectionUpdateEvent + || event instanceof PreCollectionRemoveEvent + || event instanceof PostCollectionRecreateEvent ) { + List snapshot = (List) eventEntry.getSnapshotAtTimeOfEventHandling(); + assertEquals( expectedCollectionEntrySnapshot.size(), snapshot.size() ); + for ( int i = 0; i < expectedCollectionEntrySnapshot.size(); i++ ) { + Identifiable expected = expectedCollectionEntrySnapshot.get( i ); + Identifiable found = snapshot.get( i ); + assertEquals( expected.getClass().getName(), found.getClass().getName() ); + assertEquals( expected.getId(), found.getId() ); + } + } + } + + private List copy(List source) { + ArrayList copy = new ArrayList( source.size() ); + copy.addAll( source ); + return copy; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java index 333da328dc..6d10287564 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java +++ b/hibernate-core/src/test/java/org/hibernate/test/event/collection/detached/MultipleCollectionListeners.java @@ -203,10 +203,10 @@ public class MultipleCollectionListeners { public void addEvent(AbstractCollectionEvent event, Listener listener) { + CollectionEntry collectionEntry = event.getSession() .getPersistenceContext() .getCollectionEntry(event.getCollection()); - Serializable snapshot = collectionEntry.getSnapshot(); log.debug("add Event: " + event.getClass() + "; listener = "