HHH-5580 - Tracking entity names in revision
This commit is contained in:
parent
86c15fd212
commit
98342a7e2d
|
@ -243,6 +243,22 @@
|
||||||
evaluates to true
|
evaluates to true
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<property>org.hibernate.envers.track_entities_changed_in_revision</property>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
false
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Should entity types, that have been modified during each revision, be tracked. The default
|
||||||
|
implementation creates <literal>REVENTITY</literal> table that stores fully qualified names
|
||||||
|
of Java classes changed in a specified revision. Each record encapsulates the revision
|
||||||
|
identifier (foreign key to <literal>REVINFO</literal> table) and a string value. For more
|
||||||
|
information refer to <xref linkend="envers-tracking-modified-entities-reventity"/> and
|
||||||
|
<xref linkend="envers-tracking-modified-entities-queries"/>.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
@ -446,6 +462,142 @@ public class ExampleListener implements RevisionListener {
|
||||||
|
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
|
<section id="envers-tracking-modified-entities-reventity">
|
||||||
|
<title>Tracking entity types modified in revision</title>
|
||||||
|
<para>
|
||||||
|
By default entity types that have been changed in each revision are not being tracked. This implies the
|
||||||
|
necessity to query all tables storing audited data in order to retrieve changes made during
|
||||||
|
specified revision. Envers provides a simple mechanism that creates <literal>REVENTITY</literal>
|
||||||
|
table which stores fully qualified names of Java classes changed in each revision.
|
||||||
|
Single record encapsulates the revision identifier (foreign key to <literal>REVINFO</literal> table)
|
||||||
|
and a string value.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Tracking of modified entity types can be enabled in three different ways:
|
||||||
|
</para>
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
Set <property>org.hibernate.envers.track_entities_changed_in_revision</property> parameter to
|
||||||
|
<literal>true</literal>. In this case
|
||||||
|
<classname>org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity</classname> will
|
||||||
|
be implicitly used as the revision log entity.
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
Create a custom revision entity that extends
|
||||||
|
<classname>org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity</classname> class.
|
||||||
|
<programlisting>
|
||||||
|
<![CDATA[@Entity
|
||||||
|
@RevisionEntity
|
||||||
|
public class ExtendedRevisionEntity extends DefaultTrackingModifiedTypesRevisionEntity {
|
||||||
|
...
|
||||||
|
}]]></programlisting>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
Mark an appropriate field of a custom revision entity with
|
||||||
|
<interfacename>@org.hibernate.envers.ModifiedEntityNames</interfacename> annotation. The property is
|
||||||
|
required to be of <literal><![CDATA[Set<String>]]></literal> type.
|
||||||
|
<programlisting>
|
||||||
|
<![CDATA[@Entity
|
||||||
|
@RevisionEntity
|
||||||
|
public class AnnotatedTrackingRevisionEntity {
|
||||||
|
...
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@JoinTable(name = "REVENTITY", joinColumns = @JoinColumn(name = "REV"))
|
||||||
|
@Column(name = "ENTITYNAME")
|
||||||
|
@ModifiedEntityNames
|
||||||
|
private Set<String> modifiedEntityNames;
|
||||||
|
|
||||||
|
...
|
||||||
|
}]]></programlisting>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
<para>
|
||||||
|
Users utilizing one of the approaches listed above can retrieve all entities modified in each revision
|
||||||
|
by using API described in <xref linkend="envers-tracking-modified-entities-queries"/>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Users are also allowed to implement custom mechanism of tracking modified entity types. In this case, they
|
||||||
|
shall pass their own implementation of
|
||||||
|
<interfacename>org.hibernate.envers.EntityTrackingRevisionListener</interfacename> interface as the value
|
||||||
|
of <interfacename>@org.hibernate.envers.RevisionEntity</interfacename> annotation.
|
||||||
|
<interfacename>org.hibernate.envers.EntityTrackingRevisionListener</interfacename> interface exposes two
|
||||||
|
methods that notify user whenever tracking of each audited entity instance is started
|
||||||
|
(<methodname>addEntityToRevision</methodname>) or stopped (<methodname>removeEntityFromRevision</methodname>)
|
||||||
|
within the current revision boundaries.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<example>
|
||||||
|
<title>Custom implementation of tracking entity types modified in revision</title>
|
||||||
|
<programlisting>
|
||||||
|
<filename>CustomEntityTrackingRevisionListener.java</filename>
|
||||||
|
<![CDATA[
|
||||||
|
public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener {
|
||||||
|
@Override
|
||||||
|
public void addEntityToRevision(String entityName, Object revisionEntity) {
|
||||||
|
((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityName(entityName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeEntityFromRevision(String entityName, Object revisionEntity) {
|
||||||
|
((CustomTrackingRevisionEntity)revisionEntity).removeModifiedEntityName(entityName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newRevision(Object revisionEntity) {
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
<programlisting>
|
||||||
|
<filename>CustomTrackingRevisionEntity.java</filename>
|
||||||
|
<![CDATA[
|
||||||
|
@Entity
|
||||||
|
@RevisionEntity(CustomEntityTrackingRevisionListener.class)
|
||||||
|
public class CustomTrackingRevisionEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
@RevisionNumber
|
||||||
|
private int customId;
|
||||||
|
|
||||||
|
@RevisionTimestamp
|
||||||
|
private long customTimestamp;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy="revision", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
|
||||||
|
private Set<ModifiedEntityNameEntity> modifiedEntityNames = new HashSet<ModifiedEntityNameEntity>();
|
||||||
|
|
||||||
|
public void addModifiedEntityName(String entityName) {
|
||||||
|
modifiedEntityNames.add(new ModifiedEntityNameEntity(this, entityName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeModifiedEntityName(String entityName) {
|
||||||
|
modifiedEntityNames.remove(new ModifiedEntityNameEntity(this, entityName));
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]]></programlisting>
|
||||||
|
<programlisting>
|
||||||
|
<filename>ModifiedEntityNameEntity.java</filename>
|
||||||
|
<![CDATA[
|
||||||
|
@Entity
|
||||||
|
public class ModifiedEntityNameEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private CustomTrackingRevisionEntity revision;
|
||||||
|
|
||||||
|
private String entityName;
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]]></programlisting>
|
||||||
|
<programlisting><![CDATA[CustomTrackingRevisionEntity revEntity =
|
||||||
|
getAuditReader().findRevision(CustomTrackingRevisionEntity.class, revisionNumber);
|
||||||
|
List<String> modifiedEntityNames = revEntity.getModifiedEntityNames()]]></programlisting>
|
||||||
|
</example>
|
||||||
|
</section>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="envers-queries">
|
<section id="envers-queries">
|
||||||
|
@ -637,6 +789,38 @@ query.add(AuditEntity.relatedId("address").eq(relatedEntityId));]]></programlist
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="envers-tracking-modified-entities-queries">
|
||||||
|
<title>Querying for entities modified at a given revision</title>
|
||||||
|
<para>
|
||||||
|
The basic query allows retrieving entity classes modified in a specified revision:
|
||||||
|
</para>
|
||||||
|
<programlisting><![CDATA[List<Class> modifiedEntities = getAuditReader()
|
||||||
|
.findEntityTypesChangedInRevision(revisionNumber);]]></programlisting>
|
||||||
|
<para>
|
||||||
|
Other queries (accessible from <interfacename>org.hibernate.envers.AuditReader</interfacename>):
|
||||||
|
</para>
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<firstterm><methodname>List findEntitiesChangedInRevision(Number)</methodname></firstterm>
|
||||||
|
- Returns snapshots of all audited entities changed (added, updated and removed) in a given revision.
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<firstterm><methodname>List findEntitiesChangedInRevision(Number, RevisionType)</methodname></firstterm>
|
||||||
|
- Returns snapshots of all audited entities changed (added, updated or removed) in a given revision
|
||||||
|
filtered by modification type.
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<firstterm><methodname><![CDATA[Map<RevisionType, List>]]> findEntitiesChangedInRevisionGroupByRevisionType(Number)</methodname></firstterm>
|
||||||
|
- Returns a map containing lists of entity snapshots grouped by modification operation (e.g.
|
||||||
|
addition, update and removal).
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
<para>
|
||||||
|
Note that methods described above can be legally used only when default mechanism of
|
||||||
|
tracking changed entity names is enabled (see <xref linkend="envers-tracking-modified-entities-reventity"/>).
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -27,13 +27,15 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.envers.exception.AuditException;
|
||||||
import org.hibernate.envers.exception.NotAuditedException;
|
import org.hibernate.envers.exception.NotAuditedException;
|
||||||
import org.hibernate.envers.exception.RevisionDoesNotExistException;
|
import org.hibernate.envers.exception.RevisionDoesNotExistException;
|
||||||
import org.hibernate.envers.query.AuditQueryCreator;
|
import org.hibernate.envers.query.AuditQueryCreator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Hernán Chanfreau
|
* @author Hernán Chanfreau
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public interface AuditReader {
|
public interface AuditReader {
|
||||||
/**
|
/**
|
||||||
|
@ -176,35 +178,95 @@ public interface AuditReader {
|
||||||
AuditQueryCreator createQuery();
|
AuditQueryCreator createQuery();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the entityClass was configured to be audited. Calling
|
* Checks if the entityClass was configured to be audited. Calling
|
||||||
* isEntityNameAudited() with the string of the class name will return the
|
* isEntityNameAudited() with the string of the class name will return the
|
||||||
* same value.
|
* same value.
|
||||||
*
|
*
|
||||||
* @param entityClass
|
* @param entityClass
|
||||||
* Class of the entity asking for audit support
|
* Class of the entity asking for audit support
|
||||||
* @return true if the entityClass is audited.
|
* @return true if the entityClass is audited.
|
||||||
*/
|
*/
|
||||||
boolean isEntityClassAudited(Class<?> entityClass);
|
boolean isEntityClassAudited(Class<?> entityClass);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the entityName was configured to be audited.
|
* Checks if the entityName was configured to be audited.
|
||||||
*
|
*
|
||||||
* @param entityClass
|
* @param entityName
|
||||||
* EntityName of the entity asking for audit support.
|
* EntityName of the entity asking for audit support.
|
||||||
* @return true if the entityName is audited.
|
* @return true if the entityName is audited.
|
||||||
*/
|
*/
|
||||||
boolean isEntityNameAudited(String entityName);
|
boolean isEntityNameAudited(String entityName);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param entity
|
* @param entity
|
||||||
* that was obtained previously from the same AuditReader.
|
* that was obtained previously from the same AuditReader.
|
||||||
*
|
*
|
||||||
* @return the entityName for the given entity, null in case the entity is
|
* @return the entityName for the given entity, null in case the entity is
|
||||||
* not associated with this AuditReader instance.
|
* not associated with this AuditReader instance.
|
||||||
*/
|
*/
|
||||||
String getEntityName(Object primaryKey, Number revision, Object entity)
|
String getEntityName(Object primaryKey, Number revision, Object entity)
|
||||||
throws HibernateException;
|
throws HibernateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all entities changed (added, updated and removed) in a given revision.
|
||||||
|
* @param revision Revision number.
|
||||||
|
* @return Snapshots of all audited entities changed in a given revision.
|
||||||
|
* @throws IllegalStateException If the associated entity manager is closed.
|
||||||
|
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
|
||||||
|
* @throws AuditException If none of the following conditions is satisfied:
|
||||||
|
* <ul>
|
||||||
|
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
List findEntitiesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all entities changed (added, updated or removed) in a given revision.
|
||||||
|
* @param revision Revision number.
|
||||||
|
* @param revisionType Type of modification.
|
||||||
|
* @return Snapshots of all audited entities changed in a given revision filtered by modification type.
|
||||||
|
* @throws IllegalStateException If the associated entity manager is closed.
|
||||||
|
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
|
||||||
|
* @throws AuditException If none of the following conditions is satisfied:
|
||||||
|
* <ul>
|
||||||
|
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
List findEntitiesChangedInRevision(Number revision, RevisionType revisionType) throws IllegalStateException, IllegalArgumentException, AuditException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all entities changed (added, updated and removed) in a given revision grouped by modification type.
|
||||||
|
* @param revision Revision number.
|
||||||
|
* @return Map containing lists of entity snapshots grouped by modification operation (e.g. addition, update, removal).
|
||||||
|
* @throws IllegalStateException If the associated entity manager is closed.
|
||||||
|
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
|
||||||
|
* @throws AuditException If none of the following conditions is satisfied:
|
||||||
|
* <ul>
|
||||||
|
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
Map<RevisionType, List> findEntitiesChangedInRevisionGroupByRevisionType(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of entity classes modified in a given revision.
|
||||||
|
* @param revision Revision number.
|
||||||
|
* @return List of classes modified in a given revision.
|
||||||
|
* @throws IllegalStateException If the associated entity manager is closed.
|
||||||
|
* @throws IllegalArgumentException If a revision number is <code>null</code>, less or equal to 0.
|
||||||
|
* @throws AuditException If none of the following conditions is satisfied:
|
||||||
|
* <ul>
|
||||||
|
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity} base class.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames} interface.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
List<Class> findEntityTypesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.hibernate.envers;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of {@link DefaultRevisionEntity} that allows tracking entity types changed in each revision. This revision
|
||||||
|
* entity is implicitly used when one of the following conditions is satisfied:
|
||||||
|
* <ul>
|
||||||
|
* <li><code>org.hibernate.envers.track_entities_changed_in_revision</code> parameter is set to <code>true</code>.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) extends {@link DefaultTrackingModifiedTypesRevisionEntity}.</li>
|
||||||
|
* <li>Custom revision entity (annotated with {@link RevisionEntity}) encapsulates a field marked with {@link ModifiedEntityNames}.</li>
|
||||||
|
* </ul>
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@MappedSuperclass
|
||||||
|
public class DefaultTrackingModifiedTypesRevisionEntity extends DefaultRevisionEntity {
|
||||||
|
@ElementCollection
|
||||||
|
@JoinTable(name = "REVENTITY", joinColumns = @JoinColumn(name = "REV"))
|
||||||
|
@Column(name = "ENTITYNAME")
|
||||||
|
@ModifiedEntityNames
|
||||||
|
private Set<String> modifiedEntityNames = new HashSet<String>();
|
||||||
|
|
||||||
|
public Set<String> getModifiedEntityNames() {
|
||||||
|
return modifiedEntityNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedEntityNames(Set<String> modifiedEntityNames) {
|
||||||
|
this.modifiedEntityNames = modifiedEntityNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof DefaultTrackingModifiedTypesRevisionEntity)) return false;
|
||||||
|
if (!super.equals(o)) return false;
|
||||||
|
|
||||||
|
DefaultTrackingModifiedTypesRevisionEntity that = (DefaultTrackingModifiedTypesRevisionEntity) o;
|
||||||
|
|
||||||
|
if (modifiedEntityNames != null ? !modifiedEntityNames.equals(that.modifiedEntityNames)
|
||||||
|
: that.modifiedEntityNames != null) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = 31 * result + (modifiedEntityNames != null ? modifiedEntityNames.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "DefaultTrackingModifiedTypesRevisionEntity(" + super.toString() + ", modifiedEntityNames = " + modifiedEntityNames.toString() + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.hibernate.envers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of standard {@link RevisionListener} that notifies whenever tracking of each audited entity
|
||||||
|
* instance is started or stopped within the current revision boundaries.
|
||||||
|
* @see RevisionListener
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public interface EntityTrackingRevisionListener extends RevisionListener {
|
||||||
|
/**
|
||||||
|
* Called after audited entity data has been persisted.
|
||||||
|
* @param entityName Name of the audited entity.
|
||||||
|
* @param revisionEntity An instance of the entity annotated with {@link RevisionEntity}.
|
||||||
|
*/
|
||||||
|
void addEntityToRevision(String entityName, Object revisionEntity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when persistence of a given audited entity snapshot has been already performed in a previous unit of work.
|
||||||
|
* @param entityName Name of the audited entity.
|
||||||
|
* @param revisionEntity An instance of the entity annotated with {@link RevisionEntity}.
|
||||||
|
*/
|
||||||
|
void removeEntityFromRevision(String entityName, Object revisionEntity);
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.hibernate.envers;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a property which will hold the collection of entity names modified during each revision.
|
||||||
|
* This annotation expects field of Set<String> type.
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||||
|
public @interface ModifiedEntityNames {
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import java.util.Properties;
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Nicolas Doroskevich
|
* @author Nicolas Doroskevich
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class GlobalConfiguration {
|
public class GlobalConfiguration {
|
||||||
// Should a revision be generated when a not-owned relation field changes
|
// 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.
|
// The default name of the catalog of the audit tables.
|
||||||
private final String defaultCatalogName;
|
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
|
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 ...).
|
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(
|
correlatedSubqueryOperator = "org.hibernate.dialect.HSQLDialect".equals(
|
||||||
properties.getProperty("hibernate.dialect")) ? "in" : "=";
|
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() {
|
public boolean isGenerateRevisionsForCollections() {
|
||||||
|
@ -102,4 +112,12 @@ public class GlobalConfiguration {
|
||||||
public String getDefaultCatalogName() {
|
public String getDefaultCatalogName() {
|
||||||
return defaultCatalogName;
|
return defaultCatalogName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTrackEntitiesChangedInRevisionEnabled() {
|
||||||
|
return trackEntitiesChangedInRevisionEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrackEntitiesChangedInRevisionEnabled(boolean trackEntitiesChangedInRevisionEnabled) {
|
||||||
|
this.trackEntitiesChangedInRevisionEnabled = trackEntitiesChangedInRevisionEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
package org.hibernate.envers.configuration;
|
package org.hibernate.envers.configuration;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import org.dom4j.Document;
|
import org.dom4j.Document;
|
||||||
import org.dom4j.DocumentHelper;
|
import org.dom4j.DocumentHelper;
|
||||||
|
@ -33,19 +34,11 @@ import org.hibernate.annotations.common.reflection.ReflectionManager;
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.envers.Audited;
|
import org.hibernate.envers.*;
|
||||||
import org.hibernate.envers.DefaultRevisionEntity;
|
|
||||||
import org.hibernate.envers.RevisionEntity;
|
|
||||||
import org.hibernate.envers.RevisionListener;
|
|
||||||
import org.hibernate.envers.RevisionNumber;
|
|
||||||
import org.hibernate.envers.RevisionTimestamp;
|
|
||||||
import org.hibernate.envers.configuration.metadata.AuditTableData;
|
import org.hibernate.envers.configuration.metadata.AuditTableData;
|
||||||
import org.hibernate.envers.configuration.metadata.MetadataTools;
|
import org.hibernate.envers.configuration.metadata.MetadataTools;
|
||||||
import org.hibernate.envers.entities.PropertyData;
|
import org.hibernate.envers.entities.PropertyData;
|
||||||
import org.hibernate.envers.revisioninfo.DefaultRevisionInfoGenerator;
|
import org.hibernate.envers.revisioninfo.*;
|
||||||
import org.hibernate.envers.revisioninfo.RevisionInfoGenerator;
|
|
||||||
import org.hibernate.envers.revisioninfo.RevisionInfoNumberReader;
|
|
||||||
import org.hibernate.envers.revisioninfo.RevisionInfoQueryCreator;
|
|
||||||
import org.hibernate.envers.tools.MutableBoolean;
|
import org.hibernate.envers.tools.MutableBoolean;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.type.LongType;
|
import org.hibernate.type.LongType;
|
||||||
|
@ -53,11 +46,13 @@ import org.hibernate.type.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class RevisionInfoConfiguration {
|
public class RevisionInfoConfiguration {
|
||||||
private String revisionInfoEntityName;
|
private String revisionInfoEntityName;
|
||||||
private PropertyData revisionInfoIdData;
|
private PropertyData revisionInfoIdData;
|
||||||
private PropertyData revisionInfoTimestampData;
|
private PropertyData revisionInfoTimestampData;
|
||||||
|
private PropertyData modifiedEntityNamesData;
|
||||||
private Type revisionInfoTimestampType;
|
private Type revisionInfoTimestampType;
|
||||||
private GlobalConfiguration globalCfg;
|
private GlobalConfiguration globalCfg;
|
||||||
|
|
||||||
|
@ -69,6 +64,7 @@ public class RevisionInfoConfiguration {
|
||||||
revisionInfoEntityName = "org.hibernate.envers.DefaultRevisionEntity";
|
revisionInfoEntityName = "org.hibernate.envers.DefaultRevisionEntity";
|
||||||
revisionInfoIdData = new PropertyData("id", "id", "field", null);
|
revisionInfoIdData = new PropertyData("id", "id", "field", null);
|
||||||
revisionInfoTimestampData = new PropertyData("timestamp", "timestamp", "field", null);
|
revisionInfoTimestampData = new PropertyData("timestamp", "timestamp", "field", null);
|
||||||
|
modifiedEntityNamesData = new PropertyData("modifiedEntityNames", "modifiedEntityNames", "field", null);
|
||||||
revisionInfoTimestampType = new LongType();
|
revisionInfoTimestampType = new LongType();
|
||||||
|
|
||||||
revisionPropType = "integer";
|
revisionPropType = "integer";
|
||||||
|
@ -90,9 +86,39 @@ public class RevisionInfoConfiguration {
|
||||||
revisionInfoTimestampType.getName(), true, false);
|
revisionInfoTimestampType.getName(), true, false);
|
||||||
MetadataTools.addColumn(timestampProperty, "REVTSTMP", null, 0, 0, null, null, null, false);
|
MetadataTools.addColumn(timestampProperty, "REVTSTMP", null, 0, 0, null, null, null, false);
|
||||||
|
|
||||||
|
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()) {
|
||||||
|
generateEntityNamesTrackingTableMapping(class_mapping, "modifiedEntityNames", "REVENTITY", "REV", "ENTITYNAME", "string");
|
||||||
|
}
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates mapping that represents a set of strings.<br />
|
||||||
|
* <code>
|
||||||
|
* <set name="propertyName" table="joinTableName" cascade="persist, delete"><br />
|
||||||
|
* <key column="joinTablePrimaryKeyColumnName" /><br />
|
||||||
|
* <element type="joinTableValueColumnType"><br />
|
||||||
|
* <column name="joinTableValueColumnName" /><br />
|
||||||
|
* </element><br />
|
||||||
|
* </set>
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
private void generateEntityNamesTrackingTableMapping(Element class_mapping, String propertyName,
|
||||||
|
String joinTableName, String joinTablePrimaryKeyColumnName,
|
||||||
|
String joinTableValueColumnName, String joinTableValueColumnType) {
|
||||||
|
Element set = class_mapping.addElement("set");
|
||||||
|
set.addAttribute("name", propertyName);
|
||||||
|
set.addAttribute("table", joinTableName);
|
||||||
|
set.addAttribute("cascade", "persist, delete");
|
||||||
|
Element key = set.addElement("key");
|
||||||
|
key.addAttribute("column", joinTablePrimaryKeyColumnName);
|
||||||
|
Element element = set.addElement("element");
|
||||||
|
element.addAttribute("type", joinTableValueColumnType);
|
||||||
|
Element column = element.addElement("column");
|
||||||
|
column.addAttribute("name", joinTableValueColumnName);
|
||||||
|
}
|
||||||
|
|
||||||
private Element generateRevisionInfoRelationMapping() {
|
private Element generateRevisionInfoRelationMapping() {
|
||||||
Document document = DocumentHelper.createDocument();
|
Document document = DocumentHelper.createDocument();
|
||||||
Element rev_rel_mapping = document.addElement("key-many-to-one");
|
Element rev_rel_mapping = document.addElement("key-many-to-one");
|
||||||
|
@ -109,10 +135,11 @@ public class RevisionInfoConfiguration {
|
||||||
|
|
||||||
private void searchForRevisionInfoCfgInProperties(XClass clazz, ReflectionManager reflectionManager,
|
private void searchForRevisionInfoCfgInProperties(XClass clazz, ReflectionManager reflectionManager,
|
||||||
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
|
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
|
||||||
String accessType) {
|
MutableBoolean modifiedEntityNamesFound, String accessType) {
|
||||||
for (XProperty property : clazz.getDeclaredProperties(accessType)) {
|
for (XProperty property : clazz.getDeclaredProperties(accessType)) {
|
||||||
RevisionNumber revisionNumber = property.getAnnotation(RevisionNumber.class);
|
RevisionNumber revisionNumber = property.getAnnotation(RevisionNumber.class);
|
||||||
RevisionTimestamp revisionTimestamp = property.getAnnotation(RevisionTimestamp.class);
|
RevisionTimestamp revisionTimestamp = property.getAnnotation(RevisionTimestamp.class);
|
||||||
|
ModifiedEntityNames modifiedEntityNames = property.getAnnotation(ModifiedEntityNames.class);
|
||||||
|
|
||||||
if (revisionNumber != null) {
|
if (revisionNumber != null) {
|
||||||
if (revisionNumberFound.isSet()) {
|
if (revisionNumberFound.isSet()) {
|
||||||
|
@ -162,20 +189,35 @@ public class RevisionInfoConfiguration {
|
||||||
"long, Long, java.util.Date or java.sql.Date");
|
"long, Long, java.util.Date or java.sql.Date");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modifiedEntityNames != null) {
|
||||||
|
if (modifiedEntityNamesFound.isSet()) {
|
||||||
|
throw new MappingException("Only one property may be annotated with @ModifiedEntityNames!");
|
||||||
|
}
|
||||||
|
XClass modifiedEntityNamesClass = property.getType();
|
||||||
|
if (reflectionManager.equals(modifiedEntityNamesClass, Set.class) &&
|
||||||
|
reflectionManager.equals(property.getElementClass(), String.class)) {
|
||||||
|
modifiedEntityNamesData = new PropertyData(property.getName(), property.getName(), accessType, null);
|
||||||
|
modifiedEntityNamesFound.set();
|
||||||
|
} else {
|
||||||
|
throw new MappingException("The field annotated with @ModifiedEntityNames must be of type Set<String>.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void searchForRevisionInfoCfg(XClass clazz, ReflectionManager reflectionManager,
|
private void searchForRevisionInfoCfg(XClass clazz, ReflectionManager reflectionManager,
|
||||||
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound) {
|
MutableBoolean revisionNumberFound, MutableBoolean revisionTimestampFound,
|
||||||
|
MutableBoolean modifiedEntityNamesFound) {
|
||||||
XClass superclazz = clazz.getSuperclass();
|
XClass superclazz = clazz.getSuperclass();
|
||||||
if (!"java.lang.Object".equals(superclazz.getName())) {
|
if (!"java.lang.Object".equals(superclazz.getName())) {
|
||||||
searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound);
|
searchForRevisionInfoCfg(superclazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
|
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
|
||||||
"field");
|
modifiedEntityNamesFound, "field");
|
||||||
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
|
searchForRevisionInfoCfgInProperties(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound,
|
||||||
"property");
|
modifiedEntityNamesFound, "property");
|
||||||
}
|
}
|
||||||
|
|
||||||
public RevisionInfoConfigurationResult configure(Configuration cfg, ReflectionManager reflectionManager) {
|
public RevisionInfoConfigurationResult configure(Configuration cfg, ReflectionManager reflectionManager) {
|
||||||
|
@ -209,8 +251,9 @@ public class RevisionInfoConfiguration {
|
||||||
|
|
||||||
MutableBoolean revisionNumberFound = new MutableBoolean();
|
MutableBoolean revisionNumberFound = new MutableBoolean();
|
||||||
MutableBoolean revisionTimestampFound = new MutableBoolean();
|
MutableBoolean revisionTimestampFound = new MutableBoolean();
|
||||||
|
MutableBoolean modifiedEntityNamesFound = new MutableBoolean();
|
||||||
|
|
||||||
searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound);
|
searchForRevisionInfoCfg(clazz, reflectionManager, revisionNumberFound, revisionTimestampFound, modifiedEntityNamesFound);
|
||||||
|
|
||||||
if (!revisionNumberFound.isSet()) {
|
if (!revisionNumberFound.isSet()) {
|
||||||
throw new MappingException("An entity annotated with @RevisionEntity must have a field annotated " +
|
throw new MappingException("An entity annotated with @RevisionEntity must have a field annotated " +
|
||||||
|
@ -226,8 +269,15 @@ public class RevisionInfoConfiguration {
|
||||||
|
|
||||||
revisionInfoClass = pc.getMappedClass();
|
revisionInfoClass = pc.getMappedClass();
|
||||||
revisionInfoTimestampType = pc.getProperty(revisionInfoTimestampData.getName()).getType();
|
revisionInfoTimestampType = pc.getProperty(revisionInfoTimestampData.getName()).getType();
|
||||||
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
|
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled() || DefaultTrackingModifiedTypesRevisionEntity.class.isAssignableFrom(revisionInfoClass) || modifiedEntityNamesFound.isSet()) {
|
||||||
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate());
|
// If modified entities tracking parameter is enabled, custom revision info class is a subtype of DefaultTrackingModifiedTypesRevisionEntity, or @ModifiedEntityNames annotation is used.
|
||||||
|
revisionInfoGenerator = new DefaultTrackingModifiedTypesRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
|
||||||
|
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate(), modifiedEntityNamesData);
|
||||||
|
globalCfg.setTrackEntitiesChangedInRevisionEnabled(true);
|
||||||
|
} else {
|
||||||
|
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
|
||||||
|
revisionEntity.value(), revisionInfoTimestampData, isTimestampAsDate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,16 +285,23 @@ public class RevisionInfoConfiguration {
|
||||||
Document revisionInfoXmlMapping = null;
|
Document revisionInfoXmlMapping = null;
|
||||||
|
|
||||||
if (revisionInfoGenerator == null) {
|
if (revisionInfoGenerator == null) {
|
||||||
revisionInfoClass = DefaultRevisionEntity.class;
|
if (globalCfg.isTrackEntitiesChangedInRevisionEnabled()) {
|
||||||
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
|
revisionInfoClass = DefaultTrackingModifiedTypesRevisionEntity.class;
|
||||||
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate());
|
revisionInfoEntityName = DefaultTrackingModifiedTypesRevisionEntity.class.getName();
|
||||||
|
revisionInfoGenerator = new DefaultTrackingModifiedTypesRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
|
||||||
|
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate(), modifiedEntityNamesData);
|
||||||
|
} else {
|
||||||
|
revisionInfoClass = DefaultRevisionEntity.class;
|
||||||
|
revisionInfoGenerator = new DefaultRevisionInfoGenerator(revisionInfoEntityName, revisionInfoClass,
|
||||||
|
RevisionListener.class, revisionInfoTimestampData, isTimestampAsDate());
|
||||||
|
}
|
||||||
revisionInfoXmlMapping = generateDefaultRevisionInfoXmlMapping();
|
revisionInfoXmlMapping = generateDefaultRevisionInfoXmlMapping();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RevisionInfoConfigurationResult(
|
return new RevisionInfoConfigurationResult(
|
||||||
revisionInfoGenerator, revisionInfoXmlMapping,
|
revisionInfoGenerator, revisionInfoXmlMapping,
|
||||||
new RevisionInfoQueryCreator(revisionInfoEntityName, revisionInfoIdData.getName(),
|
new RevisionInfoQueryCreator(revisionInfoEntityName, revisionInfoIdData.getName(),
|
||||||
revisionInfoTimestampData.getName(), isTimestampAsDate()),
|
revisionInfoTimestampData.getName(), isTimestampAsDate(), modifiedEntityNamesData.getName()),
|
||||||
generateRevisionInfoRelationMapping(),
|
generateRevisionInfoRelationMapping(),
|
||||||
new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData), revisionInfoEntityName,
|
new RevisionInfoNumberReader(revisionInfoClass, revisionInfoIdData), revisionInfoEntityName,
|
||||||
revisionInfoClass, revisionInfoTimestampData);
|
revisionInfoClass, revisionInfoTimestampData);
|
||||||
|
@ -312,5 +369,4 @@ class RevisionInfoConfigurationResult {
|
||||||
public PropertyData getRevisionInfoTimestampData() {
|
public PropertyData getRevisionInfoTimestampData() {
|
||||||
return revisionInfoTimestampData;
|
return revisionInfoTimestampData;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
package org.hibernate.envers.query;
|
package org.hibernate.envers.query;
|
||||||
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
|
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
|
||||||
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
|
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
|
||||||
|
|
||||||
|
import org.hibernate.Query;
|
||||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||||
import org.hibernate.envers.query.impl.EntitiesAtRevisionQuery;
|
import org.hibernate.envers.query.impl.EntitiesAtRevisionQuery;
|
||||||
import org.hibernate.envers.query.impl.RevisionsOfEntityQuery;
|
import org.hibernate.envers.query.impl.RevisionsOfEntityQuery;
|
||||||
|
@ -32,6 +34,7 @@ import org.hibernate.envers.reader.AuditReaderImplementor;
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Hern<EFBFBD>n Chanfreau
|
* @author Hern<EFBFBD>n Chanfreau
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class AuditQueryCreator {
|
public class AuditQueryCreator {
|
||||||
private final AuditConfiguration auditCfg;
|
private final AuditConfiguration auditCfg;
|
||||||
|
@ -52,9 +55,18 @@ public class AuditQueryCreator {
|
||||||
* projection is added.
|
* projection is added.
|
||||||
*/
|
*/
|
||||||
public AuditQuery forEntitiesAtRevision(Class<?> c, Number revision) {
|
public AuditQuery forEntitiesAtRevision(Class<?> c, Number revision) {
|
||||||
|
return forEntitiesAtRevision(c, revision, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns deleted entities as well. Removed entities data depends on the value of
|
||||||
|
* <code>org.hibernate.envers.store_data_at_delete</code> parameter.
|
||||||
|
* @see #forEntitiesAtRevision(Class, Number)
|
||||||
|
*/
|
||||||
|
public AuditQuery forEntitiesAtRevision(Class<?> c, Number revision, boolean selectDeletedEntities) {
|
||||||
checkNotNull(revision, "Entity revision");
|
checkNotNull(revision, "Entity revision");
|
||||||
checkPositive(revision, "Entity revision");
|
checkPositive(revision, "Entity revision");
|
||||||
return new EntitiesAtRevisionQuery(auditCfg, auditReaderImplementor, c, revision);
|
return new EntitiesAtRevisionQuery(auditCfg, auditReaderImplementor, c, revision, selectDeletedEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,5 +133,4 @@ public class AuditQueryCreator {
|
||||||
public AuditQuery forRevisionsOfEntity(Class<?> c, String entityName, boolean selectEntitiesOnly, boolean selectDeletedEntities) {
|
public AuditQuery forRevisionsOfEntity(Class<?> c, String entityName, boolean selectEntitiesOnly, boolean selectDeletedEntities) {
|
||||||
return new RevisionsOfEntityQuery(auditCfg, auditReaderImplementor, c, entityName, selectEntitiesOnly,selectDeletedEntities);
|
return new RevisionsOfEntityQuery(auditCfg, auditReaderImplementor, c, entityName, selectEntitiesOnly,selectDeletedEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,18 +39,28 @@ import org.hibernate.envers.reader.AuditReaderImplementor;
|
||||||
*/
|
*/
|
||||||
public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
|
public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
|
||||||
private final Number revision;
|
private final Number revision;
|
||||||
|
private final boolean selectDeletedEntities;
|
||||||
|
|
||||||
|
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
|
||||||
|
AuditReaderImplementor versionsReader, Class<?> cls, String entityName,
|
||||||
|
Number revision) {
|
||||||
|
this(verCfg, versionsReader, cls, entityName, revision, false);
|
||||||
|
}
|
||||||
|
|
||||||
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
|
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
|
||||||
AuditReaderImplementor versionsReader, Class<?> cls,
|
AuditReaderImplementor versionsReader, Class<?> cls,
|
||||||
Number revision) {
|
Number revision, boolean selectDeletedEntities) {
|
||||||
super(verCfg, versionsReader, cls);
|
super(verCfg, versionsReader, cls);
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
|
this.selectDeletedEntities = selectDeletedEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
|
public EntitiesAtRevisionQuery(AuditConfiguration verCfg,
|
||||||
AuditReaderImplementor versionsReader, Class<?> cls, String entityName, Number revision) {
|
AuditReaderImplementor versionsReader, Class<?> cls, String entityName,
|
||||||
|
Number revision, boolean selectDeletedEntities) {
|
||||||
super(verCfg, versionsReader, cls, entityName);
|
super(verCfg, versionsReader, cls, entityName);
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
|
this.selectDeletedEntities = selectDeletedEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
@SuppressWarnings({"unchecked"})
|
||||||
|
@ -84,9 +94,11 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
|
||||||
verCfg.getAuditStrategy().addEntityAtRevisionRestriction(verCfg.getGlobalCfg(), qb, revisionPropertyPath,
|
verCfg.getAuditStrategy().addEntityAtRevisionRestriction(verCfg.getGlobalCfg(), qb, revisionPropertyPath,
|
||||||
verEntCfg.getRevisionEndFieldName(), true, referencedIdData,
|
verEntCfg.getRevisionEndFieldName(), true, referencedIdData,
|
||||||
revisionPropertyPath, originalIdPropertyName, "e", "e2");
|
revisionPropertyPath, originalIdPropertyName, "e", "e2");
|
||||||
|
|
||||||
// e.revision_type != DEL
|
if (!selectDeletedEntities) {
|
||||||
qb.getRootParameters().addWhereWithParam(verEntCfg.getRevisionTypePropName(), "<>", RevisionType.DEL);
|
// e.revision_type != DEL
|
||||||
|
qb.getRootParameters().addWhereWithParam(verEntCfg.getRevisionTypePropName(), "<>", RevisionType.DEL);
|
||||||
|
}
|
||||||
|
|
||||||
// all specified conditions
|
// all specified conditions
|
||||||
for (AuditCriterion criterion : criterions) {
|
for (AuditCriterion criterion : criterions) {
|
||||||
|
|
|
@ -24,31 +24,30 @@
|
||||||
package org.hibernate.envers.reader;
|
package org.hibernate.envers.reader;
|
||||||
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
|
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
|
||||||
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
|
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.persistence.NoResultException;
|
import javax.persistence.NoResultException;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.NonUniqueResultException;
|
import org.hibernate.NonUniqueResultException;
|
||||||
import org.hibernate.Query;
|
import org.hibernate.Query;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.engine.SessionImplementor;
|
import org.hibernate.engine.SessionImplementor;
|
||||||
|
import org.hibernate.envers.RevisionType;
|
||||||
import org.hibernate.envers.configuration.AuditConfiguration;
|
import org.hibernate.envers.configuration.AuditConfiguration;
|
||||||
import org.hibernate.envers.exception.AuditException;
|
import org.hibernate.envers.exception.AuditException;
|
||||||
import org.hibernate.envers.exception.NotAuditedException;
|
import org.hibernate.envers.exception.NotAuditedException;
|
||||||
import org.hibernate.envers.exception.RevisionDoesNotExistException;
|
import org.hibernate.envers.exception.RevisionDoesNotExistException;
|
||||||
import org.hibernate.envers.query.AuditEntity;
|
import org.hibernate.envers.query.AuditEntity;
|
||||||
import org.hibernate.envers.query.AuditQueryCreator;
|
import org.hibernate.envers.query.AuditQueryCreator;
|
||||||
|
import org.hibernate.envers.query.criteria.RevisionTypeAuditExpression;
|
||||||
import org.hibernate.envers.synchronization.AuditProcess;
|
import org.hibernate.envers.synchronization.AuditProcess;
|
||||||
import org.hibernate.event.EventSource;
|
import org.hibernate.event.EventSource;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Hernán Chanfreau
|
* @author Hernán Chanfreau
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class AuditReaderImpl implements AuditReaderImplementor {
|
public class AuditReaderImpl implements AuditReaderImplementor {
|
||||||
private final AuditConfiguration verCfg;
|
private final AuditConfiguration verCfg;
|
||||||
|
@ -235,7 +234,64 @@ public class AuditReaderImpl implements AuditReaderImplementor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public List findEntitiesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException {
|
||||||
|
List<Class> clazz = findEntityTypesChangedInRevision(revision);
|
||||||
|
List result = new ArrayList(clazz.size());
|
||||||
|
for (Class c : clazz) {
|
||||||
|
result.addAll(createQuery().forEntitiesAtRevision(c, revision, true).getResultList());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public List findEntitiesChangedInRevision(Number revision, RevisionType revisionType) throws IllegalStateException, IllegalArgumentException, AuditException {
|
||||||
|
List<Class> clazz = findEntityTypesChangedInRevision(revision);
|
||||||
|
List result = new ArrayList(clazz.size());
|
||||||
|
for (Class c : clazz) {
|
||||||
|
result.addAll(createQuery().forEntitiesAtRevision(c, revision, true).add(new RevisionTypeAuditExpression(revisionType, "=")).getResultList());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public Map<RevisionType, List> findEntitiesChangedInRevisionGroupByRevisionType(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException {
|
||||||
|
List<Class> clazz = findEntityTypesChangedInRevision(revision);
|
||||||
|
Map<RevisionType, List> result = new HashMap<RevisionType, List>();
|
||||||
|
for (RevisionType revisionType : RevisionType.values()) {
|
||||||
|
result.put(revisionType, new ArrayList());
|
||||||
|
for (Class c : clazz) {
|
||||||
|
List list = createQuery().forEntitiesAtRevision(c, revision, true).add(new RevisionTypeAuditExpression(revisionType, "=")).getResultList();
|
||||||
|
result.get(revisionType).addAll(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public List<Class> findEntityTypesChangedInRevision(Number revision) throws IllegalStateException, IllegalArgumentException, AuditException {
|
||||||
|
checkNotNull(revision, "Entity revision");
|
||||||
|
checkPositive(revision, "Entity revision");
|
||||||
|
checkSession();
|
||||||
|
if (!verCfg.getGlobalCfg().isTrackEntitiesChangedInRevisionEnabled()) {
|
||||||
|
throw new AuditException("This query is designed for Envers default mechanism of tracking entities modified in a given revision."
|
||||||
|
+ " Extend DefaultTrackingModifiedTypesRevisionEntity, utilize @ModifiedEntityNames annotation or set "
|
||||||
|
+ "'org.hibernate.envers.track_entities_changed_in_revision' parameter to true.");
|
||||||
|
}
|
||||||
|
Query query = verCfg.getRevisionInfoQueryCreator().getEntitiesChangedInRevisionQuery(session, revision);
|
||||||
|
Set<String> modifiedEntityNames = new HashSet<String>(query.list());
|
||||||
|
List<Class> result = new ArrayList<Class>(modifiedEntityNames.size());
|
||||||
|
for (String entityName : modifiedEntityNames) {
|
||||||
|
try {
|
||||||
|
result.add(Class.forName(entityName));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
public <T> T getCurrentRevision(Class<T> revisionEntityClass, boolean persist) {
|
public <T> T getCurrentRevision(Class<T> revisionEntityClass, boolean persist) {
|
||||||
if (!(session instanceof EventSource)) {
|
if (!(session instanceof EventSource)) {
|
||||||
throw new IllegalArgumentException("The provided session is not an EventSource!");
|
throw new IllegalArgumentException("The provided session is not an EventSource!");
|
||||||
|
@ -253,34 +309,34 @@ public class AuditReaderImpl implements AuditReaderImplementor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEntityClassAudited(Class<?> entityClass) {
|
public boolean isEntityClassAudited(Class<?> entityClass) {
|
||||||
return this.isEntityNameAudited(entityClass.getName());
|
return this.isEntityNameAudited(entityClass.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isEntityNameAudited(String entityName) {
|
public boolean isEntityNameAudited(String entityName) {
|
||||||
checkNotNull(entityName, "Entity name");
|
checkNotNull(entityName, "Entity name");
|
||||||
checkSession();
|
checkSession();
|
||||||
return (verCfg.getEntCfg().isVersioned(entityName));
|
return (verCfg.getEntCfg().isVersioned(entityName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getEntityName(Object primaryKey, Number revision ,Object entity) throws HibernateException{
|
public String getEntityName(Object primaryKey, Number revision ,Object entity) throws HibernateException{
|
||||||
checkNotNull(primaryKey, "Primary key");
|
checkNotNull(primaryKey, "Primary key");
|
||||||
checkNotNull(revision, "Entity revision");
|
checkNotNull(revision, "Entity revision");
|
||||||
checkPositive(revision, "Entity revision");
|
checkPositive(revision, "Entity revision");
|
||||||
checkNotNull(entity, "Entity");
|
checkNotNull(entity, "Entity");
|
||||||
checkSession();
|
checkSession();
|
||||||
|
|
||||||
// Unwrap if necessary
|
// Unwrap if necessary
|
||||||
if(entity instanceof HibernateProxy) {
|
if(entity instanceof HibernateProxy) {
|
||||||
entity = ((HibernateProxy)entity).getHibernateLazyInitializer().getImplementation();
|
entity = ((HibernateProxy)entity).getHibernateLazyInitializer().getImplementation();
|
||||||
}
|
}
|
||||||
if(firstLevelCache.containsEntityName(primaryKey, revision, entity)) {
|
if(firstLevelCache.containsEntityName(primaryKey, revision, entity)) {
|
||||||
// it's on envers FLC!
|
// it's on envers FLC!
|
||||||
return firstLevelCache.getFromEntityNameCache(primaryKey, revision, entity);
|
return firstLevelCache.getFromEntityNameCache(primaryKey, revision, entity);
|
||||||
} else {
|
} else {
|
||||||
throw new HibernateException(
|
throw new HibernateException(
|
||||||
"Envers can't resolve entityName for historic entity. The id, revision and entity is not on envers first level cache.");
|
"Envers can't resolve entityName for historic entity. The id, revision and entity is not on envers first level cache.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,8 +23,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.envers.revisioninfo;
|
package org.hibernate.envers.revisioninfo;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.envers.EntityTrackingRevisionListener;
|
||||||
import org.hibernate.envers.RevisionListener;
|
import org.hibernate.envers.RevisionListener;
|
||||||
import org.hibernate.envers.entities.PropertyData;
|
import org.hibernate.envers.entities.PropertyData;
|
||||||
import org.hibernate.envers.tools.reflection.ReflectionTools;
|
import org.hibernate.envers.tools.reflection.ReflectionTools;
|
||||||
|
@ -32,6 +34,7 @@ import org.hibernate.property.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
||||||
private final String revisionInfoEntityName;
|
private final String revisionInfoEntityName;
|
||||||
|
@ -86,4 +89,16 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
||||||
|
|
||||||
return revisionInfo;
|
return revisionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addEntityToRevision(String entityName, Object revisionInfo) {
|
||||||
|
if (listener instanceof EntityTrackingRevisionListener) {
|
||||||
|
((EntityTrackingRevisionListener) listener).addEntityToRevision(entityName, revisionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeEntityFromRevision(String entityName, Object revisionInfo) {
|
||||||
|
if (listener instanceof EntityTrackingRevisionListener) {
|
||||||
|
((EntityTrackingRevisionListener) listener).removeEntityFromRevision(entityName, revisionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.hibernate.envers.revisioninfo;
|
||||||
|
|
||||||
|
import org.hibernate.envers.RevisionListener;
|
||||||
|
import org.hibernate.envers.entities.PropertyData;
|
||||||
|
import org.hibernate.envers.tools.reflection.ReflectionTools;
|
||||||
|
import org.hibernate.property.Getter;
|
||||||
|
import org.hibernate.property.Setter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically adds and removes entity names changed during current revision.
|
||||||
|
* @see org.hibernate.envers.ModifiedEntityNames
|
||||||
|
* @see org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class DefaultTrackingModifiedTypesRevisionInfoGenerator extends DefaultRevisionInfoGenerator {
|
||||||
|
private final Setter modifiedEntityNamesSetter;
|
||||||
|
private final Getter modifiedEntityNamesGetter;
|
||||||
|
|
||||||
|
public DefaultTrackingModifiedTypesRevisionInfoGenerator(String revisionInfoEntityName,
|
||||||
|
Class<?> revisionInfoClass,
|
||||||
|
Class<? extends RevisionListener> listenerClass,
|
||||||
|
PropertyData revisionInfoTimestampData, boolean timestampAsDate,
|
||||||
|
PropertyData modifiedEntityNamesData) {
|
||||||
|
super(revisionInfoEntityName, revisionInfoClass, listenerClass, revisionInfoTimestampData, timestampAsDate);
|
||||||
|
modifiedEntityNamesSetter = ReflectionTools.getSetter(revisionInfoClass, modifiedEntityNamesData);
|
||||||
|
modifiedEntityNamesGetter = ReflectionTools.getGetter(revisionInfoClass, modifiedEntityNamesData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public void addEntityToRevision(String entityName, Object revisionInfo) {
|
||||||
|
super.addEntityToRevision(entityName, revisionInfo);
|
||||||
|
Set<String> modifiedEntityNames = (Set<String>) modifiedEntityNamesGetter.get(revisionInfo);
|
||||||
|
if (modifiedEntityNames == null) {
|
||||||
|
modifiedEntityNames = new HashSet<String>();
|
||||||
|
}
|
||||||
|
modifiedEntityNames.add(entityName);
|
||||||
|
modifiedEntityNamesSetter.set(revisionInfo, modifiedEntityNames, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public void removeEntityFromRevision(String entityName, Object revisionInfo) {
|
||||||
|
super.removeEntityFromRevision(entityName, revisionInfo);
|
||||||
|
Set<String> modifiedEntityNames = (Set<String>) modifiedEntityNamesGetter.get(revisionInfo);
|
||||||
|
if (modifiedEntityNames == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modifiedEntityNames.remove(entityName);
|
||||||
|
modifiedEntityNamesSetter.set(revisionInfo, modifiedEntityNames, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,4 +30,14 @@ import org.hibernate.Session;
|
||||||
public interface RevisionInfoGenerator {
|
public interface RevisionInfoGenerator {
|
||||||
void saveRevisionData(Session session, Object revisionData);
|
void saveRevisionData(Session session, Object revisionData);
|
||||||
Object generate();
|
Object generate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.hibernate.envers.EntityTrackingRevisionListener#addEntityToRevision(String, Object)
|
||||||
|
*/
|
||||||
|
void addEntityToRevision(String entityName, Object revisionInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.hibernate.envers.EntityTrackingRevisionListener#removeEntityFromRevision(String, Object)
|
||||||
|
*/
|
||||||
|
void removeEntityFromRevision(String entityName, Object revisionInfo);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,18 +26,22 @@ import java.util.Date;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.hibernate.Query;
|
import org.hibernate.Query;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.transform.Transformers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
*/
|
*/
|
||||||
public class RevisionInfoQueryCreator {
|
public class RevisionInfoQueryCreator {
|
||||||
private final String revisionDateQuery;
|
private final String revisionDateQuery;
|
||||||
private final String revisionNumberForDateQuery;
|
private final String revisionNumberForDateQuery;
|
||||||
private final String revisionsQuery;
|
private final String revisionsQuery;
|
||||||
|
private final String entitiesChangedInRevisionQuery;
|
||||||
private final boolean timestampAsDate;
|
private final boolean timestampAsDate;
|
||||||
|
|
||||||
public RevisionInfoQueryCreator(String revisionInfoEntityName, String revisionInfoIdName,
|
public RevisionInfoQueryCreator(String revisionInfoEntityName, String revisionInfoIdName,
|
||||||
String revisionInfoTimestampName, boolean timestampAsDate) {
|
String revisionInfoTimestampName, boolean timestampAsDate,
|
||||||
|
String modifiedEntityNamesName) {
|
||||||
this.timestampAsDate = timestampAsDate;
|
this.timestampAsDate = timestampAsDate;
|
||||||
|
|
||||||
revisionDateQuery = new StringBuilder()
|
revisionDateQuery = new StringBuilder()
|
||||||
|
@ -57,6 +61,12 @@ public class RevisionInfoQueryCreator {
|
||||||
.append(" rev where ").append(revisionInfoIdName)
|
.append(" rev where ").append(revisionInfoIdName)
|
||||||
.append(" in (:_revision_numbers)")
|
.append(" in (:_revision_numbers)")
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
|
entitiesChangedInRevisionQuery = new StringBuilder()
|
||||||
|
.append("select elements(rev.").append(modifiedEntityNamesName)
|
||||||
|
.append(") from ").append(revisionInfoEntityName)
|
||||||
|
.append(" rev where rev.").append(revisionInfoIdName).append(" = :_revision_number")
|
||||||
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Query getRevisionDateQuery(Session session, Number revision) {
|
public Query getRevisionDateQuery(Session session, Number revision) {
|
||||||
|
@ -70,4 +80,8 @@ public class RevisionInfoQueryCreator {
|
||||||
public Query getRevisionsQuery(Session session, Set<Number> revisions) {
|
public Query getRevisionsQuery(Session session, Set<Number> revisions) {
|
||||||
return session.createQuery(revisionsQuery).setParameterList("_revision_numbers", revisions);
|
return session.createQuery(revisionsQuery).setParameterList("_revision_numbers", revisions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Query getEntitiesChangedInRevisionQuery(Session session, Number revision) {
|
||||||
|
return session.createQuery(entitiesChangedInRevisionQuery).setParameter("_revision_number", revision);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,17 +98,19 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
|
||||||
|
|
||||||
private void executeInSession(Session session) {
|
private void executeInSession(Session session) {
|
||||||
// Making sure the revision data is persisted.
|
// Making sure the revision data is persisted.
|
||||||
getCurrentRevisionData(session, true);
|
Object currentRevisionData = getCurrentRevisionData(session, true);
|
||||||
|
|
||||||
AuditWorkUnit vwu;
|
AuditWorkUnit vwu;
|
||||||
|
|
||||||
// First undoing any performed work units
|
// First undoing any performed work units
|
||||||
while ((vwu = undoQueue.poll()) != null) {
|
while ((vwu = undoQueue.poll()) != null) {
|
||||||
vwu.undo(session);
|
vwu.undo(session);
|
||||||
|
revisionInfoGenerator.removeEntityFromRevision(vwu.getEntityName(), currentRevisionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((vwu = workUnits.poll()) != null) {
|
while ((vwu = workUnits.poll()) != null) {
|
||||||
vwu.perform(session, revisionData);
|
vwu.perform(session, revisionData);
|
||||||
|
revisionInfoGenerator.addEntityToRevision(vwu.getEntityName(), currentRevisionData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.envers.ModifiedEntityNames;
|
||||||
|
import org.hibernate.envers.RevisionEntity;
|
||||||
|
import org.hibernate.envers.RevisionNumber;
|
||||||
|
import org.hibernate.envers.RevisionTimestamp;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@RevisionEntity
|
||||||
|
public class AnnotatedTrackingRevisionEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
@RevisionNumber
|
||||||
|
private int customId;
|
||||||
|
|
||||||
|
@RevisionTimestamp
|
||||||
|
private long customTimestamp;
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@JoinTable(name = "REVENTITY", joinColumns = @JoinColumn(name = "REV"))
|
||||||
|
@Column(name = "ENTITYNAME")
|
||||||
|
@ModifiedEntityNames
|
||||||
|
private Set<String> modifiedEntityNames;
|
||||||
|
|
||||||
|
public int getCustomId() {
|
||||||
|
return customId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomId(int customId) {
|
||||||
|
this.customId = customId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCustomTimestamp() {
|
||||||
|
return customTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomTimestamp(long customTimestamp) {
|
||||||
|
this.customTimestamp = customTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getModifiedEntityNames() {
|
||||||
|
return modifiedEntityNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedEntityNames(Set<String> modifiedEntityNames) {
|
||||||
|
this.modifiedEntityNames = modifiedEntityNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof AnnotatedTrackingRevisionEntity)) return false;
|
||||||
|
|
||||||
|
AnnotatedTrackingRevisionEntity that = (AnnotatedTrackingRevisionEntity) o;
|
||||||
|
|
||||||
|
if (customId != that.customId) return false;
|
||||||
|
if (customTimestamp != that.customTimestamp) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int result = customId;
|
||||||
|
result = 31 * result + (int) (customTimestamp ^ (customTimestamp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AnnotatedTrackingRevisionEntity(customId = " + customId + ", customTimestamp = " + customTimestamp + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.envers.EntityTrackingRevisionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class CustomEntityTrackingRevisionListener implements EntityTrackingRevisionListener {
|
||||||
|
@Override
|
||||||
|
public void addEntityToRevision(String entityName, Object revisionEntity) {
|
||||||
|
((CustomTrackingRevisionEntity)revisionEntity).addModifiedEntityName(entityName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeEntityFromRevision(String entityName, Object revisionEntity) {
|
||||||
|
((CustomTrackingRevisionEntity)revisionEntity).removeModifiedEntityName(entityName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newRevision(Object revisionEntity) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.envers.RevisionEntity;
|
||||||
|
import org.hibernate.envers.RevisionNumber;
|
||||||
|
import org.hibernate.envers.RevisionTimestamp;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@RevisionEntity(CustomEntityTrackingRevisionListener.class)
|
||||||
|
public class CustomTrackingRevisionEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
@RevisionNumber
|
||||||
|
private int customId;
|
||||||
|
|
||||||
|
@RevisionTimestamp
|
||||||
|
private long customTimestamp;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy="revision", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
|
||||||
|
private Set<ModifiedEntityNameEntity> modifiedEntityNames = new HashSet<ModifiedEntityNameEntity>();
|
||||||
|
|
||||||
|
public int getCustomId() {
|
||||||
|
return customId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomId(int customId) {
|
||||||
|
this.customId = customId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCustomTimestamp() {
|
||||||
|
return customTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomTimestamp(long customTimestamp) {
|
||||||
|
this.customTimestamp = customTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ModifiedEntityNameEntity> getModifiedEntityNames() {
|
||||||
|
return modifiedEntityNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedEntityNames(Set<ModifiedEntityNameEntity> modifiedEntityNames) {
|
||||||
|
this.modifiedEntityNames = modifiedEntityNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addModifiedEntityName(String entityName) {
|
||||||
|
modifiedEntityNames.add(new ModifiedEntityNameEntity(this, entityName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeModifiedEntityName(String entityName) {
|
||||||
|
modifiedEntityNames.remove(new ModifiedEntityNameEntity(this, entityName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof CustomTrackingRevisionEntity)) return false;
|
||||||
|
|
||||||
|
CustomTrackingRevisionEntity that = (CustomTrackingRevisionEntity) o;
|
||||||
|
|
||||||
|
if (customId != that.customId) return false;
|
||||||
|
if (customTimestamp != that.customTimestamp) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int result = customId;
|
||||||
|
result = 31 * result + (int) (customTimestamp ^ (customTimestamp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CustomTrackingRevisionEntity(customId = " + customId + ", customTimestamp = " + customTimestamp + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.envers.DefaultRevisionEntity;
|
||||||
|
import org.hibernate.envers.DefaultTrackingModifiedTypesRevisionEntity;
|
||||||
|
import org.hibernate.envers.RevisionEntity;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@RevisionEntity(ExtendedRevisionListener.class)
|
||||||
|
public class ExtendedRevisionEntity extends DefaultTrackingModifiedTypesRevisionEntity {
|
||||||
|
private String commnent;
|
||||||
|
|
||||||
|
public String getCommnent() {
|
||||||
|
return commnent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommnent(String commnent) {
|
||||||
|
this.commnent = commnent;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.envers.RevisionListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class ExtendedRevisionListener implements RevisionListener {
|
||||||
|
public static final String COMMENT_VALUE = "Comment";
|
||||||
|
|
||||||
|
public void newRevision(Object revisionEntity) {
|
||||||
|
((ExtendedRevisionEntity)revisionEntity).setCommnent(COMMENT_VALUE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.hibernate.envers.test.entities.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class ModifiedEntityNameEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private CustomTrackingRevisionEntity revision;
|
||||||
|
|
||||||
|
private String entityName;
|
||||||
|
|
||||||
|
public ModifiedEntityNameEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModifiedEntityNameEntity(String entityName) {
|
||||||
|
this.entityName = entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModifiedEntityNameEntity(CustomTrackingRevisionEntity revision, String entityName) {
|
||||||
|
this.revision = revision;
|
||||||
|
this.entityName = entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomTrackingRevisionEntity getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRevision(CustomTrackingRevisionEntity revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntityName() {
|
||||||
|
return entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityName(String entityName) {
|
||||||
|
this.entityName = entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof ModifiedEntityNameEntity)) return false;
|
||||||
|
|
||||||
|
ModifiedEntityNameEntity that = (ModifiedEntityNameEntity) o;
|
||||||
|
|
||||||
|
if (entityName != null ? !entityName.equals(that.entityName) : that.entityName != null) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return entityName != null ? entityName.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CustomTrackingRevisionEntity(entityName = " + entityName + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.ejb.Ejb3Configuration;
|
||||||
|
import org.hibernate.envers.test.AbstractEntityTest;
|
||||||
|
import org.hibernate.envers.test.Priority;
|
||||||
|
import org.hibernate.envers.test.entities.StrIntTestEntity;
|
||||||
|
import org.hibernate.envers.test.entities.StrTestEntity;
|
||||||
|
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.AnnotatedTrackingRevisionEntity;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class AnnotatedTrackingEntitiesTest extends DefaultTrackingEntitiesTest {
|
||||||
|
@Override
|
||||||
|
public void configure(Ejb3Configuration cfg) {
|
||||||
|
super.configure(cfg);
|
||||||
|
cfg.addAnnotatedClass(AnnotatedTrackingRevisionEntity.class);
|
||||||
|
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "false");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.ejb.Ejb3Configuration;
|
||||||
|
import org.hibernate.envers.AuditReader;
|
||||||
|
import org.hibernate.envers.exception.AuditException;
|
||||||
|
import org.hibernate.envers.test.AbstractEntityTest;
|
||||||
|
import org.hibernate.envers.test.Priority;
|
||||||
|
import org.hibernate.envers.test.entities.StrIntTestEntity;
|
||||||
|
import org.hibernate.envers.test.entities.StrTestEntity;
|
||||||
|
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.CustomTrackingRevisionEntity;
|
||||||
|
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ModifiedEntityNameEntity;
|
||||||
|
import org.hibernate.envers.test.tools.TestTools;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class CustomTrackingEntitiesTest extends AbstractEntityTest {
|
||||||
|
private Integer steId = null;
|
||||||
|
private Integer siteId = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Ejb3Configuration cfg) {
|
||||||
|
cfg.addAnnotatedClass(ModifiedEntityNameEntity.class);
|
||||||
|
cfg.addAnnotatedClass(CustomTrackingRevisionEntity.class);
|
||||||
|
cfg.addAnnotatedClass(StrTestEntity.class);
|
||||||
|
cfg.addAnnotatedClass(StrIntTestEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Priority(10)
|
||||||
|
public void initData() {
|
||||||
|
EntityManager em = getEntityManager();
|
||||||
|
|
||||||
|
// Revision 1 - Adding two entities
|
||||||
|
em.getTransaction().begin();
|
||||||
|
StrTestEntity ste = new StrTestEntity("x");
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 1);
|
||||||
|
em.persist(ste);
|
||||||
|
em.persist(site);
|
||||||
|
steId = ste.getId();
|
||||||
|
siteId = site.getId();
|
||||||
|
em.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 2 - Modifying one entity
|
||||||
|
em.getTransaction().begin();
|
||||||
|
site = em.find(StrIntTestEntity.class, siteId);
|
||||||
|
site.setNumber(2);
|
||||||
|
em.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 3 - Deleting both entities
|
||||||
|
em.getTransaction().begin();
|
||||||
|
ste = em.find(StrTestEntity.class, steId);
|
||||||
|
site = em.find(StrIntTestEntity.class, siteId);
|
||||||
|
em.remove(ste);
|
||||||
|
em.remove(site);
|
||||||
|
em.getTransaction().commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackAddedEntities() {
|
||||||
|
ModifiedEntityNameEntity steDescriptor = new ModifiedEntityNameEntity(StrTestEntity.class.getName());
|
||||||
|
ModifiedEntityNameEntity siteDescriptor = new ModifiedEntityNameEntity(StrIntTestEntity.class.getName());
|
||||||
|
|
||||||
|
AuditReader vr = getAuditReader();
|
||||||
|
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 1);
|
||||||
|
|
||||||
|
assert ctre.getModifiedEntityNames() != null;
|
||||||
|
assert ctre.getModifiedEntityNames().size() == 2;
|
||||||
|
assert TestTools.makeSet(steDescriptor, siteDescriptor).equals(ctre.getModifiedEntityNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackModifiedEntities() {
|
||||||
|
ModifiedEntityNameEntity siteDescriptor = new ModifiedEntityNameEntity(StrIntTestEntity.class.getName());
|
||||||
|
|
||||||
|
AuditReader vr = getAuditReader();
|
||||||
|
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 2);
|
||||||
|
|
||||||
|
assert ctre.getModifiedEntityNames() != null;
|
||||||
|
assert ctre.getModifiedEntityNames().size() == 1;
|
||||||
|
assert TestTools.makeSet(siteDescriptor).equals(ctre.getModifiedEntityNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackDeletedEntities() {
|
||||||
|
ModifiedEntityNameEntity steDescriptor = new ModifiedEntityNameEntity(StrTestEntity.class.getName());
|
||||||
|
ModifiedEntityNameEntity siteDescriptor = new ModifiedEntityNameEntity(StrIntTestEntity.class.getName());
|
||||||
|
|
||||||
|
AuditReader vr = getAuditReader();
|
||||||
|
CustomTrackingRevisionEntity ctre = vr.findRevision(CustomTrackingRevisionEntity.class, 3);
|
||||||
|
|
||||||
|
assert ctre.getModifiedEntityNames() != null;
|
||||||
|
assert ctre.getModifiedEntityNames().size() == 2;
|
||||||
|
assert TestTools.makeSet(steDescriptor, siteDescriptor).equals(ctre.getModifiedEntityNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = AuditException.class)
|
||||||
|
public void testFindEntitiesChangedInRevisionException() {
|
||||||
|
getAuditReader().findEntitiesChangedInRevision(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.ejb.Ejb3Configuration;
|
||||||
|
import org.hibernate.envers.RevisionType;
|
||||||
|
import org.hibernate.envers.test.AbstractEntityTest;
|
||||||
|
import org.hibernate.envers.test.Priority;
|
||||||
|
import org.hibernate.envers.test.entities.StrIntTestEntity;
|
||||||
|
import org.hibernate.envers.test.entities.StrTestEntity;
|
||||||
|
import org.hibernate.mapping.Column;
|
||||||
|
import org.hibernate.mapping.Table;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public class DefaultTrackingEntitiesTest extends AbstractEntityTest {
|
||||||
|
private Integer steId = null;
|
||||||
|
private Integer siteId = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Ejb3Configuration cfg) {
|
||||||
|
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "true");
|
||||||
|
cfg.addAnnotatedClass(StrTestEntity.class);
|
||||||
|
cfg.addAnnotatedClass(StrIntTestEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Priority(10)
|
||||||
|
public void initData() {
|
||||||
|
EntityManager em = getEntityManager();
|
||||||
|
|
||||||
|
// Revision 1 - Adding two entities
|
||||||
|
em.getTransaction().begin();
|
||||||
|
StrTestEntity ste = new StrTestEntity("x");
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 1);
|
||||||
|
em.persist(ste);
|
||||||
|
em.persist(site);
|
||||||
|
steId = ste.getId();
|
||||||
|
siteId = site.getId();
|
||||||
|
em.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 2 - Modifying one entity
|
||||||
|
em.getTransaction().begin();
|
||||||
|
site = em.find(StrIntTestEntity.class, siteId);
|
||||||
|
site.setNumber(2);
|
||||||
|
em.getTransaction().commit();
|
||||||
|
|
||||||
|
// Revision 3 - Deleting both entities
|
||||||
|
em.getTransaction().begin();
|
||||||
|
ste = em.find(StrTestEntity.class, steId);
|
||||||
|
site = em.find(StrIntTestEntity.class, siteId);
|
||||||
|
em.remove(ste);
|
||||||
|
em.remove(site);
|
||||||
|
em.getTransaction().commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevEntityTableCreation() {
|
||||||
|
Iterator<Table> tableIterator = getCfg().getTableMappings();
|
||||||
|
while (tableIterator.hasNext()) {
|
||||||
|
Table table = tableIterator.next();
|
||||||
|
if ("REVENTITY".equals(table.getName())) {
|
||||||
|
assert table.getColumnSpan() == 2;
|
||||||
|
assert table.getColumn(new Column("REV")) != null;
|
||||||
|
assert table.getColumn(new Column("ENTITYNAME")) != null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackAddedEntities() {
|
||||||
|
StrTestEntity ste = new StrTestEntity("x", steId);
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
|
||||||
|
|
||||||
|
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackModifiedEntities() {
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
|
||||||
|
|
||||||
|
assert Arrays.asList(site).equals(getAuditReader().findEntitiesChangedInRevision(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackDeletedEntities() {
|
||||||
|
StrTestEntity ste = new StrTestEntity(null, steId);
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
|
||||||
|
|
||||||
|
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindChangesInInvalidRevision() {
|
||||||
|
assert getAuditReader().findEntitiesChangedInRevision(4).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackAddedEntitiesGroupByRevisionType() {
|
||||||
|
StrTestEntity ste = new StrTestEntity("x", steId);
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
|
||||||
|
|
||||||
|
Map<RevisionType, List> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(1);
|
||||||
|
assert Arrays.asList(ste, site).equals(result.get(RevisionType.ADD));
|
||||||
|
assert Arrays.asList().equals(result.get(RevisionType.MOD));
|
||||||
|
assert Arrays.asList().equals(result.get(RevisionType.DEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackModifiedEntitiesGroupByRevisionType() {
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
|
||||||
|
|
||||||
|
Map<RevisionType, List> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(2);
|
||||||
|
assert Arrays.asList().equals(result.get(RevisionType.ADD));
|
||||||
|
assert Arrays.asList(site).equals(result.get(RevisionType.MOD));
|
||||||
|
assert Arrays.asList().equals(result.get(RevisionType.DEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrackDeletedEntitiesGroupByRevisionType() {
|
||||||
|
StrTestEntity ste = new StrTestEntity(null, steId);
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
|
||||||
|
|
||||||
|
Map<RevisionType, List> result = getAuditReader().findEntitiesChangedInRevisionGroupByRevisionType(3);
|
||||||
|
assert Arrays.asList().equals(result.get(RevisionType.ADD));
|
||||||
|
assert Arrays.asList().equals(result.get(RevisionType.MOD));
|
||||||
|
assert Arrays.asList(ste, site).equals(result.get(RevisionType.DEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindChangedEntitiesByRevisionTypeADD() {
|
||||||
|
StrTestEntity ste = new StrTestEntity("x", steId);
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 1, siteId);
|
||||||
|
|
||||||
|
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(1, RevisionType.ADD));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindChangedEntitiesByRevisionTypeMOD() {
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity("y", 2, siteId);
|
||||||
|
|
||||||
|
assert Arrays.asList(site).equals(getAuditReader().findEntitiesChangedInRevision(2, RevisionType.MOD));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindChangedEntitiesByRevisionTypeDEL() {
|
||||||
|
StrTestEntity ste = new StrTestEntity(null, steId);
|
||||||
|
StrIntTestEntity site = new StrIntTestEntity(null, null, siteId);
|
||||||
|
|
||||||
|
assert Arrays.asList(ste, site).equals(getAuditReader().findEntitiesChangedInRevision(3, RevisionType.DEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindEntityTypesChangedInRevision() {
|
||||||
|
assert Arrays.asList(StrTestEntity.class, StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(1));
|
||||||
|
assert Arrays.asList(StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(2));
|
||||||
|
assert Arrays.asList(StrTestEntity.class, StrIntTestEntity.class).equals(getAuditReader().findEntityTypesChangedInRevision(3));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.hibernate.envers.test.integration.reventity.trackmodifiedentities;
|
||||||
|
|
||||||
|
import org.hibernate.ejb.Ejb3Configuration;
|
||||||
|
import org.hibernate.envers.AuditReader;
|
||||||
|
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ExtendedRevisionEntity;
|
||||||
|
import org.hibernate.envers.test.entities.reventity.trackmodifiedentities.ExtendedRevisionListener;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class ExtendedRevisionEntityTest extends DefaultTrackingEntitiesTest {
|
||||||
|
@Override
|
||||||
|
public void configure(Ejb3Configuration cfg) {
|
||||||
|
super.configure(cfg);
|
||||||
|
cfg.addAnnotatedClass(ExtendedRevisionEntity.class);
|
||||||
|
cfg.setProperty("org.hibernate.envers.track_entities_changed_in_revision", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommentPropertyValue() {
|
||||||
|
AuditReader vr = getAuditReader();
|
||||||
|
ExtendedRevisionEntity ere = vr.findRevision(ExtendedRevisionEntity.class, 1);
|
||||||
|
|
||||||
|
assert ExtendedRevisionListener.COMMENT_VALUE.equals(ere.getCommnent());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue