HHH-5580 - Tracking entity names in revision
This commit is contained in:
parent
86c15fd212
commit
98342a7e2d
|
@ -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>
|
||||
|
|
|
@ -27,6 +27,7 @@ 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;
|
||||
|
@ -34,6 +35,7 @@ import org.hibernate.envers.query.AuditQueryCreator;
|
|||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
* @author Hernán Chanfreau
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
*/
|
||||
public interface AuditReader {
|
||||
/**
|
||||
|
@ -189,7 +191,7 @@ public interface AuditReader {
|
|||
/**
|
||||
* Checks if the entityName was configured to be audited.
|
||||
*
|
||||
* @param entityClass
|
||||
* @param entityName
|
||||
* EntityName of the entity asking for audit support.
|
||||
* @return true if the entityName is audited.
|
||||
*/
|
||||
|
@ -207,4 +209,64 @@ public interface AuditReader {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -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() + ")";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<String> type.
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
public @interface ModifiedEntityNames {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
* <set name="propertyName" table="joinTableName" cascade="persist, delete"><br />
|
||||
* <key column="joinTablePrimaryKeyColumnName" /><br />
|
||||
* <element type="joinTableValueColumnType"><br />
|
||||
* <column name="joinTableValueColumnName" /><br />
|
||||
* </element><br />
|
||||
* </set>
|
||||
* </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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"})
|
||||
|
@ -85,8 +95,10 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
|
|||
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) {
|
||||
|
|
|
@ -24,24 +24,22 @@
|
|||
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;
|
||||
|
@ -49,6 +47,7 @@ import org.hibernate.proxy.HibernateProxy;
|
|||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
* @author Herná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!");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 + ")";
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue