/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * 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.ogm.compensation.impl; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.hibernate.ogm.compensation.ErrorHandler; import org.hibernate.ogm.compensation.ErrorHandlingStrategy; import org.hibernate.ogm.compensation.operation.CreateAssociationWithKey; import org.hibernate.ogm.compensation.operation.CreateTuple; import org.hibernate.ogm.compensation.operation.CreateTupleWithKey; import org.hibernate.ogm.compensation.operation.ExecuteBatch; import org.hibernate.ogm.compensation.operation.FlushPendingOperations; import org.hibernate.ogm.compensation.operation.GridDialectOperation; import org.hibernate.ogm.compensation.operation.InsertOrUpdateAssociation; import org.hibernate.ogm.compensation.operation.InsertTuple; import org.hibernate.ogm.compensation.operation.RemoveAssociation; import org.hibernate.ogm.compensation.operation.RemoveTuple; import org.hibernate.ogm.compensation.operation.UpdateTupleWithOptimisticLock; import org.hibernate.ogm.compensation.operation.impl.CreateAssociationWithKeyImpl; import org.hibernate.ogm.compensation.operation.impl.CreateTupleImpl; import org.hibernate.ogm.compensation.operation.impl.CreateTupleWithKeyImpl; import org.hibernate.ogm.compensation.operation.impl.ExecuteBatchImpl; import org.hibernate.ogm.compensation.operation.impl.FlushPendingOperationsImpl; import org.hibernate.ogm.compensation.operation.impl.InsertOrUpdateAssociationImpl; import org.hibernate.ogm.compensation.operation.impl.InsertOrUpdateTupleImpl; import org.hibernate.ogm.compensation.operation.impl.InsertTupleImpl; import org.hibernate.ogm.compensation.operation.impl.RemoveAssociationImpl; import org.hibernate.ogm.compensation.operation.impl.RemoveTupleImpl; import org.hibernate.ogm.compensation.operation.impl.RemoveTupleWithOptimisticLockImpl; import org.hibernate.ogm.compensation.operation.impl.UpdateTupleWithOptimisticLockImpl; import org.hibernate.ogm.dialect.batch.spi.GroupedChangesToEntityOperation; import org.hibernate.ogm.dialect.batch.spi.InsertOrUpdateAssociationOperation; import org.hibernate.ogm.dialect.batch.spi.InsertOrUpdateTupleOperation; import org.hibernate.ogm.dialect.batch.spi.Operation; import org.hibernate.ogm.dialect.batch.spi.OperationsQueue; import org.hibernate.ogm.dialect.batch.spi.RemoveAssociationOperation; import org.hibernate.ogm.dialect.batch.spi.RemoveTupleOperation; import org.hibernate.ogm.dialect.eventstate.impl.EventContextManager; import org.hibernate.ogm.dialect.impl.ForwardingGridDialect; import org.hibernate.ogm.dialect.spi.AssociationContext; import org.hibernate.ogm.dialect.spi.GridDialect; import org.hibernate.ogm.dialect.spi.OperationContext; import org.hibernate.ogm.dialect.spi.TupleAlreadyExistsException; import org.hibernate.ogm.dialect.spi.TupleContext; import org.hibernate.ogm.entityentry.impl.TuplePointer; import org.hibernate.ogm.exception.impl.Exceptions; import org.hibernate.ogm.model.key.spi.AssociationKey; import org.hibernate.ogm.model.key.spi.EntityKey; import org.hibernate.ogm.model.key.spi.EntityKeyMetadata; import org.hibernate.ogm.model.spi.Association; import org.hibernate.ogm.model.spi.Tuple; /** * A grid dialect which tracks all applied and failing operations and passes them on to the {@link OperationCollector} * which in turn makes them available to the registered {@link ErrorHandler}. * * @author Gunnar Morling */ public class InvocationCollectingGridDialect extends ForwardingGridDialect<Serializable> { private final EventContextManager eventContext; public InvocationCollectingGridDialect(GridDialect gridDialect, EventContextManager eventContextManager) { super( gridDialect ); this.eventContext = eventContextManager; } @Override public void insertOrUpdateTuple(EntityKey key, TuplePointer tuplePointer, TupleContext tupleContext) { InsertOrUpdateTupleImpl insertOrUpdateTuple = new InsertOrUpdateTupleImpl( key, tuplePointer.getTuple() ); try { super.insertOrUpdateTuple( key, tuplePointer, tupleContext ); } catch (Exception e) { handleException( insertOrUpdateTuple, e ); } handleAppliedOperation( insertOrUpdateTuple ); } public void onInsertOrUpdateTupleFailure(EntityKey key, Tuple tuple, TupleAlreadyExistsException e) { handleException( new InsertOrUpdateTupleImpl( key, tuple ), e ); } @Override public void executeBatch(OperationsQueue queue) { OperationsQueue newQueue = new OperationsQueue(); List<GridDialectOperation> operations = new ArrayList<>(); if ( !queue.isClosed() ) { Operation operation = queue.poll(); // TODO OGM-766 Avoid the looping + re-creation while ( operation != null ) { newQueue.add( operation ); if ( operation instanceof GroupedChangesToEntityOperation ) { GroupedChangesToEntityOperation groupedChangesOnEntity = (GroupedChangesToEntityOperation) operation; for ( Operation groupedOperation : groupedChangesOnEntity.getOperations() ) { operations.add( getSimpleGridDialectOperations( groupedOperation ) ); } } else { operations.add( getSimpleGridDialectOperations( operation ) ); } operation = queue.poll(); } } ExecuteBatch executeBatch = new ExecuteBatchImpl( operations ); try { super.executeBatch( newQueue ); } catch (Exception e) { handleException( executeBatch, e ); } handleAppliedOperation( executeBatch ); } @Override public void flushPendingOperations(EntityKey entityKey, TupleContext tupleContext) { OperationsQueue queue = tupleContext.getOperationsQueue(); OperationsQueue newQueue = new OperationsQueue(); List<GridDialectOperation> operations = new ArrayList<>(); if ( !queue.isClosed() ) { Operation operation = queue.poll(); // TODO OGM-766 Avoid the looping + re-creation while ( operation != null ) { newQueue.add( operation ); if ( operation instanceof GroupedChangesToEntityOperation ) { GroupedChangesToEntityOperation groupedChangesOnEntity = (GroupedChangesToEntityOperation) operation; for ( Operation groupedOperation : groupedChangesOnEntity.getOperations() ) { operations.add( getSimpleGridDialectOperations( groupedOperation ) ); } } else { operations.add( getSimpleGridDialectOperations( operation ) ); } operation = queue.poll(); } } FlushPendingOperations flushPendingOperations = new FlushPendingOperationsImpl( operations ); try { super.flushPendingOperations( entityKey, tupleContext ); } catch (Exception e) { handleException( flushPendingOperations, e ); } handleAppliedOperation( flushPendingOperations ); } private GridDialectOperation getSimpleGridDialectOperations(Operation operation) { GridDialectOperation gridDialectOperation; if ( operation instanceof InsertOrUpdateTupleOperation ) { InsertOrUpdateTupleOperation insertOrUpdateTuple = (InsertOrUpdateTupleOperation) operation; gridDialectOperation = new InsertOrUpdateTupleImpl( insertOrUpdateTuple.getEntityKey(), insertOrUpdateTuple.getTuplePointer().getTuple() ); } else if ( operation instanceof RemoveTupleOperation ) { RemoveTupleOperation removeTuple = (RemoveTupleOperation) operation; gridDialectOperation = new RemoveTupleImpl( removeTuple.getEntityKey() ); } else if ( operation instanceof InsertOrUpdateAssociationOperation ) { InsertOrUpdateAssociationOperation insertOrUpdateAssociationOperation = (InsertOrUpdateAssociationOperation) operation; gridDialectOperation = new InsertOrUpdateAssociationImpl( insertOrUpdateAssociationOperation.getAssociationKey(), insertOrUpdateAssociationOperation.getAssociation() ); } else if ( operation instanceof RemoveAssociationOperation ) { RemoveAssociationOperation removeAssociationOperation = (RemoveAssociationOperation) operation; gridDialectOperation = new RemoveAssociationImpl( removeAssociationOperation.getAssociationKey() ); } else { throw new IllegalStateException( "Unsupported operation " + operation ); } return gridDialectOperation; } @Override public Tuple createTuple(EntityKey key, OperationContext operationContext) { Tuple tuple = null; CreateTupleWithKey createTupleWithKey = new CreateTupleWithKeyImpl( key ); try { tuple = super.createTuple( key, operationContext ); } catch (Exception e) { handleException( createTupleWithKey, e ); } handleAppliedOperation( createTupleWithKey ); return tuple; } @Override public void removeTuple(EntityKey key, TupleContext tupleContext) { RemoveTuple removeTuple = new RemoveTupleImpl( key ); try { super.removeTuple( key, tupleContext ); } catch (Exception e) { handleException( removeTuple, e ); } handleAppliedOperation( removeTuple ); } @Override public Association createAssociation(AssociationKey key, AssociationContext associationContext) { Association association = null; CreateAssociationWithKey createAssociationWithKey = new CreateAssociationWithKeyImpl( key ); try { association = super.createAssociation( key, associationContext ); } catch (Exception e) { handleException( createAssociationWithKey, e ); } handleAppliedOperation( createAssociationWithKey ); return association; } @Override public void insertOrUpdateAssociation(AssociationKey key, Association association, AssociationContext associationContext) { InsertOrUpdateAssociation insertOrUpdateAssociation = new InsertOrUpdateAssociationImpl( key, association ); try { super.insertOrUpdateAssociation( key, association, associationContext ); } catch (Exception e) { handleException( insertOrUpdateAssociation, e ); } handleAppliedOperation( insertOrUpdateAssociation ); } @Override public void removeAssociation(AssociationKey key, AssociationContext associationContext) { RemoveAssociation removeAssociation = new RemoveAssociationImpl( key ); try { super.removeAssociation( key, associationContext ); } catch (Exception e) { handleException( removeAssociation, e ); } handleAppliedOperation( removeAssociation ); } // IdentityColumnAwareGridDialect @Override public Tuple createTuple(EntityKeyMetadata entityKeyMetadata, OperationContext operationContext) { Tuple tuple = null; CreateTuple createTuple = new CreateTupleImpl( entityKeyMetadata ); try { tuple = super.createTuple( entityKeyMetadata, operationContext ); } catch (Exception e) { handleException( createTuple, e ); } handleAppliedOperation( createTuple ); return tuple; } @Override public void insertTuple(EntityKeyMetadata entityKeyMetadata, Tuple tuple, TupleContext tupleContext) { InsertTuple insertTuple = new InsertTupleImpl( entityKeyMetadata, tuple ); try { super.insertTuple( entityKeyMetadata, tuple, tupleContext ); } catch (Exception e) { handleException( insertTuple, e ); } handleAppliedOperation( insertTuple ); } // OptimisticLockingAwareGridDialect @Override public boolean updateTupleWithOptimisticLock(EntityKey entityKey, Tuple oldLockState, Tuple tuple, TupleContext tupleContext) { UpdateTupleWithOptimisticLock updateTupleWithOptimisticLock = new UpdateTupleWithOptimisticLockImpl( entityKey, oldLockState, tuple ); boolean success = false; try { success = super.updateTupleWithOptimisticLock( entityKey, oldLockState, tuple, tupleContext ); } catch (Exception e) { handleException( updateTupleWithOptimisticLock, e ); } // applied/failed operation logging triggered by persister as per operation outcome return success; } public void onUpdateTupleWithOptimisticLockSuccess(EntityKey entityKey, Tuple oldLockState, Tuple tuple) { handleAppliedOperation( new UpdateTupleWithOptimisticLockImpl( entityKey, oldLockState, tuple ) ); } public void onUpdateTupleWithOptimisticLockFailure(EntityKey entityKey, Tuple oldLockState, Tuple tuple, Exception e) { handleException( new UpdateTupleWithOptimisticLockImpl( entityKey, oldLockState, tuple ), e ); } @Override public boolean removeTupleWithOptimisticLock(EntityKey entityKey, Tuple oldLockState, TupleContext tupleContext) { RemoveTupleWithOptimisticLockImpl removeTupleWithOptimisticLock = new RemoveTupleWithOptimisticLockImpl( entityKey, oldLockState ); boolean success = false; try { success = super.removeTupleWithOptimisticLock( entityKey, oldLockState, tupleContext ); } catch (Exception e) { handleException( removeTupleWithOptimisticLock, e ); } // applied/failed operation logging triggered by persister as per operation outcome return success; } public void onRemoveTupleWithOptimisticLockSuccess(EntityKey entityKey, Tuple oldLockState) { handleAppliedOperation( new RemoveTupleWithOptimisticLockImpl( entityKey, oldLockState ) ); } public void onRemoveTupleWithOptimisticLockFailure(EntityKey entityKey, Tuple oldLockState, Exception e) { handleException( new RemoveTupleWithOptimisticLockImpl( entityKey, oldLockState ), e ); } private void handleAppliedOperation(GridDialectOperation operation) { getOperationCollector().addAppliedOperation( operation ); } private void handleException(GridDialectOperation operation, Exception e) { ErrorHandlingStrategy result = getOperationCollector().onFailedOperation( operation, e ); if ( result == ErrorHandlingStrategy.ABORT ) { Exceptions.<RuntimeException>sneakyThrow( e ); } } /** * Returns the {@link OperationCollector}. Must not store this as a field as it is flush-cycle scoped! */ private OperationCollector getOperationCollector() { return eventContext.get( OperationCollector.class ); } }