HHH-13051 - Refactored strategy solution.
This commit is contained in:
parent
68df2792c6
commit
c8166b3f5d
|
@ -181,7 +181,7 @@ public class EnversServiceImpl implements EnversService, Configurable, Stoppable
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy-specific initialization
|
// Strategy-specific initialization
|
||||||
strategy.initialize( revisionInfoClass, revisionInfoTimestampData, serviceRegistry );
|
strategy.postInitialize( revisionInfoClass, revisionInfoTimestampData, serviceRegistry );
|
||||||
|
|
||||||
return strategy;
|
return strategy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.hibernate.envers.internal.entities.mapper.SubclassPropertyMapper;
|
||||||
import org.hibernate.envers.internal.tools.StringTools;
|
import org.hibernate.envers.internal.tools.StringTools;
|
||||||
import org.hibernate.envers.internal.tools.Triple;
|
import org.hibernate.envers.internal.tools.Triple;
|
||||||
import org.hibernate.envers.strategy.AuditStrategy;
|
import org.hibernate.envers.strategy.AuditStrategy;
|
||||||
|
import org.hibernate.envers.strategy.spi.MappingContext;
|
||||||
import org.hibernate.mapping.Collection;
|
import org.hibernate.mapping.Collection;
|
||||||
import org.hibernate.mapping.Join;
|
import org.hibernate.mapping.Join;
|
||||||
import org.hibernate.mapping.OneToOne;
|
import org.hibernate.mapping.OneToOne;
|
||||||
|
@ -44,7 +45,6 @@ import org.hibernate.type.CollectionType;
|
||||||
import org.hibernate.type.ComponentType;
|
import org.hibernate.type.ComponentType;
|
||||||
import org.hibernate.type.ManyToOneType;
|
import org.hibernate.type.ManyToOneType;
|
||||||
import org.hibernate.type.OneToOneType;
|
import org.hibernate.type.OneToOneType;
|
||||||
import org.hibernate.type.TimestampType;
|
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
|
@ -174,7 +174,7 @@ public final class AuditMetadataGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAdditionalColumns(Element anyMapping) {
|
void addAdditionalColumns(Element anyMapping) {
|
||||||
auditStrategy.addAdditionalColumns(anyMapping, revisionInfoRelationMapping, verEntCfg);
|
auditStrategy.addAdditionalColumns( new MappingContext( anyMapping, revisionInfoRelationMapping, verEntCfg ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addValueInFirstPass(
|
private void addValueInFirstPass(
|
||||||
|
|
|
@ -9,26 +9,21 @@ package org.hibernate.envers.strategy;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.service.ServiceRegistry;
|
|
||||||
import org.hibernate.envers.boot.internal.EnversService;
|
import org.hibernate.envers.boot.internal.EnversService;
|
||||||
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
||||||
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
|
||||||
import org.hibernate.envers.internal.entities.PropertyData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
||||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
|
||||||
import org.hibernate.envers.internal.tools.query.Parameters;
|
|
||||||
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
|
||||||
import org.dom4j.Element;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Behaviours of different audit strategy for populating audit data.
|
* Behaviours of different audit strategy for populating audit data.
|
||||||
*
|
*
|
||||||
|
* @deprecated (since 5.4), use {@link org.hibernate.envers.strategy.spi.AuditStrategy} instead.
|
||||||
|
*
|
||||||
* @author Stephanie Pau
|
* @author Stephanie Pau
|
||||||
* @author Adam Warski (adam at warski dot org)
|
* @author Adam Warski (adam at warski dot org)
|
||||||
* @author Chris Cranford
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public interface AuditStrategy {
|
@Deprecated
|
||||||
|
public interface AuditStrategy extends org.hibernate.envers.strategy.spi.AuditStrategy {
|
||||||
/**
|
/**
|
||||||
* Perform the persistence of audited data for regular entities.
|
* Perform the persistence of audited data for regular entities.
|
||||||
*
|
*
|
||||||
|
@ -58,23 +53,6 @@ public interface AuditStrategy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the persistence of audited data for regular entities.
|
|
||||||
*
|
|
||||||
* @param session Session, which can be used to persist the data.
|
|
||||||
* @param entityName Name of the entity, in which the audited change happens
|
|
||||||
* @param auditEntitiesConfiguration The audit entity configuration.
|
|
||||||
* @param id Id of the entity.
|
|
||||||
* @param data Audit data to persist.
|
|
||||||
* @param revision Current revision data.
|
|
||||||
*/
|
|
||||||
void perform(
|
|
||||||
Session session,
|
|
||||||
String entityName,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
|
||||||
Serializable id,
|
|
||||||
Object data,
|
|
||||||
Object revision);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform the persistence of audited data for collection ("middle") entities.
|
* Perform the persistence of audited data for collection ("middle") entities.
|
||||||
|
@ -104,130 +82,4 @@ public interface AuditStrategy {
|
||||||
revision
|
revision
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the persistence of audited data for collection ("middle") entities.
|
|
||||||
*
|
|
||||||
* @param session Session, which can be used to persist the data.
|
|
||||||
* @param entityName Name of the entity, in which the audited change happens.
|
|
||||||
* @param propertyName The name of the property holding the persistent collection
|
|
||||||
* @param auditEntitiesConfiguration audit entity configuration
|
|
||||||
* @param persistentCollectionChangeData Collection change data to be persisted.
|
|
||||||
* @param revision Current revision data
|
|
||||||
*/
|
|
||||||
void performCollectionChange(
|
|
||||||
Session session,
|
|
||||||
String entityName,
|
|
||||||
String propertyName,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
|
||||||
PersistentCollectionChangeData persistentCollectionChangeData,
|
|
||||||
Object revision);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a two-entity relation.
|
|
||||||
* This WHERE clause depends on the AuditStrategy, as follows:
|
|
||||||
* <ul>
|
|
||||||
* <li>For {@link DefaultAuditStrategy} a subquery is created:
|
|
||||||
* <p><code>e.revision = (SELECT max(...) ...)</code></p>
|
|
||||||
* </li>
|
|
||||||
* <li>for {@link ValidityAuditStrategy} the revision-end column is used:
|
|
||||||
* <p><code>e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)</code></p>
|
|
||||||
* </li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param globalCfg the {@link GlobalConfiguration}
|
|
||||||
* @param rootQueryBuilder the {@link QueryBuilder} that will be updated
|
|
||||||
* @param parameters root parameters to which restrictions shall be added
|
|
||||||
* @param revisionProperty property of the revision column
|
|
||||||
* @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param addAlias {@code boolean} indicator if a left alias is needed
|
|
||||||
* @param idData id-information for the two-entity relation (only used for {@link DefaultAuditStrategy})
|
|
||||||
* @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param alias1 an alias used for subquery (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param alias2 an alias used for subquery (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param inclusive indicates whether revision number shall be treated as inclusive or exclusive
|
|
||||||
*/
|
|
||||||
void addEntityAtRevisionRestriction(
|
|
||||||
GlobalConfiguration globalCfg,
|
|
||||||
QueryBuilder rootQueryBuilder,
|
|
||||||
Parameters parameters,
|
|
||||||
String revisionProperty,
|
|
||||||
String revisionEndProperty,
|
|
||||||
boolean addAlias,
|
|
||||||
MiddleIdData idData,
|
|
||||||
String revisionPropertyPath,
|
|
||||||
String originalIdPropertyName,
|
|
||||||
String alias1,
|
|
||||||
String alias2,
|
|
||||||
boolean inclusive);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a middle-entity
|
|
||||||
* association. This WHERE clause depends on the AuditStrategy, as follows:
|
|
||||||
* <ul>
|
|
||||||
* <li>For {@link DefaultAuditStrategy} a subquery is created:
|
|
||||||
* <p><code>e.revision = (SELECT max(...) ...)</code></p>
|
|
||||||
* </li>
|
|
||||||
* <li>for {@link ValidityAuditStrategy} the revision-end column is used:
|
|
||||||
* <p><code>e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null)</code></p>
|
|
||||||
* </li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param rootQueryBuilder the {@link QueryBuilder} that will be updated
|
|
||||||
* @param parameters root parameters to which restrictions shall be added
|
|
||||||
* @param revisionProperty property of the revision column
|
|
||||||
* @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param addAlias {@code boolean} indicator if a left alias is needed
|
|
||||||
* @param referencingIdData id-information for the middle-entity association (only used for {@link DefaultAuditStrategy})
|
|
||||||
* @param versionsMiddleEntityName name of the middle-entity
|
|
||||||
* @param eeOriginalIdPropertyPath name of the id property (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy})
|
|
||||||
* @param alias1 an alias used for subqueries (only used for {@link DefaultAuditStrategy})
|
|
||||||
* @param inclusive indicates whether revision number shall be treated as inclusive or exclusive
|
|
||||||
* @param componentDatas information about the middle-entity relation
|
|
||||||
*/
|
|
||||||
void addAssociationAtRevisionRestriction(
|
|
||||||
QueryBuilder rootQueryBuilder,
|
|
||||||
Parameters parameters,
|
|
||||||
String revisionProperty,
|
|
||||||
String revisionEndProperty,
|
|
||||||
boolean addAlias,
|
|
||||||
MiddleIdData referencingIdData,
|
|
||||||
String versionsMiddleEntityName,
|
|
||||||
String eeOriginalIdPropertyPath,
|
|
||||||
String revisionPropertyPath,
|
|
||||||
String originalIdPropertyName,
|
|
||||||
String alias1,
|
|
||||||
boolean inclusive,
|
|
||||||
MiddleComponentData... componentDatas);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to add additional Columns to the aud-tables.
|
|
||||||
*
|
|
||||||
* @param anyMapping the class mapping
|
|
||||||
* @param revMapping the mapping of the revInfo-relation
|
|
||||||
* @param auditEntitiesConfiguration the configuration of the audit entities
|
|
||||||
*/
|
|
||||||
default void addAdditionalColumns(
|
|
||||||
Element anyMapping,
|
|
||||||
Element revMapping,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration) {
|
|
||||||
//For backward compatibility
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method do add additional initialization behavior
|
|
||||||
*
|
|
||||||
* @param revisionInfoClass the class of the revision info
|
|
||||||
* @param revisionInfoTimestampData the timestamp of the revision info
|
|
||||||
* @param serviceRegistry the service registry
|
|
||||||
*/
|
|
||||||
default void initialize(
|
|
||||||
Class<?> revisionInfoClass,
|
|
||||||
PropertyData revisionInfoTimestampData,
|
|
||||||
ServiceRegistry serviceRegistry) {
|
|
||||||
//For backward compatibility
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,138 +6,16 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.envers.strategy;
|
package org.hibernate.envers.strategy;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.service.ServiceRegistry;
|
|
||||||
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
|
||||||
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
|
||||||
import org.hibernate.envers.internal.entities.PropertyData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
|
||||||
import org.hibernate.envers.internal.synchronization.SessionCacheCleaner;
|
|
||||||
import org.hibernate.envers.internal.tools.query.Parameters;
|
|
||||||
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
|
||||||
import org.dom4j.Element;
|
|
||||||
|
|
||||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS_DEF_AUD_STR;
|
|
||||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default strategy is to simply persist the audit data.
|
* Default strategy is to simply persist the audit data.
|
||||||
*
|
*
|
||||||
|
* @deprecated (since 5.4), use {@link org.hibernate.envers.strategy.internal.DefaultAuditStrategy} instead.
|
||||||
|
*
|
||||||
* @author Adam Warski
|
* @author Adam Warski
|
||||||
* @author Stephanie Pau
|
* @author Stephanie Pau
|
||||||
* @author Chris Cranford
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public class DefaultAuditStrategy implements AuditStrategy {
|
@Deprecated
|
||||||
private final SessionCacheCleaner sessionCacheCleaner;
|
public class DefaultAuditStrategy extends org.hibernate.envers.strategy.internal.DefaultAuditStrategy {
|
||||||
|
|
||||||
public DefaultAuditStrategy() {
|
|
||||||
sessionCacheCleaner = new SessionCacheCleaner();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void perform(
|
|
||||||
Session session,
|
|
||||||
String entityName,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
|
||||||
Serializable id,
|
|
||||||
Object data,
|
|
||||||
Object revision) {
|
|
||||||
session.save( auditEntitiesConfiguration.getAuditEntityName( entityName ), data );
|
|
||||||
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void performCollectionChange(
|
|
||||||
Session session,
|
|
||||||
String entityName,
|
|
||||||
String propertyName,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
|
||||||
PersistentCollectionChangeData persistentCollectionChangeData,
|
|
||||||
Object revision) {
|
|
||||||
session.save( persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData() );
|
|
||||||
sessionCacheCleaner.scheduleAuditDataRemoval( session, persistentCollectionChangeData.getData() );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addEntityAtRevisionRestriction(
|
|
||||||
GlobalConfiguration globalCfg,
|
|
||||||
QueryBuilder rootQueryBuilder,
|
|
||||||
Parameters parameters,
|
|
||||||
String revisionProperty,
|
|
||||||
String revisionEndProperty,
|
|
||||||
boolean addAlias,
|
|
||||||
MiddleIdData idData,
|
|
||||||
String revisionPropertyPath,
|
|
||||||
String originalIdPropertyName,
|
|
||||||
String alias1,
|
|
||||||
String alias2,
|
|
||||||
boolean inclusive) {
|
|
||||||
// create a subquery builder
|
|
||||||
// SELECT max(e.revision) FROM versionsReferencedEntity e2
|
|
||||||
QueryBuilder maxERevQb = rootQueryBuilder.newSubQueryBuilder( idData.getAuditEntityName(), alias2 );
|
|
||||||
maxERevQb.addProjection( "max", alias2, revisionPropertyPath, false );
|
|
||||||
// WHERE
|
|
||||||
Parameters maxERevQbParameters = maxERevQb.getRootParameters();
|
|
||||||
// e2.revision <= :revision
|
|
||||||
maxERevQbParameters.addWhereWithNamedParam( revisionPropertyPath, inclusive ? "<=" : "<", REVISION_PARAMETER );
|
|
||||||
// e2.id_ref_ed = e.id_ref_ed
|
|
||||||
idData.getOriginalMapper().addIdsEqualToQuery(
|
|
||||||
maxERevQbParameters,
|
|
||||||
alias1 + "." + originalIdPropertyName, alias2 + "." + originalIdPropertyName
|
|
||||||
);
|
|
||||||
|
|
||||||
// add subquery to rootParameters
|
|
||||||
String subqueryOperator = globalCfg.getCorrelatedSubqueryOperator();
|
|
||||||
parameters.addWhere( revisionProperty, addAlias, subqueryOperator, maxERevQb );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAssociationAtRevisionRestriction(
|
|
||||||
QueryBuilder rootQueryBuilder,
|
|
||||||
Parameters parameters,
|
|
||||||
String revisionProperty,
|
|
||||||
String revisionEndProperty,
|
|
||||||
boolean addAlias,
|
|
||||||
MiddleIdData referencingIdData,
|
|
||||||
String versionsMiddleEntityName,
|
|
||||||
String eeOriginalIdPropertyPath,
|
|
||||||
String revisionPropertyPath,
|
|
||||||
String originalIdPropertyName,
|
|
||||||
String alias1,
|
|
||||||
boolean inclusive,
|
|
||||||
MiddleComponentData... componentDatas) {
|
|
||||||
// SELECT max(ee2.revision) FROM middleEntity ee2
|
|
||||||
QueryBuilder maxEeRevQb = rootQueryBuilder.newSubQueryBuilder(
|
|
||||||
versionsMiddleEntityName,
|
|
||||||
MIDDLE_ENTITY_ALIAS_DEF_AUD_STR
|
|
||||||
);
|
|
||||||
maxEeRevQb.addProjection( "max", MIDDLE_ENTITY_ALIAS_DEF_AUD_STR, revisionPropertyPath, false );
|
|
||||||
// WHERE
|
|
||||||
Parameters maxEeRevQbParameters = maxEeRevQb.getRootParameters();
|
|
||||||
// ee2.revision <= :revision
|
|
||||||
maxEeRevQbParameters.addWhereWithNamedParam( revisionPropertyPath, inclusive ? "<=" : "<", REVISION_PARAMETER );
|
|
||||||
// ee2.originalId.* = ee.originalId.*
|
|
||||||
String ee2OriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS_DEF_AUD_STR + "." + originalIdPropertyName;
|
|
||||||
referencingIdData.getPrefixedMapper().addIdsEqualToQuery(
|
|
||||||
maxEeRevQbParameters,
|
|
||||||
eeOriginalIdPropertyPath,
|
|
||||||
ee2OriginalIdPropertyPath
|
|
||||||
);
|
|
||||||
for ( MiddleComponentData componentData : componentDatas ) {
|
|
||||||
componentData.getComponentMapper().addMiddleEqualToQuery(
|
|
||||||
maxEeRevQbParameters,
|
|
||||||
eeOriginalIdPropertyPath,
|
|
||||||
alias1,
|
|
||||||
ee2OriginalIdPropertyPath,
|
|
||||||
MIDDLE_ENTITY_ALIAS_DEF_AUD_STR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add subquery to rootParameters
|
|
||||||
parameters.addWhere( revisionProperty, addAlias, "=", maxEeRevQb );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,50 +6,6 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.envers.strategy;
|
package org.hibernate.envers.strategy;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.dom4j.Element;
|
|
||||||
|
|
||||||
import org.hibernate.LockOptions;
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
|
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|
||||||
import org.hibernate.envers.RevisionType;
|
|
||||||
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
|
||||||
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
|
||||||
import org.hibernate.envers.configuration.internal.metadata.MetadataTools;
|
|
||||||
import org.hibernate.envers.internal.entities.PropertyData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
|
|
||||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
|
||||||
import org.hibernate.envers.internal.synchronization.SessionCacheCleaner;
|
|
||||||
import org.hibernate.envers.internal.tools.ReflectionTools;
|
|
||||||
import org.hibernate.envers.internal.tools.query.Parameters;
|
|
||||||
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
|
||||||
import org.hibernate.event.spi.EventSource;
|
|
||||||
import org.hibernate.jdbc.ReturningWork;
|
|
||||||
import org.hibernate.persister.entity.Queryable;
|
|
||||||
import org.hibernate.persister.entity.UnionSubclassEntityPersister;
|
|
||||||
import org.hibernate.property.access.spi.Getter;
|
|
||||||
import org.hibernate.sql.Update;
|
|
||||||
import org.hibernate.type.CollectionType;
|
|
||||||
import org.hibernate.type.ComponentType;
|
|
||||||
import org.hibernate.type.MapType;
|
|
||||||
import org.hibernate.type.MaterializedClobType;
|
|
||||||
import org.hibernate.type.MaterializedNClobType;
|
|
||||||
import org.hibernate.type.Type;
|
|
||||||
import org.hibernate.type.TimestampType;
|
|
||||||
import org.hibernate.service.ServiceRegistry;
|
|
||||||
|
|
||||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS;
|
|
||||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audit strategy which persists and retrieves audit information using a validity algorithm, based on the
|
* Audit strategy which persists and retrieves audit information using a validity algorithm, based on the
|
||||||
* start-revision and end-revision of a row in the audit tables.
|
* start-revision and end-revision of a row in the audit tables.
|
||||||
|
@ -69,390 +25,14 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
|
||||||
* </ul>
|
* </ul>
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
* @deprecated (since 5.4), use {@link org.hibernate.envers.strategy.internal.ValidityAuditStrategy} instead.
|
||||||
|
*
|
||||||
* @author Stephanie Pau
|
* @author Stephanie Pau
|
||||||
* @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)
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
* @author Chris Cranford
|
* @author Chris Cranford
|
||||||
*/
|
*/
|
||||||
public class ValidityAuditStrategy implements AuditStrategy {
|
@Deprecated
|
||||||
/**
|
public class ValidityAuditStrategy extends org.hibernate.envers.strategy.internal.ValidityAuditStrategy {
|
||||||
* getter for the revision entity field annotated with @RevisionTimestamp
|
|
||||||
*/
|
|
||||||
private Getter revisionTimestampGetter;
|
|
||||||
|
|
||||||
private final SessionCacheCleaner sessionCacheCleaner;
|
|
||||||
|
|
||||||
public ValidityAuditStrategy() {
|
|
||||||
sessionCacheCleaner = new SessionCacheCleaner();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void perform(
|
|
||||||
final Session session,
|
|
||||||
final String entityName,
|
|
||||||
final AuditEntitiesConfiguration audEntitiesCfg,
|
|
||||||
final Serializable id,
|
|
||||||
final Object data,
|
|
||||||
final Object revision) {
|
|
||||||
final String auditedEntityName = audEntitiesCfg.getAuditEntityName( entityName );
|
|
||||||
final String revisionInfoEntityName = audEntitiesCfg.getRevisionInfoEntityName();
|
|
||||||
|
|
||||||
// Save the audit data
|
|
||||||
session.save( auditedEntityName, data );
|
|
||||||
|
|
||||||
// Update the end date of the previous row.
|
|
||||||
//
|
|
||||||
// When application reuses identifiers of previously removed entities:
|
|
||||||
// The UPDATE statement will no-op if an entity with a given identifier has been
|
|
||||||
// inserted for the first time. But in case a deleted primary key value was
|
|
||||||
// reused, this guarantees correct strategy behavior: exactly one row with
|
|
||||||
// null end date exists for each identifier.
|
|
||||||
final boolean reuseEntityIdentifier = audEntitiesCfg.getEnversService().getGlobalConfiguration().isAllowIdentifierReuse();
|
|
||||||
if ( reuseEntityIdentifier || getRevisionType( audEntitiesCfg, data ) != RevisionType.ADD ) {
|
|
||||||
// Register transaction completion process to guarantee execution of UPDATE statement after INSERT.
|
|
||||||
( (EventSource) session ).getActionQueue().registerProcess( new BeforeTransactionCompletionProcess() {
|
|
||||||
@Override
|
|
||||||
public void doBeforeTransactionCompletion(final SessionImplementor sessionImplementor) {
|
|
||||||
final Queryable productionEntityQueryable = getQueryable( entityName, sessionImplementor );
|
|
||||||
final Queryable rootProductionEntityQueryable = getQueryable(
|
|
||||||
productionEntityQueryable.getRootEntityName(), sessionImplementor
|
|
||||||
);
|
|
||||||
final Queryable auditedEntityQueryable = getQueryable( auditedEntityName, sessionImplementor );
|
|
||||||
final Queryable rootAuditedEntityQueryable = getQueryable(
|
|
||||||
auditedEntityQueryable.getRootEntityName(), sessionImplementor
|
|
||||||
);
|
|
||||||
|
|
||||||
final String updateTableName;
|
|
||||||
if ( UnionSubclassEntityPersister.class.isInstance( rootProductionEntityQueryable ) ) {
|
|
||||||
// this is the condition causing all the problems in terms of the generated SQL UPDATE
|
|
||||||
// the problem being that we currently try to update the in-line view made up of the union query
|
|
||||||
//
|
|
||||||
// this is extremely hacky means to get the root table name for the union subclass style entities.
|
|
||||||
// hacky because it relies on internal behavior of UnionSubclassEntityPersister
|
|
||||||
// !!!!!! NOTICE - using subclass persister, not root !!!!!!
|
|
||||||
updateTableName = auditedEntityQueryable.getSubclassTableName( 0 );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
updateTableName = rootAuditedEntityQueryable.getTableName();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type revisionInfoIdType = sessionImplementor.getFactory().getMetamodel().entityPersister( revisionInfoEntityName ).getIdentifierType();
|
|
||||||
final String revEndColumnName = rootAuditedEntityQueryable.toColumns( audEntitiesCfg.getRevisionEndFieldName() )[0];
|
|
||||||
|
|
||||||
final boolean isRevisionEndTimestampEnabled = audEntitiesCfg.isRevisionEndTimestampEnabled();
|
|
||||||
|
|
||||||
// update audit_ent set REVEND = ? [, REVEND_TSTMP = ?] where (prod_ent_id) = ? and REV <> ? and REVEND is null
|
|
||||||
final Update update = new Update( sessionImplementor.getFactory().getJdbcServices().getDialect() ).setTableName( updateTableName );
|
|
||||||
// set REVEND = ?
|
|
||||||
update.addColumn( revEndColumnName );
|
|
||||||
// set [, REVEND_TSTMP = ?]
|
|
||||||
if ( isRevisionEndTimestampEnabled ) {
|
|
||||||
update.addColumn(
|
|
||||||
rootAuditedEntityQueryable.toColumns( audEntitiesCfg.getRevisionEndTimestampFieldName() )[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// where (prod_ent_id) = ?
|
|
||||||
update.addPrimaryKeyColumns( rootProductionEntityQueryable.getIdentifierColumnNames() );
|
|
||||||
// where REV <> ?
|
|
||||||
update.addWhereColumn(
|
|
||||||
rootAuditedEntityQueryable.toColumns( audEntitiesCfg.getRevisionNumberPath() )[0], "<> ?"
|
|
||||||
);
|
|
||||||
// where REVEND is null
|
|
||||||
update.addWhereColumn( revEndColumnName, " is null" );
|
|
||||||
|
|
||||||
// Now lets execute the sql...
|
|
||||||
final String updateSql = update.toStatementString();
|
|
||||||
|
|
||||||
int rowCount = sessionImplementor.doReturningWork(
|
|
||||||
new ReturningWork<Integer>() {
|
|
||||||
@Override
|
|
||||||
public Integer execute(Connection connection) throws SQLException {
|
|
||||||
PreparedStatement preparedStatement = sessionImplementor
|
|
||||||
.getJdbcCoordinator().getStatementPreparer().prepareStatement( updateSql );
|
|
||||||
|
|
||||||
try {
|
|
||||||
int index = 1;
|
|
||||||
|
|
||||||
// set REVEND = ?
|
|
||||||
final Number revisionNumber = audEntitiesCfg.getEnversService()
|
|
||||||
.getRevisionInfoNumberReader()
|
|
||||||
.getRevisionNumber( revision );
|
|
||||||
|
|
||||||
revisionInfoIdType.nullSafeSet(
|
|
||||||
preparedStatement, revisionNumber, index, sessionImplementor
|
|
||||||
);
|
|
||||||
index += revisionInfoIdType.getColumnSpan( sessionImplementor.getFactory() );
|
|
||||||
|
|
||||||
// set [, REVEND_TSTMP = ?]
|
|
||||||
if ( isRevisionEndTimestampEnabled ) {
|
|
||||||
final Object revEndTimestampObj = revisionTimestampGetter.get( revision );
|
|
||||||
final Date revisionEndTimestamp = convertRevEndTimestampToDate( revEndTimestampObj );
|
|
||||||
final Type revEndTsType = rootAuditedEntityQueryable.getPropertyType(
|
|
||||||
audEntitiesCfg.getRevisionEndTimestampFieldName()
|
|
||||||
);
|
|
||||||
revEndTsType.nullSafeSet(
|
|
||||||
preparedStatement, revisionEndTimestamp, index, sessionImplementor
|
|
||||||
);
|
|
||||||
index += revEndTsType.getColumnSpan( sessionImplementor.getFactory() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// where (prod_ent_id) = ?
|
|
||||||
final Type idType = rootProductionEntityQueryable.getIdentifierType();
|
|
||||||
idType.nullSafeSet( preparedStatement, id, index, sessionImplementor );
|
|
||||||
index += idType.getColumnSpan( sessionImplementor.getFactory() );
|
|
||||||
|
|
||||||
// where REV <> ?
|
|
||||||
final Type revType = rootAuditedEntityQueryable.getPropertyType(
|
|
||||||
audEntitiesCfg.getRevisionNumberPath()
|
|
||||||
);
|
|
||||||
revType.nullSafeSet( preparedStatement, revisionNumber, index, sessionImplementor );
|
|
||||||
|
|
||||||
// where REVEND is null
|
|
||||||
// nothing to bind....
|
|
||||||
|
|
||||||
return sessionImplementor
|
|
||||||
.getJdbcCoordinator().getResultSetReturn().executeUpdate( preparedStatement );
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
sessionImplementor.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release(
|
|
||||||
preparedStatement
|
|
||||||
);
|
|
||||||
sessionImplementor.getJdbcCoordinator().afterStatementExecution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( rowCount != 1 && ( !reuseEntityIdentifier || ( getRevisionType( audEntitiesCfg, data ) != RevisionType.ADD ) ) ) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Cannot update previous revision for entity " + auditedEntityName + " and id " + id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
|
|
||||||
}
|
|
||||||
|
|
||||||
private Queryable getQueryable(String entityName, SessionImplementor sessionImplementor) {
|
|
||||||
return (Queryable) sessionImplementor.getFactory().getMetamodel().entityPersister( entityName );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
public void performCollectionChange(
|
|
||||||
Session session,
|
|
||||||
String entityName,
|
|
||||||
String propertyName,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
|
||||||
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
|
|
||||||
final QueryBuilder qb = new QueryBuilder(
|
|
||||||
persistentCollectionChangeData.getEntityName(),
|
|
||||||
MIDDLE_ENTITY_ALIAS,
|
|
||||||
( (SharedSessionContractImplementor) session ).getFactory()
|
|
||||||
);
|
|
||||||
|
|
||||||
final String originalIdPropName = auditEntitiesConfiguration.getOriginalIdPropName();
|
|
||||||
final Map<String, Object> originalId = (Map<String, Object>) persistentCollectionChangeData.getData().get(
|
|
||||||
originalIdPropName
|
|
||||||
);
|
|
||||||
final String revisionFieldName = auditEntitiesConfiguration.getRevisionFieldName();
|
|
||||||
final String revisionTypePropName = auditEntitiesConfiguration.getRevisionTypePropName();
|
|
||||||
final String ordinalPropName = auditEntitiesConfiguration.getEmbeddableSetOrdinalPropertyName();
|
|
||||||
|
|
||||||
// Adding a parameter for each id component, except the rev number and type.
|
|
||||||
for ( Map.Entry<String, Object> originalIdEntry : originalId.entrySet() ) {
|
|
||||||
if ( !revisionFieldName.equals( originalIdEntry.getKey() )
|
|
||||||
&& !revisionTypePropName.equals( originalIdEntry.getKey() )
|
|
||||||
&& !ordinalPropName.equals( originalIdEntry.getKey() ) ) {
|
|
||||||
qb.getRootParameters().addWhereWithParam(
|
|
||||||
originalIdPropName + "." + originalIdEntry.getKey(),
|
|
||||||
true, "=", originalIdEntry.getValue()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isNonIdentifierWhereConditionsRequired( entityName, propertyName, (SessionImplementor) session ) ) {
|
|
||||||
addNonIdentifierWhereConditions( qb, persistentCollectionChangeData.getData(), originalIdPropName );
|
|
||||||
}
|
|
||||||
|
|
||||||
addEndRevisionNullRestriction( auditEntitiesConfiguration, qb.getRootParameters() );
|
|
||||||
|
|
||||||
final List<Object> l = qb.toQuery( session ).setLockOptions( LockOptions.UPGRADE ).list();
|
|
||||||
|
|
||||||
// Update the last revision if one exists.
|
|
||||||
// HHH-5967: with collections, the same element can be added and removed multiple times. So even if it's an
|
|
||||||
// ADD, we may need to update the last revision.
|
|
||||||
if ( l.size() > 0 ) {
|
|
||||||
updateLastRevision(
|
|
||||||
session, auditEntitiesConfiguration, l, originalId, persistentCollectionChangeData.getEntityName(), revision
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the audit data
|
|
||||||
session.save( persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData() );
|
|
||||||
sessionCacheCleaner.scheduleAuditDataRemoval( session, persistentCollectionChangeData.getData() );
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isNonIdentifierWhereConditionsRequired(String entityName, String propertyName, SessionImplementor session) {
|
|
||||||
final Type propertyType = session.getSessionFactory().getMetamodel().entityPersister( entityName ).getPropertyType( propertyName );
|
|
||||||
if ( propertyType.isCollectionType() ) {
|
|
||||||
final CollectionType collectionType = (CollectionType) propertyType;
|
|
||||||
final Type collectionElementType = collectionType.getElementType( session.getSessionFactory() );
|
|
||||||
if ( collectionElementType instanceof ComponentType ) {
|
|
||||||
// required for Embeddables
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if ( collectionElementType instanceof MaterializedClobType || collectionElementType instanceof MaterializedNClobType ) {
|
|
||||||
// for Map<> using @Lob annotations
|
|
||||||
return collectionType instanceof MapType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNonIdentifierWhereConditions(QueryBuilder qb, Map<String, Object> data, String originalIdPropertyName) {
|
|
||||||
final Parameters parameters = qb.getRootParameters();
|
|
||||||
for ( Map.Entry<String, Object> entry : data.entrySet() ) {
|
|
||||||
if ( !originalIdPropertyName.equals( entry.getKey() ) ) {
|
|
||||||
if ( entry.getValue() != null ) {
|
|
||||||
parameters.addWhereWithParam( entry.getKey(), true, "=", entry.getValue() );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
parameters.addNullRestriction( entry.getKey(), true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addEndRevisionNullRestriction(AuditEntitiesConfiguration auditEntitiesConfiguration, Parameters rootParameters) {
|
|
||||||
rootParameters.addWhere( auditEntitiesConfiguration.getRevisionEndFieldName(), true, "is", "null", false );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addEntityAtRevisionRestriction(
|
|
||||||
GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder,
|
|
||||||
Parameters parameters, String revisionProperty, String revisionEndProperty, boolean addAlias,
|
|
||||||
MiddleIdData idData, String revisionPropertyPath, String originalIdPropertyName,
|
|
||||||
String alias1, String alias2, boolean inclusive) {
|
|
||||||
addRevisionRestriction( parameters, revisionProperty, revisionEndProperty, addAlias, inclusive );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAssociationAtRevisionRestriction(
|
|
||||||
QueryBuilder rootQueryBuilder, Parameters parameters, String revisionProperty,
|
|
||||||
String revisionEndProperty, boolean addAlias, MiddleIdData referencingIdData,
|
|
||||||
String versionsMiddleEntityName, String eeOriginalIdPropertyPath, String revisionPropertyPath,
|
|
||||||
String originalIdPropertyName, String alias1, boolean inclusive, MiddleComponentData... componentDatas) {
|
|
||||||
addRevisionRestriction( parameters, revisionProperty, revisionEndProperty, addAlias, inclusive );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRevisionTimestampGetter(Getter revisionTimestampGetter) {
|
|
||||||
this.revisionTimestampGetter = revisionTimestampGetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addRevisionRestriction(
|
|
||||||
Parameters rootParameters, String revisionProperty, String revisionEndProperty,
|
|
||||||
boolean addAlias, boolean inclusive) {
|
|
||||||
// e.revision <= _revision and (e.endRevision > _revision or e.endRevision is null)
|
|
||||||
Parameters subParm = rootParameters.addSubParameters( "or" );
|
|
||||||
rootParameters.addWhereWithNamedParam( revisionProperty, addAlias, inclusive ? "<=" : "<", REVISION_PARAMETER );
|
|
||||||
subParm.addWhereWithNamedParam(
|
|
||||||
revisionEndProperty + ".id", addAlias, inclusive ? ">" : ">=", REVISION_PARAMETER
|
|
||||||
);
|
|
||||||
subParm.addWhere( revisionEndProperty, addAlias, "is", "null", false );
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
private RevisionType getRevisionType(AuditEntitiesConfiguration auditEntitiesConfiguration, Object data) {
|
|
||||||
return (RevisionType) ( (Map<String, Object>) data ).get( auditEntitiesConfiguration.getRevisionTypePropName() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
private void updateLastRevision(
|
|
||||||
Session session,
|
|
||||||
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
|
||||||
List<Object> l,
|
|
||||||
Object id,
|
|
||||||
String auditedEntityName,
|
|
||||||
Object revision) {
|
|
||||||
// There should be one entry
|
|
||||||
if ( l.size() == 1 ) {
|
|
||||||
// Setting the end revision to be the current rev
|
|
||||||
Object previousData = l.get( 0 );
|
|
||||||
String revisionEndFieldName = auditEntitiesConfiguration.getRevisionEndFieldName();
|
|
||||||
( (Map<String, Object>) previousData ).put( revisionEndFieldName, revision );
|
|
||||||
|
|
||||||
if ( auditEntitiesConfiguration.isRevisionEndTimestampEnabled() ) {
|
|
||||||
// Determine the value of the revision property annotated with @RevisionTimestamp
|
|
||||||
String revEndTimestampFieldName = auditEntitiesConfiguration.getRevisionEndTimestampFieldName();
|
|
||||||
Object revEndTimestampObj = this.revisionTimestampGetter.get( revision );
|
|
||||||
Date revisionEndTimestamp = convertRevEndTimestampToDate( revEndTimestampObj );
|
|
||||||
|
|
||||||
// Setting the end revision timestamp
|
|
||||||
( (Map<String, Object>) previousData ).put( revEndTimestampFieldName, revisionEndTimestamp );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Saving the previous version
|
|
||||||
session.save( auditedEntityName, previousData );
|
|
||||||
sessionCacheCleaner.scheduleAuditDataRemoval( session, previousData );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new RuntimeException( "Cannot find previous revision for entity " + auditedEntityName + " and id " + id );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAdditionalColumns(Element anyMapping, Element revMapping, AuditEntitiesConfiguration auditEntitiesConfiguration) {
|
|
||||||
// Add the end-revision field, if the appropriate strategy is used.
|
|
||||||
|
|
||||||
Element endRevMapping = (Element) revMapping.clone();
|
|
||||||
|
|
||||||
endRevMapping.setName( "many-to-one" );
|
|
||||||
endRevMapping.addAttribute( "name", auditEntitiesConfiguration.getRevisionEndFieldName() );
|
|
||||||
MetadataTools.addOrModifyColumn( endRevMapping, auditEntitiesConfiguration.getRevisionEndFieldName() );
|
|
||||||
|
|
||||||
anyMapping.add( endRevMapping );
|
|
||||||
|
|
||||||
if ( auditEntitiesConfiguration.isRevisionEndTimestampEnabled() ) {
|
|
||||||
// add a column for the timestamp of the end revision
|
|
||||||
final String revisionInfoTimestampSqlType = TimestampType.INSTANCE.getName();
|
|
||||||
final Element timestampProperty = MetadataTools.addProperty(
|
|
||||||
anyMapping,
|
|
||||||
auditEntitiesConfiguration.getRevisionEndTimestampFieldName(),
|
|
||||||
revisionInfoTimestampSqlType,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
MetadataTools.addColumn(
|
|
||||||
timestampProperty,
|
|
||||||
auditEntitiesConfiguration.getRevisionEndTimestampFieldName(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(Class<?> revisionInfoClass, PropertyData revisionInfoTimestampData, ServiceRegistry serviceRegistry) {
|
|
||||||
// further initialization required
|
|
||||||
final Getter revisionTimestampGetter = ReflectionTools.getGetter(
|
|
||||||
revisionInfoClass,
|
|
||||||
revisionInfoTimestampData,
|
|
||||||
serviceRegistry
|
|
||||||
);
|
|
||||||
setRevisionTimestampGetter( revisionTimestampGetter );
|
|
||||||
}
|
|
||||||
|
|
||||||
private Date convertRevEndTimestampToDate(Object revEndTimestampObj) {
|
|
||||||
// convert to a java.util.Date
|
|
||||||
if ( revEndTimestampObj instanceof Date ) {
|
|
||||||
return (Date) revEndTimestampObj;
|
|
||||||
}
|
|
||||||
return new Date( (Long) revEndTimestampObj );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.strategy.internal;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
||||||
|
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
||||||
|
import org.hibernate.envers.internal.synchronization.SessionCacheCleaner;
|
||||||
|
import org.hibernate.envers.internal.tools.query.Parameters;
|
||||||
|
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
||||||
|
import org.hibernate.envers.strategy.AuditStrategy;
|
||||||
|
|
||||||
|
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS_DEF_AUD_STR;
|
||||||
|
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of the {@link org.hibernate.envers.strategy.spi.AuditStrategy} contract.
|
||||||
|
* <p>
|
||||||
|
* This strategy handles the persistence of audit entity data in a very simplistic way.
|
||||||
|
*
|
||||||
|
* @author Adam Warski
|
||||||
|
* @author Stephanie Pau
|
||||||
|
* @author Chris Cranford
|
||||||
|
*/
|
||||||
|
public class DefaultAuditStrategy implements AuditStrategy {
|
||||||
|
private final SessionCacheCleaner sessionCacheCleaner;
|
||||||
|
|
||||||
|
public DefaultAuditStrategy() {
|
||||||
|
sessionCacheCleaner = new SessionCacheCleaner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(
|
||||||
|
Session session,
|
||||||
|
String entityName,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
||||||
|
Serializable id,
|
||||||
|
Object data,
|
||||||
|
Object revision) {
|
||||||
|
session.save( auditEntitiesConfiguration.getAuditEntityName( entityName ), data );
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performCollectionChange(
|
||||||
|
Session session,
|
||||||
|
String entityName,
|
||||||
|
String propertyName,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
||||||
|
PersistentCollectionChangeData persistentCollectionChangeData,
|
||||||
|
Object revision) {
|
||||||
|
session.save( persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData() );
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval( session, persistentCollectionChangeData.getData() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* For this implementation, a subquery is used
|
||||||
|
* <p>
|
||||||
|
* {@code e.revision = (SELECT max(...) ...}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addEntityAtRevisionRestriction(
|
||||||
|
GlobalConfiguration globalCfg,
|
||||||
|
QueryBuilder rootQueryBuilder,
|
||||||
|
Parameters parameters,
|
||||||
|
String revisionProperty,
|
||||||
|
String revisionEndProperty,
|
||||||
|
boolean addAlias,
|
||||||
|
MiddleIdData idData,
|
||||||
|
String revisionPropertyPath,
|
||||||
|
String originalIdPropertyName,
|
||||||
|
String alias1,
|
||||||
|
String alias2,
|
||||||
|
boolean inclusive) {
|
||||||
|
// create a subquery builder
|
||||||
|
// SELECT max(e.revision) FROM versionsReferencedEntity e2
|
||||||
|
QueryBuilder maxERevQb = rootQueryBuilder.newSubQueryBuilder( idData.getAuditEntityName(), alias2 );
|
||||||
|
maxERevQb.addProjection( "max", alias2, revisionPropertyPath, false );
|
||||||
|
// WHERE
|
||||||
|
Parameters maxERevQbParameters = maxERevQb.getRootParameters();
|
||||||
|
// e2.revision <= :revision
|
||||||
|
maxERevQbParameters.addWhereWithNamedParam( revisionPropertyPath, inclusive ? "<=" : "<", REVISION_PARAMETER );
|
||||||
|
// e2.id_ref_ed = e.id_ref_ed
|
||||||
|
idData.getOriginalMapper().addIdsEqualToQuery(
|
||||||
|
maxERevQbParameters,
|
||||||
|
alias1 + "." + originalIdPropertyName, alias2 + "." + originalIdPropertyName
|
||||||
|
);
|
||||||
|
|
||||||
|
// add subquery to rootParameters
|
||||||
|
String subqueryOperator = globalCfg.getCorrelatedSubqueryOperator();
|
||||||
|
parameters.addWhere( revisionProperty, addAlias, subqueryOperator, maxERevQb );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* For this implmeentation, the a subquery is used
|
||||||
|
* <p>
|
||||||
|
* {@code e.revision = (SELECT max(...) ...}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addAssociationAtRevisionRestriction(
|
||||||
|
QueryBuilder rootQueryBuilder,
|
||||||
|
Parameters parameters,
|
||||||
|
String revisionProperty,
|
||||||
|
String revisionEndProperty,
|
||||||
|
boolean addAlias,
|
||||||
|
MiddleIdData referencingIdData,
|
||||||
|
String versionsMiddleEntityName,
|
||||||
|
String eeOriginalIdPropertyPath,
|
||||||
|
String revisionPropertyPath,
|
||||||
|
String originalIdPropertyName,
|
||||||
|
String alias1,
|
||||||
|
boolean inclusive,
|
||||||
|
MiddleComponentData... componentDatas) {
|
||||||
|
// SELECT max(ee2.revision) FROM middleEntity ee2
|
||||||
|
QueryBuilder maxEeRevQb = rootQueryBuilder.newSubQueryBuilder(
|
||||||
|
versionsMiddleEntityName,
|
||||||
|
MIDDLE_ENTITY_ALIAS_DEF_AUD_STR
|
||||||
|
);
|
||||||
|
maxEeRevQb.addProjection( "max", MIDDLE_ENTITY_ALIAS_DEF_AUD_STR, revisionPropertyPath, false );
|
||||||
|
// WHERE
|
||||||
|
Parameters maxEeRevQbParameters = maxEeRevQb.getRootParameters();
|
||||||
|
// ee2.revision <= :revision
|
||||||
|
maxEeRevQbParameters.addWhereWithNamedParam( revisionPropertyPath, inclusive ? "<=" : "<", REVISION_PARAMETER );
|
||||||
|
// ee2.originalId.* = ee.originalId.*
|
||||||
|
String ee2OriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS_DEF_AUD_STR + "." + originalIdPropertyName;
|
||||||
|
referencingIdData.getPrefixedMapper().addIdsEqualToQuery(
|
||||||
|
maxEeRevQbParameters,
|
||||||
|
eeOriginalIdPropertyPath,
|
||||||
|
ee2OriginalIdPropertyPath
|
||||||
|
);
|
||||||
|
for ( MiddleComponentData componentData : componentDatas ) {
|
||||||
|
componentData.getComponentMapper().addMiddleEqualToQuery(
|
||||||
|
maxEeRevQbParameters,
|
||||||
|
eeOriginalIdPropertyPath,
|
||||||
|
alias1,
|
||||||
|
ee2OriginalIdPropertyPath,
|
||||||
|
MIDDLE_ENTITY_ALIAS_DEF_AUD_STR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add subquery to rootParameters
|
||||||
|
parameters.addWhere( revisionProperty, addAlias, "=", maxEeRevQb );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,498 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.strategy.internal;
|
||||||
|
|
||||||
|
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS;
|
||||||
|
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.dom4j.Element;
|
||||||
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
|
||||||
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.envers.RevisionType;
|
||||||
|
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
||||||
|
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
||||||
|
import org.hibernate.envers.configuration.internal.metadata.MetadataTools;
|
||||||
|
import org.hibernate.envers.internal.entities.PropertyData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
||||||
|
import org.hibernate.envers.internal.synchronization.SessionCacheCleaner;
|
||||||
|
import org.hibernate.envers.internal.tools.ReflectionTools;
|
||||||
|
import org.hibernate.envers.internal.tools.query.Parameters;
|
||||||
|
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
||||||
|
import org.hibernate.envers.strategy.AuditStrategy;
|
||||||
|
import org.hibernate.envers.strategy.spi.MappingContext;
|
||||||
|
import org.hibernate.event.spi.EventSource;
|
||||||
|
import org.hibernate.jdbc.ReturningWork;
|
||||||
|
import org.hibernate.persister.entity.Queryable;
|
||||||
|
import org.hibernate.persister.entity.UnionSubclassEntityPersister;
|
||||||
|
import org.hibernate.property.access.spi.Getter;
|
||||||
|
import org.hibernate.service.ServiceRegistry;
|
||||||
|
import org.hibernate.sql.Update;
|
||||||
|
import org.hibernate.type.CollectionType;
|
||||||
|
import org.hibernate.type.ComponentType;
|
||||||
|
import org.hibernate.type.MapType;
|
||||||
|
import org.hibernate.type.MaterializedClobType;
|
||||||
|
import org.hibernate.type.MaterializedNClobType;
|
||||||
|
import org.hibernate.type.TimestampType;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An audit strategy implementation that persists and fetches audit information using a validity
|
||||||
|
* algorithm, based on the start-revision and end-revision of a row in the audit table schema.
|
||||||
|
* <p>
|
||||||
|
* This algorithm works as follows:
|
||||||
|
* <ul>
|
||||||
|
* <li>For a new row, only the start-revision column is set in the row.</li>
|
||||||
|
* <li>Concurrently, the end-revision of the prior audit row is set to the current revision</li>
|
||||||
|
* <li>Queries using a between start and end revision predicate rather than using subqueries.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* This has a few important consequences which must be considered:
|
||||||
|
* <ul>
|
||||||
|
* <li>Persisting audit information is sightly slower due to an extra update required</li>
|
||||||
|
* <li>Retreiving audit information is considerably faster</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Stephanie Pau
|
||||||
|
* @author Adam Warski (adam at warski dot org)
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
* @author Chris Cranford
|
||||||
|
*/
|
||||||
|
public class ValidityAuditStrategy implements AuditStrategy {
|
||||||
|
/**
|
||||||
|
* getter for the revision entity field annotated with @RevisionTimestamp
|
||||||
|
*/
|
||||||
|
private Getter revisionTimestampGetter;
|
||||||
|
|
||||||
|
private final SessionCacheCleaner sessionCacheCleaner;
|
||||||
|
|
||||||
|
public ValidityAuditStrategy() {
|
||||||
|
sessionCacheCleaner = new SessionCacheCleaner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInitialize(
|
||||||
|
Class<?> revisionInfoClass,
|
||||||
|
PropertyData revisionInfoTimestampData,
|
||||||
|
ServiceRegistry serviceRegistry) {
|
||||||
|
// further initialization required
|
||||||
|
final Getter revisionTimestampGetter = ReflectionTools.getGetter(
|
||||||
|
revisionInfoClass,
|
||||||
|
revisionInfoTimestampData,
|
||||||
|
serviceRegistry
|
||||||
|
);
|
||||||
|
setRevisionTimestampGetter( revisionTimestampGetter );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAdditionalColumns(MappingContext mappingContext) {
|
||||||
|
// Add the end-revision field, if the appropriate strategy is used.
|
||||||
|
|
||||||
|
Element endRevMapping = (Element) mappingContext.getRevisionEntityMapping().clone();
|
||||||
|
|
||||||
|
endRevMapping.setName( "many-to-one" );
|
||||||
|
endRevMapping.addAttribute( "name", mappingContext.getAuditEntityConfiguration().getRevisionEndFieldName() );
|
||||||
|
MetadataTools.addOrModifyColumn( endRevMapping, mappingContext.getAuditEntityConfiguration().getRevisionEndFieldName() );
|
||||||
|
|
||||||
|
mappingContext.getAuditEntityMapping().add( endRevMapping );
|
||||||
|
|
||||||
|
if ( mappingContext.getAuditEntityConfiguration().isRevisionEndTimestampEnabled() ) {
|
||||||
|
// add a column for the timestamp of the end revision
|
||||||
|
final String revisionInfoTimestampSqlType = TimestampType.INSTANCE.getName();
|
||||||
|
final Element timestampProperty = MetadataTools.addProperty(
|
||||||
|
mappingContext.getAuditEntityMapping(),
|
||||||
|
mappingContext.getAuditEntityConfiguration().getRevisionEndTimestampFieldName(),
|
||||||
|
revisionInfoTimestampSqlType,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
MetadataTools.addColumn(
|
||||||
|
timestampProperty,
|
||||||
|
mappingContext.getAuditEntityConfiguration().getRevisionEndTimestampFieldName(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(
|
||||||
|
final Session session,
|
||||||
|
final String entityName,
|
||||||
|
final AuditEntitiesConfiguration audEntitiesCfg,
|
||||||
|
final Serializable id,
|
||||||
|
final Object data,
|
||||||
|
final Object revision) {
|
||||||
|
final String auditedEntityName = audEntitiesCfg.getAuditEntityName( entityName );
|
||||||
|
final String revisionInfoEntityName = audEntitiesCfg.getRevisionInfoEntityName();
|
||||||
|
|
||||||
|
// Save the audit data
|
||||||
|
session.save( auditedEntityName, data );
|
||||||
|
|
||||||
|
// Update the end date of the previous row.
|
||||||
|
//
|
||||||
|
// When application reuses identifiers of previously removed entities:
|
||||||
|
// The UPDATE statement will no-op if an entity with a given identifier has been
|
||||||
|
// inserted for the first time. But in case a deleted primary key value was
|
||||||
|
// reused, this guarantees correct strategy behavior: exactly one row with
|
||||||
|
// null end date exists for each identifier.
|
||||||
|
final boolean reuseEntityIdentifier = audEntitiesCfg.getEnversService().getGlobalConfiguration().isAllowIdentifierReuse();
|
||||||
|
if ( reuseEntityIdentifier || getRevisionType( audEntitiesCfg, data ) != RevisionType.ADD ) {
|
||||||
|
// Register transaction completion process to guarantee execution of UPDATE statement after INSERT.
|
||||||
|
( (EventSource) session ).getActionQueue().registerProcess( new BeforeTransactionCompletionProcess() {
|
||||||
|
@Override
|
||||||
|
public void doBeforeTransactionCompletion(final SessionImplementor sessionImplementor) {
|
||||||
|
final Queryable productionEntityQueryable = getQueryable( entityName, sessionImplementor );
|
||||||
|
final Queryable rootProductionEntityQueryable = getQueryable(
|
||||||
|
productionEntityQueryable.getRootEntityName(), sessionImplementor
|
||||||
|
);
|
||||||
|
final Queryable auditedEntityQueryable = getQueryable( auditedEntityName, sessionImplementor );
|
||||||
|
final Queryable rootAuditedEntityQueryable = getQueryable(
|
||||||
|
auditedEntityQueryable.getRootEntityName(), sessionImplementor
|
||||||
|
);
|
||||||
|
|
||||||
|
final String updateTableName;
|
||||||
|
if ( UnionSubclassEntityPersister.class.isInstance( rootProductionEntityQueryable ) ) {
|
||||||
|
// this is the condition causing all the problems in terms of the generated SQL UPDATE
|
||||||
|
// the problem being that we currently try to update the in-line view made up of the union query
|
||||||
|
//
|
||||||
|
// this is extremely hacky means to get the root table name for the union subclass style entities.
|
||||||
|
// hacky because it relies on internal behavior of UnionSubclassEntityPersister
|
||||||
|
// !!!!!! NOTICE - using subclass persister, not root !!!!!!
|
||||||
|
updateTableName = auditedEntityQueryable.getSubclassTableName( 0 );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateTableName = rootAuditedEntityQueryable.getTableName();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Type revisionInfoIdType = sessionImplementor.getFactory().getMetamodel().entityPersister( revisionInfoEntityName ).getIdentifierType();
|
||||||
|
final String revEndColumnName = rootAuditedEntityQueryable.toColumns( audEntitiesCfg.getRevisionEndFieldName() )[0];
|
||||||
|
|
||||||
|
final boolean isRevisionEndTimestampEnabled = audEntitiesCfg.isRevisionEndTimestampEnabled();
|
||||||
|
|
||||||
|
// update audit_ent set REVEND = ? [, REVEND_TSTMP = ?] where (prod_ent_id) = ? and REV <> ? and REVEND is null
|
||||||
|
final Update update = new Update( sessionImplementor.getFactory().getJdbcServices().getDialect() ).setTableName( updateTableName );
|
||||||
|
// set REVEND = ?
|
||||||
|
update.addColumn( revEndColumnName );
|
||||||
|
// set [, REVEND_TSTMP = ?]
|
||||||
|
if ( isRevisionEndTimestampEnabled ) {
|
||||||
|
update.addColumn(
|
||||||
|
rootAuditedEntityQueryable.toColumns( audEntitiesCfg.getRevisionEndTimestampFieldName() )[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// where (prod_ent_id) = ?
|
||||||
|
update.addPrimaryKeyColumns( rootProductionEntityQueryable.getIdentifierColumnNames() );
|
||||||
|
// where REV <> ?
|
||||||
|
update.addWhereColumn(
|
||||||
|
rootAuditedEntityQueryable.toColumns( audEntitiesCfg.getRevisionNumberPath() )[0], "<> ?"
|
||||||
|
);
|
||||||
|
// where REVEND is null
|
||||||
|
update.addWhereColumn( revEndColumnName, " is null" );
|
||||||
|
|
||||||
|
// Now lets execute the sql...
|
||||||
|
final String updateSql = update.toStatementString();
|
||||||
|
|
||||||
|
int rowCount = sessionImplementor.doReturningWork(
|
||||||
|
new ReturningWork<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer execute(Connection connection) throws SQLException {
|
||||||
|
PreparedStatement preparedStatement = sessionImplementor
|
||||||
|
.getJdbcCoordinator().getStatementPreparer().prepareStatement( updateSql );
|
||||||
|
|
||||||
|
try {
|
||||||
|
int index = 1;
|
||||||
|
|
||||||
|
// set REVEND = ?
|
||||||
|
final Number revisionNumber = audEntitiesCfg.getEnversService()
|
||||||
|
.getRevisionInfoNumberReader()
|
||||||
|
.getRevisionNumber( revision );
|
||||||
|
|
||||||
|
revisionInfoIdType.nullSafeSet(
|
||||||
|
preparedStatement, revisionNumber, index, sessionImplementor
|
||||||
|
);
|
||||||
|
index += revisionInfoIdType.getColumnSpan( sessionImplementor.getFactory() );
|
||||||
|
|
||||||
|
// set [, REVEND_TSTMP = ?]
|
||||||
|
if ( isRevisionEndTimestampEnabled ) {
|
||||||
|
final Object revEndTimestampObj = revisionTimestampGetter.get( revision );
|
||||||
|
final Date revisionEndTimestamp = convertRevEndTimestampToDate( revEndTimestampObj );
|
||||||
|
final Type revEndTsType = rootAuditedEntityQueryable.getPropertyType(
|
||||||
|
audEntitiesCfg.getRevisionEndTimestampFieldName()
|
||||||
|
);
|
||||||
|
revEndTsType.nullSafeSet(
|
||||||
|
preparedStatement, revisionEndTimestamp, index, sessionImplementor
|
||||||
|
);
|
||||||
|
index += revEndTsType.getColumnSpan( sessionImplementor.getFactory() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// where (prod_ent_id) = ?
|
||||||
|
final Type idType = rootProductionEntityQueryable.getIdentifierType();
|
||||||
|
idType.nullSafeSet( preparedStatement, id, index, sessionImplementor );
|
||||||
|
index += idType.getColumnSpan( sessionImplementor.getFactory() );
|
||||||
|
|
||||||
|
// where REV <> ?
|
||||||
|
final Type revType = rootAuditedEntityQueryable.getPropertyType(
|
||||||
|
audEntitiesCfg.getRevisionNumberPath()
|
||||||
|
);
|
||||||
|
revType.nullSafeSet( preparedStatement, revisionNumber, index, sessionImplementor );
|
||||||
|
|
||||||
|
// where REVEND is null
|
||||||
|
// nothing to bind....
|
||||||
|
|
||||||
|
return sessionImplementor
|
||||||
|
.getJdbcCoordinator().getResultSetReturn().executeUpdate( preparedStatement );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
sessionImplementor.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release(
|
||||||
|
preparedStatement
|
||||||
|
);
|
||||||
|
sessionImplementor.getJdbcCoordinator().afterStatementExecution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( rowCount != 1 && ( !reuseEntityIdentifier || ( getRevisionType( audEntitiesCfg, data ) != RevisionType.ADD ) ) ) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Cannot update previous revision for entity " + auditedEntityName + " and id " + id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
public void performCollectionChange(
|
||||||
|
Session session,
|
||||||
|
String entityName,
|
||||||
|
String propertyName,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
||||||
|
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
|
||||||
|
final QueryBuilder qb = new QueryBuilder(
|
||||||
|
persistentCollectionChangeData.getEntityName(),
|
||||||
|
MIDDLE_ENTITY_ALIAS,
|
||||||
|
( (SharedSessionContractImplementor) session ).getFactory()
|
||||||
|
);
|
||||||
|
|
||||||
|
final String originalIdPropName = auditEntitiesConfiguration.getOriginalIdPropName();
|
||||||
|
final Map<String, Object> originalId = (Map<String, Object>) persistentCollectionChangeData.getData().get(
|
||||||
|
originalIdPropName
|
||||||
|
);
|
||||||
|
final String revisionFieldName = auditEntitiesConfiguration.getRevisionFieldName();
|
||||||
|
final String revisionTypePropName = auditEntitiesConfiguration.getRevisionTypePropName();
|
||||||
|
final String ordinalPropName = auditEntitiesConfiguration.getEmbeddableSetOrdinalPropertyName();
|
||||||
|
|
||||||
|
// Adding a parameter for each id component, except the rev number and type.
|
||||||
|
for ( Map.Entry<String, Object> originalIdEntry : originalId.entrySet() ) {
|
||||||
|
if ( !revisionFieldName.equals( originalIdEntry.getKey() )
|
||||||
|
&& !revisionTypePropName.equals( originalIdEntry.getKey() )
|
||||||
|
&& !ordinalPropName.equals( originalIdEntry.getKey() ) ) {
|
||||||
|
qb.getRootParameters().addWhereWithParam(
|
||||||
|
originalIdPropName + "." + originalIdEntry.getKey(),
|
||||||
|
true, "=", originalIdEntry.getValue()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isNonIdentifierWhereConditionsRequired( entityName, propertyName, (SessionImplementor) session ) ) {
|
||||||
|
addNonIdentifierWhereConditions( qb, persistentCollectionChangeData.getData(), originalIdPropName );
|
||||||
|
}
|
||||||
|
|
||||||
|
addEndRevisionNullRestriction( auditEntitiesConfiguration, qb.getRootParameters() );
|
||||||
|
|
||||||
|
final List<Object> l = qb.toQuery( session ).setLockOptions( LockOptions.UPGRADE ).list();
|
||||||
|
|
||||||
|
// Update the last revision if one exists.
|
||||||
|
// HHH-5967: with collections, the same element can be added and removed multiple times. So even if it's an
|
||||||
|
// ADD, we may need to update the last revision.
|
||||||
|
if ( l.size() > 0 ) {
|
||||||
|
updateLastRevision(
|
||||||
|
session, auditEntitiesConfiguration, l, originalId, persistentCollectionChangeData.getEntityName(), revision
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the audit data
|
||||||
|
session.save( persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData() );
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval( session, persistentCollectionChangeData.getData() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* For this implmenetation, the revision-end column is used
|
||||||
|
* <p>
|
||||||
|
* {@code e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addEntityAtRevisionRestriction(
|
||||||
|
GlobalConfiguration globalCfg,
|
||||||
|
QueryBuilder rootQueryBuilder,
|
||||||
|
Parameters parameters,
|
||||||
|
String revisionProperty,
|
||||||
|
String revisionEndProperty,
|
||||||
|
boolean addAlias,
|
||||||
|
MiddleIdData idData,
|
||||||
|
String revisionPropertyPath,
|
||||||
|
String originalIdPropertyName,
|
||||||
|
String alias1,
|
||||||
|
String alias2,
|
||||||
|
boolean inclusive) {
|
||||||
|
addRevisionRestriction( parameters, revisionProperty, revisionEndProperty, addAlias, inclusive );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* For this implmenetation, the revision-end column is used
|
||||||
|
* <p>
|
||||||
|
* {@code e.revision <= :revision and (e.endRevision > :revision or e.endRevision is null}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addAssociationAtRevisionRestriction(
|
||||||
|
QueryBuilder rootQueryBuilder,
|
||||||
|
Parameters parameters,
|
||||||
|
String revisionProperty,
|
||||||
|
String revisionEndProperty,
|
||||||
|
boolean addAlias,
|
||||||
|
MiddleIdData referencingIdData,
|
||||||
|
String versionsMiddleEntityName,
|
||||||
|
String eeOriginalIdPropertyPath,
|
||||||
|
String revisionPropertyPath,
|
||||||
|
String originalIdPropertyName,
|
||||||
|
String alias1,
|
||||||
|
boolean inclusive,
|
||||||
|
MiddleComponentData... componentDatas) {
|
||||||
|
addRevisionRestriction( parameters, revisionProperty, revisionEndProperty, addAlias, inclusive );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 5.4 with no replacement.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void setRevisionTimestampGetter(Getter revisionTimestampGetter) {
|
||||||
|
this.revisionTimestampGetter = revisionTimestampGetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRevisionRestriction(
|
||||||
|
Parameters rootParameters, String revisionProperty, String revisionEndProperty,
|
||||||
|
boolean addAlias, boolean inclusive) {
|
||||||
|
// e.revision <= _revision and (e.endRevision > _revision or e.endRevision is null)
|
||||||
|
Parameters subParm = rootParameters.addSubParameters( "or" );
|
||||||
|
rootParameters.addWhereWithNamedParam( revisionProperty, addAlias, inclusive ? "<=" : "<", REVISION_PARAMETER );
|
||||||
|
subParm.addWhereWithNamedParam(
|
||||||
|
revisionEndProperty + ".id", addAlias, inclusive ? ">" : ">=", REVISION_PARAMETER
|
||||||
|
);
|
||||||
|
subParm.addWhere( revisionEndProperty, addAlias, "is", "null", false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
private RevisionType getRevisionType(AuditEntitiesConfiguration auditEntitiesConfiguration, Object data) {
|
||||||
|
return (RevisionType) ( (Map<String, Object>) data ).get( auditEntitiesConfiguration.getRevisionTypePropName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
private void updateLastRevision(
|
||||||
|
Session session,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
||||||
|
List<Object> l,
|
||||||
|
Object id,
|
||||||
|
String auditedEntityName,
|
||||||
|
Object revision) {
|
||||||
|
// There should be one entry
|
||||||
|
if ( l.size() == 1 ) {
|
||||||
|
// Setting the end revision to be the current rev
|
||||||
|
Object previousData = l.get( 0 );
|
||||||
|
String revisionEndFieldName = auditEntitiesConfiguration.getRevisionEndFieldName();
|
||||||
|
( (Map<String, Object>) previousData ).put( revisionEndFieldName, revision );
|
||||||
|
|
||||||
|
if ( auditEntitiesConfiguration.isRevisionEndTimestampEnabled() ) {
|
||||||
|
// Determine the value of the revision property annotated with @RevisionTimestamp
|
||||||
|
String revEndTimestampFieldName = auditEntitiesConfiguration.getRevisionEndTimestampFieldName();
|
||||||
|
Object revEndTimestampObj = this.revisionTimestampGetter.get( revision );
|
||||||
|
Date revisionEndTimestamp = convertRevEndTimestampToDate( revEndTimestampObj );
|
||||||
|
|
||||||
|
// Setting the end revision timestamp
|
||||||
|
( (Map<String, Object>) previousData ).put( revEndTimestampFieldName, revisionEndTimestamp );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saving the previous version
|
||||||
|
session.save( auditedEntityName, previousData );
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval( session, previousData );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new RuntimeException( "Cannot find previous revision for entity " + auditedEntityName + " and id " + id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date convertRevEndTimestampToDate(Object revEndTimestampObj) {
|
||||||
|
// convert to a java.util.Date
|
||||||
|
if ( revEndTimestampObj instanceof Date ) {
|
||||||
|
return (Date) revEndTimestampObj;
|
||||||
|
}
|
||||||
|
return new Date( (Long) revEndTimestampObj );
|
||||||
|
}
|
||||||
|
|
||||||
|
private Queryable getQueryable(String entityName, SessionImplementor sessionImplementor) {
|
||||||
|
return (Queryable) sessionImplementor.getFactory().getMetamodel().entityPersister( entityName );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEndRevisionNullRestriction(AuditEntitiesConfiguration auditEntitiesConfiguration, Parameters rootParameters) {
|
||||||
|
rootParameters.addWhere( auditEntitiesConfiguration.getRevisionEndFieldName(), true, "is", "null", false );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNonIdentifierWhereConditions(QueryBuilder qb, Map<String, Object> data, String originalIdPropertyName) {
|
||||||
|
final Parameters parameters = qb.getRootParameters();
|
||||||
|
for ( Map.Entry<String, Object> entry : data.entrySet() ) {
|
||||||
|
if ( !originalIdPropertyName.equals( entry.getKey() ) ) {
|
||||||
|
if ( entry.getValue() != null ) {
|
||||||
|
parameters.addWhereWithParam( entry.getKey(), true, "=", entry.getValue() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parameters.addNullRestriction( entry.getKey(), true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNonIdentifierWhereConditionsRequired(String entityName, String propertyName, SessionImplementor session) {
|
||||||
|
final Type propertyType = session.getSessionFactory().getMetamodel().entityPersister( entityName ).getPropertyType( propertyName );
|
||||||
|
if ( propertyType.isCollectionType() ) {
|
||||||
|
final CollectionType collectionType = (CollectionType) propertyType;
|
||||||
|
final Type collectionElementType = collectionType.getElementType( session.getSessionFactory() );
|
||||||
|
if ( collectionElementType instanceof ComponentType ) {
|
||||||
|
// required for Embeddables
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ( collectionElementType instanceof MaterializedClobType || collectionElementType instanceof MaterializedNClobType ) {
|
||||||
|
// for Map<> using @Lob annotations
|
||||||
|
return collectionType instanceof MapType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.strategy.spi;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
||||||
|
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
|
||||||
|
import org.hibernate.envers.internal.entities.PropertyData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.PersistentCollectionChangeData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
|
||||||
|
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
||||||
|
import org.hibernate.envers.internal.tools.query.Parameters;
|
||||||
|
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
||||||
|
import org.hibernate.envers.strategy.DefaultAuditStrategy;
|
||||||
|
import org.hibernate.envers.strategy.ValidityAuditStrategy;
|
||||||
|
import org.hibernate.service.ServiceRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A strategy abstraction for how to audit entity changes.
|
||||||
|
*
|
||||||
|
* @since 5.4
|
||||||
|
*
|
||||||
|
* @author Chris Cranford
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
public interface AuditStrategy {
|
||||||
|
|
||||||
|
// todo (6.0) - cleanup this spi
|
||||||
|
// 1. Introduce parameter objects to reduce parameter bloat on strategy methods
|
||||||
|
// 2. Eliminate the use of internal objects on the SPI interface.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add additional columns to the audit mappings.
|
||||||
|
*
|
||||||
|
* @param mappingContext The mapping context.
|
||||||
|
*/
|
||||||
|
default void addAdditionalColumns(MappingContext mappingContext) {
|
||||||
|
// Made default for backward compatibility.
|
||||||
|
// Remove default method in 6.0.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs post initialization of the audit strategy implementation.
|
||||||
|
*
|
||||||
|
* @param revisionInfoClass The revision entity class.
|
||||||
|
* @param timestampData The timestamp property data on the revision entity class.
|
||||||
|
* @param serviceRegistry The service registry.
|
||||||
|
*/
|
||||||
|
default void postInitialize(
|
||||||
|
Class<?> revisionInfoClass,
|
||||||
|
PropertyData timestampData,
|
||||||
|
ServiceRegistry serviceRegistry) {
|
||||||
|
// Made default for backward compatibility.
|
||||||
|
// Remove default method in 6.0.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the persistence of audited data for regular entities.
|
||||||
|
*
|
||||||
|
* @param session Session, which can be used to persist the data.
|
||||||
|
* @param entityName Name of the entity, in which the audited change happens
|
||||||
|
* @param auditEntitiesConfiguration The audit entity configuration.
|
||||||
|
* @param id Id of the entity.
|
||||||
|
* @param data Audit data to persist.
|
||||||
|
* @param revision Current revision data.
|
||||||
|
*/
|
||||||
|
void perform(
|
||||||
|
Session session,
|
||||||
|
String entityName,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
||||||
|
Serializable id,
|
||||||
|
Object data,
|
||||||
|
Object revision);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the persistence of audited data for collection ("middle") entities.
|
||||||
|
*
|
||||||
|
* @param session Session, which can be used to persist the data.
|
||||||
|
* @param entityName Name of the entity, in which the audited change happens.
|
||||||
|
* @param propertyName The name of the property holding the persistent collection
|
||||||
|
* @param auditEntitiesConfiguration audit entity configuration
|
||||||
|
* @param persistentCollectionChangeData Collection change data to be persisted.
|
||||||
|
* @param revision Current revision data
|
||||||
|
*/
|
||||||
|
void performCollectionChange(
|
||||||
|
Session session,
|
||||||
|
String entityName,
|
||||||
|
String propertyName,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration,
|
||||||
|
PersistentCollectionChangeData persistentCollectionChangeData,
|
||||||
|
Object revision);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a two-entity relation.
|
||||||
|
* This WHERE clause depends on the AuditStrategy.
|
||||||
|
*
|
||||||
|
* @param globalCfg the {@link GlobalConfiguration}
|
||||||
|
* @param rootQueryBuilder the {@link QueryBuilder} that will be updated
|
||||||
|
* @param parameters root parameters to which restrictions shall be added
|
||||||
|
* @param revisionProperty property of the revision column
|
||||||
|
* @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param addAlias {@code boolean} indicator if a left alias is needed
|
||||||
|
* @param idData id-information for the two-entity relation (only used for {@link DefaultAuditStrategy})
|
||||||
|
* @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param alias1 an alias used for subquery (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param alias2 an alias used for subquery (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param inclusive indicates whether revision number shall be treated as inclusive or exclusive
|
||||||
|
*/
|
||||||
|
void addEntityAtRevisionRestriction(
|
||||||
|
GlobalConfiguration globalCfg,
|
||||||
|
QueryBuilder rootQueryBuilder,
|
||||||
|
Parameters parameters,
|
||||||
|
String revisionProperty,
|
||||||
|
String revisionEndProperty,
|
||||||
|
boolean addAlias,
|
||||||
|
MiddleIdData idData,
|
||||||
|
String revisionPropertyPath,
|
||||||
|
String originalIdPropertyName,
|
||||||
|
String alias1,
|
||||||
|
String alias2,
|
||||||
|
boolean inclusive);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the rootQueryBuilder with an extra WHERE clause to restrict the revision for a middle-entity
|
||||||
|
* association. This WHERE clause depends on the AuditStrategy.
|
||||||
|
*
|
||||||
|
* @param rootQueryBuilder the {@link QueryBuilder} that will be updated
|
||||||
|
* @param parameters root parameters to which restrictions shall be added
|
||||||
|
* @param revisionProperty property of the revision column
|
||||||
|
* @param revisionEndProperty property of the revisionEnd column (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param addAlias {@code boolean} indicator if a left alias is needed
|
||||||
|
* @param referencingIdData id-information for the middle-entity association (only used for {@link DefaultAuditStrategy})
|
||||||
|
* @param versionsMiddleEntityName name of the middle-entity
|
||||||
|
* @param eeOriginalIdPropertyPath name of the id property (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param revisionPropertyPath path of the revision property (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param originalIdPropertyName name of the id property (only used for {@link ValidityAuditStrategy})
|
||||||
|
* @param alias1 an alias used for subqueries (only used for {@link DefaultAuditStrategy})
|
||||||
|
* @param inclusive indicates whether revision number shall be treated as inclusive or exclusive
|
||||||
|
* @param componentDatas information about the middle-entity relation
|
||||||
|
*/
|
||||||
|
void addAssociationAtRevisionRestriction(
|
||||||
|
QueryBuilder rootQueryBuilder,
|
||||||
|
Parameters parameters,
|
||||||
|
String revisionProperty,
|
||||||
|
String revisionEndProperty,
|
||||||
|
boolean addAlias,
|
||||||
|
MiddleIdData referencingIdData,
|
||||||
|
String versionsMiddleEntityName,
|
||||||
|
String eeOriginalIdPropertyPath,
|
||||||
|
String revisionPropertyPath,
|
||||||
|
String originalIdPropertyName,
|
||||||
|
String alias1,
|
||||||
|
boolean inclusive,
|
||||||
|
MiddleComponentData... componentDatas);
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.envers.strategy.spi;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
|
||||||
|
|
||||||
|
import org.dom4j.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes an audit mapping context.
|
||||||
|
*
|
||||||
|
* @author Chris Cranford
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
public class MappingContext {
|
||||||
|
private Element auditEntityMapping;
|
||||||
|
private Element revisionEntityMapping;
|
||||||
|
private AuditEntitiesConfiguration auditEntityConfiguration;
|
||||||
|
|
||||||
|
public MappingContext(
|
||||||
|
Element auditEntityMapping,
|
||||||
|
Element revisionEntityMapping,
|
||||||
|
AuditEntitiesConfiguration auditEntitiesConfiguration) {
|
||||||
|
this.auditEntityMapping = auditEntityMapping;
|
||||||
|
this.revisionEntityMapping = revisionEntityMapping;
|
||||||
|
this.auditEntityConfiguration = auditEntitiesConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Element getAuditEntityMapping() {
|
||||||
|
return auditEntityMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Element getRevisionEntityMapping() {
|
||||||
|
return revisionEntityMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuditEntitiesConfiguration getAuditEntityConfiguration() {
|
||||||
|
return auditEntityConfiguration;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue