/*
* 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.datastore.map.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.hibernate.LockMode;
import org.hibernate.dialect.lock.LockingStrategy;
import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy;
import org.hibernate.dialect.lock.OptimisticLockingStrategy;
import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy;
import org.hibernate.ogm.dialect.multiget.spi.MultigetGridDialect;
import org.hibernate.ogm.dialect.query.spi.ClosableIterator;
import org.hibernate.ogm.dialect.spi.AssociationContext;
import org.hibernate.ogm.dialect.spi.AssociationTypeContext;
import org.hibernate.ogm.dialect.spi.BaseGridDialect;
import org.hibernate.ogm.dialect.spi.ModelConsumer;
import org.hibernate.ogm.dialect.spi.NextValueRequest;
import org.hibernate.ogm.dialect.spi.OperationContext;
import org.hibernate.ogm.dialect.spi.TransactionContext;
import org.hibernate.ogm.dialect.spi.TupleContext;
import org.hibernate.ogm.dialect.spi.TuplesSupplier;
import org.hibernate.ogm.dialect.spi.TupleTypeContext;
import org.hibernate.ogm.entityentry.impl.TuplePointer;
import org.hibernate.ogm.model.key.spi.AssociationKey;
import org.hibernate.ogm.model.key.spi.AssociationKeyMetadata;
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.model.spi.Tuple.SnapshotType;
import org.hibernate.persister.entity.Lockable;
/**
* Grid dialect which uses a plain map for storing objects in memory. For testing purposes.
*
* @author Sanne Grinovero <sanne@hibernate.org> (C) 2011 Red Hat Inc.
*/
public class MapDialect extends BaseGridDialect implements MultigetGridDialect {
private final MapDatastoreProvider provider;
public MapDialect(MapDatastoreProvider provider) {
this.provider = provider;
}
@Override
public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) {
if ( lockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) {
return new PessimisticForceIncrementLockingStrategy( lockable, lockMode );
}
else if ( lockMode == LockMode.PESSIMISTIC_WRITE ) {
return new MapPessimisticWriteLockingStrategy( lockable, lockMode );
}
else if ( lockMode == LockMode.PESSIMISTIC_READ ) {
return new MapPessimisticReadLockingStrategy( lockable, lockMode );
}
else if ( lockMode == LockMode.OPTIMISTIC ) {
return new OptimisticLockingStrategy( lockable, lockMode );
}
else if ( lockMode == LockMode.OPTIMISTIC_FORCE_INCREMENT ) {
return new OptimisticForceIncrementLockingStrategy( lockable, lockMode );
}
return new MapPessimisticWriteLockingStrategy( lockable, lockMode );
}
@Override
public Tuple getTuple(EntityKey key, OperationContext operationContext) {
Map<String, Object> entityMap = provider.getEntityTuple( key );
if ( entityMap == null ) {
return null;
}
else {
return new Tuple( new MapTupleSnapshot( entityMap ), SnapshotType.UPDATE );
}
}
@Override
public List<Tuple> getTuples(EntityKey[] keys, TupleContext tupleContext) {
List<Map<String, Object>> mapResults = provider.getEntityTuples( keys );
List<Tuple> results = new ArrayList<>( mapResults.size() );
// should be done with a lambda for the tuple creation but that's for demo purposes
for ( Map<String, Object> entry : mapResults ) {
results.add( entry != null ? new Tuple( new MapTupleSnapshot( entry ), SnapshotType.UPDATE ) : null );
}
return results;
}
@Override
public Tuple createTuple(EntityKey key, OperationContext operationContext) {
HashMap<String,Object> tuple = new HashMap<String, Object>();
provider.putEntity( key, tuple );
return new Tuple( new MapTupleSnapshot( tuple ), SnapshotType.INSERT );
}
@Override
public void insertOrUpdateTuple(EntityKey key, TuplePointer tuplePointer, TupleContext tupleContext) {
Map<String,Object> entityRecord = ( (MapTupleSnapshot) tuplePointer.getTuple().getSnapshot() ).getMap();
MapHelpers.applyTupleOpsOnMap( tuplePointer.getTuple(), entityRecord );
}
@Override
public void removeTuple(EntityKey key, TupleContext tupleContext) {
provider.removeEntityTuple( key );
}
@Override
public Association getAssociation(AssociationKey key, AssociationContext associationContext) {
Map<RowKey, Map<String, Object>> associationMap = provider.getAssociation( key );
return associationMap == null ? null : new Association( new MapAssociationSnapshot( associationMap ) );
}
@Override
public Association createAssociation(AssociationKey key, AssociationContext associationContext) {
Map<RowKey, Map<String, Object>> associationMap = new HashMap<RowKey, Map<String,Object>>();
provider.putAssociation( key, associationMap );
return new Association( new MapAssociationSnapshot( associationMap ) );
}
@Override
public void insertOrUpdateAssociation(AssociationKey key, Association association, AssociationContext associationContext) {
MapHelpers.updateAssociation( association );
// the association might have been removed prior to the update so we need to be sure it is present in the Map
provider.putAssociation( key, ( (MapAssociationSnapshot) association.getSnapshot() ).getUnderlyingMap() );
}
@Override
public void removeAssociation(AssociationKey key, AssociationContext associationContext) {
provider.removeAssociation( key );
}
@Override
public boolean isStoredInEntityStructure(AssociationKeyMetadata associationKeyMetadata, AssociationTypeContext associationTypeContext) {
return false;
}
@Override
public Number nextValue(NextValueRequest request) {
return provider.getSharedAtomicInteger( request.getKey(), request.getInitialValue(), request.getIncrement() );
}
@Override
public void forEachTuple(ModelConsumer consumer, TupleTypeContext tupleTypeContext, EntityKeyMetadata metadata) {
Map<EntityKey, Map<String, Object>> entityMap = provider.getEntityMap();
consumer.consume( new MapTuplesSupplier( entityMap, metadata ) );
}
private static class MapTuplesSupplier implements TuplesSupplier {
private final Map<EntityKey, Map<String, Object>> entityMap;
private final EntityKeyMetadata metadata;
public MapTuplesSupplier(Map<EntityKey, Map<String, Object>> entityMap, EntityKeyMetadata metadata) {
this.entityMap = entityMap;
this.metadata = metadata;
}
@Override
public ClosableIterator<Tuple> get(TransactionContext transactionContext) {
return new MapTupleIterator( entityMap, metadata );
}
}
private static class MapTupleIterator implements ClosableIterator<Tuple> {
private final EntityKeyMetadata metadata;
private final Map<EntityKey, Map<String, Object>> entityMap;
private final Iterator<EntityKey> iterator;
private EntityKey next;
private boolean hasNext = false;
public MapTupleIterator(Map<EntityKey, Map<String, Object>> entityMap, EntityKeyMetadata metadata) {
this.entityMap = entityMap;
this.metadata = metadata;
this.iterator = entityMap.keySet().iterator();
this.next = next( this.iterator );
}
private EntityKey next(Iterator<EntityKey> iterator) {
EntityKey next = null;
hasNext = false;
while ( iterator.hasNext() ) {
next = iterator.next();
if ( isValidKey( next ) ) {
hasNext = true;
break;
}
}
if ( hasNext ) {
return next;
}
return null;
}
public boolean isValidKey(EntityKey key) {
return key.getTable().equals( metadata.getTable() );
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public Tuple next() {
if ( hasNext ) {
Tuple current = createTuple( entityMap, next );
next = next( iterator );
return current;
}
throw new NoSuchElementException();
}
@Override
public void close() {
}
}
private static Tuple createTuple(Map<EntityKey, Map<String, Object>> entityMap, EntityKey key) {
return new Tuple( new MapTupleSnapshot( entityMap.get( key ) ), SnapshotType.UPDATE );
}
}