Merge branch 'HHH-5580-POC' of git://github.com/lukasz-antoniak/hibernate-core

Conflicts:
	hibernate-envers/src/main/java/org/hibernate/envers/reader/AuditReaderImpl.java
This commit is contained in:
adamw 2011-06-02 10:00:36 +02:00
commit f865be9739
39 changed files with 1729 additions and 102 deletions

View File

@ -16,7 +16,8 @@
which can be used to identify groups of changes (much like a change set in source control). As the revisions
are global, having a revision number, you can query for various entities at that revision, retrieving a
(partial) view of the database at that revision. You can find a revision number having a date, and the other
way round, you can get the date at which a revision was committed.
way round, you can get the date at which a revision was committed. Since version 4.0, Envers enables you
to retrieve all changes performed in a certain revision.
</para>
</preface>
@ -243,6 +244,24 @@
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>REVCHANGES</literal> table that stores fully qualified names
of Java classes modified in a specified revision. Single record encapsulates the revision
identifier (foreign key to <literal>REVINFO</literal> table) and a string value. This
feature shall be used when entity name can be clearly identified by Java class type. Otherwise
extend <interfacename>org.hibernate.envers.EntityTrackingRevisionListener</interfacename>
interface. 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 +465,134 @@ public class ExampleListener implements RevisionListener {
</example>
<section id="envers-tracking-modified-entities-reventity">
<title>Tracking entity classes 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>REVCHANGES</literal>
table which stores fully qualified names of Java classes modified in each revision.
Single record encapsulates the revision identifier (foreign key to <literal>REVINFO</literal> table)
and a string value. Note that this mechanism shall be used when entity name can be clearly identified
by Java class type. Otherwise extend <interfacename>org.hibernate.envers.EntityTrackingRevisionListener</interfacename>
interface (described further).
</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.ModifiedEntityTypes</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 = "REVCHANGES", joinColumns = @JoinColumn(name = "REV"))
@Column(name = "ENTITYTYPE")
@ModifiedEntityTypes
private Set<String> modifiedEntityTypes;
...
}]]></programlisting>
</listitem>
</orderedlist>
<para>
Users, that have chosen one of the approaches listed above, can retrieve all entities modified in
specified revision by utilizing 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>EntityTrackingRevisionListener</interfacename> interface exposes one method that notifies
whenever audited entity instance has been added, modified or removed within current revision boundaries.
</para>
<example>
<title>Custom implementation of tracking entity classes modified during revisions</title>
<programlisting>
<filename>CustomEntityTrackingRevisionListener.java</filename>
<![CDATA[
public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener {
@Override
public void entityChanged(Class entityClass, String entityName, Serializable entityId, RevisionType revisionType,
Object revisionEntity) {
((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityType(entityClass.getName());
}
@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<ModifiedEntityTypeEntity> modifiedEntityTypes = new HashSet<ModifiedEntityTypeEntity>();
public void addModifiedEntityType(String entityClassName) {
modifiedEntityTypes.add(new ModifiedEntityTypeEntity(this, entityClassName));
}
...
}
]]></programlisting>
<programlisting>
<filename>ModifiedEntityTypeEntity.java</filename>
<![CDATA[
@Entity
public class ModifiedEntityTypeEntity {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private CustomTrackingRevisionEntity revision;
private String entityClassName;
...
}
]]></programlisting>
<programlisting><![CDATA[CustomTrackingRevisionEntity revEntity =
getAuditReader().findRevision(CustomTrackingRevisionEntity.class, revisionNumber);
Set<ModifiedEntityTypeEntity> modifiedEntityTypes = revEntity.getModifiedEntityTypes()]]></programlisting>
</example>
</section>
</section>
<section id="envers-queries">
@ -637,6 +784,42 @@ query.add(AuditEntity.relatedId("address").eq(relatedEntityId));]]></programlist
</section>
<section id="envers-tracking-modified-entities-queries">
<title>Querying for entities modified in a given revision</title>
<para>
The basic query allows retrieving entity types changed in a specified revision:
</para>
<programlisting><![CDATA[Set<Class> modifiedEntityTypes = getAuditReader()
.findEntityTypesChangedInRevision(revisionNumber);]]></programlisting>
<para>
Other queries (accessible from <interfacename>org.hibernate.envers.AuditReader</interfacename>):
</para>
<orderedlist>
<listitem>
<firstterm><methodname>List<![CDATA[<Object>]]> findEntitiesChangedInRevision(Number)</methodname></firstterm>
- Returns snapshots of all audited entities changed (added, updated and removed) in a given revision.
Executes <literal>n+1</literal> SQL queries, where <literal>n</literal> is a number of different entity
classes modified within specified revision.
</listitem>
<listitem>
<firstterm><methodname>List<![CDATA[<Object>]]> findEntitiesChangedInRevision(Number, RevisionType)</methodname></firstterm>
- Returns snapshots of all audited entities changed (added, updated or removed) in a given revision
filtered by modification type. Executes <literal>n+1</literal> SQL queries, where <literal>n</literal>
is a number of different entity classes modified within specified revision.
</listitem>
<listitem>
<firstterm><methodname><![CDATA[Map<RevisionType, List<Object>>]]> findEntitiesChangedInRevisionGroupByRevisionType(Number)</methodname></firstterm>
- Returns a map containing lists of entity snapshots grouped by modification operation (e.g.
addition, update and removal). Executes <literal>3n+1</literal> SQL queries, where <literal>n</literal>
is a number of different entity classes modified within specified revision.
</listitem>
</orderedlist>
<para>
Note that methods described above can be legally used only when default mechanism of
tracking changed entity types 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,118 @@ 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 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;
/**
* Find all entities changed (added, updated and removed) in a given revision. Executes <i>n+1</i> SQL queries,
* where <i>n</i> is a number of different entity classes modified within specified 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 ModifiedEntityTypes} interface.</li>
* </ul>
*/
List<Object> findEntitiesChangedInRevision(Number revision)
throws IllegalStateException, IllegalArgumentException, AuditException;
/**
* Find all entities changed (added, updated or removed) in a given revision. Executes <i>n+1</i> SQL queries,
* where <i>n</i> is a number of different entity classes modified within specified revision.
* @param revision Revision number.
* @param revisionType Type of modification.
* @return Snapshots of all audited entities changed in a given revision and 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 ModifiedEntityTypes} interface.</li>
* </ul>
*/
List<Object> 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.
* Executes <i>mn+1</i> SQL queries, where:
* <ul>
* <li><i>n</i> - number of different entity classes modified within specified revision.
* <li><i>m</i> - number of different revision types. See {@link RevisionType} enum.
* </ul>
* @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 ModifiedEntityTypes} interface.</li>
* </ul>
*/
Map<RevisionType, List<Object>> findEntitiesChangedInRevisionGroupByRevisionType(Number revision)
throws IllegalStateException, IllegalArgumentException, AuditException;
/**
* Returns set of entity classes modified in a given revision.
* @param revision Revision number.
* @return Set 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 ModifiedEntityTypes} interface.</li>
* </ul>
*/
Set<Class> findEntityTypesChangedInRevision(Number revision)
throws IllegalStateException, IllegalArgumentException, AuditException;
}

View File

@ -0,0 +1,55 @@
package org.hibernate.envers;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
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 <code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter
* is set to <code>true</code>.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@MappedSuperclass
public class DefaultTrackingModifiedTypesRevisionEntity extends DefaultRevisionEntity {
@ElementCollection(fetch = FetchType.EAGER)
@JoinTable(name = "REVCHANGES", joinColumns = @JoinColumn(name = "REV"))
@Column(name = "ENTITYTYPE")
@Fetch(FetchMode.JOIN)
@ModifiedEntityTypes
private Set<String> modifiedEntityTypes = new HashSet<String>();
public Set<String> getModifiedEntityTypes() {
return modifiedEntityTypes;
}
public void setModifiedEntityTypes(Set<String> modifiedEntityTypes) {
this.modifiedEntityTypes = modifiedEntityTypes;
}
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 (modifiedEntityTypes != null ? !modifiedEntityTypes.equals(that.modifiedEntityTypes)
: that.modifiedEntityTypes != null) return false;
return true;
}
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (modifiedEntityTypes != null ? modifiedEntityTypes.hashCode() : 0);
return result;
}
public String toString() {
return "DefaultTrackingModifiedTypesRevisionEntity(" + super.toString() + ", modifiedEntityTypes = " + modifiedEntityTypes + ")";
}
}

