diff --git a/documentation/src/main/docbook/devguide/en-US/Envers.xml b/documentation/src/main/docbook/devguide/en-US/Envers.xml index 0488ee2892..f6e533751a 100644 --- a/documentation/src/main/docbook/devguide/en-US/Envers.xml +++ b/documentation/src/main/docbook/devguide/en-US/Envers.xml @@ -308,6 +308,14 @@ @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED). Then, when reading historic versions of your entity, the relation will always point to the "current" related entity. + + + If you'd like to audit properties encapsulated by any subset of your entity's mapped superclasses (which are + not explicitly audited), list desired supertypes in auditParents attribute of + @Audited annotation. If any @MappedSuperclass + (or any of it's properties) is marked as @Audited, it's behavior is implicitly + inherited by all audited subclasses. +
diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/Audited.java b/hibernate-envers/src/main/java/org/hibernate/envers/Audited.java index a0d714e44e..784a53e5e4 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/Audited.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/Audited.java @@ -32,6 +32,7 @@ import java.lang.annotation.Target; * When applied to a field, indicates that this field should be audited. * @author Adam Warski (adam at warski dot org) * @author Tomasz Bech + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @@ -44,4 +45,12 @@ public @interface Audited { * This is useful for dictionary-like entities, which don't change and don't need to be audited. */ RelationTargetAuditMode targetAuditMode() default RelationTargetAuditMode.AUDITED; + + /** + * @return Set of superclasses which properties shall be audited. The behavior of listed classes + * is the same as if they had {@link Audited} annotation applied on a type level. The scope of this functionality + * is limited to the context of actually mapped entity and its class hierarchy. If a parent type lists any of + * its parent types using this attribute, all fields encapsulated by marked classes are implicitly audited. + */ + Class[] auditParents() default {}; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java index ea4a0f8cec..32eda20a70 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/reader/AuditedPropertiesReader.java @@ -1,6 +1,7 @@ package org.hibernate.envers.configuration.metadata.reader; import static org.hibernate.envers.tools.Tools.newHashSet; import java.lang.annotation.Annotation; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -33,6 +34,7 @@ import org.hibernate.mapping.Value; * @author Adam Warski (adam at warski dot org) * @author Erik-Berndt Scheper * @author Hern&aacut;n Chanfreau + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) */ public class AuditedPropertiesReader { protected final ModificationStore defaultStore; @@ -66,10 +68,50 @@ public class AuditedPropertiesReader { // First reading the access types for the persistent properties. readPersistentPropertiesAccess(); - // Adding all properties from the given class. - addPropertiesFromClass(persistentPropertiesSource.getXClass()); + // Retrieve classes that are explicitly marked for auditing process by any superclass of currently mapped + // entity or itself. + XClass clazz = persistentPropertiesSource.getXClass(); + Set declaredAuditedSuperclasses = new HashSet(); + doGetDeclaredAuditedSuperclasses(clazz, declaredAuditedSuperclasses); + + // Adding all properties from the given class. + addPropertiesFromClass(clazz, declaredAuditedSuperclasses); } + /** + * Recursively constructs a set of classes that have been declared for auditing process. + * @param clazz Class that is being processed. Currently mapped entity shall be passed during first invocation. + * @param declaredAuditedSuperclasses Total collection of classes listed in {@link Audited#auditParents()} property + * by any superclass starting with class specified as the first argument. + */ + @SuppressWarnings("unchecked") + private void doGetDeclaredAuditedSuperclasses(XClass clazz, Set declaredAuditedSuperclasses) { + Audited allClassAudited = clazz.getAnnotation(Audited.class); + if (allClassAudited != null && allClassAudited.auditParents().length > 0) { + for (Class c : allClassAudited.auditParents()) { + XClass parentClass = reflectionManager.toXClass(c); + checkSuperclass(clazz, parentClass); + declaredAuditedSuperclasses.add(parentClass); + } + } + XClass superclass = clazz.getSuperclass(); + if (!clazz.isInterface() && !Object.class.getName().equals(superclass.getName())) { + doGetDeclaredAuditedSuperclasses(superclass, declaredAuditedSuperclasses); + } + } + + /** + * Checks whether one class is assignable from another. If not {@link MappingException} is thrown. + * @param child Subclass. + * @param parent Superclass. + */ + private void checkSuperclass(XClass child, XClass parent) { + if (!parent.isAssignableFrom(child)) { + throw new MappingException("Class " + parent.getName() + " is not assignable from " + child.getName() + ". " + + "Please revise @Audited.auditParents value in " + child.getName() + " type."); + } + } + private void readPersistentPropertiesAccess() { Iterator propertyIter = persistentPropertiesSource.getPropertyIterator(); while (propertyIter.hasNext()) { @@ -82,8 +124,34 @@ public class AuditedPropertiesReader { } } - private void addPropertiesFromClass(XClass clazz) { - Audited allClassAudited = clazz.getAnnotation(Audited.class); + /** + * @param clazz Class which properties are currently being added. + * @param declaredAuditedSuperclasses Collection of superclasses that have been explicitly declared to be audited. + * @return {@link Audited} annotation of specified class. If processed type hasn't been explicitly marked, method + * checks whether given class exists in collection passed as the second argument. In case of success, + * {@link Audited} configuration of currently mapped entity is returned, otherwise {@code null}. + */ + private Audited computeAuditConfiguration(XClass clazz, Set declaredAuditedSuperclasses) { + Audited allClassAudited = clazz.getAnnotation(Audited.class); + // If processed class is not explicitly marked with @Audited annotation, check whether auditing is + // forced by any of its child entities configuration (@Audited.auditParents). + if (allClassAudited == null && declaredAuditedSuperclasses.contains(clazz)) { + // Declared audited parent copies @Audited.modStore and @Audited.targetAuditMode configuration from + // currently mapped entity. + allClassAudited = persistentPropertiesSource.getXClass().getAnnotation(Audited.class); + } + return allClassAudited; + } + + /** + * Recursively adds all audited properties of entity class and its superclasses. + * @param clazz Currently processed class. + * @param declaredAuditedSuperclasses Collection of classes that are declared to be audited + * (see {@link Audited#auditParents()}). + */ + private void addPropertiesFromClass(XClass clazz, Set declaredAuditedSuperclasses) { + Audited allClassAudited = computeAuditConfiguration(clazz, declaredAuditedSuperclasses); + //look in the class addFromProperties(clazz.getDeclaredProperties("field"), "field", fieldAccessedPersistentProperties, allClassAudited); addFromProperties(clazz.getDeclaredProperties("property"), "property", propertyAccessedPersistentProperties, allClassAudited); @@ -91,7 +159,7 @@ public class AuditedPropertiesReader { if(allClassAudited != null || !auditedPropertiesHolder.isEmpty()) { XClass superclazz = clazz.getSuperclass(); if (!clazz.isInterface() && !"java.lang.Object".equals(superclazz.getName())) { - addPropertiesFromClass(superclazz); + addPropertiesFromClass(superclazz, declaredAuditedSuperclasses); } } } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/BabyCompleteEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/BabyCompleteEntity.java new file mode 100644 index 0000000000..ae564aaea0 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/BabyCompleteEntity.java @@ -0,0 +1,53 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.StrIntTestEntity; + +import javax.persistence.Entity; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited(auditParents = {MappedParentEntity.class}) +public class BabyCompleteEntity extends ChildCompleteEntity { + private String baby; + + public BabyCompleteEntity() { + } + + public BabyCompleteEntity(Long id, String grandparent, String notAudited, String parent, String child, StrIntTestEntity relation, String baby) { + super(id, grandparent, notAudited, parent, child, relation); + this.baby = baby; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BabyCompleteEntity)) return false; + if (!super.equals(o)) return false; + + BabyCompleteEntity that = (BabyCompleteEntity) o; + + if (baby != null ? !baby.equals(that.baby) : that.baby != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (baby != null ? baby.hashCode() : 0); + return result; + } + + public String toString() { + return "BabyCompleteEntity(" + super.toString() + ", baby = " + baby + ")"; + } + + public String getBaby() { + return baby; + } + + public void setBaby(String baby) { + this.baby = baby; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildCompleteEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildCompleteEntity.java new file mode 100644 index 0000000000..b8152ac592 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildCompleteEntity.java @@ -0,0 +1,54 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.StrIntTestEntity; + +import javax.persistence.MappedSuperclass; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@MappedSuperclass +@Audited(auditParents = {MappedGrandparentEntity.class}) +public class ChildCompleteEntity extends MappedParentEntity { + private String child; + + public ChildCompleteEntity() { + super(null, null, null, null, null); + } + + public ChildCompleteEntity(Long id, String grandparent, String notAudited, String parent, String child, StrIntTestEntity relation) { + super(id, grandparent, notAudited, parent, relation); + this.child = child; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ChildCompleteEntity)) return false; + if (!super.equals(o)) return false; + + ChildCompleteEntity that = (ChildCompleteEntity) o; + + if (child != null ? !child.equals(that.child) : that.child != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (child != null ? child.hashCode() : 0); + return result; + } + + public String toString() { + return "ChildCompleteEntity(" + super.toString() + ", child = " + child + ")"; + } + + public String getChild() { + return child; + } + + public void setChild(String child) { + this.child = child; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildMultipleParentsEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildMultipleParentsEntity.java new file mode 100644 index 0000000000..2029067fd0 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildMultipleParentsEntity.java @@ -0,0 +1,55 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.StrIntTestEntity; + +import javax.persistence.Entity; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited(auditParents = {MappedParentEntity.class, MappedGrandparentEntity.class}) +public class ChildMultipleParentsEntity extends MappedParentEntity { + private String child; + + public ChildMultipleParentsEntity() { + super(null, null, null, null, null); + } + + public ChildMultipleParentsEntity(Long id, String grandparent, String notAudited, String parent, String child, StrIntTestEntity relation) { + super(id, grandparent, notAudited, parent, relation); + this.child = child; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ChildMultipleParentsEntity)) return false; + if (!super.equals(o)) return false; + + ChildMultipleParentsEntity that = (ChildMultipleParentsEntity) o; + + if (child != null ? !child.equals(that.child) : that.child != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (child != null ? child.hashCode() : 0); + return result; + } + + public String toString() { + return "ChildMultipleParentsEntity(" + super.toString() + ", child = " + child + ")"; + } + + public String getChild() { + return child; + } + + public void setChild(String child) { + this.child = child; + } +} + diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildSingleParentEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildSingleParentEntity.java new file mode 100644 index 0000000000..2670f3eabb --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ChildSingleParentEntity.java @@ -0,0 +1,54 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.entities.StrIntTestEntity; + +import javax.persistence.Entity; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited(auditParents = {MappedGrandparentEntity.class}) +public class ChildSingleParentEntity extends MappedParentEntity { + private String child; + + public ChildSingleParentEntity() { + super(null, null, null, null, null); + } + + public ChildSingleParentEntity(Long id, String grandparent, String notAudited, String parent, String child, StrIntTestEntity relation) { + super(id, grandparent, notAudited, parent, relation); + this.child = child; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ChildSingleParentEntity)) return false; + if (!super.equals(o)) return false; + + ChildSingleParentEntity that = (ChildSingleParentEntity) o; + + if (child != null ? !child.equals(that.child) : that.child != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (child != null ? child.hashCode() : 0); + return result; + } + + public String toString() { + return "ChildSingleParentEntity(" + super.toString() + ", child = " + child + ")"; + } + + public String getChild() { + return child; + } + + public void setChild(String child) { + this.child = child; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ExplicitTransitiveChildEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ExplicitTransitiveChildEntity.java new file mode 100644 index 0000000000..b392323571 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ExplicitTransitiveChildEntity.java @@ -0,0 +1,53 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited(auditParents = {TransitiveParentEntity.class}) +public class ExplicitTransitiveChildEntity extends TransitiveParentEntity { + private String child; + + public ExplicitTransitiveChildEntity() { + super(null, null, null, null); + } + + public ExplicitTransitiveChildEntity(Long id, String grandparent, String notAudited, String parent, String child) { + super(id, grandparent, notAudited, parent); + this.child = child; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ExplicitTransitiveChildEntity)) return false; + if (!super.equals(o)) return false; + + ExplicitTransitiveChildEntity that = (ExplicitTransitiveChildEntity) o; + + if (child != null ? !child.equals(that.child) : that.child != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (child != null ? child.hashCode() : 0); + return result; + } + + public String toString() { + return "ExplicitTransitiveChildEntity(" + super.toString() + ", child = " + child + ")"; + } + + public String getChild() { + return child; + } + + public void setChild(String child) { + this.child = child; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ImplicitTransitiveChildEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ImplicitTransitiveChildEntity.java new file mode 100644 index 0000000000..a5c010374a --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/ImplicitTransitiveChildEntity.java @@ -0,0 +1,53 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +public class ImplicitTransitiveChildEntity extends TransitiveParentEntity { + private String child; + + public ImplicitTransitiveChildEntity() { + super(null, null, null, null); + } + + public ImplicitTransitiveChildEntity(Long id, String grandparent, String notAudited, String parent, String child) { + super(id, grandparent, notAudited, parent); + this.child = child; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ImplicitTransitiveChildEntity)) return false; + if (!super.equals(o)) return false; + + ImplicitTransitiveChildEntity that = (ImplicitTransitiveChildEntity) o; + + if (child != null ? !child.equals(that.child) : that.child != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (child != null ? child.hashCode() : 0); + return result; + } + + public String toString() { + return "ImplicitTransitiveChildEntity(" + super.toString() + ", child = " + child + ")"; + } + + public String getChild() { + return child; + } + + public void setChild(String child) { + this.child = child; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MappedGrandparentEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MappedGrandparentEntity.java new file mode 100644 index 0000000000..79a4f20ff3 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MappedGrandparentEntity.java @@ -0,0 +1,74 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.NotAudited; + +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@MappedSuperclass +public class MappedGrandparentEntity { + @Id + private Long id; + + private String grandparent; + + @NotAudited + private String notAudited; + + public MappedGrandparentEntity(Long id, String grandparent, String notAudited) { + this.id = id; + this.grandparent = grandparent; + this.notAudited = notAudited; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MappedGrandparentEntity)) return false; + + MappedGrandparentEntity that = (MappedGrandparentEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (grandparent != null ? !grandparent.equals(that.grandparent) : that.grandparent != null) return false; + if (notAudited != null ? !notAudited.equals(that.notAudited) : that.notAudited != null) return false; + + return true; + } + + public int hashCode() { + int result = (id != null ? id.hashCode() : 0); + result = 31 * result + (grandparent != null ? grandparent.hashCode() : 0); + result = 31 * result + (notAudited != null ? notAudited.hashCode() : 0); + return result; + } + + public String toString() { + return "MappedGrandparentEntity(id = " + id + ", grandparent = " + grandparent + ", notAudited = " + notAudited + ")"; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getGrandparent() { + return grandparent; + } + + public void setGrandparent(String grandparent) { + this.grandparent = grandparent; + } + + public String getNotAudited() { + return notAudited; + } + + public void setNotAudited(String notAudited) { + this.notAudited = notAudited; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MappedParentEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MappedParentEntity.java new file mode 100644 index 0000000000..9d83b0e754 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MappedParentEntity.java @@ -0,0 +1,61 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.test.entities.StrIntTestEntity; + +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@MappedSuperclass +public class MappedParentEntity extends MappedGrandparentEntity { + private String parent; + + @ManyToOne + private StrIntTestEntity relation; + + public MappedParentEntity(Long id, String grandparent, String notAudited, String parent, StrIntTestEntity relation) { + super(id, grandparent, notAudited); + this.parent = parent; + this.relation = relation; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MappedParentEntity)) return false; + if (!super.equals(o)) return false; + + MappedParentEntity that = (MappedParentEntity) o; + + if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (parent != null ? parent.hashCode() : 0); + return result; + } + + public String toString() { + return "MappedParentEntity(" + super.toString() + ", parent = " + parent + ", relation = " + relation + ")"; + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } + + public StrIntTestEntity getRelation() { + return relation; + } + + public void setRelation(StrIntTestEntity relation) { + this.relation = relation; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MultipleAuditParentsTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MultipleAuditParentsTest.java new file mode 100644 index 0000000000..369133496d --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/MultipleAuditParentsTest.java @@ -0,0 +1,73 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.AbstractEntityTest; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.StrIntTestEntity; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.EntityManager; +import javax.persistence.MappedSuperclass; +import java.util.Set; + +/** + * Tests mapping of child entity that declares all of its ancestors as audited with {@link Audited#auditParents()} property. + * All supperclasses are marked with {@link MappedSuperclass} annotation but not {@link Audited}. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class MultipleAuditParentsTest extends AbstractEntityTest { + private long childMultipleId = 1L; + private Integer siteMultipleId = null; + + @Override + public void configure(Ejb3Configuration cfg) { + cfg.addAnnotatedClass(MappedGrandparentEntity.class); + cfg.addAnnotatedClass(MappedParentEntity.class); + cfg.addAnnotatedClass(ChildMultipleParentsEntity.class); + cfg.addAnnotatedClass(StrIntTestEntity.class); + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + // Revision 1 + em.getTransaction().begin(); + StrIntTestEntity siteMultiple = new StrIntTestEntity("data 1", 1); + em.persist(siteMultiple); + em.persist(new ChildMultipleParentsEntity(childMultipleId, "grandparent 1", "notAudited 1", "parent 1", "child 1", siteMultiple)); + em.getTransaction().commit(); + siteMultipleId = siteMultiple.getId(); + } + + @Test + public void testCreatedAuditTable() { + Set expectedColumns = TestTools.makeSet("child", "parent", "relation_id", "grandparent", "id"); + Set unexpectedColumns = TestTools.makeSet("notAudited"); + + Table table = getCfg().getClassMapping("org.hibernate.envers.test.integration.superclass.auditparents.ChildMultipleParentsEntity_AUD").getTable(); + + for (String columnName : expectedColumns) { + // Check whether expected column exists. + Assert.assertNotNull(table.getColumn(new Column(columnName))); + } + for (String columnName : unexpectedColumns) { + // Check whether unexpected column does not exist. + Assert.assertNull(table.getColumn(new Column(columnName))); + } + } + + @Test + public void testMultipleAuditParents() { + // expectedMultipleChild.notAudited shall be null, because it is not audited. + ChildMultipleParentsEntity expectedMultipleChild = new ChildMultipleParentsEntity(childMultipleId, "grandparent 1", null, "parent 1", "child 1", new StrIntTestEntity("data 1", 1, siteMultipleId)); + ChildMultipleParentsEntity child = getAuditReader().find(ChildMultipleParentsEntity.class, childMultipleId, 1); + Assert.assertEquals(expectedMultipleChild, child); + Assert.assertEquals(expectedMultipleChild.getRelation().getId(), child.getRelation().getId()); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/SingleAuditParentsTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/SingleAuditParentsTest.java new file mode 100644 index 0000000000..37c8b9f374 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/SingleAuditParentsTest.java @@ -0,0 +1,73 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.AbstractEntityTest; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.StrIntTestEntity; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.EntityManager; +import javax.persistence.MappedSuperclass; +import java.util.Set; + +/** + * Tests mapping of child entity that declares one of its ancestors as audited with {@link Audited#auditParents()} property. + * All supperclasses are marked with {@link MappedSuperclass} annotation but not {@link Audited}. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class SingleAuditParentsTest extends AbstractEntityTest { + private long childSingleId = 1L; + private Integer siteSingleId = null; + + @Override + public void configure(Ejb3Configuration cfg) { + cfg.addAnnotatedClass(MappedGrandparentEntity.class); + cfg.addAnnotatedClass(MappedParentEntity.class); + cfg.addAnnotatedClass(ChildSingleParentEntity.class); + cfg.addAnnotatedClass(StrIntTestEntity.class); + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + // Revision 1 + em.getTransaction().begin(); + StrIntTestEntity siteSingle = new StrIntTestEntity("data 1", 1); + em.persist(siteSingle); + em.persist(new ChildSingleParentEntity(childSingleId, "grandparent 1", "notAudited 1", "parent 1", "child 1", siteSingle)); + em.getTransaction().commit(); + siteSingleId = siteSingle.getId(); + } + + @Test + public void testCreatedAuditTable() { + Set expectedColumns = TestTools.makeSet("child", "grandparent", "id"); + Set unexpectedColumns = TestTools.makeSet("parent", "relation_id", "notAudited"); + + Table table = getCfg().getClassMapping("org.hibernate.envers.test.integration.superclass.auditparents.ChildSingleParentEntity_AUD").getTable(); + + for (String columnName : expectedColumns) { + // Check whether expected column exists. + Assert.assertNotNull(table.getColumn(new Column(columnName))); + } + for (String columnName : unexpectedColumns) { + // Check whether unexpected column does not exist. + Assert.assertNull(table.getColumn(new Column(columnName))); + } + } + + @Test + public void testSingleAuditParent() { + // expectedSingleChild.parent, expectedSingleChild.relation and expectedSingleChild.notAudited shall be null, because they are not audited. + ChildSingleParentEntity expectedSingleChild = new ChildSingleParentEntity(childSingleId, "grandparent 1", null, null, "child 1", null); + ChildSingleParentEntity child = getAuditReader().find(ChildSingleParentEntity.class, childSingleId, 1); + Assert.assertEquals(expectedSingleChild, child); + Assert.assertNull(child.getRelation()); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TotalAuditParentsTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TotalAuditParentsTest.java new file mode 100644 index 0000000000..0176ac0b91 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TotalAuditParentsTest.java @@ -0,0 +1,74 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.AbstractEntityTest; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.entities.StrIntTestEntity; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.EntityManager; +import java.util.Set; + +/** + * Tests mapping of baby entity which declares its parent as audited with {@link Audited#auditParents()} property. + * Moreover, child class (mapped superclass of baby entity) declares grandparent entity as audited. In this case all + * attributes of baby class shall be audited. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class TotalAuditParentsTest extends AbstractEntityTest { + private long babyCompleteId = 1L; + private Integer siteCompleteId = null; + + @Override + public void configure(Ejb3Configuration cfg) { + cfg.addAnnotatedClass(MappedGrandparentEntity.class); + cfg.addAnnotatedClass(MappedParentEntity.class); + cfg.addAnnotatedClass(StrIntTestEntity.class); + cfg.addAnnotatedClass(ChildCompleteEntity.class); + cfg.addAnnotatedClass(BabyCompleteEntity.class); + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + // Revision 1 + em.getTransaction().begin(); + StrIntTestEntity siteComplete = new StrIntTestEntity("data 1", 1); + em.persist(siteComplete); + em.persist(new BabyCompleteEntity(babyCompleteId, "grandparent 1", "notAudited 1", "parent 1", "child 1", siteComplete, "baby 1")); + em.getTransaction().commit(); + siteCompleteId = siteComplete.getId(); + } + + @Test + public void testCreatedAuditTable() { + Set expectedColumns = TestTools.makeSet("baby", "child", "parent", "relation_id", "grandparent", "id"); + Set unexpectedColumns = TestTools.makeSet("notAudited"); + + Table table = getCfg().getClassMapping("org.hibernate.envers.test.integration.superclass.auditparents.BabyCompleteEntity_AUD").getTable(); + + for (String columnName : expectedColumns) { + // Check whether expected column exists. + Assert.assertNotNull(table.getColumn(new Column(columnName))); + } + for (String columnName : unexpectedColumns) { + // Check whether unexpected column does not exist. + Assert.assertNull(table.getColumn(new Column(columnName))); + } + } + + @Test + public void testCompleteAuditParents() { + // expectedBaby.notAudited shall be null, because it is not audited. + BabyCompleteEntity expectedBaby = new BabyCompleteEntity(babyCompleteId, "grandparent 1", null, "parent 1", "child 1", new StrIntTestEntity("data 1", 1, siteCompleteId), "baby 1"); + BabyCompleteEntity baby = getAuditReader().find(BabyCompleteEntity.class, babyCompleteId, 1); + Assert.assertEquals(expectedBaby, baby); + Assert.assertEquals(expectedBaby.getRelation().getId(), baby.getRelation().getId()); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TransitiveAuditParentsTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TransitiveAuditParentsTest.java new file mode 100644 index 0000000000..9cfa82d7de --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TransitiveAuditParentsTest.java @@ -0,0 +1,84 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.envers.Audited; +import org.hibernate.envers.test.AbstractEntityTest; +import org.hibernate.envers.test.Priority; +import org.hibernate.envers.test.tools.TestTools; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; +import org.junit.Assert; +import org.junit.Test; + +import javax.persistence.EntityManager; +import java.util.Set; + +/** + * Tests mapping of child entity which parent declares one of its ancestors as audited with {@link Audited#auditParents()} + * property. Child entity may mark explicitly its parent as audited or not. + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +public class TransitiveAuditParentsTest extends AbstractEntityTest { + private long childImpTransId = 1L; + private long childExpTransId = 2L; + + @Override + public void configure(Ejb3Configuration cfg) { + cfg.addAnnotatedClass(MappedGrandparentEntity.class); + cfg.addAnnotatedClass(TransitiveParentEntity.class); + cfg.addAnnotatedClass(ImplicitTransitiveChildEntity.class); + cfg.addAnnotatedClass(ExplicitTransitiveChildEntity.class); + } + + @Test + @Priority(10) + public void initData() { + EntityManager em = getEntityManager(); + + // Revision 1 + em.getTransaction().begin(); + em.persist(new ImplicitTransitiveChildEntity(childImpTransId, "grandparent 1", "notAudited 1", "parent 1", "child 1")); + em.getTransaction().commit(); + + // Revision 2 + em.getTransaction().begin(); + em.persist(new ExplicitTransitiveChildEntity(childExpTransId, "grandparent 2", "notAudited 2", "parent 2", "child 2")); + em.getTransaction().commit(); + } + + @Test + public void testCreatedAuditTables() { + Table explicitTransChildTable = getCfg().getClassMapping("org.hibernate.envers.test.integration.superclass.auditparents.ExplicitTransitiveChildEntity_AUD").getTable(); + checkTableColumns(TestTools.makeSet("child", "parent", "grandparent", "id"), TestTools.makeSet("notAudited"), explicitTransChildTable); + + Table implicitTransChildTable = getCfg().getClassMapping("org.hibernate.envers.test.integration.superclass.auditparents.ImplicitTransitiveChildEntity_AUD").getTable(); + checkTableColumns(TestTools.makeSet("child", "parent", "grandparent", "id"), TestTools.makeSet("notAudited"), implicitTransChildTable); + } + + private void checkTableColumns(Set expectedColumns, Set unexpectedColumns, Table table) { + for (String columnName : expectedColumns) { + // Check whether expected column exists. + Assert.assertNotNull(table.getColumn(new Column(columnName))); + } + for (String columnName : unexpectedColumns) { + // Check whether unexpected column does not exist. + Assert.assertNull(table.getColumn(new Column(columnName))); + } + } + + @Test + public void testImplicitTransitiveAuditParents() { + // expectedChild.notAudited shall be null, because it is not audited. + ImplicitTransitiveChildEntity expectedChild = new ImplicitTransitiveChildEntity(childImpTransId, "grandparent 1", null, "parent 1", "child 1"); + ImplicitTransitiveChildEntity child = getAuditReader().find(ImplicitTransitiveChildEntity.class, childImpTransId, 1); + Assert.assertEquals(expectedChild, child); + } + + @Test + public void testExplicitTransitiveAuditParents() { + // expectedChild.notAudited shall be null, because it is not audited. + ExplicitTransitiveChildEntity expectedChild = new ExplicitTransitiveChildEntity(childExpTransId, "grandparent 2", null, "parent 2", "child 2"); + ExplicitTransitiveChildEntity child = getAuditReader().find(ExplicitTransitiveChildEntity.class, childExpTransId, 2); + Assert.assertEquals(expectedChild, child); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TransitiveParentEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TransitiveParentEntity.java new file mode 100644 index 0000000000..e58c979f65 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/superclass/auditparents/TransitiveParentEntity.java @@ -0,0 +1,53 @@ +package org.hibernate.envers.test.integration.superclass.auditparents; + +import org.hibernate.envers.Audited; + +import javax.persistence.MappedSuperclass; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@MappedSuperclass +@Audited(auditParents = {MappedGrandparentEntity.class}) +public class TransitiveParentEntity extends MappedGrandparentEntity { + private String parent; + + public TransitiveParentEntity() { + super(null, null, null); + } + + public TransitiveParentEntity(Long id, String grandparent, String notAudited, String parent) { + super(id, grandparent, notAudited); + this.parent = parent; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TransitiveParentEntity)) return false; + if (!super.equals(o)) return false; + + TransitiveParentEntity that = (TransitiveParentEntity) o; + + if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false; + + return true; + } + + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (parent != null ? parent.hashCode() : 0); + return result; + } + + public String toString() { + return "TransitiveParentEntity(" + super.toString() + ", parent = " + parent + ")"; + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } +}