diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPostUpdateEventListenerImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPostUpdateEventListenerImpl.java index 944c99eb2f..44b5650db6 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPostUpdateEventListenerImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/event/EnversPostUpdateEventListenerImpl.java @@ -29,6 +29,7 @@ import org.hibernate.envers.synchronization.work.AuditWorkUnit; import org.hibernate.envers.synchronization.work.ModWorkUnit; import org.hibernate.event.spi.PostUpdateEvent; import org.hibernate.event.spi.PostUpdateEventListener; +import org.hibernate.persister.entity.EntityPersister; /** * @author Adam Warski (adam at warski dot org) @@ -47,13 +48,15 @@ public class EnversPostUpdateEventListenerImpl extends BaseEnversEventListener i if ( getAuditConfiguration().getEntCfg().isVersioned(entityName) ) { AuditProcess auditProcess = getAuditConfiguration().getSyncManager().get(event.getSession()); + final Object[] newDbState = postUpdateDBState( event ); + AuditWorkUnit workUnit = new ModWorkUnit( event.getSession(), event.getPersister().getEntityName(), getAuditConfiguration(), event.getId(), event.getPersister(), - event.getState(), + newDbState, event.getOldState() ); auditProcess.addWorkUnit( workUnit ); @@ -63,11 +66,24 @@ public class EnversPostUpdateEventListenerImpl extends BaseEnversEventListener i auditProcess, event.getPersister(), entityName, - event.getState(), + newDbState, event.getOldState(), event.getSession() ); } } } + + private Object[] postUpdateDBState(PostUpdateEvent event) { + Object[] newDbState = event.getState().clone(); + EntityPersister entityPersister = event.getPersister(); + for ( int i = 0; i < entityPersister.getPropertyNames().length; ++i ) { + if ( !entityPersister.getPropertyUpdateability()[i] ) { + // Assuming that PostUpdateEvent#getOldState() returns database state of the record before modification. + // Otherwise, we would have to execute SQL query to be sure of @Column(updatable = false) column value. + newDbState[i] = event.getOldState()[i]; + } + } + return newDbState; + } } diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/notupdatable/PropertyNotUpdatableEntity.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/notupdatable/PropertyNotUpdatableEntity.java new file mode 100644 index 0000000000..a2518649cb --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/notupdatable/PropertyNotUpdatableEntity.java @@ -0,0 +1,105 @@ +package org.hibernate.envers.test.integration.notupdatable; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class PropertyNotUpdatableEntity implements Serializable { + @Id + @GeneratedValue + private Long id; + + private String data; + + @Column(updatable = false) + private String constantData1; + + @Column(updatable = false) + private String constantData2; + + public PropertyNotUpdatableEntity() { + } + + public PropertyNotUpdatableEntity(String data, String constantData1, String constantData2) { + this.constantData1 = constantData1; + this.constantData2 = constantData2; + this.data = data; + } + + public PropertyNotUpdatableEntity(String data, String constantData1, String constantData2, Long id) { + this.data = data; + this.id = id; + this.constantData1 = constantData1; + this.constantData2 = constantData2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PropertyNotUpdatableEntity)) return false; + + PropertyNotUpdatableEntity that = (PropertyNotUpdatableEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (data != null ? !data.equals(that.data) : that.data != null) return false; + if (constantData1 != null ? !constantData1.equals(that.constantData1) : that.constantData1 != null) return false; + if (constantData2 != null ? !constantData2.equals(that.constantData2) : that.constantData2 != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (data != null ? data.hashCode() : 0); + result = 31 * result + (constantData1 != null ? constantData1.hashCode() : 0); + result = 31 * result + (constantData2 != null ? constantData2.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "PropertyNotUpdatableEntity(id = " + id + ", data = " + data + ", constantData1 = " + constantData1 + ", constantData2 = " + constantData2 + ")"; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getConstantData1() { + return constantData1; + } + + public void setConstantData1(String constantData1) { + this.constantData1 = constantData1; + } + + public String getConstantData2() { + return constantData2; + } + + public void setConstantData2(String constantData2) { + this.constantData2 = constantData2; + } +} diff --git a/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/notupdatable/PropertyNotUpdatableTest.java b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/notupdatable/PropertyNotUpdatableTest.java new file mode 100644 index 0000000000..edc43b1343 --- /dev/null +++ b/hibernate-envers/src/matrix/java/org/hibernate/envers/test/integration/notupdatable/PropertyNotUpdatableTest.java @@ -0,0 +1,95 @@ +package org.hibernate.envers.test.integration.notupdatable; + +import junit.framework.Assert; +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import javax.persistence.EntityManager; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue(jiraKey = "HHH-5411") +public class PropertyNotUpdatableTest extends BaseEnversJPAFunctionalTestCase { + private Long id = null; + + @Override + protected void addConfigOptions(Map options) { + options.put("org.hibernate.envers.store_data_at_delete", "true"); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { PropertyNotUpdatableEntity.class }; + } + + @Test + @Priority(10) + public void initData() { + // Revision 1 + EntityManager em = getEntityManager(); + em.getTransaction().begin(); + PropertyNotUpdatableEntity entity = new PropertyNotUpdatableEntity("data", "constant data 1", "constant data 2"); + em.persist(entity); + em.getTransaction().commit(); + + id = entity.getId(); + + // Revision 2 + em.getTransaction().begin(); + entity = em.find(PropertyNotUpdatableEntity.class, entity.getId()); + entity.setData("modified data"); + entity.setConstantData1(null); + em.merge(entity); + em.getTransaction().commit(); + + em.close(); + em = getEntityManager(); // Re-opening entity manager to re-initialize non-updatable fields + // with database values. Otherwise PostUpdateEvent#getOldState() returns previous + // memory state. This can be achieved using EntityManager#refresh(Object) method as well. + + // Revision 3 + em.getTransaction().begin(); + entity = em.find(PropertyNotUpdatableEntity.class, entity.getId()); + entity.setData("another modified data"); + entity.setConstantData2("invalid data"); + em.merge(entity); + em.getTransaction().commit(); + + // Revision 4 + em.getTransaction().begin(); + em.refresh(entity); + em.remove(entity); + em.getTransaction().commit(); + } + + @Test + public void testRevisionsCounts() { + Assert.assertEquals(Arrays.asList(1, 2, 3, 4), getAuditReader().getRevisions(PropertyNotUpdatableEntity.class, id)); + } + + @Test + public void testHistoryOfId() { + PropertyNotUpdatableEntity ver1 = new PropertyNotUpdatableEntity("data", "constant data 1", "constant data 2", id); + Assert.assertEquals(ver1, getAuditReader().find(PropertyNotUpdatableEntity.class, id, 1)); + + PropertyNotUpdatableEntity ver2 = new PropertyNotUpdatableEntity("modified data", "constant data 1", "constant data 2", id); + Assert.assertEquals(ver2, getAuditReader().find(PropertyNotUpdatableEntity.class, id, 2)); + + PropertyNotUpdatableEntity ver3 = new PropertyNotUpdatableEntity("another modified data", "constant data 1", "constant data 2", id); + Assert.assertEquals(ver3, getAuditReader().find(PropertyNotUpdatableEntity.class, id, 3)); + } + + @Test + public void testDeleteState() { + PropertyNotUpdatableEntity delete = new PropertyNotUpdatableEntity("another modified data", "constant data 1", "constant data 2", id); + List results = getAuditReader().createQuery().forRevisionsOfEntity(PropertyNotUpdatableEntity.class, true, true).getResultList(); + Assert.assertEquals(delete, results.get(3)); + } +}