View File

@ -0,0 +1,23 @@
package org.hibernate.envers;
import java.io.Serializable;
/**
* Extension of standard {@link RevisionListener} that notifies whenever an entity instance has been
* added, modified or removed within 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 entityClass Audited entity class.
* @param entityName Name of the audited entity. May be useful when Java class is mapped multiple times,
* potentially to different tables.
* @param entityId Identifier of modified entity.
* @param revisionType Modification type (addition, update or removal).
* @param revisionEntity An instance of the entity annotated with {@link RevisionEntity}.
*/
void entityChanged(Class entityClass, String entityName, Serializable entityId, RevisionType revisionType,
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 holds entity class names that have been modified during each revision.
* This annotation expects field of <code>{@literal Set<String>}</code> type.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface ModifiedEntityTypes {
}

View File

@ -30,6 +30,7 @@ import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.cfg.Configuration;
import org.hibernate.envers.entities.EntitiesConfigurations;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.revisioninfo.ModifiedEntityTypesReader;
import org.hibernate.envers.revisioninfo.RevisionInfoNumberReader;
import org.hibernate.envers.revisioninfo.RevisionInfoQueryCreator;
import org.hibernate.envers.strategy.AuditStrategy;
@ -50,6 +51,7 @@ public class AuditConfiguration {
private final EntitiesConfigurations entCfg;
private final RevisionInfoQueryCreator revisionInfoQueryCreator;
private final RevisionInfoNumberReader revisionInfoNumberReader;
private final ModifiedEntityTypesReader modifiedEntityTypesReader;
public AuditEntitiesConfiguration getAuditEntCfg() {
return auditEntCfg;
@ -75,6 +77,10 @@ public class AuditConfiguration {
return revisionInfoNumberReader;
}
public ModifiedEntityTypesReader getModifiedEntityTypesReader() {
return modifiedEntityTypesReader;
}
public AuditStrategy getAuditStrategy() {
return auditStrategy;
}
@ -90,6 +96,7 @@ public class AuditConfiguration {
auditProcessManager = new AuditProcessManager(revInfoCfgResult.getRevisionInfoGenerator());
revisionInfoQueryCreator = revInfoCfgResult.getRevisionInfoQueryCreator();
revisionInfoNumberReader = revInfoCfgResult.getRevisionInfoNumberReader();
modifiedEntityTypesReader = revInfoCfgResult.getModifiedEntityTypesReader();
auditStrategy = initializeAuditStrategy(revInfoCfgResult.getRevisionInfoClass(),
revInfoCfgResult.getRevisionInfoTimestampData());
entCfg = new EntitiesConfigurator().configure(cfg, reflectionManager, globalCfg, auditEntCfg, auditStrategy,

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;
@ -35,6 +36,8 @@ 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.DefaultTrackingModifiedTypesRevisionEntity;
import org.hibernate.envers.ModifiedEntityTypes;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionListener;
import org.hibernate.envers.RevisionNumber;
@ -43,6 +46,8 @@ 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.DefaultTrackingModifiedTypesRevisionInfoGenerator;
import org.hibernate.envers.revisioninfo.ModifiedEntityTypesReader;
import org.hibernate.envers.revisioninfo.RevisionInfoGenerator;
import org.hibernate.envers.revisioninfo.RevisionInfoNumberReader;
import org.hibernate.envers.revisioninfo.RevisionInfoQueryCreator;
@ -53,11 +58,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 modifiedEntityTypesData;
private Type revisionInfoTimestampType;
private GlobalConfiguration globalCfg;
@ -69,6 +76,7 @@ public class RevisionInfoConfiguration {
revisionInfoEntityName = "org.hibernate.envers.DefaultRevisionEntity";
revisionInfoIdData = new PropertyData("id", "id", "field", null);
revisionInfoTimestampData = new PropertyData("timestamp", "timestamp", "field", null);
modifiedEntityTypesData = new PropertyData("modifiedEntityTypes", "modifiedEntityTypes", "field", null);
revisionInfoTimestampType = new LongType();
revisionPropType = "integer";
@ -90,9 +98,41 @@ public class RevisionInfoConfiguration {
revisionInfoTimestampType.getName(), true, false);
MetadataTools.addColumn(timestampProperty, "REVTSTMP", null, 0, 0, null, null, null, false);
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()) {
generateEntityTypesTrackingTableMapping(class_mapping, "modifiedEntityTypes", "REVCHANGES", "REV", "ENTITYTYPE", "string");
}
return document;
}
/**
* Generates mapping that represents a set of primitive types.<br />
* <code>
* &lt;set name="propertyName" table="joinTableName" cascade="persist, delete" lazy="false" fetch="join"&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 generateEntityTypesTrackingTableMapping(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");
set.addAttribute("fetch", "join");
set.addAttribute("lazy", "false");
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 +149,11 @@ public class RevisionInfoConfiguration {
private void searchForRevisionInfoCfgInProperties(XClass clazz, ReflectionManager reflectionManager,
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
String accessType) {
MutableBoolean modifiedEntityTypesFound, String accessType) {
for (XProperty property : clazz.getDeclaredProperties(accessType)) {
RevisionNumber revisionNumber = property.getAnnotation(RevisionNumber.class);
RevisionTimestamp revisionTimestamp = property.getAnnotation(RevisionTimestamp.class);
ModifiedEntityTypes modifiedEntityTypes = property.getAnnotation(ModifiedEntityTypes.class);
if (revisionNumber != null) {
if (revisionNumberFound.isSet()) {
@ -162,20 +203,35 @@ public class RevisionInfoConfiguration {
"long, Long, java.util.Date or java.sql.Date");
}
}
if (modifiedEntityTypes != null) {
if (modifiedEntityTypesFound.isSet()) {
throw new MappingException("Only one property may be annotated with @ModifiedEntityTypes!");
}
XClass modifiedEntityTypesClass = property.getType();
if (reflectionManager.equals(modifiedEntityTypesClass, Set.class) &&
reflectionManager.equals(property.getElementClass(), String.class)) {
modifiedEntityTypesData = new PropertyData(property.getName(), property.getName(), accessType, null);
modifiedEntityTypesFound.set();
} else {
throw new MappingException("The field annotated with @ModifiedEntityTypes must be of Set<String> type.");
}
}
}
}
private void searchForRevisionInfoCfg(XClass clazz, ReflectionManager reflectionManager,
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound) {
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
MutableBoolean modifiedEntityTypesFound) {
XClass superclazz = clazz.getSuperclass();
if (!"java.lang.Object".equals(superclazz.getName())) {
searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound);
searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityTypesFound);
}
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
"field");
modifiedEntityTypesFound, "field");
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
"property");
modifiedEntityTypesFound, "property");
}
public RevisionInfoConfigurationResult configure(Configuration cfg, ReflectionManager reflectionManager) {
@ -209,8 +265,9 @@ public class RevisionInfoConfiguration {
MutableBoolean revisionNumberFound = new MutableBoolean();
MutableBoolean revisionTimestampFound = new MutableBoolean();
MutableBoolean modifiedEntityTypesFound = new MutableBoolean();
searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound);
searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityTypesFound);
if (!revisionNumberFound.isSet()) {
throw new MappingException("An entity annotated with @RevisionEntity must have a field annotated " +
@ -226,8 +283,19 @@ 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) ||
modifiedEntityTypesFound.isSet()) {
// If tracking modified entities parameter is enabled, custom revision info entity is a subtype
// of DefaultTrackingModifiedTypesRevisionEntity class, or @ModifiedEntityTypes annotation is used.
revisionInfoGenerator = new DefaultTrackingModifiedTypesRevisionInfoGenerator(revisionInfoEntityName,
revisionInfoClass, revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate(),
modifiedEntityTypesData);
globalCfg.setTrackEntitiesChangedInRevisionEnabled(true);
} else {
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate());
}
}
}
@ -235,9 +303,16 @@ 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(), modifiedEntityTypesData);
} else {
revisionInfoClass = DefaultRevisionEntity.class;
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate());
}
revisionInfoXmlMapping = generateDefaultRevisionInfoXmlMapping();
}
@ -246,8 +321,10 @@ public class RevisionInfoConfiguration {
new RevisionInfoQueryCreator(revisionInfoEntityName, revisionInfoIdData.getName(),
revisionInfoTimestampData.getName(), isTimestampAsDate()),
generateRevisionInfoRelationMapping(),
new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData), revisionInfoEntityName,
revisionInfoClass, revisionInfoTimestampData);
new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData),
globalCfg.isTrackEntitiesChangedInRevisionEnabled() ? new ModifiedEntityTypesReader(revisionInfoClass, modifiedEntityTypesData)
: null,
revisionInfoEntityName, revisionInfoClass, revisionInfoTimestampData);
}
private boolean isTimestampAsDate() {
@ -262,20 +339,22 @@ class RevisionInfoConfigurationResult {
private final RevisionInfoQueryCreator revisionInfoQueryCreator;
private final Element revisionInfoRelationMapping;
private final RevisionInfoNumberReader revisionInfoNumberReader;
private final ModifiedEntityTypesReader modifiedEntityTypesReader;
private final String revisionInfoEntityName;
private final Class<?> revisionInfoClass;
private final PropertyData revisionInfoTimestampData;
RevisionInfoConfigurationResult(RevisionInfoGenerator revisionInfoGenerator,
Document revisionInfoXmlMapping, RevisionInfoQueryCreator revisionInfoQueryCreator,
Element revisionInfoRelationMapping,
RevisionInfoNumberReader revisionInfoNumberReader, String revisionInfoEntityName, Class<?> revisionInfoClass,
PropertyData revisionInfoTimestampData) {
Element revisionInfoRelationMapping, RevisionInfoNumberReader revisionInfoNumberReader,
ModifiedEntityTypesReader modifiedEntityTypesReader, String revisionInfoEntityName,
Class<?> revisionInfoClass, PropertyData revisionInfoTimestampData) {
this.revisionInfoGenerator = revisionInfoGenerator;
this.revisionInfoXmlMapping = revisionInfoXmlMapping;
this.revisionInfoQueryCreator = revisionInfoQueryCreator;
this.revisionInfoRelationMapping = revisionInfoRelationMapping;
this.revisionInfoNumberReader = revisionInfoNumberReader;
this.modifiedEntityTypesReader = modifiedEntityTypesReader;
this.revisionInfoEntityName = revisionInfoEntityName;
this.revisionInfoClass = revisionInfoClass;
this.revisionInfoTimestampData = revisionInfoTimestampData;
@ -313,4 +392,7 @@ class RevisionInfoConfigurationResult {
return revisionInfoTimestampData;
}
public ModifiedEntityTypesReader getModifiedEntityTypesReader() {
return modifiedEntityTypesReader;
}
}

View File

@ -25,6 +25,7 @@ package org.hibernate.envers.query;
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.query.impl.EntitiesModifiedAtRevisionQuery;
import org.hibernate.envers.query.impl.EntitiesAtRevisionQuery;
import org.hibernate.envers.query.impl.RevisionsOfEntityQuery;
import org.hibernate.envers.reader.AuditReaderImplementor;
@ -32,6 +33,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;
@ -71,7 +73,38 @@ public class AuditQueryCreator {
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
return new EntitiesAtRevisionQuery(auditCfg, auditReaderImplementor, c, entityName, revision);
}
}
/**
* In comparison to {@link #forEntitiesAtRevision(Class, String, Number)} this query will return an empty
* collection if an entity of a certain type has not been changed in a given revision.
* @param c Class of the entities for which to query.
* @param entityName Name of the entity (if can't be guessed basing on the {@code c}).
* @param revision Revision number at which to execute the query.
* @return A query for entities changed at a given revision, to which conditions can be added and which
* can then be executed.
* @see #forEntitiesAtRevision(Class, String, Number)
*/
public AuditQuery forEntitiesModifiedAtRevision(Class<?> c, String entityName, Number revision) {
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
return new EntitiesModifiedAtRevisionQuery(auditCfg, auditReaderImplementor, c, entityName, revision);
}
/**
* In comparison to {@link #forEntitiesAtRevision(Class, Number)} this query will return an empty
* collection if an entity of a certain type has not been changed in a given revision.
* @param c Class of the entities for which to query.
* @param revision Revision number at which to execute the query.
* @return A query for entities changed at a given revision, to which conditions can be added and which
* can then be executed.
* @see #forEntitiesAtRevision(Class, Number)
*/
public AuditQuery forEntitiesModifiedAtRevision(Class<?> c, Number revision) {
checkNotNull(revision, "Entity revision");
checkPositive(revision, "Entity revision");
return new EntitiesModifiedAtRevisionQuery(auditCfg, auditReaderImplementor, c, revision);
}
/**
* Creates a query, which selects the revisions, at which the given entity was modified.
@ -121,5 +154,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

@ -0,0 +1,63 @@
package org.hibernate.envers.query.impl;
import org.hibernate.Query;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.configuration.AuditEntitiesConfiguration;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.reader.AuditReaderImplementor;
import java.util.ArrayList;
import java.util.List;
/**
* In comparison to {@link EntitiesAtRevisionQuery} this query returns an empty collection if an entity
* of a certain type has not been changed in a given revision.
* @see EntitiesAtRevisionQuery
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
private final Number revision;
public EntitiesModifiedAtRevisionQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader,
Class<?> cls, Number revision) {
super(verCfg, versionsReader, cls);
this.revision = revision;
}
public EntitiesModifiedAtRevisionQuery(AuditConfiguration verCfg, AuditReaderImplementor versionsReader,
Class<?> cls, String entityName, Number revision) {
super(verCfg, versionsReader, cls, entityName);
this.revision = revision;
}
@SuppressWarnings({"unchecked"})
public List list() {
/*
* The query that we need to create:
* SELECT new list(e) FROM versionsReferencedEntity e
* WHERE
* (all specified conditions, transformed, on the "e" entity) AND
* e.revision = :revision
*/
AuditEntitiesConfiguration verEntCfg = verCfg.getAuditEntCfg();
String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
qb.getRootParameters().addWhereWithParam(revisionPropertyPath, "=", revision);
// all specified conditions
for (AuditCriterion criterion : criterions) {
criterion.addToQuery(verCfg, entityName, qb, qb.getRootParameters());
}
Query query = buildQuery();
List queryResult = query.list();
if (hasProjection) {
return queryResult;
} else {
List result = new ArrayList();
entityInstantiator.addInstancesFromVersionsEntities(entityName, result, queryResult, revision);
return result;
}
}
}

View File

@ -24,6 +24,8 @@
package org.hibernate.envers.reader;
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@ -31,17 +33,19 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.NoResultException;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.engine.spi.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.spi.EventSource;
import org.hibernate.proxy.HibernateProxy;
@ -49,6 +53,7 @@ import org.hibernate.proxy.HibernateProxy;
/**
* @author Adam Warski (adam at warski dot org)
* @author Hern&aacute;n Chanfreau
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class AuditReaderImpl implements AuditReaderImplementor {
private final AuditConfiguration verCfg;
@ -152,7 +157,7 @@ public class AuditReaderImpl implements AuditReaderImplementor {
checkPositive(revision, "Entity revision");
checkSession();
Query query = verCfg.getRevisionInfoQueryCreator().getRevisionDateQuery(session, revision);
Criteria query = verCfg.getRevisionInfoQueryCreator().getRevisionDateQuery(session, revision);
try {
Object timestampObject = query.uniqueResult();
@ -171,7 +176,7 @@ public class AuditReaderImpl implements AuditReaderImplementor {
checkNotNull(date, "Date of revision");
checkSession();
Query query = verCfg.getRevisionInfoQueryCreator().getRevisionNumberForDateQuery(session, date);
Criteria query = verCfg.getRevisionInfoQueryCreator().getRevisionNumberForDateQuery(session, date);
try {
Number res = (Number) query.uniqueResult();
@ -194,7 +199,7 @@ public class AuditReaderImpl implements AuditReaderImplementor {
Set<Number> revisions = new HashSet<Number>(1);
revisions.add(revision);
Query query = verCfg.getRevisionInfoQueryCreator().getRevisionsQuery(session, revisions);
Criteria query = verCfg.getRevisionInfoQueryCreator().getRevisionsQuery(session, revisions);
try {
T revisionData = (T) query.uniqueResult();
@ -220,7 +225,7 @@ public class AuditReaderImpl implements AuditReaderImplementor {
}
checkSession();
Query query = verCfg.getRevisionInfoQueryCreator().getRevisionsQuery(session, revisions);
Criteria query = verCfg.getRevisionInfoQueryCreator().getRevisionsQuery(session, revisions);
try {
List<T> revisionList = query.list();
@ -235,6 +240,67 @@ public class AuditReaderImpl implements AuditReaderImplementor {
}
}
@SuppressWarnings({"unchecked"})
public List<Object> findEntitiesChangedInRevision(Number revision) throws IllegalStateException,
IllegalArgumentException, AuditException {
Set<Class> clazz = findEntityTypesChangedInRevision(revision);
List<Object> result = new ArrayList<Object>();
for (Class c : clazz) {
result.addAll(createQuery().forEntitiesModifiedAtRevision(c, revision).getResultList());
}
return result;
}
@SuppressWarnings({"unchecked"})
public List<Object> findEntitiesChangedInRevision(Number revision, RevisionType revisionType)
throws IllegalStateException, IllegalArgumentException, AuditException {
Set<Class> clazz = findEntityTypesChangedInRevision(revision);
List<Object> result = new ArrayList<Object>();
for (Class c : clazz) {
result.addAll(createQuery().forEntitiesModifiedAtRevision(c, revision)
.add(new RevisionTypeAuditExpression(revisionType, "=")).getResultList());
}
return result;
}
@SuppressWarnings({"unchecked"})
public Map<RevisionType, List<Object>> findEntitiesChangedInRevisionGroupByRevisionType(Number revision)
throws IllegalStateException, IllegalArgumentException, AuditException {
Set<Class> clazz = findEntityTypesChangedInRevision(revision);
Map<RevisionType, List<Object>> result = new HashMap<RevisionType, List<Object>>();
for (RevisionType revisionType : RevisionType.values()) {
result.put(revisionType, new ArrayList());
for (Class c : clazz) {
List<Object> list = createQuery().forEntitiesModifiedAtRevision(c, revision)
.add(new RevisionTypeAuditExpression(revisionType, "=")).getResultList();
result.get(revisionType).addAll(list);
}
}
return result;
}
@SuppressWarnings({"unchecked"})
public Set<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 @ModifiedEntityTypes annotation or set "
+ "'org.hibernate.envers.track_entities_changed_in_revision' parameter to true.");
}
Set<Number> revisions = new HashSet<Number>(1);
revisions.add(revision);
Criteria query = verCfg.getRevisionInfoQueryCreator().getRevisionsQuery(session, revisions);
Object revisionInfo = query.uniqueResult();
if (revisionInfo != null) {
// If revision exists
return verCfg.getModifiedEntityTypesReader().getModifiedEntityTypes(revisionInfo);
}
return Collections.EMPTY_SET;
}
@SuppressWarnings({"unchecked"})
public <T> T getCurrentRevision(Class<T> revisionEntityClass, boolean persist) {
if (!(session instanceof EventSource)) {

View File

@ -22,16 +22,22 @@
* Boston, MA 02110-1301 USA
*/
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.RevisionType;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.tools.reflection.ReflectionTools;
import org.hibernate.property.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* @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 +92,12 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
return revisionInfo;
}
public void entityChanged(Class entityClass, String entityName, Serializable entityId, RevisionType revisionType,
Object revisionInfo) {
if (listener instanceof EntityTrackingRevisionListener) {
((EntityTrackingRevisionListener) listener).entityChanged(entityClass, entityName, entityId, revisionType,
revisionInfo);
}
}
}

