diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java b/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java index d0bae463f5..5317e88e32 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/event/BaseEnversCollectionEventListener.java @@ -231,7 +231,7 @@ public abstract class BaseEnversCollectionEventListener extends BaseEnversEventL auditProcess.addWorkUnit( new CollectionChangeWorkUnit( event.getSession(), - relatedEntityName, + event.getSession().bestGuessEntityName(relatedObj), getAuditConfiguration(), relatedId, relatedObj diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/AuditProcess.java b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/AuditProcess.java index 7937006539..38b286b61f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/AuditProcess.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/synchronization/AuditProcess.java @@ -34,9 +34,11 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.envers.revisioninfo.RevisionInfoGenerator; import org.hibernate.envers.synchronization.work.AuditWorkUnit; import org.hibernate.envers.tools.Pair; +import org.hibernate.tuple.entity.EntityMetamodel; /** * @author Adam Warski (adam at warski dot org) + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class AuditProcess implements BeforeTransactionCompletionProcess { private final RevisionInfoGenerator revisionInfoGenerator; @@ -78,9 +80,8 @@ public class AuditProcess implements BeforeTransactionCompletionProcess { String entityName = vwu.getEntityName(); Pair usedIdsKey = Pair.make(entityName, entityId); - if (usedIds.containsKey(usedIdsKey)) { - AuditWorkUnit other = usedIds.get(usedIdsKey); - + AuditWorkUnit other = getAlreadyScheduledWorkUnit(usedIdsKey); + if (other != null) { AuditWorkUnit result = vwu.dispatch(other); if (result != other) { @@ -99,6 +100,25 @@ public class AuditProcess implements BeforeTransactionCompletionProcess { } } + /** + * Checks if another work unit associated with the same entity hierarchy and identifier has already been scheduled. + * @param idKey Work unit's identifier. + * @return Corresponding work unit or {@code null} if no satisfying result was found. + */ + private AuditWorkUnit getAlreadyScheduledWorkUnit(Pair idKey) { + EntityMetamodel entityMetamodel = session.getFactory().getEntityPersister(idKey.getFirst()).getEntityMetamodel(); + String rootEntityName = entityMetamodel.getRootName(); + EntityMetamodel rootEntityMetamodel = session.getFactory().getEntityPersister(rootEntityName).getEntityMetamodel(); + // Checking all possible subtypes, supertypes and the actual class. + for (Object entityName : rootEntityMetamodel.getSubclassEntityNames()) { + Pair key = Pair.make((String) entityName, idKey.getSecond()); + if (usedIds.containsKey(key)) { + return usedIds.get(key); + } + } + return null; + } + private void executeInSession(Session session) { // Making sure the revision data is persisted. Object currentRevisionData = getCurrentRevisionData(session, true); diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/ParentReferencingChildTest.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/ParentReferencingChildTest.java new file mode 100644 index 0000000000..f4eb7c48a7 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/ParentReferencingChildTest.java @@ -0,0 +1,155 @@ +package org.hibernate.envers.test.integration.inheritance.joined.relation; + +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.test.AbstractEntityTest; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.testing.TestForIssue; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.EntityManager; +import java.util.Arrays; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-3843") +public class ParentReferencingChildTest extends AbstractEntityTest { + Person expLukaszRev1 = null; + Person expLukaszRev2 = null; + Person expAdamRev4 = null; + Role expDirectorRev3 = null; + Role expAdminRev2 = null; + Role expAdminRev1 = null; + + public void configure(Ejb3Configuration cfg) { + cfg.addAnnotatedClass(Person.class); + cfg.addAnnotatedClass(Role.class); + cfg.addAnnotatedClass(RightsSubject.class); + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 + em.getTransaction().begin(); + Person lukasz = new Person(); + lukasz.setName("Lukasz"); + lukasz.setGroup("IT"); + em.persist(lukasz); + Role admin = new Role(); + admin.setName("Admin"); + admin.setGroup("Confidential"); + lukasz.getRoles().add(admin); + admin.getMembers().add(lukasz); + em.persist(admin); + em.getTransaction().commit(); + + expLukaszRev1 = new Person(lukasz.getId(), "IT", "Lukasz"); + expAdminRev1 = new Role(admin.getId(), "Confidential", "Admin"); + + // Revision 2 + em.getTransaction().begin(); + lukasz = em.find(Person.class, lukasz.getId()); + lukasz.setGroup("Senior IT"); + lukasz.setName("Lukasz Antoniak"); + admin = em.find(Role.class, admin.getId()); + admin.setGroup("Very Confidential"); + em.getTransaction().commit(); + + expAdminRev2 = new Role(admin.getId(), "Very Confidential", "Admin"); + expLukaszRev2 = new Person(lukasz.getId(), "Senior IT", "Lukasz Antoniak"); + + // Revision 3 + em.getTransaction().begin(); + lukasz = em.find(Person.class, lukasz.getId()); + Role director = new Role(); + director.setName("Director"); + director.getMembers().add(lukasz); + em.persist(director); + lukasz.getRoles().add(director); + em.getTransaction().commit(); + + expDirectorRev3 = new Role(director.getId(), null, "Director"); + + // Revision 4 + em.getTransaction().begin(); + Person adam = new Person(); + adam.setName("Adam"); + adam.setGroup("CEO"); + em.persist(adam); + director = em.find(Role.class, director.getId()); + director.getMembers().add(adam); + adam.getRoles().add(director); + em.getTransaction().commit(); + + expAdamRev4 = new Person(adam.getId(), "CEO", "Adam"); + + // Revision 5 + em.getTransaction().begin(); + adam = em.find(Person.class, adam.getId()); + admin = em.find(Role.class, admin.getId()); + admin.getMembers().add(adam); + em.getTransaction().commit(); + + // Revision 6 + em.getTransaction().begin(); + adam = em.find(Person.class, adam.getId()); + adam.setName("Adam Warski"); + em.getTransaction().commit(); + } + + @Test + public void testRevisionsCounts() { + Assert.assertEquals(Arrays.asList(1, 2, 3), getAuditReader().getRevisions(Person.class, expLukaszRev1.getId())); + Assert.assertEquals(Arrays.asList(1, 2, 3), getAuditReader().getRevisions(RightsSubject.class, expLukaszRev1.getId())); + + Assert.assertEquals(Arrays.asList(4, 5, 6), getAuditReader().getRevisions(Person.class, expAdamRev4.getId())); + Assert.assertEquals(Arrays.asList(4, 5, 6), getAuditReader().getRevisions(RightsSubject.class, expAdamRev4.getId())); + + Assert.assertEquals(Arrays.asList(1, 2, 5), getAuditReader().getRevisions(Role.class, expAdminRev1.getId())); + Assert.assertEquals(Arrays.asList(1, 2, 5), getAuditReader().getRevisions(RightsSubject.class, expAdminRev1.getId())); + + Assert.assertEquals(Arrays.asList(3, 4), getAuditReader().getRevisions(Role.class, expDirectorRev3.getId())); + Assert.assertEquals(Arrays.asList(3, 4), getAuditReader().getRevisions(RightsSubject.class, expDirectorRev3.getId())); + } + + @Test + public void testHistoryOfAdam() { + Person adamRev4 = getAuditReader().find(Person.class, expAdamRev4.getId(), 4); + RightsSubject rightsSubjectRev5 = getAuditReader().find(RightsSubject.class, expAdamRev4.getId(), 5); + + Assert.assertEquals(expAdamRev4, adamRev4); + Assert.assertEquals(TestTools.makeSet(expDirectorRev3), adamRev4.getRoles()); + Assert.assertEquals(TestTools.makeSet(expDirectorRev3, expAdminRev2), rightsSubjectRev5.getRoles()); + } + + @Test + public void testHistoryOfLukasz() { + Person lukaszRev1 = getAuditReader().find(Person.class, expLukaszRev1.getId(), 1); + Person lukaszRev2 = getAuditReader().find(Person.class, expLukaszRev1.getId(), 2); + RightsSubject rightsSubjectRev3 = getAuditReader().find(RightsSubject.class, expLukaszRev1.getId(), 3); + Person lukaszRev3 = getAuditReader().find(Person.class, expLukaszRev1.getId(), 3); + + Assert.assertEquals(expLukaszRev1, lukaszRev1); + Assert.assertEquals(expLukaszRev2, lukaszRev2); + Assert.assertEquals(TestTools.makeSet(expAdminRev1), lukaszRev1.getRoles()); + Assert.assertEquals(TestTools.makeSet(expAdminRev2, expDirectorRev3), rightsSubjectRev3.getRoles()); + Assert.assertEquals(TestTools.makeSet(expAdminRev2, expDirectorRev3), lukaszRev3.getRoles()); + } + + @Test + public void testHistoryOfAdmin() { + Role adminRev1 = getAuditReader().find(Role.class, expAdminRev1.getId(), 1); + Role adminRev2 = getAuditReader().find(Role.class, expAdminRev1.getId(), 2); + Role adminRev5 = getAuditReader().find(Role.class, expAdminRev1.getId(), 5); + + Assert.assertEquals(expAdminRev1, adminRev1); + Assert.assertEquals(expAdminRev2, adminRev2); + Assert.assertEquals(TestTools.makeSet(expLukaszRev1), adminRev1.getMembers()); + Assert.assertEquals(TestTools.makeSet(expLukaszRev2, expAdamRev4), adminRev5.getMembers()); + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/Person.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/Person.java new file mode 100644 index 0000000000..59a8963e68 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/Person.java @@ -0,0 +1,56 @@ +package org.hibernate.envers.test.integration.inheritance.joined.relation; + +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Table(name = "APP_PERSON") +@Audited +public class Person extends RightsSubject { + private String name; + + public Person() { + } + + public Person(Long id, String group, String name) { + super(id, group); + this.name = name; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Person)) return false; + if (!super.equals(o)) return false; + + Person person = (Person) o; + + if (name != null ? !name.equals(person.name) : person.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Person(" + super.toString() + ", name = " + name + ")"; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/RightsSubject.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/RightsSubject.java new file mode 100644 index 0000000000..9926523f0a --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/RightsSubject.java @@ -0,0 +1,94 @@ +package org.hibernate.envers.test.integration.inheritance.joined.relation; + +import org.hibernate.envers.Audited; + +import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Table(name = "APP_RIGHTSSUBJCT") +@Inheritance(strategy = InheritanceType.JOINED) +@Audited +public class RightsSubject { + @Id + @GeneratedValue + private Long id; + + @Version + private Long version; + + @Column(name = "APP_GROUP") + private String group; + + @ManyToMany(mappedBy="members") + private Set roles = new HashSet(); + + public RightsSubject() { + } + + public RightsSubject(Long id, String group) { + this.id = id; + this.group = group; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RightsSubject)) return false; + + RightsSubject that = (RightsSubject) o; + + if (group != null ? !group.equals(that.group) : that.group != null) return false; + if (id != null ? !id.equals(that.id) : that.id != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (group != null ? group.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "RightsSubject(id = " + id + ", version = " + version + ", group = " + group + ")"; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/Role.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/Role.java new file mode 100644 index 0000000000..37233ba056 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/inheritance/joined/relation/Role.java @@ -0,0 +1,70 @@ +package org.hibernate.envers.test.integration.inheritance.joined.relation; + +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Table(name = "APP_ROLE") +@Audited +public class Role extends RightsSubject { + private String name; + + @ManyToMany + private Set members = new HashSet(); + + public Role() { + } + + public Role(Long id, String group, String name) { + super(id, group); + this.name = name; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Role)) return false; + if (!super.equals(o)) return false; + + Role role = (Role) o; + + if (name != null ? !name.equals(role.name) : role.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Role(" + super.toString() + ", name = " + name + ")"; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getMembers() { + return members; + } + + public void setMembers(Set members) { + this.members = members; + } +}