org.hibernate.envers.internal.synchronization.AuditProcess Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-envers Show documentation
Show all versions of hibernate-envers Show documentation
Hibernate's entity version (audit/history) support
/*
* 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.internal.synchronization;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.revisioninfo.RevisionInfoGenerator;
import org.hibernate.envers.internal.synchronization.work.AuditWorkUnit;
import org.hibernate.envers.tools.Pair;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.jboss.logging.Logger;
/**
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class AuditProcess implements BeforeTransactionCompletionProcess {
private static final Logger log = Logger.getLogger( AuditProcess.class );
private final RevisionInfoGenerator revisionInfoGenerator;
private final SessionImplementor session;
private final LinkedList workUnits;
private final Queue undoQueue;
private final Map, AuditWorkUnit> usedIds;
private final Map, Object[]> entityStateCache;
private final EntityChangeNotifier entityChangeNotifier;
private Object revisionData;
public AuditProcess(RevisionInfoGenerator revisionInfoGenerator, SessionImplementor session) {
this.revisionInfoGenerator = revisionInfoGenerator;
this.session = session;
workUnits = new LinkedList<>();
undoQueue = new LinkedList<>();
usedIds = new HashMap<>();
entityStateCache = new HashMap<>();
entityChangeNotifier = new EntityChangeNotifier( revisionInfoGenerator, session );
}
public void cacheEntityState(Object id, String entityName, Object[] snapshot) {
final Pair key = new Pair<>( entityName, id );
if ( entityStateCache.containsKey( key ) ) {
throw new AuditException( "The entity [" + entityName + "] with id [" + id + "] is already cached." );
}
entityStateCache.put( key, snapshot );
}
public Object[] getCachedEntityState(Object id, String entityName) {
final Pair key = new Pair<>( entityName, id );
final Object[] entityState = entityStateCache.get( key );
if ( entityState != null ) {
entityStateCache.remove( key );
}
return entityState;
}
private void removeWorkUnit(AuditWorkUnit vwu) {
workUnits.remove( vwu );
if ( vwu.isPerformed() ) {
// If this work unit has already been performed, it must be deleted (undone) first.
undoQueue.offer( vwu );
}
}
public void addWorkUnit(AuditWorkUnit vwu) {
if ( vwu.containsWork() ) {
final Object entityId = vwu.getEntityId();
if ( entityId == null ) {
// Just adding the work unit - it's not associated with any persistent entity.
workUnits.offer( vwu );
}
else {
final String entityName = vwu.getEntityName();
final Pair usedIdsKey = Pair.make( entityName, entityId );
if ( usedIds.containsKey( usedIdsKey ) ) {
final AuditWorkUnit other = usedIds.get( usedIdsKey );
final AuditWorkUnit result = vwu.dispatch( other );
if ( result != other ) {
removeWorkUnit( other );
if ( result != null ) {
usedIds.put( usedIdsKey, result );
workUnits.offer( result );
}
// else: a null result means that no work unit should be kept
}
// else: the result is the same as the work unit already added. No need to do anything.
}
else {
usedIds.put( usedIdsKey, vwu );
workUnits.offer( vwu );
}
}
}
}
private void executeInSession(Session session) {
// Making sure the revision data is persisted.
final Object currentRevisionData = getCurrentRevisionData( session, true );
AuditWorkUnit vwu;
// First undoing any performed work units
while ( (vwu = undoQueue.poll()) != null ) {
vwu.undo( session );
}
while ( (vwu = workUnits.poll()) != null ) {
vwu.perform( session, revisionData );
entityChangeNotifier.entityChanged( session, currentRevisionData, vwu );
}
}
public Object getCurrentRevisionData(Session session, boolean persist) {
// Generating the revision data if not yet generated
if ( revisionData == null ) {
revisionData = revisionInfoGenerator.generate();
}
// Saving the revision data, if not yet saved and persist is true
if ( !session.contains( revisionData ) && persist ) {
revisionInfoGenerator.saveRevisionData( session, revisionData );
}
return revisionData;
}
@Override
public void doBeforeTransactionCompletion(SessionImplementor session) {
if ( workUnits.size() == 0 && undoQueue.size() == 0 ) {
return;
}
if ( !session.getTransactionCoordinator().isActive() ) {
log.debug( "Skipping envers transaction hook due to non-active (most likely marked-rollback-only) transaction" );
return;
}
// see: http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4178431
if ( FlushMode.MANUAL.equals( session.getHibernateFlushMode() ) || session.isClosed() ) {
Session temporarySession = null;
try {
temporarySession = session.sessionWithOptions()
.connection()
.autoClose( false )
.connectionHandlingMode( PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION )
.noInterceptor()
.openSession();
executeInSession( temporarySession );
temporarySession.flush();
}
finally {
if ( temporarySession != null ) {
temporarySession.close();
}
}
}
else {
executeInSession( session );
// Explicitly flushing the session, as the auto-flush may have already happened.
session.flush();
}
}
}