View File

@ -0,0 +1,47 @@
package org.hibernate.envers.revisioninfo;
import org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity;
import org.hibernate.envers.ModifiedEntityTypes;
import org.hibernate.envers.RevisionListener;
import org.hibernate.envers.RevisionType;
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.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* Automatically adds entity class names, that have been changed during current revision, to revision entity.
* @see ModifiedEntityTypes
* @see DefaultTrackingModifiedTypesRevisionEntity
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class DefaultTrackingModifiedTypesRevisionInfoGenerator extends DefaultRevisionInfoGenerator {
private final Setter modifiedEntityTypesSetter;
private final Getter modifiedEntityTypesGetter;
public DefaultTrackingModifiedTypesRevisionInfoGenerator(String revisionInfoEntityName, Class<?> revisionInfoClass,
Class<? extends RevisionListener> listenerClass,
PropertyData revisionInfoTimestampData, boolean timestampAsDate,
PropertyData modifiedEntityTypesData) {
super(revisionInfoEntityName, revisionInfoClass, listenerClass, revisionInfoTimestampData, timestampAsDate);
modifiedEntityTypesSetter = ReflectionTools.getSetter(revisionInfoClass, modifiedEntityTypesData);
modifiedEntityTypesGetter = ReflectionTools.getGetter(revisionInfoClass, modifiedEntityTypesData);
}
@Override
@SuppressWarnings({"unchecked"})
public void entityChanged(Class entityClass, String entityName, Serializable entityId, RevisionType revisionType,
Object revisionEntity) {
super.entityChanged(entityClass, entityName, entityId, revisionType, revisionEntity);
Set<String> modifiedEntityTypes = (Set<String>) modifiedEntityTypesGetter.get(revisionEntity);
if (modifiedEntityTypes == null) {
modifiedEntityTypes = new HashSet<String>();
modifiedEntityTypesSetter.set(revisionEntity, modifiedEntityTypes, null);
}
modifiedEntityTypes.add(entityClass.getName());
}
}

