[hibernate-commits] Hibernate SVN: r18163 - in core/trunk/envers/src: test/java/org/hibernate/envers/test/integration/manytomany/ternary and 1 other directory.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Dec 8 11:08:13 EST 2009


Author: adamw
Date: 2009-12-08 11:08:13 -0500 (Tue, 08 Dec 2009)
New Revision: 18163

Added:
   core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/ternary/TernaryMapFlush.java
Modified:
   core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java
Log:
HHH-4650:
- fix for removing an item from a persistent collection, flushing, and adding the same item again: merging the collection change work units data
- test

Modified: core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java
===================================================================
--- core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java	2009-12-08 16:01:36 UTC (rev 18162)
+++ core/trunk/envers/src/main/java/org/hibernate/envers/synchronization/work/PersistentCollectionChangeWorkUnit.java	2009-12-08 16:08:13 UTC (rev 18163)
@@ -26,6 +26,8 @@
 import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
 
 import org.hibernate.envers.configuration.AuditConfiguration;
 import org.hibernate.envers.configuration.AuditEntitiesConfiguration;
@@ -45,14 +47,14 @@
     private final String referencingPropertyName;
 
     public PersistentCollectionChangeWorkUnit(SessionImplementor sessionImplementor, String entityName,
-											  AuditConfiguration verCfg, PersistentCollection collection,
+											  AuditConfiguration auditCfg, PersistentCollection collection,
 											  CollectionEntry collectionEntry, Serializable snapshot, Serializable id) {
-        super(sessionImplementor, entityName, verCfg, null);
+        super(sessionImplementor, entityName, auditCfg, new PersistentCollectionChangeWorkUnitId(id, collectionEntry.getRole()));
 
 		String ownerEntityName = ((AbstractCollectionPersister) collectionEntry.getLoadedPersister()).getOwnerEntityName();
 		referencingPropertyName = collectionEntry.getRole().substring(ownerEntityName.length() + 1);
 
-        collectionChanges = verCfg.getEntCfg().get(getEntityName()).getPropertyMapper()
+        collectionChanges = auditCfg.getEntCfg().get(getEntityName()).getPropertyMapper()
                 .mapCollectionChanges(referencingPropertyName, collection, snapshot, id);
     }
 
@@ -98,6 +100,82 @@
     }
 
     public KeepCheckResult dispatch(KeepCheckVisitor first) {
-        return null;
+        if (first instanceof PersistentCollectionChangeWorkUnit) {
+            PersistentCollectionChangeWorkUnit original = (PersistentCollectionChangeWorkUnit) first;
+
+            // Merging the collection changes in both work units.
+
+            // First building a map from the ids of the collection-entry-entities from the "second" collection changes,
+            // to the PCCD objects. That way, we will be later able to check if an "original" collection change
+            // should be added, or if it is overshadowed by a new one.
+            Map<Object, PersistentCollectionChangeData> newChangesIdMap = new HashMap<Object, PersistentCollectionChangeData>();
+            for (PersistentCollectionChangeData persistentCollectionChangeData : getCollectionChanges()) {
+                newChangesIdMap.put(
+                        getOriginalId(persistentCollectionChangeData),
+                        persistentCollectionChangeData);
+            }
+
+            // Storing the current changes
+            List<PersistentCollectionChangeData> newChanges = new ArrayList<PersistentCollectionChangeData>();
+            newChanges.addAll(collectionChanges);
+
+            // And building the change list again
+            collectionChanges.clear();
+            for (PersistentCollectionChangeData originalCollectionChangeData : original.getCollectionChanges()) {
+                if (!newChangesIdMap.containsKey(getOriginalId(originalCollectionChangeData))) {
+                    collectionChanges.add(originalCollectionChangeData);
+                }
+            }
+
+            // Finally adding all of the new changes to the end of the list
+            collectionChanges.addAll(newChanges);
+        } else {
+            throw new RuntimeException("Trying to merge a " + first + " with a PersitentCollectionChangeWorkUnit. " +
+                    "This is not really possible.");
+        }
+
+        return KeepCheckResult.SECOND;
     }
+
+    private Object getOriginalId(PersistentCollectionChangeData persistentCollectionChangeData) {
+        return persistentCollectionChangeData.getData().get(verCfg.getAuditEntCfg().getOriginalIdPropName());
+    }
+
+    /**
+     * A unique identifier for a collection work unit. Consists of an id of the owning entity and the name of
+     * the entity plus the name of the field (the role). This is needed because such collections aren't entities
+     * in the "normal" mapping, but they are entities for Envers.
+     */
+    private static class PersistentCollectionChangeWorkUnitId implements Serializable {
+        private static final long serialVersionUID = -8007831518629167537L;
+        
+        private final Serializable ownerId;
+        private final String role;
+
+        public PersistentCollectionChangeWorkUnitId(Serializable ownerId, String role) {
+            this.ownerId = ownerId;
+            this.role = role;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            PersistentCollectionChangeWorkUnitId that = (PersistentCollectionChangeWorkUnitId) o;
+
+            if (ownerId != null ? !ownerId.equals(that.ownerId) : that.ownerId != null) return false;
+            //noinspection RedundantIfStatement
+            if (role != null ? !role.equals(that.role) : that.role != null) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = ownerId != null ? ownerId.hashCode() : 0;
+            result = 31 * result + (role != null ? role.hashCode() : 0);
+            return result;
+        }
+    }
 }

Copied: core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/ternary/TernaryMapFlush.java (from rev 18113, core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/ternary/TernaryMap.java)
===================================================================
--- core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/ternary/TernaryMapFlush.java	                        (rev 0)
+++ core/trunk/envers/src/test/java/org/hibernate/envers/test/integration/manytomany/ternary/TernaryMapFlush.java	2009-12-08 16:08:13 UTC (rev 18163)
@@ -0,0 +1,144 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.envers.test.integration.manytomany.ternary;
+
+import org.hibernate.ejb.Ejb3Configuration;
+import org.hibernate.envers.test.AbstractEntityTest;
+import org.hibernate.envers.test.entities.IntTestEntity;
+import org.hibernate.envers.test.entities.StrTestEntity;
+import org.hibernate.envers.test.tools.TestTools;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.Test;
+
+import javax.persistence.EntityManager;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * @author Adam Warski (adam at warski dot org)
+ */
+public class TernaryMapFlush extends AbstractEntityTest {
+    private Integer str1_id;
+    private Integer str2_id;
+    private Integer int1_id;
+    private Integer int2_id;
+    private Integer map1_id;
+
+    public void configure(Ejb3Configuration cfg) {
+        cfg.addAnnotatedClass(TernaryMapEntity.class);
+        cfg.addAnnotatedClass(StrTestEntity.class);
+        cfg.addAnnotatedClass(IntTestEntity.class);
+    }
+
+    @Test
+    public void createData() {
+        EntityManager em = getEntityManager();
+
+        StrTestEntity str1 = new StrTestEntity("a");
+        StrTestEntity str2 = new StrTestEntity("b");
+        IntTestEntity int1 = new IntTestEntity(1);
+        IntTestEntity int2 = new IntTestEntity(2);
+        TernaryMapEntity map1 = new TernaryMapEntity();
+
+        // Revision 1 (int1 -> str1)
+        em.getTransaction().begin();
+
+        em.persist(str1);
+        em.persist(str2);
+        em.persist(int1);
+        em.persist(int2);
+
+        map1.getMap().put(int1, str1);
+
+        em.persist(map1);
+
+        em.getTransaction().commit();
+
+        // Revision 2 (removing int1->str1, flushing, adding int1->str1 again and a new int2->str2 mapping to force a change)
+
+        em.getTransaction().begin();
+
+        map1 = em.find(TernaryMapEntity.class, map1.getId());
+        str1 = em.find(StrTestEntity.class, str1.getId());
+        int1 = em.find(IntTestEntity.class, int1.getId());
+
+        map1.setMap(new HashMap<IntTestEntity, StrTestEntity>());
+        
+        em.flush();
+
+        map1.getMap().put(int1, str1);
+        map1.getMap().put(int2, str2);
+
+        em.getTransaction().commit();
+
+        // Revision 3 (removing int1->str1, flushing, overwriting int2->str1)
+
+        em.getTransaction().begin();
+
+        map1 = em.find(TernaryMapEntity.class, map1.getId());
+        str1 = em.find(StrTestEntity.class, str1.getId());
+        int1 = em.find(IntTestEntity.class, int1.getId());
+
+        map1.getMap().remove(int1);
+
+        em.flush();
+
+        map1.getMap().put(int2, str1);
+
+        em.getTransaction().commit();
+
+        //
+
+        map1_id = map1.getId();
+        str1_id = str1.getId();
+        str2_id = str2.getId();
+        int1_id = int1.getId();
+        int2_id = int2.getId();
+    }
+
+    @Test(dependsOnMethods = "createData")
+    public void testRevisionsCounts() {
+        assertEquals(Arrays.asList(1, 2, 3), getAuditReader().getRevisions(TernaryMapEntity.class, map1_id));
+        assertEquals(Arrays.asList(1), getAuditReader().getRevisions(StrTestEntity.class, str1_id));
+        assertEquals(Arrays.asList(1), getAuditReader().getRevisions(StrTestEntity.class, str2_id));
+        assertEquals(Arrays.asList(1) ,getAuditReader().getRevisions(IntTestEntity.class, int1_id));
+        assertEquals(Arrays.asList(1) ,getAuditReader().getRevisions(IntTestEntity.class, int2_id));
+    }
+
+    @Test(dependsOnMethods = "createData")
+    public void testHistoryOfMap1() {
+        StrTestEntity str1 = getEntityManager().find(StrTestEntity.class, str1_id);
+        StrTestEntity str2 = getEntityManager().find(StrTestEntity.class, str2_id);
+        IntTestEntity int1 = getEntityManager().find(IntTestEntity.class, int1_id);
+        IntTestEntity int2 = getEntityManager().find(IntTestEntity.class, int2_id);
+
+        TernaryMapEntity rev1 = getAuditReader().find(TernaryMapEntity.class, map1_id, 1);
+        TernaryMapEntity rev2 = getAuditReader().find(TernaryMapEntity.class, map1_id, 2);
+        TernaryMapEntity rev3 = getAuditReader().find(TernaryMapEntity.class, map1_id, 3);
+
+        assertEquals(rev1.getMap(), TestTools.makeMap(int1, str1));
+        assertEquals(rev2.getMap(), TestTools.makeMap(int1, str1, int2, str2));
+        assertEquals(rev3.getMap(), TestTools.makeMap(int2, str1));
+    }
+}
\ No newline at end of file



More information about the hibernate-commits mailing list