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;
+ }
+}