View File

@ -0,0 +1,41 @@
package org.hibernate.envers.revisioninfo;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.tools.reflection.ReflectionTools;
import org.hibernate.property.Getter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Returns modified entity types from a persisted revision info entity.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ModifiedEntityTypesReader {
private final Getter modifiedEntityTypesGetter;
public ModifiedEntityTypesReader(Class<?> revisionInfoClass, PropertyData modifiedEntityTypesData) {
modifiedEntityTypesGetter = ReflectionTools.getGetter(revisionInfoClass, modifiedEntityTypesData);
}
@SuppressWarnings({"unchecked"})
public Set<Class> getModifiedEntityTypes(Object revisionEntity) {
// The default mechanism of tracking entity types that have been changed during each revision stores
// fully qualified Java class names.
Set<String> modifiedEntityClassNames = (Set<String>) modifiedEntityTypesGetter.get(revisionEntity);
if (modifiedEntityClassNames != null) {
Set<Class> result = new HashSet<Class>(modifiedEntityClassNames.size());
for (String entityClassName : modifiedEntityClassNames) {
try {
result.add(Thread.currentThread().getContextClassLoader().loadClass(entityClassName));
} catch (ClassNotFoundException e) {
// This shall never happen
throw new RuntimeException(e);
}
}
return result;
}
return Collections.EMPTY_SET;
}
}

View File

@ -22,7 +22,12 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.revisioninfo;
import org.hibernate.Session;
import org.hibernate.envers.EntityTrackingRevisionListener;
import org.hibernate.envers.RevisionType;
import java.io.Serializable;
/**
* @author Adam Warski (adam at warski dot org)
@ -30,4 +35,10 @@ import org.hibernate.Session;
public interface RevisionInfoGenerator {
void saveRevisionData(Session session, Object revisionData);
Object generate();
/**
* @see EntityTrackingRevisionListener#entityChanged(Class, String, Serializable, RevisionType, Object)
*/
void entityChanged(Class entityClass, String entityName, Serializable entityId, RevisionType revisionType,
Object revisionEntity);
}

View File

@ -22,52 +22,44 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.revisioninfo;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import java.util.Date;
import java.util.Set;
import org.hibernate.Query;
import org.hibernate.Session;
/**
* @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 revisionInfoEntityName;
private final String revisionInfoIdName;
private final String revisionInfoTimestampName;
private final boolean timestampAsDate;
public RevisionInfoQueryCreator(String revisionInfoEntityName, String revisionInfoIdName,
String revisionInfoTimestampName, boolean timestampAsDate) {
this.revisionInfoEntityName = revisionInfoEntityName;
this.revisionInfoIdName = revisionInfoIdName;
this.revisionInfoTimestampName = revisionInfoTimestampName;
this.timestampAsDate = timestampAsDate;
revisionDateQuery = new StringBuilder()
.append("select rev.").append(revisionInfoTimestampName)
.append(" from ").append(revisionInfoEntityName)
.append(" rev where ").append(revisionInfoIdName).append(" = :_revision_number")
.toString();
revisionNumberForDateQuery = new StringBuilder()
.append("select max(rev.").append(revisionInfoIdName)
.append(") from ").append(revisionInfoEntityName)
.append(" rev where ").append(revisionInfoTimestampName).append(" <= :_revision_date")
.toString();
revisionsQuery = new StringBuilder()
.append("select rev from ").append(revisionInfoEntityName)
.append(" rev where ").append(revisionInfoIdName)
.append(" in (:_revision_numbers)")
.toString();
}
public Query getRevisionDateQuery(Session session, Number revision) {
return session.createQuery(revisionDateQuery).setParameter("_revision_number", revision);
public Criteria getRevisionDateQuery(Session session, Number revision) {
return session.createCriteria(revisionInfoEntityName).setProjection(Projections.property(revisionInfoTimestampName))
.add(Restrictions.eq(revisionInfoIdName, revision));
}
public Query getRevisionNumberForDateQuery(Session session, Date date) {
return session.createQuery(revisionNumberForDateQuery).setParameter("_revision_date", timestampAsDate ? date : date.getTime());
public Criteria getRevisionNumberForDateQuery(Session session, Date date) {
return session.createCriteria(revisionInfoEntityName).setProjection(Projections.max(revisionInfoIdName))
.add(Restrictions.le(revisionInfoTimestampName, timestampAsDate ? date : date.getTime()));
}
public Query getRevisionsQuery(Session session, Set<Number> revisions) {
return session.createQuery(revisionsQuery).setParameterList("_revision_numbers", revisions);
public Criteria getRevisionsQuery(Session session, Set<Number> revisions) {
return session.createCriteria(revisionInfoEntityName).add(Restrictions.in(revisionInfoIdName, revisions));
}
}

View File

@ -22,6 +22,7 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.synchronization;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
@ -44,6 +45,7 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
private final LinkedList<AuditWorkUnit> workUnits;
private final Queue<AuditWorkUnit> undoQueue;
private final Map<Pair<String, Object>, AuditWorkUnit> usedIds;
private final EntityChangeNotifier entityChangeNotifier;
private Object revisionData;
@ -54,6 +56,7 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
workUnits = new LinkedList<AuditWorkUnit>();
undoQueue = new LinkedList<AuditWorkUnit>();
usedIds = new HashMap<Pair<String, Object>, AuditWorkUnit>();
entityChangeNotifier = new EntityChangeNotifier(revisionInfoGenerator, session);
}
private void removeWorkUnit(AuditWorkUnit vwu) {
@ -98,7 +101,7 @@ 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;
@ -109,6 +112,7 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
while ((vwu = workUnits.poll()) != null) {
vwu.perform(session, revisionData);
entityChangeNotifier.entityChanged(session, currentRevisionData, vwu);
}
}

View File

@ -0,0 +1,51 @@
package org.hibernate.envers.synchronization;
import org.hibernate.Session;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.revisioninfo.RevisionInfoGenerator;
import org.hibernate.envers.synchronization.work.AuditWorkUnit;
import org.hibernate.envers.synchronization.work.PersistentCollectionChangeWorkUnit;
import org.hibernate.persister.entity.EntityPersister;
import java.io.Serializable;
/**
* Notifies {@link RevisionInfoGenerator} about changes made in the current revision.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class EntityChangeNotifier {
private final RevisionInfoGenerator revisionInfoGenerator;
private final SessionImplementor sessionImplementor;
public EntityChangeNotifier(RevisionInfoGenerator revisionInfoGenerator, SessionImplementor sessionImplementor) {
this.revisionInfoGenerator = revisionInfoGenerator;
this.sessionImplementor = sessionImplementor;
}
/**
* Notifies {@link RevisionInfoGenerator} about changes made in the current revision. Provides information
* about modified entity class, entity name and its id, as well as {@link RevisionType} and revision log entity.
* @param session Active session.
* @param currentRevisionData Revision log entity.
* @param vwu Performed work unit.
*/
public void entityChanged(Session session, Object currentRevisionData, AuditWorkUnit vwu) {
Serializable entityId = vwu.getEntityId();
if (entityId instanceof PersistentCollectionChangeWorkUnit.PersistentCollectionChangeWorkUnitId) {
// Notify about a change in collection owner entity.
entityId = ((PersistentCollectionChangeWorkUnit.PersistentCollectionChangeWorkUnitId) entityId).getOwnerId();
}
Class entityClass = getEntityClass(session, vwu.getEntityName());
revisionInfoGenerator.entityChanged(entityClass, vwu.getEntityName(), entityId, vwu.getRevisionType(),
currentRevisionData);
}
/**
* @return Java class mapped to specified entity name.
*/
private Class getEntityClass(Session session, String entityName) {
EntityPersister entityPersister = sessionImplementor.getFactory().getEntityPersister(entityName);
return entityPersister.getClassMetadata().getMappedClass(session.getEntityMode());
}
}

View File

