package com.kryptnostic.rhizome.mapstores.cassandra;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MapStoreConfig;
import com.kryptnostic.rhizome.configuration.cassandra.CassandraConfiguration;
import com.kryptnostic.rhizome.mappers.SelfRegisteringKeyMapper;
import com.kryptnostic.rhizome.mappers.SelfRegisteringValueMapper;
import com.kryptnostic.rhizome.mapstores.TestableSelfRegisteringMapStore;
/**
* Base cassandra-backed mapstore. This class should not execute any queries directly
*
* @author Drew Bailey drew@kryptnostic.com
* @author Matthew Tamayo-Rios matthew@kryptnostic.com
*
* @param <K>
* @param <V>
*/
public abstract class BaseCassandraMapStore<K, V> implements TestableSelfRegisteringMapStore<K, V> {
private static final Logger logger = LoggerFactory
.getLogger(
BaseCassandraMapStore.class );
static final String DEFAULT_KEY_COLUMN_NAME = "id";
static final String DEFAULT_VALUE_COLUMN_NAME = "data";
protected static final int DEFAULT_QUERY_LIMIT = 10_000;
protected final String mapName;
protected final SelfRegisteringValueMapper<V> valueMapper;
protected final SelfRegisteringKeyMapper<K> keyMapper;
protected final Session session;
protected final String table;
protected final String keyspace;
protected final int replicationFactor;
protected boolean SUPPORTS_ASYNC_LOADS = true;
public BaseCassandraMapStore(
String table,
String mapName,
SelfRegisteringKeyMapper<K> keyMapper,
SelfRegisteringValueMapper<V> mapper,
CassandraConfiguration config,
Session session ) {
this.table = table;
this.keyMapper = keyMapper;
this.valueMapper = mapper;
this.session = session;
this.mapName = mapName;
this.replicationFactor = config.getReplicationFactor();
this.keyspace = config.getKeyspace();
}
@Override
public Map<K, V> loadAll( Collection<K> keys ) {
if ( SUPPORTS_ASYNC_LOADS ) {
return asyncLoadAll( keys );
}
return defaultLoadAll( keys );
}
private Map<K, V> asyncLoadAll( Collection<K> keys ) {
Map<K, V> resultMap = Maps.newHashMapWithExpectedSize( keys.size() );
CountDownLatch latch = new CountDownLatch( keys.size() );
for ( K key : keys ) {
Futures.addCallback( asyncLoad( key ), new FutureCallback<ResultSet>() {
@Override
public void onSuccess( ResultSet results ) {
for ( Row row : results ) {
V mapToValue = mapToValue( row );
resultMap.put( key, mapToValue );
if ( mapToValue == null ) {
String error = "Found null value for key " + key + " in map " + mapName + ", table "
+ table;
System.err.println( error );
logger.error( error );
}
}
latch.countDown();
}
@Override
public void onFailure( Throwable t ) {
System.err.println( "Asynchronous cassandra queries failed" );
t.printStackTrace();
latch.countDown();
}
} );
}
try {
latch.await();
} catch ( InterruptedException e ) {
System.err.println( "Async queries timed out." );
e.printStackTrace();
}
return resultMap;
}
private Map<K, V> defaultLoadAll( Collection<K> keys ) {
// Naive implementation for now :(
Map<K, V> result = Maps.newHashMap();
for ( K key : keys ) {
V value = load( key );
if ( value != null ) {
result.put( key, value );
}
}
return result;
}
protected abstract ResultSetFuture asyncLoad( K key );
protected abstract K mapToKey( Row row );
protected abstract V mapToValue( Row row );
@Override
public void storeAll( Map<K, V> map ) {
for ( Entry<K, V> ent : map.entrySet() ) {
store( ent.getKey(), ent.getValue() );
}
}
@Override
public MapStoreConfig getMapStoreConfig() {
return new MapStoreConfig().setImplementation( this ).setEnabled( true )
.setWriteDelaySeconds( 0 );
}
@Override
public MapConfig getMapConfig() {
return new MapConfig( mapName ).setBackupCount( this.replicationFactor )
.setMapStoreConfig( getMapStoreConfig() );
}
@Override
public String getMapName() {
return mapName;
}
@Override
public String getTable() {
return table;
}
@Override
public K generateTestKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException(
"THIS METHOD HAS NOT BEEN IMPLEMENTED. Override this method in your subclass!" );
}
@Override
public V generateTestValue() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException(
"THIS METHOD HAS NOT BEEN IMPLEMENTED. Override this method in your subclass!" );
}
}