/* * 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.utils; import java.io.IOException; import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.hibernate.ogm.datastore.spi.DatastoreProvider; 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.impl.ForwardingGridDialect; import org.hibernate.ogm.dialect.query.spi.BackendQuery; import org.hibernate.ogm.dialect.query.spi.ClosableIterator; import org.hibernate.ogm.dialect.query.spi.QueryParameters; 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.TupleContext; import org.hibernate.ogm.entityentry.impl.TuplePointer; 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.key.spi.RowKey; import org.hibernate.ogm.model.spi.Association; import org.hibernate.ogm.model.spi.Tuple; import org.hibernate.ogm.util.impl.StringHelper; /** * A testing dialect wrapper which logs all the invoked {@link GridDialect} operations. * * @author Gunnar Morling */ public class InvokedOperationsLoggingDialect extends ForwardingGridDialect<Serializable> { /** * Path for the logging file to be created, relative to the current working directory. */ private static final String PATH = "invocations.log"; /** * Set to {@code true} if a log with all the ops and their parameters should be written into the file specified * above. */ private final boolean writeFile = false; private int opIndex = 0; private Path file; private final List<String> operations = new ArrayList<String>(); public InvokedOperationsLoggingDialect(DatastoreProvider provider) { super( TestHelper.getCurrentGridDialect( provider ) ); if ( writeFile ) { resetFile(); } } public List<String> getOperations() { return Collections.unmodifiableList( operations ); } public void reset() { operations.clear(); if ( writeFile ) { resetFile(); } } @Override public Tuple getTuple(EntityKey key, OperationContext operationContext) { Tuple tuple = super.getTuple( key, operationContext ); log( "getTuple", key.toString(), tuple != null ? tuple.toString() : "null" ); return tuple; } @Override public List<Tuple> getTuples(EntityKey[] keys, TupleContext tupleContext) { List<Tuple> tuples = super.getTuples( keys, tupleContext ); log( "getTuples", Arrays.toString( keys ), tuples != null ? tuples.toString() : "null" ); return tuples; } @Override public Tuple createTuple(EntityKey key, OperationContext operationContext) { Tuple tuple = super.createTuple( key, operationContext ); log( "createTuple", key.toString(), tuple != null ? tuple.toString() : "null" ); return tuple; } @Override public void insertOrUpdateTuple(EntityKey key, TuplePointer tuplePointer, TupleContext tupleContext) { super.insertOrUpdateTuple( key, tuplePointer, tupleContext ); log( "insertOrUpdateTuple", key.toString() + ", " + tuplePointer.toString(), "VOID" ); } @Override public void removeTuple(EntityKey key, TupleContext tupleContext) { super.removeTuple( key, tupleContext ); log( "removeTuple", key.toString(), "VOID" ); } @Override public Association getAssociation(AssociationKey key, AssociationContext associationContext) { Association association = super.getAssociation( key, associationContext ); log( "getAssociation", key.toString(), toShortString( association ) ); return association; } @Override public Association createAssociation(AssociationKey key, AssociationContext associationContext) { Association association = super.createAssociation( key, associationContext ); log( "createAssociation", key.toString(), toShortString( association ) ); return association; } @Override public void insertOrUpdateAssociation(AssociationKey key, Association association, AssociationContext associationContext) { super.insertOrUpdateAssociation( key, association, associationContext ); log( "insertOrUpdateAssociation", key.toString() + ", " + toShortString( association ), "VOID" ); } @Override public void removeAssociation(AssociationKey key, AssociationContext associationContext) { super.removeAssociation( key, associationContext ); log( "removeAssociation", key.toString(), "VOID" ); } @Override public boolean updateTupleWithOptimisticLock(EntityKey entityKey, Tuple oldLockState, Tuple tuple, TupleContext tupleContext) { boolean success = super.updateTupleWithOptimisticLock( entityKey, oldLockState, tuple, tupleContext ); log( "updateTuple", entityKey.toString() + ", " + tuple.toString(), String.valueOf( success ) ); return success; } @Override public boolean removeTupleWithOptimisticLock(EntityKey entityKey, Tuple oldLockState, TupleContext tupleContext) { boolean success = super.removeTupleWithOptimisticLock( entityKey, oldLockState, tupleContext ); log( "removeTuple", entityKey.toString(), String.valueOf( success ) ); return success; } @Override public Tuple createTuple(EntityKeyMetadata entityKeyMetadata, OperationContext operationContext) { return super.createTuple( entityKeyMetadata, operationContext ); } @Override public void insertTuple(EntityKeyMetadata entityKeyMetadata, Tuple tuple, TupleContext tupleContext) { super.insertTuple( entityKeyMetadata, tuple, tupleContext ); log( "insertTuple", entityKeyMetadata.toString() + ", " + tuple.toString(), "VOID" ); } @Override public ClosableIterator<Tuple> executeBackendQuery(BackendQuery<Serializable> query, QueryParameters queryParameters, TupleContext tupleContext) { ClosableIterator<Tuple> result = super.executeBackendQuery( query, queryParameters, tupleContext ); log( "executeBackendQuery", query.toString() + ", " + queryParameters.toString(), "tbd." ); return result; } @Override public void executeBatch(OperationsQueue queue) { OperationsQueue newQueue = new OperationsQueue(); StringBuilder sb = new StringBuilder(); List<String> subOperations = new ArrayList<String>(); if ( !queue.isClosed() ) { Operation operation = queue.poll(); while ( operation != null ) { newQueue.add( operation ); if ( operation instanceof GroupedChangesToEntityOperation ) { List<String> groupedOperations = new ArrayList<String>(); sb.append( "group[" ); for ( Operation groupedOperation : ( (GroupedChangesToEntityOperation) operation ).getOperations() ) { if ( !groupedOperations.isEmpty() ) { sb.append( ", " ); } appendSimpleOperation( sb, groupedOperations, groupedOperation ); } sb.append( "]" ); subOperations.add( "group[" + StringHelper.join( groupedOperations, "," ) + "]" ); } else { appendSimpleOperation( sb, subOperations, operation ); } operation = queue.poll(); if ( operation != null ) { sb.append( ", " ); } } } super.executeBatch( newQueue ); log( "executeBatch[" + StringHelper.join( subOperations, "," ) + "]", sb.toString(), "VOID" ); } private void appendSimpleOperation(StringBuilder log, List<String> subOperations, Operation operation) { if ( operation instanceof InsertOrUpdateTupleOperation ) { log.append( "InsertOrUpdateTuple(" ).append( ( (InsertOrUpdateTupleOperation) operation ).getEntityKey() ).append( " )" ); subOperations.add( "insertOrUpdateTuple" ); } else if ( operation instanceof RemoveTupleOperation ) { log.append( "RemoveTuple(" ).append( ( (RemoveTupleOperation) operation ).getEntityKey() ).append( " )" ); subOperations.add( "removeTuple" ); } else if ( operation instanceof InsertOrUpdateAssociationOperation ) { log.append( "InsertOrUpdateAssociation(" ).append( ( (InsertOrUpdateAssociationOperation) operation ).getAssociationKey() ).append( " )" ); subOperations.add( "insertOrUpdateAssociation" ); } else if ( operation instanceof RemoveAssociationOperation ) { log.append( "RemoveAssociation(" ).append( ( (RemoveAssociationOperation) operation ).getAssociationKey() ).append( " )" ); subOperations.add( "removeAssociation" ); } } private void log(String operation, String parameters, String returnValue) { operations.add( operation ); if ( !writeFile ) { return; } String line = opIndex + "\t| " + operation + "\t| " + parameters + "\t| " + returnValue + System.lineSeparator(); try { Files.write( file, line.getBytes(), StandardOpenOption.APPEND ); } catch (IOException e) { throw new RuntimeException( e ); } opIndex++; } private String toShortString(Association association) { if ( association == null ) { return null; } StringBuilder sb = new StringBuilder( "Association[" ); Iterator<RowKey> rowKeys = association.getKeys().iterator(); while ( rowKeys.hasNext() ) { RowKey rowKey = rowKeys.next(); sb.append( toShortString( rowKey ) ).append( "=" ).append( toShortString( association.get( rowKey ) ) ); if ( rowKeys.hasNext() ) { sb.append( "," ).append( StringHelper.lineSeparator() ); } } sb.append( "]" ); return sb.toString(); } private String toShortString(RowKey rowKey) { final StringBuilder sb = new StringBuilder(); sb.append( "(" ); int i = 0; for ( String column : rowKey.getColumnNames() ) { sb.append( column ).append( "=" ).append( rowKey.getColumnValue( column ) ); i++; if ( i < rowKey.getColumnNames().length ) { sb.append( ", " ); } } sb.append( ")" ); return sb.toString(); } private String toShortString(Tuple tuple) { StringBuilder sb = new StringBuilder( "(" ); int i = 0; for ( String column : tuple.getColumnNames() ) { sb.append( column ).append( "=" ).append( tuple.get( column ) ); i++; if ( i < tuple.getColumnNames().size() ) { sb.append( ", " ); } } sb.append( ")" ); return sb.toString(); } private void resetFile() { file = Paths.get( PATH ); try { if ( !Files.exists( file ) ) { file = Files.createFile( file ); } Files.write( file, ( "#\top\tparameters\tresult" + System.lineSeparator() ).getBytes(), StandardOpenOption.TRUNCATE_EXISTING ); } catch (IOException e) { throw new RuntimeException( e ); } } }