@ -22,6 +22,7 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.synchronization.work;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@ -35,6 +36,7 @@ import org.hibernate.envers.strategy.AuditStrategy;
/**
* @author Adam Warski (adam at warski dot org)
* @author Stephanie Pau at Markit Group Plc
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
protected final SessionImplementor sessionImplementor;
@ -42,19 +44,21 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
protected final Serializable id;
protected final String entityName;
protected final AuditStrategy auditStrategy;
protected final RevisionType revisionType;
private Object performedData;
protected AbstractAuditWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id) {
Serializable id, RevisionType revisionType) {
this.sessionImplementor = sessionImplementor;
this.verCfg = verCfg;
this.id = id;
this.entityName = entityName;
this.revisionType = revisionType;
this.auditStrategy = verCfg.getAuditStrategy();
}
protected void fillDataWithId(Map<String, Object> data, Object revision, RevisionType revisionType) {
protected void fillDataWithId(Map<String, Object> data, Object revision) {
AuditEntitiesConfiguration entitiesCfg = verCfg.getAuditEntCfg();
Map<String, Object> originalId = new HashMap<String, Object>();
@ -73,7 +77,7 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
setPerformed(data);
}
public Object getEntityId() {
public Serializable getEntityId() {
return id;
}
@ -95,4 +99,8 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
session.flush();
}
}
public RevisionType getRevisionType() {
return revisionType;
}
}

View File

@ -38,7 +38,7 @@ public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
public AddWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id, EntityPersister entityPersister, Object[] state) {
super(sessionImplementor, entityName, verCfg, id);
super(sessionImplementor, entityName, verCfg, id, RevisionType.ADD);
data = new HashMap<String, Object>();
verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,
@ -47,7 +47,7 @@ public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
public AddWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id, Map<String, Object> data) {
super(sessionImplementor, entityName, verCfg, id);
super(sessionImplementor, entityName, verCfg, id, RevisionType.ADD);
this.data = data;
}
@ -57,7 +57,7 @@ public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
}
public Map<String, Object> generateData(Object revisionData) {
fillDataWithId(data, revisionData, RevisionType.ADD);
fillDataWithId(data, revisionData);
return data;
}

View File

@ -23,14 +23,16 @@
*/
package org.hibernate.envers.synchronization.work;
import java.util.Map;
import java.io.Serializable;
import org.hibernate.Session;
import org.hibernate.envers.RevisionType;
/**
* TODO: refactor constructors into factory methods
* @author Adam Warski (adam at warski dot org)
*/
public interface AuditWorkUnit extends WorkUnitMergeVisitor, WorkUnitMergeDispatcher {
Object getEntityId();
Serializable getEntityId();
String getEntityName();
boolean containsWork();
@ -52,4 +54,9 @@ public interface AuditWorkUnit extends WorkUnitMergeVisitor, WorkUnitMergeDispat
* @return Generates data that should be saved when performing this work unit.
*/
Map<String, Object> generateData(Object revisionData);
/**
* @return Performed modification type.
*/
RevisionType getRevisionType();
}

View File

@ -37,7 +37,7 @@ public class CollectionChangeWorkUnit extends AbstractAuditWorkUnit implements A
public CollectionChangeWorkUnit(SessionImplementor session, String entityName, AuditConfiguration verCfg,
Serializable id, Object entity) {
super(session, entityName, verCfg, id);
super(session, entityName, verCfg, id, RevisionType.MOD);
this.entity = entity;
}
@ -48,7 +48,7 @@ public class CollectionChangeWorkUnit extends AbstractAuditWorkUnit implements A
public Map<String, Object> generateData(Object revisionData) {
Map<String, Object> data = new HashMap<String, Object>();
fillDataWithId(data, revisionData, RevisionType.MOD);
fillDataWithId(data, revisionData);
verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().mapToMapFromEntity(sessionImplementor,
data, entity, null);

View File

@ -39,7 +39,7 @@ public class DelWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
public DelWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id, EntityPersister entityPersister, Object[] state) {
super(sessionImplementor, entityName, verCfg, id);
super(sessionImplementor, entityName, verCfg, id, RevisionType.DEL);
this.state = state;
this.propertyNames = entityPersister.getPropertyNames();
@ -51,7 +51,7 @@ public class DelWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
public Map<String, Object> generateData(Object revisionData) {
Map<String, Object> data = new HashMap<String, Object>();
fillDataWithId(data, revisionData, RevisionType.DEL);
fillDataWithId(data, revisionData);
if (verCfg.getGlobalCfg().isStoreDataAtDelete()) {
verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,

View File

@ -28,7 +28,7 @@ public class FakeBidirectionalRelationWorkUnit extends AbstractAuditWorkUnit imp
RelationDescription rd, RevisionType revisionType,
Object index,
AuditWorkUnit nestedWorkUnit) {
super(sessionImplementor, entityName, verCfg, id);
super(sessionImplementor, entityName, verCfg, id, revisionType);
this.nestedWorkUnit = nestedWorkUnit;
// Adding the change for the relation.
@ -39,14 +39,14 @@ public class FakeBidirectionalRelationWorkUnit extends AbstractAuditWorkUnit imp
public FakeBidirectionalRelationWorkUnit(FakeBidirectionalRelationWorkUnit original,
Map<String, FakeRelationChange> fakeRelationChanges,
AuditWorkUnit nestedWorkUnit) {
super(original.sessionImplementor, original.entityName, original.verCfg, original.id);
super(original.sessionImplementor, original.entityName, original.verCfg, original.id, original.revisionType);
this.fakeRelationChanges = fakeRelationChanges;
this.nestedWorkUnit = nestedWorkUnit;
}
public FakeBidirectionalRelationWorkUnit(FakeBidirectionalRelationWorkUnit original, AuditWorkUnit nestedWorkUnit) {
super(original.sessionImplementor, original.entityName, original.verCfg, original.id);
super(original.sessionImplementor, original.entityName, original.verCfg, original.id, original.revisionType);
this.nestedWorkUnit = nestedWorkUnit;

View File

@ -39,7 +39,7 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
public ModWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id, EntityPersister entityPersister, Object[] newState, Object[] oldState) {
super(sessionImplementor, entityName, verCfg, id);
super(sessionImplementor, entityName, verCfg, id, RevisionType.MOD);
data = new HashMap<String, Object>();
changes = verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,
@ -51,7 +51,7 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
}
public Map<String, Object> generateData(Object revisionData) {
fillDataWithId(data, revisionData, RevisionType.MOD);
fillDataWithId(data, revisionData);
return data;
}

View File

@ -47,7 +47,7 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
AuditConfiguration auditCfg, PersistentCollection collection,
CollectionEntry collectionEntry, Serializable snapshot, Serializable id,
String referencingPropertyName) {
super(sessionImplementor, entityName, auditCfg, new PersistentCollectionChangeWorkUnitId(id, collectionEntry.getRole()));
super(sessionImplementor, entityName, auditCfg, new PersistentCollectionChangeWorkUnitId(id, collectionEntry.getRole()), RevisionType.MOD);
this.referencingPropertyName = referencingPropertyName;
@ -59,7 +59,7 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
AuditConfiguration verCfg, Serializable id,
List<PersistentCollectionChangeData> collectionChanges,
String referencingPropertyName) {
super(sessionImplementor, entityName, verCfg, id);
super(sessionImplementor, entityName, verCfg, id, RevisionType.MOD);
this.collectionChanges = collectionChanges;
this.referencingPropertyName = referencingPropertyName;
@ -171,7 +171,7 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
* the entity plus the name of the field (the role). This is needed because such collections aren't entities
* in the "normal" mapping, but they are entities for Envers.
*/
private static class PersistentCollectionChangeWorkUnitId implements Serializable {
public static class PersistentCollectionChangeWorkUnitId implements Serializable {
private static final long serialVersionUID = -8007831518629167537L;
private final Serializable ownerId;
@ -202,5 +202,9 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
result = 31 * result + (role != null ? role.hashCode() : 0);
return result;
}
public Serializable getOwnerId() {
return ownerId;
}
}
}

View File

@ -0,0 +1,80 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.ModifiedEntityTypes;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import javax.persistence.*;
import java.util.Set;
/**
* Sample revision entity that uses {@link ModifiedEntityTypes} annotation.
* @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 = "REVCHANGES", joinColumns = @JoinColumn(name = "REV"))
@Column(name = "ENTITYTYPE")
@ModifiedEntityTypes
private Set<String> entityTypes;
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> getEntityTypes() {
return entityTypes;
}
public void setEntityTypes(Set<String> entityTypes) {
this.entityTypes = entityTypes;
}
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;
if (entityTypes != null ? !entityTypes.equals(that.entityTypes) : that.entityTypes != null) return false;
return true;
}
public int hashCode() {
int result = customId;
result = 31 * result + (int) (customTimestamp ^ (customTimestamp >>> 32));
result = 31 * result + (entityTypes != null ? entityTypes.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AnnotatedTrackingRevisionEntity(customId = " + customId + ", customTimestamp = " + customTimestamp + ", entityTypes=" + entityTypes + ")";
}
}

View File

@ -0,0 +1,79 @@
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;
/**
* Revision entity which {@code modifiedEntityTypes} field is manually populated by {@link CustomTrackingRevisionListener}.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@RevisionEntity(CustomTrackingRevisionListener.class)
public class CustomTrackingRevisionEntity {
@Id
@GeneratedValue
@RevisionNumber
private int customId;
@RevisionTimestamp
private long customTimestamp;
@OneToMany(mappedBy="revision", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private Set<ModifiedEntityTypeEntity> modifiedEntityTypes = new HashSet<ModifiedEntityTypeEntity>();
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<ModifiedEntityTypeEntity> getModifiedEntityTypes() {
return modifiedEntityTypes;
}
public void setModifiedEntityTypes(Set<ModifiedEntityTypeEntity> modifiedEntityTypes) {
this.modifiedEntityTypes = modifiedEntityTypes;
}
public void addModifiedEntityType(String entityClassName) {
modifiedEntityTypes.add(new ModifiedEntityTypeEntity(this, entityClassName));
}
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,21 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import org.hibernate.envers.EntityTrackingRevisionListener;
import org.hibernate.envers.RevisionType;
import java.io.Serializable;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class CustomTrackingRevisionListener implements EntityTrackingRevisionListener {
@Override
public void entityChanged(Class entityClass, String entityName, Serializable entityId, RevisionType revisionType,
Object revisionEntity) {
((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityType(entityClass.getName());
}
@Override
public void newRevision(Object revisionEntity) {
}
}

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,70 @@
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
/**
* Custom detail of revision entity.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
public class ModifiedEntityTypeEntity {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private CustomTrackingRevisionEntity revision;
private String entityClassName;
public ModifiedEntityTypeEntity() {
}
public ModifiedEntityTypeEntity(String entityClassName) {
this.entityClassName = entityClassName;
}
public ModifiedEntityTypeEntity(CustomTrackingRevisionEntity revision, String entityClassName) {
this.revision = revision;
this.entityClassName = entityClassName;
}
public CustomTrackingRevisionEntity getRevision() {
return revision;
}
public void setRevision(CustomTrackingRevisionEntity revision) {
this.revision = revision;
}
public String getEntityClassName() {
return entityClassName;
}
public void setEntityClassName(String entityClassName) {
this.entityClassName = entityClassName;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ModifiedEntityTypeEntity)) return false;
ModifiedEntityTypeEntity that = (ModifiedEntityTypeEntity) o;
if (entityClassName != null ? !entityClassName.equals(that.entityClassName) : that.entityClassName != null) return false;
return true;
}
public int hashCode() {
return entityClassName != null ? entityClassName.hashCode() : 0;
}
@Override
public String toString() {
return "CustomTrackingRevisionEntity(entityClassName = " + entityClassName + ")";
}
}

View File

@ -31,6 +31,7 @@ import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.StrIntTestEntity;
import org.hibernate.envers.test.tools.TestTools;
import org.junit.Assert;
import org.junit.Test;
import javax.persistence.EntityManager;
@ -40,6 +41,7 @@ import java.util.List;
/**
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@SuppressWarnings({"unchecked"})
public class SimpleQuery extends AbstractEntityTest {
@ -267,4 +269,59 @@ public class SimpleQuery extends AbstractEntityTest {
assert result.size() == 0;
}
@Test
public void testEntitiesAddedAtRevision() {
StrIntTestEntity site1 = new StrIntTestEntity("a", 10, id1);
StrIntTestEntity site2 = new StrIntTestEntity("a", 10, id2);
StrIntTestEntity site3 = new StrIntTestEntity("b", 5, id3);
List result = getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, StrIntTestEntity.class.getName(), 1).getResultList();
RevisionType revisionType = (RevisionType) getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 1)
.addProjection(AuditEntity.revisionType()).add(AuditEntity.id().eq(id1))
.getSingleResult();
Assert.assertTrue(TestTools.checkList(result, site1, site2, site3));
Assert.assertEquals(revisionType, RevisionType.ADD);
}
@Test
public void testEntitiesChangedAtRevision() {
StrIntTestEntity site1 = new StrIntTestEntity("c", 10, id1);
StrIntTestEntity site2 = new StrIntTestEntity("a", 20, id2);
List result = getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 2).getResultList();
RevisionType revisionType = (RevisionType) getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 2)
.addProjection(AuditEntity.revisionType()).add(AuditEntity.id().eq(id1))
.getSingleResult();
Assert.assertTrue(TestTools.checkList(result, site1, site2));
Assert.assertEquals(revisionType, RevisionType.MOD);
}
@Test
public void testEntitiesRemovedAtRevision() {
StrIntTestEntity site1 = new StrIntTestEntity(null, null, id1);
List result = getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 4).getResultList();
RevisionType revisionType = (RevisionType) getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 4)
.addProjection(AuditEntity.revisionType()).add(AuditEntity.id().eq(id1))
.getSingleResult();
Assert.assertTrue(TestTools.checkList(result, site1));
Assert.assertEquals(revisionType, RevisionType.DEL);
}
@Test
public void testEntityNotModifiedAtRevision() {
List result = getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 3)
.add(AuditEntity.id().eq(id1)).getResultList();
Assert.assertTrue(result.isEmpty());
}
@Test
public void testNoEntitiesModifiedAtRevision() {
List result = getAuditReader().createQuery().forEntitiesModifiedAtRevision(StrIntTestEntity.class, 5).getResultList();
Assert.assertTrue(result.isEmpty());
}
}

View File

@ -0,0 +1,18 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.ModifiedEntityTypes;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.AnnotatedTrackingRevisionEntity;
/**
* Tests proper behavior of revision entity that utilizes {@link ModifiedEntityTypes} annotation.
* @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,109 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.EntityTrackingRevisionListener;
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.CustomTrackingRevisionListener;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ModifiedEntityTypeEntity;
import org.hibernate.envers.test.tools.TestTools;
import org.junit.Test;
import javax.persistence.EntityManager;
/**
* Tests proper behavior of entity listener that implements {@link EntityTrackingRevisionListener}
* interface. {@link CustomTrackingRevisionListener} shall be notified whenever an entity instance has been
* added, modified or removed, so that changed entity type can be persisted.
* @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(ModifiedEntityTypeEntity.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() {
ModifiedEntityTypeEntity steDescriptor = new ModifiedEntityTypeEntity(StrTestEntity.class.getName());
ModifiedEntityTypeEntity siteDescriptor = new ModifiedEntityTypeEntity(StrIntTestEntity.class.getName());
AuditReader vr = getAuditReader();
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 1);
assert ctre.getModifiedEntityTypes() != null;
assert ctre.getModifiedEntityTypes().size() == 2;
assert TestTools.makeSet(steDescriptor, siteDescriptor).equals(ctre.getModifiedEntityTypes());
}
@Test
public void testTrackModifiedEntities() {
ModifiedEntityTypeEntity siteDescriptor = new ModifiedEntityTypeEntity(StrIntTestEntity.class.getName());
AuditReader vr = getAuditReader();
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 2);
assert ctre.getModifiedEntityTypes() != null;
assert ctre.getModifiedEntityTypes().size() == 1;
assert TestTools.makeSet(siteDescriptor).equals(ctre.getModifiedEntityTypes());
}
@Test
public void testTrackDeletedEntities() {
ModifiedEntityTypeEntity steDescriptor = new ModifiedEntityTypeEntity(StrTestEntity.class.getName());
ModifiedEntityTypeEntity siteDescriptor = new ModifiedEntityTypeEntity(StrIntTestEntity.class.getName());
AuditReader vr = getAuditReader();
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 3);
assert ctre.getModifiedEntityTypes() != null;
assert ctre.getModifiedEntityTypes().size() == 2;
assert TestTools.makeSet(steDescriptor, siteDescriptor).equals(ctre.getModifiedEntityTypes());
}
@Test(expected = AuditException.class)
public void testFindEntitiesChangedInRevisionException() {
getAuditReader().findEntitiesChangedInRevision(1);
}
}

View File

@ -0,0 +1,170 @@
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.envers.test.tools.TestTools;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Table;
import org.junit.Test;
import javax.persistence.EntityManager;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Tests proper behavior of tracking modified entity types when {@code org.hibernate.envers.track_entities_changed_in_revision}
* parameter is set to {@code true}.
* @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 ("REVCHANGES".equals(table.getName())) {
assert table.getColumnSpan() == 2;
assert table.getColumn(new Column("REV")) != null;
assert table.getColumn(new Column("ENTITYTYPE")) != null;
return;
}
}
assert false;
}
@Test
public void testTrackAddedEntities() {
StrTestEntity ste = new StrTestEntity("x", steId);
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
assert TestTools.checkList(getAuditReader().findEntitiesChangedInRevision(1), ste, site);
}
@Test
public void testTrackModifiedEntities() {
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
assert TestTools.checkList(getAuditReader().findEntitiesChangedInRevision(2), site);
}
@Test
public void testTrackDeletedEntities() {
StrTestEntity ste = new StrTestEntity(null, steId);
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
assert TestTools.checkList(getAuditReader().findEntitiesChangedInRevision(3), site, ste);
}
@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<Object>> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(1);
assert TestTools.checkList(result.get(RevisionType.ADD), site, ste);
assert TestTools.checkList(result.get(RevisionType.MOD));
assert TestTools.checkList(result.get(RevisionType.DEL));
}
@Test
public void testTrackModifiedEntitiesGroupByRevisionType() {
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
Map<RevisionType, List<Object>> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(2);
assert TestTools.checkList(result.get(RevisionType.ADD));
assert TestTools.checkList(result.get(RevisionType.MOD), site);
assert TestTools.checkList(result.get(RevisionType.DEL));
}
@Test
public void testTrackDeletedEntitiesGroupByRevisionType() {
StrTestEntity ste = new StrTestEntity(null, steId);
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
Map<RevisionType, List<Object>> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(3);
assert TestTools.checkList(result.get(RevisionType.ADD));
assert TestTools.checkList(result.get(RevisionType.MOD));
assert TestTools.checkList(result.get(RevisionType.DEL), site, ste);
}
@Test
public void testFindChangedEntitiesByRevisionTypeADD() {
StrTestEntity ste = new StrTestEntity("x", steId);
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
assert TestTools.checkList(getAuditReader().findEntitiesChangedInRevision(1, RevisionType.ADD), ste, site);
}
@Test
public void testFindChangedEntitiesByRevisionTypeMOD() {
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
assert TestTools.checkList(getAuditReader().findEntitiesChangedInRevision(2, RevisionType.MOD), site);
}
@Test
public void testFindChangedEntitiesByRevisionTypeDEL() {
StrTestEntity ste = new StrTestEntity(null, steId);
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
assert TestTools.checkList(getAuditReader().findEntitiesChangedInRevision(3, RevisionType.DEL), ste, site);
}
@Test
public void testFindEntityTypesChangedInRevision() {
assert TestTools.makeSet(StrTestEntity.class, StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(1));
assert TestTools.makeSet(StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(2));
assert TestTools.makeSet(StrTestEntity.class, StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(3));
}
}

View File

@ -0,0 +1,68 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.MappingException;
import org.hibernate.envers.test.AbstractSessionTest;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.integration.entityNames.manyToManyAudited.Car;
import org.hibernate.envers.test.integration.entityNames.manyToManyAudited.Person;
import org.hibernate.envers.test.tools.TestTools;
import org.junit.Test;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class EntityNamesTest extends AbstractSessionTest {
protected void initMappings() throws MappingException, URISyntaxException {
URL url = Thread.currentThread().getContextClassLoader().getResource("mappings/entityNames/manyToManyAudited/mappings.hbm.xml");
config.addFile(new File(url.toURI()));
config.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "true");
}
@Test
@Priority(10)
public void initData() {
Person pers1 = new Person("Hernan", 28);
Person pers2 = new Person("Leandro", 29);
Person pers3 = new Person("Barba", 32);
Person pers4 = new Person("Camomo", 15);
// Revision 1
getSession().getTransaction().begin();
List<Person > owners = new ArrayList<Person>();
owners.add(pers1);
owners.add(pers2);
owners.add(pers3);
Car car1 = new Car(5, owners);
getSession().persist(car1);
getSession().getTransaction().commit();
long person1Id = pers1.getId();
// Revision 2
owners = new ArrayList<Person>();
owners.add(pers2);
owners.add(pers3);
owners.add(pers4);
Car car2 = new Car(27, owners);
getSession().getTransaction().begin();
Person person1 = (Person)getSession().get("Personaje", person1Id);
person1.setName("Hernan David");
person1.setAge(40);
getSession().persist(car1);
getSession().persist(car2);
getSession().getTransaction().commit();
}
@Test
@SuppressWarnings("unchecked")
public void testModifiedEntityTypes() {
assert TestTools.makeSet(Car.class, Person.class).equals(getAuditReader().findEntityTypesChangedInRevision(1));
assert TestTools.makeSet(Car.class, Person.class).equals(getAuditReader().findEntityTypesChangedInRevision(2));
}
}

View File

@ -0,0 +1,29 @@
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ExtendedRevisionEntity;
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ExtendedRevisionListener;
import org.junit.Test;
/**
* Tests proper behavior of revision entity that extends {@link DefaultTrackingModifiedTypesRevisionEntity}.
* @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());
}
}

View File

@ -0,0 +1,79 @@
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.StrTestEntity;
import org.junit.Test;
import javax.persistence.EntityManager;
import java.util.Arrays;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class TrackingEntitiesMultipleChangesTest extends AbstractEntityTest {
private Integer steId1 = null;
private Integer steId2 = null;
@Override
public void configure(Ejb3Configuration cfg) {
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "true");
cfg.addAnnotatedClass(StrTestEntity.class);
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// Revision 1 - Adding two entities
em.getTransaction().begin();
StrTestEntity ste1 = new StrTestEntity("x");
StrTestEntity ste2 = new StrTestEntity("y");
em.persist(ste1);
em.persist(ste2);
steId1 = ste1.getId();
steId2 = ste2.getId();
em.getTransaction().commit();
// Revision 2 - Adding first and removing second entity
em.getTransaction().begin();
ste1 = em.find(StrTestEntity.class, steId1);
ste2 = em.find(StrTestEntity.class, steId2);
ste1.setStr("z");
em.remove(ste2);
em.getTransaction().commit();
// Revision 3 - Modifying and removing the same entity.
em.getTransaction().begin();
ste1 = em.find(StrTestEntity.class, steId1);
ste1.setStr("a");
em.merge(ste1);
em.remove(ste1);
em.getTransaction().commit();
}
@Test
public void testTrackAddedTwoEntities() {
StrTestEntity ste1 = new StrTestEntity("x", steId1);
StrTestEntity ste2 = new StrTestEntity("y", steId2);
assert Arrays.asList(ste1, ste2).equals(getAuditReader().findEntitiesChangedInRevision(1));
}
@Test
public void testTrackUpdateAndRemoveDifferentEntities() {
StrTestEntity ste1 = new StrTestEntity("z", steId1);
StrTestEntity ste2 = new StrTestEntity(null, steId2);
assert Arrays.asList(ste1, ste2).equals(getAuditReader().findEntitiesChangedInRevision(2));
}
@Test
public void testTrackUpdateAndRemoveTheSameEntity() {
StrTestEntity ste1 = new StrTestEntity(null, steId1);
assert Arrays.asList(ste1).equals(getAuditReader().findEntitiesChangedInRevision(3));
}
}