Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 .
*/
package org.hibernate.envers.strategy.internal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.hibernate.FlushMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.model.BasicAttribute;
import org.hibernate.envers.boot.model.Column;
import org.hibernate.envers.boot.model.ManyToOneAttribute;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.configuration.internal.metadata.RevisionInfoHelper;
import org.hibernate.envers.exception.AuditException;
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.revisioninfo.RevisionInfoNumberReader;
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 org.hibernate.envers.strategy.spi.AuditStrategyContext;
import org.hibernate.envers.strategy.spi.MappingContext;
import org.hibernate.event.spi.EventSource;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.JoinedSubclassEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.UnionSubclassEntityPersister;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.sql.ComparisonRestriction;
import org.hibernate.sql.Update;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.MapType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
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;
/**
* 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.
*
* This algorithm works as follows:
*
*
For a new row, only the start-revision column is set in the row.
*
Concurrently, the end-revision of the prior audit row is set to the current revision
*
Queries using a between start and end revision predicate rather than using subqueries.
*
*
* This has a few important consequences which must be considered:
*
*
Persisting audit information is sightly slower due to an extra update required
*
Retreiving audit information is considerably faster
*
*
* @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(AuditStrategyContext context) {
setRevisionTimestampGetter( context.getRevisionInfoTimestampAccessor() );
}
@Override
public void addAdditionalColumns(MappingContext mappingContext) {
if ( !mappingContext.isRevisionEndTimestampOnly() ) {
// Add revision end field since mapping is not requesting only the timestamp.
final ManyToOneAttribute revEndMapping = new ManyToOneAttribute(
mappingContext.getConfiguration().getRevisionEndFieldName(),
mappingContext.getRevisionInfoPropertyType(),
true,
true,
false,
mappingContext.getRevisionInfoExplicitTypeName()
);
RevisionInfoHelper.addOrModifyColumn(
revEndMapping,
mappingContext.getConfiguration().getRevisionEndFieldName()
);
mappingContext.getEntityMapping().addAttribute( revEndMapping );
}
if ( mappingContext.getConfiguration().isRevisionEndTimestampEnabled() ) {
// add a column for the timestamp of the end revision
final String revisionInfoTimestampTypeName;
if ( mappingContext.getConfiguration().isRevisionEndTimestampNumeric() ) {
revisionInfoTimestampTypeName = StandardBasicTypes.LONG.getName();
}
else {
revisionInfoTimestampTypeName = StandardBasicTypes.TIMESTAMP.getName();
}
String revEndTimestampPropertyName = mappingContext.getConfiguration().getRevisionEndTimestampFieldName();
String revEndTimestampColumnName = revEndTimestampPropertyName;
if ( !mappingContext.getConfiguration().isRevisionEndTimestampUseLegacyPlacement() ) {
if ( mappingContext.isRevisionEndTimestampOnly() ) {
// properties across a joined inheritance model cannot have the same name.
// what is done here is we adjust just the property name so it is seen as unique in
// the mapping model but keep the column representation with the configured timestamp column name.
revEndTimestampPropertyName = mappingContext.getConfiguration().getRevisionEndTimestampFieldName()
+ "_"
+ mappingContext.getEntityMapping().getAuditTableData().getAuditTableName();
}
}
final BasicAttribute revEndTimestampMapping = new BasicAttribute(
revEndTimestampPropertyName,
revisionInfoTimestampTypeName,
true,
true,
false
);
revEndTimestampMapping.addColumn( new Column( revEndTimestampColumnName ) );
mappingContext.getEntityMapping().addAttribute( revEndTimestampMapping );
}
}
@Override
public void perform(
final Session session,
final String entityName,
final Configuration configuration,
final Object id,
final Object data,
final Object revision) {
final String auditedEntityName = configuration.getAuditEntityName( entityName );
// 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 = configuration.isAllowIdentifierReuse();
if ( reuseEntityIdentifier || getRevisionType( configuration, data ) != RevisionType.ADD ) {
// Register transaction completion process to guarantee execution of UPDATE statement after INSERT.
( (EventSource) session ).getActionQueue().registerProcess( sessionImplementor -> {
// Construct the update contexts
final List contexts = getUpdateContexts(
entityName,
auditedEntityName,
sessionImplementor,
configuration,
id,
revision
);
if ( contexts.isEmpty() ) {
throw new AuditException(
String.format(
Locale.ENGLISH,
"Failed to build update contexts for entity %s and id %s",
auditedEntityName,
id
)
);
}
for ( UpdateContext context : contexts ) {
final int rows = executeUpdate( sessionImplementor, context );
if ( rows != 1 ) {
final RevisionType revisionType = getRevisionType( configuration, data );
if ( !reuseEntityIdentifier || revisionType != RevisionType.ADD ) {
throw new AuditException(
String.format(
Locale.ENGLISH,
"Cannot update previous revision for entity %s and id %s (%s rows modified).",
auditedEntityName,
id,
rows
)
);
}
}
}
} );
}
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
}
@Override
@SuppressWarnings("unchecked")
public void performCollectionChange(
Session session,
String entityName,
String propertyName,
Configuration configuration,
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
final QueryBuilder qb = new QueryBuilder(
persistentCollectionChangeData.getEntityName(),
MIDDLE_ENTITY_ALIAS,
( (SharedSessionContractImplementor) session ).getFactory()
);
final String originalIdPropName = configuration.getOriginalIdPropertyName();
final Map originalId = (Map) persistentCollectionChangeData.getData().get(
originalIdPropName
);
final String revisionFieldName = configuration.getRevisionFieldName();
final String revisionTypePropName = configuration.getRevisionTypePropertyName();
final String ordinalPropName = configuration.getEmbeddableSetOrdinalPropertyName();
// Adding a parameter for each id component, except the rev number and type.
for ( Map.Entry 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( configuration, qb.getRootParameters() );
final List