HHH-5580 - Tracking entity names in revision

This commit is contained in:
Lukasz Antoniak 2011-04-27 23:49:22 +02:00
parent 86c15fd212
commit 98342a7e2d
25 changed files with 1294 additions and 94 deletions

View File

@ -243,6 +243,22 @@
evaluates to true
</entry>
</row>
<row>
<entry>
<property>org.hibernate.envers.track_entities_changed_in_revision</property>
</entry>
<entry>
false
</entry>
<entry>
Should entity types, that have been modified during each revision, be tracked. The default
implementation creates <literal>REVENTITY</literal> table that stores fully qualified names
of Java classes changed in a specified revision. Each record encapsulates the revision
identifier (foreign key to <literal>REVINFO</literal> table) and a string value. For more
information refer to <xref linkend="envers-tracking-modified-entities-reventity"/> and
<xref linkend="envers-tracking-modified-entities-queries"/>.
</entry>
</row>
</tbody>
</tgroup>
</table>
@ -446,6 +462,142 @@ public class ExampleListener implements RevisionListener {
</example>
<section id="envers-tracking-modified-entities-reventity">
<title>Tracking entity types modified in revision</title>
<para>
By default entity types that have been changed in each revision are not being tracked. This implies the
necessity to query all tables storing audited data in order to retrieve changes made during
specified revision. Envers provides a simple mechanism that creates <literal>REVENTITY</literal>
table which stores fully qualified names of Java classes changed in each revision.
Single record encapsulates the revision identifier (foreign key to <literal>REVINFO</literal> table)
and a string value.
</para>
<para>
Tracking of modified entity types can be enabled in three different ways:
</para>
<orderedlist>
<listitem>
Set <property>org.hibernate.envers.track_entities_changed_in_revision</property> parameter to
<literal>true</literal>. In this case
<classname>org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity</classname> will
be implicitly used as the revision log entity.
</listitem>
<listitem>
Create a custom revision entity that extends
<classname>org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity</classname> class.
<programlisting>
<![CDATA[@Entity
@RevisionEntity
public class ExtendedRevisionEntity extends DefaultTrackingModifiedTypesRevisionEntity {
...
}]]></programlisting>
</listitem>
<listitem>
Mark an appropriate field of a custom revision entity with
<interfacename>@org.hibernate.envers.ModifiedEntityNames</interfacename> annotation. The property is
required to be of <literal><![CDATA[Set<String>]]></literal> type.
<programlisting>
<![CDATA[@Entity
@RevisionEntity
public class AnnotatedTrackingRevisionEntity {
...
@ElementCollection
@JoinTable(name = "REVENTITY", joinColumns = @JoinColumn(name = "REV"))
@Column(name = "ENTITYNAME")
@ModifiedEntityNames
private Set<String> modifiedEntityNames;
...
}]]></programlisting>
</listitem>
</orderedlist>
<para>
Users utilizing one of the approaches listed above can retrieve all entities modified in each revision
by using API described in <xref linkend="envers-tracking-modified-entities-queries"/>.
</para>
<para>
Users are also allowed to implement custom mechanism of tracking modified entity types. In this case, they
shall pass their own implementation of
<interfacename>org.hibernate.envers.EntityTrackingRevisionListener</interfacename> interface as the value
of <interfacename>@org.hibernate.envers.RevisionEntity</interfacename> annotation.
<interfacename>org.hibernate.envers.EntityTrackingRevisionListener</interfacename> interface exposes two
methods that notify user whenever tracking of each audited entity instance is started
(<methodname>addEntityToRevision</methodname>) or stopped (<methodname>removeEntityFromRevision</methodname>)
within the current revision boundaries.
</para>
<example>
<title>Custom implementation of tracking entity types modified in revision</title>
<programlisting>
<filename>CustomEntityTrackingRevisionListener.java</filename>
<![CDATA[
public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener {
@Override
public void addEntityToRevision(String entityName, Object revisionEntity) {
((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityName(entityName);
}
@Override
public void removeEntityFromRevision(String entityName, Object revisionEntity) {
((CustomTrackingRevisionEntity)revisionEntity).removeModifiedEntityName(entityName);
}
@Override
public void newRevision(Object revisionEntity) {
}
}]]></programlisting>
<programlisting>
<filename>CustomTrackingRevisionEntity.java</filename>
<![CDATA[
@Entity
@RevisionEntity(CustomEntityTrackingRevisionListener.class)
public class CustomTrackingRevisionEntity {
@Id
@GeneratedValue
@RevisionNumber
private int customId;
@RevisionTimestamp
private long customTimestamp;
@OneToMany(mappedBy="revision", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private Set<ModifiedEntityNameEntity> modifiedEntityNames = new HashSet<ModifiedEntityNameEntity>();
public void addModifiedEntityName(String entityName) {
modifiedEntityNames.add(new ModifiedEntityNameEntity(this, entityName));
}
public void removeModifiedEntityName(String entityName) {
modifiedEntityNames.remove(new ModifiedEntityNameEntity(this, entityName));
}
...
}
]]></programlisting>
<programlisting>
<filename>ModifiedEntityNameEntity.java</filename>
<![CDATA[
@Entity
public class ModifiedEntityNameEntity {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private CustomTrackingRevisionEntity revision;
private String entityName;
...
}
]]></programlisting>
<programlisting><![CDATA[CustomTrackingRevisionEntity revEntity =
getAuditReader().findRevision(CustomTrackingRevisionEntity.class, revisionNumber);
List<String> modifiedEntityNames = revEntity.getModifiedEntityNames()]]></programlisting>
</example>
</section>
</section>
<section id="envers-queries">
@ -637,6 +789,38 @@ query.add(AuditEntity.relatedId("address").eq(relatedEntityId));]]></programlist
</section>
<section id="envers-tracking-modified-entities-queries">
<title>Querying for entities modified at a given revision</title>
<para>
The basic query allows retrieving entity classes modified in a specified revision:
</para>
<programlisting><![CDATA[List<Class> modifiedEntities = getAuditReader()
.findEntityTypesChangedInRevision(revisionNumber);]]></programlisting>
<para>
Other queries (accessible from <interfacename>org.hibernate.envers.AuditReader</interfacename>):
</para>
<orderedlist>
<listitem>
<firstterm><methodname>List findEntitiesChangedInRevision(Number)</methodname></firstterm>
- Returns snapshots of all audited entities changed (added, updated and removed) in a given revision.
</listitem>
<listitem>
<firstterm><methodname>List findEntitiesChangedInRevision(Number, RevisionType)</methodname></firstterm>
- Returns snapshots of all audited entities changed (added, updated or removed) in a given revision
filtered by modification type.
</listitem>
<listitem>
<firstterm><methodname><![CDATA[Map<RevisionType, List>]]> findEntitiesChangedInRevisionGroupByRevisionType(Number)</methodname></firstterm>
- Returns a map containing lists of entity snapshots grouped by modification operation (e.g.
addition, update and removal).
</listitem>
</orderedlist>
<para>
Note that methods described above can be legally used only when default mechanism of
tracking changed entity names is enabled (see <xref linkend="envers-tracking-modified-entities-reventity"/>).
</para>
</section>
</section>
<section>

View File

@ -27,13 +27,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.exception.RevisionDoesNotExistException;
import org.hibernate.envers.query.AuditQueryCreator;
/**
* @author Adam Warski (adam at warski dot org)
* @author Hern&aacute;n Chanfreau
* @author Hern&aacute;n Chanfreau
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public interface AuditReader {
/**
@ -176,35 +178,95 @@ public interface AuditReader {
AuditQueryCreator createQuery();
/**
* Checks if the entityClass was configured to be audited. Calling
* isEntityNameAudited() with the string of the class name will return the
* same value.
* Checks if the entityClass was configured to be audited. Calling
* isEntityNameAudited() with the string of the class name will return the
* same value.
*
* @param entityClass
* Class of the entity asking for audit support
* @param entityClass
* Class of the entity asking for audit support
* @return true if the entityClass is audited.
*/
boolean isEntityClassAudited(Class<?> entityClass);
/**
* Checks if the entityName was configured to be audited.
*
* @param entityClass
* EntityName of the entity asking for audit support.
* @return true if the entityName is audited.
*/
boolean isEntityNameAudited(String entityName);
/**
*
* @param entity
* that was obtained previously from the same AuditReader.
*
* @return the entityName for the given entity, null in case the entity is
* not associated with this AuditReader instance.
*/
String getEntityName(Object primaryKey, Number revision, Object entity)
throws HibernateException;
/**
* Checks if the entityName was configured to be audited.
*
* @param entityName
* EntityName of the entity asking for audit support.
* @return true if the entityName is audited.
*/
boolean isEntityNameAudited(String entityName);
/**
*
* @param entity
* that was obtained previously from the same AuditReader.
*
* @return the entityName for the given entity, null in case the entity is
* not associated with this AuditReader instance.
*/
String getEntityName(Object primaryKey, Number revision, Object entity)
throws HibernateException;
/**
* Find all entities changed (added, updated and removed) in a given revision.
* @param revision Revision number.
* @return Snapshots of all audited entities changed in a given revision.
* @throws IllegalStateException If the associated entity manager is closed.
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
* @throws AuditException If none of the following conditions is satisfied:
* <ul>
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
* </ul>
*/
List findEntitiesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException;
/**
* Find all entities changed (added, updated or removed) in a given revision.
* @param revision Revision number.
* @param revisionType Type of modification.
* @return Snapshots of all audited entities changed in a given revision filtered by modification type.
* @throws IllegalStateException If the associated entity manager is closed.
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
* @throws AuditException If none of the following conditions is satisfied:
* <ul>
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
* </ul>
*/
List findEntitiesChangedInRevision(Number revision, RevisionType revisionType) throws IllegalStateException, IllegalArgumentException, AuditException;
/**
* Find all entities changed (added, updated and removed) in a given revision grouped by modification type.
* @param revision Revision number.
* @return Map containing lists of entity snapshots grouped by modification operation (e.g. addition, update, removal).
* @throws IllegalStateException If the associated entity manager is closed.
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
* @throws AuditException If none of the following conditions is satisfied:
* <ul>
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
* </ul>
*/
Map<RevisionType, List> findEntitiesChangedInRevisionGroupByRevisionType(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException;
/**
* Returns list of entity classes modified in a given revision.
* @param revision Revision number.
* @return List of classes modified in a given revision.
* @throws IllegalStateException If the associated entity manager is closed.
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
* @throws AuditException If none of the following conditions is satisfied:
* <ul>
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
* </ul>
*/
List<Class> findEntityTypesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException;
}

View File

@ -0,0 +1,55 @@
package org.hibernate.envers;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
/**
* Extension of {@link DefaultRevisionEntity} that allows tracking entity types changed in each revision. This revision
* entity is implicitly used when one of the following conditions is satisfied:
* <ul>
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity}.</li>
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames}.</li>
* </ul>
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@MappedSuperclass
public class DefaultTrackingModifiedTypesRevisionEntity extends DefaultRevisionEntity {
@ElementCollection
@JoinTable(name = "REVENTITY", joinColumns = @JoinColumn(name = "REV"))
@Column(name = "ENTITYNAME")
@ModifiedEntityNames
private Set<String> modifiedEntityNames = new HashSet<String>();
public Set<String> getModifiedEntityNames() {
return modifiedEntityNames;
}
public void setModifiedEntityNames(Set<String> modifiedEntityNames) {
this.modifiedEntityNames = modifiedEntityNames;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DefaultTrackingModifiedTypesRevisionEntity)) return false;
if (!super.equals(o)) return false;
DefaultTrackingModifiedTypesRevisionEntity that = (DefaultTrackingModifiedTypesRevisionEntity) o;
if (modifiedEntityNames != null ? !modifiedEntityNames.equals(that.modifiedEntityNames)
: that.modifiedEntityNames != null) return false;
return true;
}
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (modifiedEntityNames != null ? modifiedEntityNames.hashCode() : 0);
return result;
}
public String toString() {
return "DefaultTrackingModifiedTypesRevisionEntity(" + super.toString() + ", modifiedEntityNames = " + modifiedEntityNames.toString() + ")";
}
}

View File

@ -0,0 +1,23 @@
package org.hibernate.envers;
/**
* Extension of standard {@link RevisionListener} that notifies whenever tracking of each audited entity
* instance is started or stopped within the current revision boundaries.
* @see RevisionListener
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public interface EntityTrackingRevisionListener extends RevisionListener {
/**
* Called after audited entity data has been persisted.
* @param entityName Name of the audited entity.
* @param revisionEntity An instance of the entity annotated with {@link RevisionEntity}.
*/
void addEntityToRevision(String entityName, Object revisionEntity);
/**
* Called when persistence of a given audited entity snapshot has been already performed in a previous unit of work.
* @param entityName Name of the audited entity.
* @param revisionEntity An instance of the entity annotated with {@link RevisionEntity}.
*/
void removeEntityFromRevision(String entityName, Object revisionEntity);
}

View File

@ -0,0 +1,16 @@
package org.hibernate.envers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a property which will hold the collection of entity names modified during each revision.
* This annotation expects field of Set&lt;String&gt; type.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface ModifiedEntityNames {
}

View File

@ -28,6 +28,7 @@ import java.util.Properties;
/**
* @author Adam Warski (adam at warski dot org)
* @author Nicolas Doroskevich
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class GlobalConfiguration {
// Should a revision be generated when a not-owned relation field changes
@ -45,6 +46,9 @@ public class GlobalConfiguration {
// The default name of the catalog of the audit tables.
private final String defaultCatalogName;
// Should Envers track (persist) entity types that have been changed during each revision.
private boolean trackEntitiesChangedInRevisionEnabled;
/*
Which operator to use in correlated subqueries (when we want a property to be equal to the result of
a correlated subquery, for example: e.p <operator> (select max(e2.p) where e2.p2 = e.p2 ...).
@ -77,6 +81,12 @@ public class GlobalConfiguration {
correlatedSubqueryOperator = "org.hibernate.dialect.HSQLDialect".equals(
properties.getProperty("hibernate.dialect")) ? "in" : "=";
String trackEntitiesChangedInRevisionEnabledStr = getProperty(properties,
"org.hibernate.envers.track_entities_changed_in_revision",
"org.hibernate.envers.track_entities_changed_in_revision",
"false");
trackEntitiesChangedInRevisionEnabled = Boolean.parseBoolean(trackEntitiesChangedInRevisionEnabledStr);
}
public boolean isGenerateRevisionsForCollections() {
@ -102,4 +112,12 @@ public class GlobalConfiguration {
public String getDefaultCatalogName() {
return defaultCatalogName;
}
public boolean isTrackEntitiesChangedInRevisionEnabled() {
return trackEntitiesChangedInRevisionEnabled;
}
public void setTrackEntitiesChangedInRevisionEnabled(boolean trackEntitiesChangedInRevisionEnabled) {
this.trackEntitiesChangedInRevisionEnabled = trackEntitiesChangedInRevisionEnabled;
}
}

View File

@ -24,6 +24,7 @@
package org.hibernate.envers.configuration;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.Column;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
@ -33,19 +34,11 @@ import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.cfg.Configuration;
import org.hibernate.envers.Audited;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionListener;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import org.hibernate.envers.*;
import org.hibernate.envers.configuration.metadata.AuditTableData;
import org.hibernate.envers.configuration.metadata.MetadataTools;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.revisioninfo.DefaultRevisionInfoGenerator;
import org.hibernate.envers.revisioninfo.RevisionInfoGenerator;
import org.hibernate.envers.revisioninfo.RevisionInfoNumberReader;
import org.hibernate.envers.revisioninfo.RevisionInfoQueryCreator;
import org.hibernate.envers.revisioninfo.*;
import org.hibernate.envers.tools.MutableBoolean;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.type.LongType;
@ -53,11 +46,13 @@ import org.hibernate.type.Type;
/**
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class RevisionInfoConfiguration {
private String revisionInfoEntityName;
private PropertyData revisionInfoIdData;
private PropertyData revisionInfoTimestampData;
private PropertyData modifiedEntityNamesData;
private Type revisionInfoTimestampType;
private GlobalConfiguration globalCfg;
@ -69,6 +64,7 @@ public class RevisionInfoConfiguration {
revisionInfoEntityName = "org.hibernate.envers.DefaultRevisionEntity";
revisionInfoIdData = new PropertyData("id", "id", "field", null);
revisionInfoTimestampData = new PropertyData("timestamp", "timestamp", "field", null);
modifiedEntityNamesData = new PropertyData("modifiedEntityNames", "modifiedEntityNames", "field", null);
revisionInfoTimestampType = new LongType();
revisionPropType = "integer";
@ -90,9 +86,39 @@ public class RevisionInfoConfiguration {
revisionInfoTimestampType.getName(), true, false);
MetadataTools.addColumn(timestampProperty, "REVTSTMP", null, 0, 0, null, null, null, false);
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()) {
generateEntityNamesTrackingTableMapping(class_mapping, "modifiedEntityNames", "REVENTITY", "REV", "ENTITYNAME", "string");
}
return document;
}
/**
* Generates mapping that represents a set of strings.<br />
* <code>
* &lt;set name="propertyName" table="joinTableName" cascade="persist, delete"&gt;<br />
* &nbsp;&nbsp;&nbsp;&lt;key column="joinTablePrimaryKeyColumnName" /&gt;<br />
* &nbsp;&nbsp;&nbsp;&lt;element type="joinTableValueColumnType"&gt;<br />
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;column name="joinTableValueColumnName" /&gt;<br />
* &nbsp;&nbsp;&nbsp;&lt;/element&gt;<br />
* &lt;/set&gt;
* </code>
*/
private void generateEntityNamesTrackingTableMapping(Element class_mapping, String propertyName,
String joinTableName, String joinTablePrimaryKeyColumnName,
String joinTableValueColumnName, String joinTableValueColumnType) {
Element set = class_mapping.addElement("set");
set.addAttribute("name", propertyName);
set.addAttribute("table", joinTableName);
set.addAttribute("cascade", "persist, delete");
Element key = set.addElement("key");
key.addAttribute("column", joinTablePrimaryKeyColumnName);
Element element = set.addElement("element");
element.addAttribute("type", joinTableValueColumnType);
Element column = element.addElement("column");
column.addAttribute("name", joinTableValueColumnName);
}
private Element generateRevisionInfoRelationMapping() {
Document document = DocumentHelper.createDocument();
Element rev_rel_mapping = document.addElement("key-many-to-one");
@ -109,10 +135,11 @@ public class RevisionInfoConfiguration {
private void searchForRevisionInfoCfgInProperties(XClass clazz, ReflectionManager reflectionManager,
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
String accessType) {
MutableBoolean modifiedEntityNamesFound, String accessType) {
for (XProperty property : clazz.getDeclaredProperties(accessType)) {
RevisionNumber revisionNumber = property.getAnnotation(RevisionNumber.class);
RevisionTimestamp revisionTimestamp = property.getAnnotation(RevisionTimestamp.class);
ModifiedEntityNames modifiedEntityNames = property.getAnnotation(ModifiedEntityNames.class);
if (revisionNumber != null) {
if (revisionNumberFound.isSet()) {
@ -162,20 +189,35 @@ public class RevisionInfoConfiguration {
"long, Long, java.util.Date or java.sql.Date");
}
}
if (modifiedEntityNames != null) {
if (modifiedEntityNamesFound.isSet()) {
throw new MappingException("Only one property may be annotated with @ModifiedEntityNames!");
}
XClass modifiedEntityNamesClass = property.getType();
if (reflectionManager.equals(modifiedEntityNamesClass, Set.class) &&
reflectionManager.equals(property.getElementClass(), String.class)) {
modifiedEntityNamesData = new PropertyData(property.getName(), property.getName(), accessType, null);
modifiedEntityNamesFound.set();
} else {
throw new MappingException("The field annotated with @ModifiedEntityNames must be of type Set<String>.");
}
}
}
}
private void searchForRevisionInfoCfg(XClass clazz, ReflectionManager reflectionManager,
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound) {
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
MutableBoolean modifiedEntityNamesFound) {
XClass superclazz = clazz.getSuperclass();
if (!"java.lang.Object".equals(superclazz.getName())) {
searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound);
searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound);
}
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
"field");
modifiedEntityNamesFound, "field");
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
"property");
modifiedEntityNamesFound, "property");
}
public RevisionInfoConfigurationResult configure(Configuration cfg, ReflectionManager reflectionManager) {
@ -209,8 +251,9 @@ public class RevisionInfoConfiguration {
MutableBoolean revisionNumberFound = new MutableBoolean();
MutableBoolean revisionTimestampFound = new MutableBoolean();
MutableBoolean modifiedEntityNamesFound = new MutableBoolean();
searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound);
searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound);
if (!revisionNumberFound.isSet()) {
throw new MappingException("An entity annotated with @RevisionEntity must have a field annotated " +
@ -226,8 +269,15 @@ public class RevisionInfoConfiguration {
revisionInfoClass = pc.getMappedClass();
revisionInfoTimestampType = pc.getProperty(revisionInfoTimestampData.getName()).getType();
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate());
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled() || DefaultTrackingModifiedTypesRevisionEntity.class.isAssignableFrom(revisionInfoClass) || modifiedEntityNamesFound.isSet()) {
// If modified entities tracking parameter is enabled, custom revision info class is a subtype of DefaultTrackingModifiedTypesRevisionEntity, or @ModifiedEntityNames annotation is used.
revisionInfoGenerator = new DefaultTrackingModifiedTypesRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate(), modifiedEntityNamesData);
globalCfg.setTrackEntitiesChangedInRevisionEnabled(true);
} else {
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate());
}
}
}
@ -235,16 +285,23 @@ public class RevisionInfoConfiguration {
Document revisionInfoXmlMapping = null;
if (revisionInfoGenerator == null) {
revisionInfoClass = DefaultRevisionEntity.class;
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate());
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()) {
revisionInfoClass = DefaultTrackingModifiedTypesRevisionEntity.class;
revisionInfoEntityName = DefaultTrackingModifiedTypesRevisionEntity.class.getName();
revisionInfoGenerator = new DefaultTrackingModifiedTypesRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate(), modifiedEntityNamesData);
} else {
revisionInfoClass = DefaultRevisionEntity.class;
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate());
}
revisionInfoXmlMapping = generateDefaultRevisionInfoXmlMapping();
}
return new RevisionInfoConfigurationResult(
revisionInfoGenerator, revisionInfoXmlMapping,
new RevisionInfoQueryCreator(revisionInfoEntityName, revisionInfoIdData.getName(),
revisionInfoTimestampData.getName(), isTimestampAsDate()),
revisionInfoTimestampData.getName(), isTimestampAsDate(), modifiedEntityNamesData.getName()),
generateRevisionInfoRelationMapping(),
new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData), revisionInfoEntityName,
revisionInfoClass, revisionInfoTimestampData);
@ -312,5 +369,4 @@ class RevisionInfoConfigurationResult {
public PropertyData getRevisionInfoTimestampData() {
return revisionInfoTimestampData;
}
}

View File

@ -24,6 +24,8 @@
package org.hibernate.envers.query;
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
import org.hibernate.Query;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.query.impl.EntitiesAtRevisionQuery;
import org.hibernate.envers.query.impl.RevisionsOfEntityQuery;
@ -32,6 +34,7 @@ import org.hibernate.envers.reader.AuditReaderImplementor;
/**
* @author Adam Warski (adam at warski dot org)
* @author Hern<EFBFBD>n Chanfreau
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class AuditQueryCreator {
private final AuditConfiguration auditCfg;
@ -52,9 +55,18 @@ public class AuditQueryCreator {
* projection is added.
*/
public AuditQuery forEntitiesAtRevision(Class<?> c, Number revision) {
return forEntitiesAtRevision(c, revision, false);
}
/**
* Returns deleted entities as well. Removed entities data depends on the value of
* <code>org.hibernate.envers.store_data_at_delete</code> parameter.
* @see #forEntitiesAtRevision(Class, Number)
*/
public AuditQuery forEntitiesAtRevision(Class<?> c, Number revision, boolean selectDeletedEntities) {
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
return new EntitiesAtRevisionQuery(auditCfg, auditReaderImplementor, c, revision);
return new EntitiesAtRevisionQuery(auditCfg, auditReaderImplementor, c, revision, selectDeletedEntities);
}
/**
@ -121,5 +133,4 @@ public class AuditQueryCreator {
public AuditQuery forRevisionsOfEntity(Class<?> c, String entityName, boolean selectEntitiesOnly, boolean selectDeletedEntities) {
return new RevisionsOfEntityQuery(auditCfg, auditReaderImplementor, c, entityName, selectEntitiesOnly,selectDeletedEntities);
}
}

View File

@ -39,18 +39,28 @@ import org.hibernate.envers.reader.AuditReaderImplementor;
*/
public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
private final Number revision;
private final boolean selectDeletedEntities;
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
AuditReaderImplementor versionsReader, Class<?> cls, String entityName,
Number revision) {
this(verCfg, versionsReader, cls, entityName, revision, false);
}
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
AuditReaderImplementor versionsReader, Class<?> cls,
Number revision) {
Number revision, boolean selectDeletedEntities) {
super(verCfg, versionsReader, cls);
this.revision = revision;
this.selectDeletedEntities = selectDeletedEntities;
}
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
AuditReaderImplementor versionsReader, Class<?> cls, String entityName, Number revision) {
AuditReaderImplementor versionsReader, Class<?> cls, String entityName,
Number revision, boolean selectDeletedEntities) {
super(verCfg, versionsReader, cls, entityName);
this.revision = revision;
this.selectDeletedEntities = selectDeletedEntities;
}
@SuppressWarnings({"unchecked"})
@ -84,9 +94,11 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
verCfg.getAuditStrategy().addEntityAtRevisionRestriction(verCfg.getGlobalCfg(), qb, revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(), true, referencedIdData,
revisionPropertyPath, originalIdPropertyName, "e", "e2");
// e.revision_type != DEL
qb.getRootParameters().addWhereWithParam(verEntCfg.getRevisionTypePropName(), "<>", RevisionType.DEL);
if (!selectDeletedEntities) {
// e.revision_type != DEL
qb.getRootParameters().addWhereWithParam(verEntCfg.getRevisionTypePropName(), "<>", RevisionType.DEL);
}
// all specified conditions
for (AuditCriterion criterion : criterions) {

View File

@ -24,31 +24,30 @@
package org.hibernate.envers.reader;
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import javax.persistence.NoResultException;
import org.hibernate.HibernateException;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.exception.RevisionDoesNotExistException;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQueryCreator;
import org.hibernate.envers.query.criteria.RevisionTypeAuditExpression;
import org.hibernate.envers.synchronization.AuditProcess;
import org.hibernate.event.EventSource;
import org.hibernate.proxy.HibernateProxy;
/**
* @author Adam Warski (adam at warski dot org)
* @author Hern&aacute;n Chanfreau
* @author Hern&aacute;n Chanfreau
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class AuditReaderImpl implements AuditReaderImplementor {
private final AuditConfiguration verCfg;
@ -235,7 +234,64 @@ public class AuditReaderImpl implements AuditReaderImplementor {
}
}
@SuppressWarnings({"unchecked"})
@SuppressWarnings({"unchecked"})
public List findEntitiesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException {
List<Class> clazz = findEntityTypesChangedInRevision(revision);
List result = new ArrayList(clazz.size());
for (Class c : clazz) {
result.addAll(createQuery().forEntitiesAtRevision(c, revision, true).getResultList());
}
return result;
}
@SuppressWarnings({"unchecked"})
public List findEntitiesChangedInRevision(Number revision, RevisionType revisionType) throws IllegalStateException, IllegalArgumentException, AuditException {
List<Class> clazz = findEntityTypesChangedInRevision(revision);
List result = new ArrayList(clazz.size());
for (Class c : clazz) {
result.addAll(createQuery().forEntitiesAtRevision(c, revision, true).add(new RevisionTypeAuditExpression(revisionType, "=")).getResultList());
}
return result;
}
@SuppressWarnings({"unchecked"})
public Map<RevisionType, List> findEntitiesChangedInRevisionGroupByRevisionType(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException {
List<Class> clazz = findEntityTypesChangedInRevision(revision);
Map<RevisionType, List> result = new HashMap<RevisionType, List>();
for (RevisionType revisionType : RevisionType.values()) {
result.put(revisionType, new ArrayList());
for (Class c : clazz) {
List list = createQuery().forEntitiesAtRevision(c, revision, true).add(new RevisionTypeAuditExpression(revisionType, "=")).getResultList();
result.get(revisionType).addAll(list);
}
}
return result;
}
@SuppressWarnings({"unchecked"})
public List<Class> findEntityTypesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException {
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
checkSession();
if (!verCfg.getGlobalCfg().isTrackEntitiesChangedInRevisionEnabled()) {
throw new AuditException("This query is designed for Envers default mechanism of tracking entities modified in a given revision."
+ " Extend DefaultTrackingModifiedTypesRevisionEntity, utilize @ModifiedEntityNames annotation or set "
+ "'org.hibernate.envers.track_entities_changed_in_revision' parameter to true.");
}
Query query = verCfg.getRevisionInfoQueryCreator().getEntitiesChangedInRevisionQuery(session, revision);
Set<String> modifiedEntityNames = new HashSet<String>(query.list());
List<Class> result = new ArrayList<Class>(modifiedEntityNames.size());
for (String entityName : modifiedEntityNames) {
try {
result.add(Class.forName(entityName));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return result;
}
@SuppressWarnings({"unchecked"})
public <T> T getCurrentRevision(Class<T> revisionEntityClass, boolean persist) {
if (!(session instanceof EventSource)) {
throw new IllegalArgumentException("The provided session is not an EventSource!");
@ -253,34 +309,34 @@ public class AuditReaderImpl implements AuditReaderImplementor {
}
public boolean isEntityClassAudited(Class<?> entityClass) {
return this.isEntityNameAudited(entityClass.getName());
}
public boolean isEntityNameAudited(String entityName) {
checkNotNull(entityName, "Entity name");
checkSession();
return (verCfg.getEntCfg().isVersioned(entityName));
}
public String getEntityName(Object primaryKey, Number revision ,Object entity) throws HibernateException{
checkNotNull(primaryKey, "Primary key");
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
checkNotNull(entity, "Entity");
return this.isEntityNameAudited(entityClass.getName());
}
public boolean isEntityNameAudited(String entityName) {
checkNotNull(entityName, "Entity name");
checkSession();
return (verCfg.getEntCfg().isVersioned(entityName));
}
public String getEntityName(Object primaryKey, Number revision ,Object entity) throws HibernateException{
checkNotNull(primaryKey, "Primary key");
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
checkNotNull(entity, "Entity");
checkSession();
// Unwrap if necessary
if(entity instanceof HibernateProxy) {
entity = ((HibernateProxy)entity).getHibernateLazyInitializer().getImplementation();
}
if(firstLevelCache.containsEntityName(primaryKey, revision, entity)) {
// it's on envers FLC!
return firstLevelCache.getFromEntityNameCache(primaryKey, revision, entity);
} else {
throw new HibernateException(
"Envers can't resolve entityName for historic entity. The id, revision and entity is not on envers first level cache.");
// Unwrap if necessary
if(entity instanceof HibernateProxy) {
entity = ((HibernateProxy)entity).getHibernateLazyInitializer().getImplementation();
}
if(firstLevelCache.containsEntityName(primaryKey, revision, entity)) {
// it's on envers FLC!
return firstLevelCache.getFromEntityNameCache(primaryKey, revision, entity);
} else {
throw new HibernateException(
"Envers can't resolve entityName for historic entity. The id, revision and entity is not on envers first level cache.");
}
}
}

View File

@ -23,8 +23,10 @@
*/
package org.hibernate.envers.revisioninfo;
import java.util.Date;
import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.envers.EntityTrackingRevisionListener;
import org.hibernate.envers.RevisionListener;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.tools.reflection.ReflectionTools;
@ -32,6 +34,7 @@ import org.hibernate.property.Setter;
/**
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
private final String revisionInfoEntityName;
@ -86,4 +89,16 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
return revisionInfo;
}
public void addEntityToRevision(String entityName, Object revisionInfo) {
if (listener instanceof EntityTrackingRevisionListener) {
((EntityTrackingRevisionListener) listener).addEntityToRevision(entityName, revisionInfo);
}
}
public void removeEntityFromRevision(String entityName, Object revisionInfo) {
if (listener instanceof EntityTrackingRevisionListener) {
((EntityTrackingRevisionListener) listener).removeEntityFromRevision(entityName, revisionInfo);
}
}
}

View File

@ -0,0 +1,55 @@
package org.hibernate.envers.revisioninfo;
import org.hibernate.envers.RevisionListener;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.tools.reflection.ReflectionTools;
import org.hibernate.property.Getter;
import org.hibernate.property.Setter;
import java.util.HashSet;
import java.util.Set;
/**
* Automatically adds and removes entity names changed during current revision.
* @see org.hibernate.envers.ModifiedEntityNames
* @see org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class DefaultTrackingModifiedTypesRevisionInfoGenerator extends DefaultRevisionInfoGenerator {
private final Setter modifiedEntityNamesSetter;
private final Getter modifiedEntityNamesGetter;
public DefaultTrackingModifiedTypesRevisionInfoGenerator(String revisionInfoEntityName,
Class<?> revisionInfoClass,
Class<? extends RevisionListener> listenerClass,
PropertyData revisionInfoTimestampData, boolean timestampAsDate,
PropertyData modifiedEntityNamesData) {
super(revisionInfoEntityName, revisionInfoClass, listenerClass, revisionInfoTimestampData, timestampAsDate);
modifiedEntityNamesSetter = ReflectionTools.getSetter(revisionInfoClass, modifiedEntityNamesData);
modifiedEntityNamesGetter = ReflectionTools.getGetter(revisionInfoClass, modifiedEntityNamesData);
}
@Override
@SuppressWarnings({"unchecked"})
public void addEntityToRevision(String entityName, Object revisionInfo) {
super.addEntityToRevision(entityName, revisionInfo);
Set<String> modifiedEntityNames = (Set<String>) modifiedEntityNamesGetter.get(revisionInfo);
if (modifiedEntityNames == null) {
modifiedEntityNames = new HashSet<String>();
}
modifiedEntityNames.add(entityName);
modifiedEntityNamesSetter.set(revisionInfo, modifiedEntityNames, null);
}
@Override
@SuppressWarnings({"unchecked"})
public void removeEntityFromRevision(String entityName, Object revisionInfo) {
super.removeEntityFromRevision(entityName, revisionInfo);
Set<String> modifiedEntityNames = (Set<String>) modifiedEntityNamesGetter.get(revisionInfo);
if (modifiedEntityNames == null) {
return;
}
modifiedEntityNames.remove(entityName);
modifiedEntityNamesSetter.set(revisionInfo, modifiedEntityNames, null);
}
}

View File

@ -30,4 +30,14 @@ import org.hibernate.Session;
public interface RevisionInfoGenerator {
void saveRevisionData(Session session, Object revisionData);
Object generate();
/**
* @see org.hibernate.envers.EntityTrackingRevisionListener#addEntityToRevision(String, Object)
*/
void addEntityToRevision(String entityName, Object revisionInfo);
/**
* @see org.hibernate.envers.EntityTrackingRevisionListener#removeEntityFromRevision(String, Object)
*/
void removeEntityFromRevision(String entityName, Object revisionInfo);
}

View File

@ -26,18 +26,22 @@ import java.util.Date;
import java.util.Set;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.transform.Transformers;
/**
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class RevisionInfoQueryCreator {
private final String revisionDateQuery;
private final String revisionNumberForDateQuery;
private final String revisionsQuery;
private final String entitiesChangedInRevisionQuery;
private final boolean timestampAsDate;
public RevisionInfoQueryCreator(String revisionInfoEntityName, String revisionInfoIdName,
String revisionInfoTimestampName, boolean timestampAsDate) {
String revisionInfoTimestampName, boolean timestampAsDate,
String modifiedEntityNamesName) {
this.timestampAsDate = timestampAsDate;
revisionDateQuery = new StringBuilder()
@ -57,6 +61,12 @@ public class RevisionInfoQueryCreator {
.append(" rev where ").append(revisionInfoIdName)
.append(" in (:_revision_numbers)")
.toString();
entitiesChangedInRevisionQuery = new StringBuilder()
.append("select elements(rev.").append(modifiedEntityNamesName)
.append(") from ").append(revisionInfoEntityName)
.append(" rev where rev.").append(revisionInfoIdName).append(" = :_revision_number")
.toString();
}
public Query getRevisionDateQuery(Session session, Number revision) {
@ -70,4 +80,8 @@ public class RevisionInfoQueryCreator {
public Query getRevisionsQuery(Session session, Set<Number> revisions) {
return session.createQuery(revisionsQuery).setParameterList("_revision_numbers", revisions);
}
public Query getEntitiesChangedInRevisionQuery(Session session, Number revision) {
return session.createQuery(entitiesChangedInRevisionQuery).setParameter("_revision_number", revision);
}
}

View File

@ -98,17 +98,19 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
private void executeInSession(Session session) {
// Making sure the revision data is persisted.
getCurrentRevisionData(session, true);
Object currentRevisionData = getCurrentRevisionData(session, true);
AuditWorkUnit vwu;
// First undoing any performed work units
while ((vwu = undoQueue.poll()) != null) {
vwu.undo(session);
revisionInfoGenerator.removeEntityFromRevision(vwu.getEntityName(), currentRevisionData);
}
while ((vwu = workUnits.poll()) != null) {
vwu.perform(session, revisionData);
revisionInfoGenerator.addEntityToRevision(vwu.getEntityName(), currentRevisionData);
}
}

View File

@ -0,0 +1,78 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.ModifiedEntityNames;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@RevisionEntity
public class AnnotatedTrackingRevisionEntity {
@Id
@GeneratedValue
@RevisionNumber
private int customId;
@RevisionTimestamp
private long customTimestamp;
@ElementCollection
@JoinTable(name = "REVENTITY", joinColumns = @JoinColumn(name = "REV"))
@Column(name = "ENTITYNAME")
@ModifiedEntityNames
private Set<String> modifiedEntityNames;
public int getCustomId() {
return customId;
}
public void setCustomId(int customId) {
this.customId = customId;
}
public long getCustomTimestamp() {
return customTimestamp;
}
public void setCustomTimestamp(long customTimestamp) {
this.customTimestamp = customTimestamp;
}
public Set<String> getModifiedEntityNames() {
return modifiedEntityNames;
}
public void setModifiedEntityNames(Set<String> modifiedEntityNames) {
this.modifiedEntityNames = modifiedEntityNames;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AnnotatedTrackingRevisionEntity)) return false;
AnnotatedTrackingRevisionEntity that = (AnnotatedTrackingRevisionEntity) o;
if (customId != that.customId) return false;
if (customTimestamp != that.customTimestamp) return false;
return true;
}
public int hashCode() {
int result = customId;
result = 31 * result + (int) (customTimestamp ^ (customTimestamp >>> 32));
return result;
}
@Override
public String toString() {
return "AnnotatedTrackingRevisionEntity(customId = " + customId + ", customTimestamp = " + customTimestamp + ")";
}
}

View File

@ -0,0 +1,22 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.EntityTrackingRevisionListener;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener {
@Override
public void addEntityToRevision(String entityName, Object revisionEntity) {
((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityName(entityName);
}
@Override
public void removeEntityFromRevision(String entityName, Object revisionEntity) {
((CustomTrackingRevisionEntity)revisionEntity).removeModifiedEntityName(entityName);
}
@Override
public void newRevision(Object revisionEntity) {
}
}

View File

@ -0,0 +1,82 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@RevisionEntity(CustomEntityTrackingRevisionListener.class)
public class CustomTrackingRevisionEntity {
@Id
@GeneratedValue
@RevisionNumber
private int customId;
@RevisionTimestamp
private long customTimestamp;
@OneToMany(mappedBy="revision", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private Set<ModifiedEntityNameEntity> modifiedEntityNames = new HashSet<ModifiedEntityNameEntity>();
public int getCustomId() {
return customId;
}
public void setCustomId(int customId) {
this.customId = customId;
}
public long getCustomTimestamp() {
return customTimestamp;
}
public void setCustomTimestamp(long customTimestamp) {
this.customTimestamp = customTimestamp;
}
public Set<ModifiedEntityNameEntity> getModifiedEntityNames() {
return modifiedEntityNames;
}
public void setModifiedEntityNames(Set<ModifiedEntityNameEntity> modifiedEntityNames) {
this.modifiedEntityNames = modifiedEntityNames;
}
public void addModifiedEntityName(String entityName) {
modifiedEntityNames.add(new ModifiedEntityNameEntity(this, entityName));
}
public void removeModifiedEntityName(String entityName) {
modifiedEntityNames.remove(new ModifiedEntityNameEntity(this, entityName));
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CustomTrackingRevisionEntity)) return false;
CustomTrackingRevisionEntity that = (CustomTrackingRevisionEntity) o;
if (customId != that.customId) return false;
if (customTimestamp != that.customTimestamp) return false;
return true;
}
public int hashCode() {
int result = customId;
result = 31 * result + (int) (customTimestamp ^ (customTimestamp >>> 32));
return result;
}
@Override
public String toString() {
return "CustomTrackingRevisionEntity(customId = " + customId + ", customTimestamp = " + customTimestamp + ")";
}
}

View File

@ -0,0 +1,24 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import javax.persistence.Entity;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@RevisionEntity(ExtendedRevisionListener.class)
public class ExtendedRevisionEntity extends DefaultTrackingModifiedTypesRevisionEntity {
private String commnent;
public String getCommnent() {
return commnent;
}
public void setCommnent(String commnent) {
this.commnent = commnent;
}
}

View File

@ -0,0 +1,14 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.RevisionListener;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ExtendedRevisionListener implements RevisionListener {
public static final String COMMENT_VALUE = "Comment";
public void newRevision(Object revisionEntity) {
((ExtendedRevisionEntity)revisionEntity).setCommnent(COMMENT_VALUE);
}
}

View File

@ -0,0 +1,69 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
public class ModifiedEntityNameEntity {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private CustomTrackingRevisionEntity revision;
private String entityName;
public ModifiedEntityNameEntity() {
}
public ModifiedEntityNameEntity(String entityName) {
this.entityName = entityName;
}
public ModifiedEntityNameEntity(CustomTrackingRevisionEntity revision, String entityName) {
this.revision = revision;
this.entityName = entityName;
}
public CustomTrackingRevisionEntity getRevision() {
return revision;
}
public void setRevision(CustomTrackingRevisionEntity revision) {
this.revision = revision;
}
public String getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ModifiedEntityNameEntity)) return false;
ModifiedEntityNameEntity that = (ModifiedEntityNameEntity) o;
if (entityName != null ? !entityName.equals(that.entityName) : that.entityName != null) return false;
return true;
}
public int hashCode() {
return entityName != null ? entityName.hashCode() : 0;
}
@Override
public String toString() {
return "CustomTrackingRevisionEntity(entityName = " + entityName + ")";
}
}

View File

@ -0,0 +1,23 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.StrIntTestEntity;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.AnnotatedTrackingRevisionEntity;
import org.junit.Test;
import javax.persistence.EntityManager;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class AnnotatedTrackingEntitiesTest extends DefaultTrackingEntitiesTest {
@Override
public void configure(Ejb3Configuration cfg) {
super.configure(cfg);
cfg.addAnnotatedClass(AnnotatedTrackingRevisionEntity.class);
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "false");
}
}

View File

@ -0,0 +1,104 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.StrIntTestEntity;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.CustomTrackingRevisionEntity;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ModifiedEntityNameEntity;
import org.hibernate.envers.test.tools.TestTools;
import org.junit.Test;
import javax.persistence.EntityManager;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class CustomTrackingEntitiesTest extends AbstractEntityTest {
private Integer steId = null;
private Integer siteId = null;
@Override
public void configure(Ejb3Configuration cfg) {
cfg.addAnnotatedClass(ModifiedEntityNameEntity.class);
cfg.addAnnotatedClass(CustomTrackingRevisionEntity.class);
cfg.addAnnotatedClass(StrTestEntity.class);
cfg.addAnnotatedClass(StrIntTestEntity.class);
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// Revision 1 - Adding two entities
em.getTransaction().begin();
StrTestEntity ste = new StrTestEntity("x");
StrIntTestEntity site = new StrIntTestEntity("y", 1);
em.persist(ste);
em.persist(site);
steId = ste.getId();
siteId = site.getId();
em.getTransaction().commit();
// Revision 2 - Modifying one entity
em.getTransaction().begin();
site = em.find(StrIntTestEntity.class, siteId);
site.setNumber(2);
em.getTransaction().commit();
// Revision 3 - Deleting both entities
em.getTransaction().begin();
ste = em.find(StrTestEntity.class, steId);
site = em.find(StrIntTestEntity.class, siteId);
em.remove(ste);
em.remove(site);
em.getTransaction().commit();
}
@Test
public void testTrackAddedEntities() {
ModifiedEntityNameEntity steDescriptor = new ModifiedEntityNameEntity(StrTestEntity.class.getName());
ModifiedEntityNameEntity siteDescriptor = new ModifiedEntityNameEntity(StrIntTestEntity.class.getName());
AuditReader vr = getAuditReader();
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 1);
assert ctre.getModifiedEntityNames() != null;
assert ctre.getModifiedEntityNames().size() == 2;
assert TestTools.makeSet(steDescriptor, siteDescriptor).equals(ctre.getModifiedEntityNames());
}
@Test
public void testTrackModifiedEntities() {
ModifiedEntityNameEntity siteDescriptor = new ModifiedEntityNameEntity(StrIntTestEntity.class.getName());
AuditReader vr = getAuditReader();
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 2);
assert ctre.getModifiedEntityNames() != null;
assert ctre.getModifiedEntityNames().size() == 1;
assert TestTools.makeSet(siteDescriptor).equals(ctre.getModifiedEntityNames());
}
@Test
public void testTrackDeletedEntities() {
ModifiedEntityNameEntity steDescriptor = new ModifiedEntityNameEntity(StrTestEntity.class.getName());
ModifiedEntityNameEntity siteDescriptor = new ModifiedEntityNameEntity(StrIntTestEntity.class.getName());
AuditReader vr = getAuditReader();
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 3);
assert ctre.getModifiedEntityNames() != null;
assert ctre.getModifiedEntityNames().size() == 2;
assert TestTools.makeSet(steDescriptor, siteDescriptor).equals(ctre.getModifiedEntityNames());
}
@Test(expected = AuditException.class)
public void testFindEntitiesChangedInRevisionException() {
getAuditReader().findEntitiesChangedInRevision(1);
}
}

View File

@ -0,0 +1,168 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.StrIntTestEntity;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Table;
import org.junit.Test;
import javax.persistence.EntityManager;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@SuppressWarnings({"unchecked"})
public class DefaultTrackingEntitiesTest extends AbstractEntityTest {
private Integer steId = null;
private Integer siteId = null;
@Override
public void configure(Ejb3Configuration cfg) {
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "true");
cfg.addAnnotatedClass(StrTestEntity.class);
cfg.addAnnotatedClass(StrIntTestEntity.class);
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// Revision 1 - Adding two entities
em.getTransaction().begin();
StrTestEntity ste = new StrTestEntity("x");
StrIntTestEntity site = new StrIntTestEntity("y", 1);
em.persist(ste);
em.persist(site);
steId = ste.getId();
siteId = site.getId();
em.getTransaction().commit();
// Revision 2 - Modifying one entity
em.getTransaction().begin();
site = em.find(StrIntTestEntity.class, siteId);
site.setNumber(2);
em.getTransaction().commit();
// Revision 3 - Deleting both entities
em.getTransaction().begin();
ste = em.find(StrTestEntity.class, steId);
site = em.find(StrIntTestEntity.class, siteId);
em.remove(ste);
em.remove(site);
em.getTransaction().commit();
}
@Test
public void testRevEntityTableCreation() {
Iterator<Table> tableIterator = getCfg().getTableMappings();
while (tableIterator.hasNext()) {
Table table = tableIterator.next();
if ("REVENTITY".equals(table.getName())) {
assert table.getColumnSpan() == 2;
assert table.getColumn(new Column("REV")) != null;
assert table.getColumn(new Column("ENTITYNAME")) != null;
return;
}
}
assert false;
}
@Test
public void testTrackAddedEntities() {
StrTestEntity ste = new StrTestEntity("x", steId);
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(1));
}
@Test
public void testTrackModifiedEntities() {
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
assert Arrays.asList(site).equals(getAuditReader().findEntitiesChangedInRevision(2));
}
@Test
public void testTrackDeletedEntities() {
StrTestEntity ste = new StrTestEntity(null, steId);
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(3));
}
@Test
public void testFindChangesInInvalidRevision() {
assert getAuditReader().findEntitiesChangedInRevision(4).isEmpty();
}
@Test
public void testTrackAddedEntitiesGroupByRevisionType() {
StrTestEntity ste = new StrTestEntity("x", steId);
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
Map<RevisionType, List> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(1);
assert Arrays.asList(ste, site).equals(result.get(RevisionType.ADD));
assert Arrays.asList().equals(result.get(RevisionType.MOD));
assert Arrays.asList().equals(result.get(RevisionType.DEL));
}
@Test
public void testTrackModifiedEntitiesGroupByRevisionType() {
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
Map<RevisionType, List> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(2);
assert Arrays.asList().equals(result.get(RevisionType.ADD));
assert Arrays.asList(site).equals(result.get(RevisionType.MOD));
assert Arrays.asList().equals(result.get(RevisionType.DEL));
}
@Test
public void testTrackDeletedEntitiesGroupByRevisionType() {
StrTestEntity ste = new StrTestEntity(null, steId);
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
Map<RevisionType, List> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(3);
assert Arrays.asList().equals(result.get(RevisionType.ADD));
assert Arrays.asList().equals(result.get(RevisionType.MOD));
assert Arrays.asList(ste, site).equals(result.get(RevisionType.DEL));
}
@Test
public void testFindChangedEntitiesByRevisionTypeADD() {
StrTestEntity ste = new StrTestEntity("x", steId);
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(1, RevisionType.ADD));
}
@Test
public void testFindChangedEntitiesByRevisionTypeMOD() {
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
assert Arrays.asList(site).equals(getAuditReader().findEntitiesChangedInRevision(2, RevisionType.MOD));
}
@Test
public void testFindChangedEntitiesByRevisionTypeDEL() {
StrTestEntity ste = new StrTestEntity(null, steId);
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(3, RevisionType.DEL));
}
@Test
public void testFindEntityTypesChangedInRevision() {
assert Arrays.asList(StrTestEntity.class, StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(1));
assert Arrays.asList(StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(2));
assert Arrays.asList(StrTestEntity.class, StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(3));
}
}

View File

@ -0,0 +1,27 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ExtendedRevisionEntity;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ExtendedRevisionListener;
import org.junit.Test;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ExtendedRevisionEntityTest extends DefaultTrackingEntitiesTest {
@Override
public void configure(Ejb3Configuration cfg) {
super.configure(cfg);
cfg.addAnnotatedClass(ExtendedRevisionEntity.class);
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "false");
}
@Test
public void testCommentPropertyValue() {
AuditReader vr = getAuditReader();
ExtendedRevisionEntity ere = vr.findRevision(ExtendedRevisionEntity.class, 1);
assert ExtendedRevisionListener.COMMENT_VALUE.equals(ere.getCommnent());
}
}