package com.feedly.cassandra.dao;
import java.lang.management.ManagementFactory;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import me.prettyprint.cassandra.serializers.BytesArraySerializer;
import me.prettyprint.cassandra.serializers.CompositeSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.AbstractComposite.ComponentEquality;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.Composite;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.SliceQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feedly.cassandra.EConsistencyLevel;
import com.feedly.cassandra.IKeyspaceFactory;
import com.feedly.cassandra.PersistenceManager;
import com.feedly.cassandra.entity.EIndexType;
import com.feedly.cassandra.entity.EntityMetadata;
import com.feedly.cassandra.entity.IndexMetadata;
import com.feedly.cassandra.entity.SimplePropertyMetadata;
import com.feedly.cassandra.entity.enhance.IEnhancedEntity;
/**
* This class serves as a foundation class for saving and retrieving entities in Cassandra. Subclasses can define additional business logic
* related methods. Typically those methods then use functionality provided by this class.
*
* @author kireet
*
* @param <K> the key type - should match the type of the Entity's row key field.
* @param <V> the value type - the Entity itself.
*
* @see ICassandraDao
*/
public class CassandraDaoBase<K, V> implements ICassandraDao<K, V>
{
private static final Logger _logger = LoggerFactory.getLogger(CassandraDaoBase.class.getName());
static final int COL_RANGE_SIZE = 100;
static final int ROW_RANGE_SIZE = 100;
private final EntityMetadata<V> _entityMeta;
private final EConsistencyLevel _defaultConsistency;
private final List<SimplePropertyMetadata> _rangeIndexedProps;
private GetHelper<K, V> _getHelper;
private FindHelper<K, V> _findHelper;
private PutHelper<K, V> _putHelper;
private DeleteHelper<K, V> _deleteHelper;
private IKeyspaceFactory _keyspaceFactory;
private IStaleIndexValueStrategy _staleIndexValueStrategy;
private int _statsSize = MBeanUtils.DEFAULT_STATS_SIZE;
private OperationStatistics _walRecoveryStats;
protected CassandraDaoBase()
{
this(null, null, null);
}
@SuppressWarnings("unchecked")
protected CassandraDaoBase(Class<K> keyClass, Class<V> valueClass, EConsistencyLevel defaultConsistency)
{
if(keyClass == null)
{
keyClass = (Class<K>) getGenericClass(0);
if(keyClass == null)
throw new IllegalStateException("could not determine key class");
}
if(valueClass == null)
{
valueClass = (Class<V>) getGenericClass(1);
if(valueClass == null)
throw new IllegalStateException("could not determine value class");
}
_defaultConsistency = defaultConsistency != null ? defaultConsistency : EConsistencyLevel.QUOROM;
_entityMeta = new EntityMetadata<V>(valueClass);
if(!keyClassMatches(_entityMeta.getKeyMetadata().getFieldType(), keyClass))
throw new IllegalArgumentException(String.format("DAO/entity key mismatch: %s != %s",
keyClass.getName(),
_entityMeta.getKeyMetadata().getFieldType().getName()));
List<SimplePropertyMetadata> props = new ArrayList<SimplePropertyMetadata>();
for(IndexMetadata idxMeta : _entityMeta.getIndexes())
{
if(idxMeta.getType() == EIndexType.RANGE)
{
props.addAll(idxMeta.getIndexedProperties());
}
}
_rangeIndexedProps = Collections.unmodifiableList(props);
_logger.info("{} [{}]:\n{}", new Object[] {getClass().getSimpleName(), _entityMeta.getFamilyName(), _entityMeta.toString()});
}
public void setStatsSize(int s)
{
_statsSize = s;
}
public void setKeyspaceFactory(IKeyspaceFactory keyspaceFactory)
{
_keyspaceFactory = keyspaceFactory;
}
public void setStaleValueIndexStrategy(IStaleIndexValueStrategy strategy)
{
_staleIndexValueStrategy = strategy;
}
public void destroy()
{
unregisterMBeans();
}
public void init()
{
if(_keyspaceFactory == null)
throw new IllegalStateException("keyspace factory not set");
if(_staleIndexValueStrategy == null)
{
_staleIndexValueStrategy =
new IStaleIndexValueStrategy()
{
public void handle(EntityMetadata<?> entity, IndexMetadata index, Keyspace keyspace, Collection<StaleIndexValue> values)
{
_logger.warn("not handling {} stale values for {}", values.size(), entity.getFamilyName());
}
};
}
IKeyspaceFactory withDefault =
new IKeyspaceFactory()
{
@Override
public Keyspace createKeyspace(EConsistencyLevel level)
{
if(level == null)
level = _defaultConsistency;
return _keyspaceFactory.createKeyspace(level);
}
};
_getHelper = new GetHelper<K, V>(_entityMeta, withDefault, _statsSize);
_findHelper = new FindHelper<K, V>(_entityMeta, withDefault, _staleIndexValueStrategy, _statsSize);
_putHelper = new PutHelper<K, V>(_entityMeta, withDefault, _statsSize);
_deleteHelper = new DeleteHelper<K, V>(_entityMeta, withDefault, _statsSize);
_walRecoveryStats = new OperationStatistics(_statsSize);
registerMBeans();
}
private ObjectName mBeanName(String name) throws MalformedObjectNameException
{
return MBeanUtils.mBeanName(this, _entityMeta.getType().getSimpleName(), name);
}
private void registerMBeans()
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try
{
mbs.registerMBean(new OperationStatisticsMonitor(_walRecoveryStats), mBeanName("walStats"));
mbs.registerMBean(new OperationStatisticsMonitor(getStats()), mBeanName("getStats"));
mbs.registerMBean(new OperationStatisticsMonitor(deleteStats()), mBeanName("deleteStats"));
mbs.registerMBean(new OperationStatisticsMonitor(putStats()), mBeanName("putStats"));
mbs.registerMBean(new OperationStatisticsMonitor(putIndexStats()), mBeanName("putIdxStats"));
mbs.registerMBean(new OperationStatisticsMonitor(hashFindStats()), mBeanName("hashFindStats"));
mbs.registerMBean(new OperationStatisticsMonitor(hashFindIndexStats()), mBeanName("hashFindIdxStats"));
mbs.registerMBean(new OperationStatisticsMonitor(rangeFindStats()), mBeanName("rangeFindStats"));
mbs.registerMBean(new OperationStatisticsMonitor(rangeFindIndexStats()), mBeanName("rangeFindIdxStats"));
_logger.info("monitoring registration complete for {}", getClass().getSimpleName());
}
catch(InstanceAlreadyExistsException e)
{
_logger.warn("mbean already exists, new mbeans will not be registered mbeans");
}
catch(Exception e)
{
_logger.warn("error registering mbeans", e);
}
}
private void unregisterMBeans()
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try
{
mbs.unregisterMBean(mBeanName("walStats"));
mbs.unregisterMBean(mBeanName("getStats"));
mbs.unregisterMBean(mBeanName("deleteStats"));
mbs.unregisterMBean(mBeanName("putStats"));
mbs.unregisterMBean(mBeanName("putIdxStats"));
mbs.unregisterMBean(mBeanName("hashFindStats"));
mbs.unregisterMBean(mBeanName("hashFindIdxStats"));
mbs.unregisterMBean(mBeanName("rangeFindStats"));
mbs.unregisterMBean(mBeanName("rangeFindIdxStats"));
_logger.info("monitoring unregistration complete for {}", getClass().getSimpleName());
}
catch(Exception e)
{
_logger.warn("error unregistering mbeans", e);
}
}
private boolean keyClassMatches(Class<?> fieldType, Class<?> keyType)
{
if(fieldType.equals(keyType))
return true;
if(fieldType.isPrimitive())
{
if(fieldType.equals(boolean.class))
fieldType = Boolean.class;
if(fieldType.equals(byte.class))
fieldType = Byte.class;
if(fieldType.equals(char.class))
fieldType = Character.class;
if(fieldType.equals(short.class))
fieldType = Short.class;
if(fieldType.equals(int.class))
fieldType = Integer.class;
if(fieldType.equals(long.class))
fieldType = Long.class;
if(fieldType.equals(float.class))
fieldType = Float.class;
if(fieldType.equals(double.class))
fieldType = Double.class;
return fieldType.equals(keyType);
}
return false;
}
private Class<?> getGenericClass(int idx)
{
Class<?> clazz = getClass();
while(clazz != null)
{
if(clazz.getGenericSuperclass() instanceof ParameterizedType)
{
ParameterizedType t = (ParameterizedType) clazz.getGenericSuperclass();
Type[] types = t.getActualTypeArguments();
if(!(types[idx] instanceof Class<?>))
clazz = clazz.getSuperclass();
else
{
return (Class<?>) types[idx];
}
}
else
clazz = clazz.getSuperclass();
}
return null;
}
@Override
public void put(V value)
{
put(value, null);
}
@Override
public void put(V value, PutOptions options)
{
if(options == null)
options = new PutOptions();
_putHelper.put(value, options);
}
@Override
public void mput(Collection<V> values)
{
mput(values, null);
}
@Override
public void mput(Collection<V> values, PutOptions options)
{
if(options == null)
options = new PutOptions();
_putHelper.mput(values, options);
}
@Override
public V get(K key)
{
return get(key, null, null);
}
@Override
public V get(K key, V value, GetOptions options)
{
if(options == null)
options = new GetOptions();
return _getHelper.get(key, value, options);
}
@Override
public Collection<V> mget(Collection<K> keys)
{
return _getHelper.mget(keys);
}
@Override
public List<V> mget(List<K> keys, List<V> values, GetOptions options)
{
if(options == null)
options = new GetOptions();
return _getHelper.mget(keys, values, options);
}
@Override
public Collection<V> mgetAll()
{
return mgetAll(null);
}
@Override
public Collection<V> mgetAll(GetAllOptions options)
{
if(options == null)
options = new GetAllOptions();
return _getHelper.mgetAll(options);
}
@Override
public V find(V template)
{
return find(template, null);
}
@Override
public V find(V template, FindOptions options)
{
if(options == null)
options = new FindOptions();
return _findHelper.find(template, options);
}
@Override
public Collection<V> mfind(V template)
{
return mfind(template, null);
}
@Override
public Collection<V> mfind(V template, FindOptions options)
{
if(options == null)
options = new FindOptions();
return _findHelper.mfind(template, options);
}
@Override
public Collection<V> mfindBetween(V startTemplate, V endTemplate)
{
return mfindBetween(startTemplate, endTemplate, null);
}
@Override
public Collection<V> mfindBetween(V startTemplate, V endTemplate, FindBetweenOptions options)
{
if(options == null)
options = new FindBetweenOptions();
return _findHelper.mfindBetween(startTemplate, endTemplate, options);
}
@Override
public void delete(K key)
{
delete(key, null);
}
@Override
public void delete(K key, DeleteOptions options)
{
if(options == null)
options = new DeleteOptions();
_deleteHelper.delete(key, options);
}
@Override
public void mdelete(Collection<K> keys)
{
mdelete(keys, null);
}
@Override
public void mdelete(Collection<K> keys, DeleteOptions options)
{
if(options == null)
options = new DeleteOptions();
_deleteHelper.mdelete(keys, options);
}
@SuppressWarnings("unchecked")
public int checkWal(long before)
{
_walRecoveryStats.incrNumOps(1);
long startTime = System.nanoTime();
int cnt = 0;
if(!_rangeIndexedProps.isEmpty())
{
Keyspace keyspace = _keyspaceFactory.createKeyspace(EConsistencyLevel.ALL);
SliceQuery<byte[],Composite,byte[]> query = HFactory.createSliceQuery(keyspace,
BytesArraySerializer.get(), CompositeSerializer.get(), BytesArraySerializer.get());
query.setKey(_entityMeta.getFamilyNameBytes());
query.setColumnFamily(PersistenceManager.CF_IDXWAL);
Composite start = null;
Composite finish = new Composite();
finish.addComponent(before, DaoHelperBase.SER_LONG);
finish.setEquality(ComponentEquality.LESS_THAN_EQUAL);
while(true)
{
query.setRange(start, finish, false, 100);
ColumnSlice<Composite,byte[]> slice = query.execute().get();
_walRecoveryStats.incrNumCassandraOps(1);
List<HColumn<Composite, byte[]>> columns = slice.getColumns();
if(columns.isEmpty())
break;
Set<String> includes = new HashSet<String>(_rangeIndexedProps.size());
for(SimplePropertyMetadata spm : _rangeIndexedProps)
includes.add(spm.getName());
GetOptions opts = new GetOptions(includes, null);
for(HColumn<Composite, byte[]> col : columns)
{
cnt++;
_walRecoveryStats.incrNumCols(1);
K key = (K) col.getName().getComponent(1).getValue(_entityMeta.getKeyMetadata().getSerializer());
V val = get(key, null, opts);
if(val != null)
{
IEnhancedEntity e = (IEnhancedEntity) val;
for(SimplePropertyMetadata spm : _rangeIndexedProps)
e.getModifiedFields().set(_entityMeta.getPropertyPosition(spm));
PutOptions popts = new PutOptions();
popts.setConsistencyLevel(EConsistencyLevel.ALL);
_putHelper.put(val, popts, col.getClock());
}
Mutator<byte[]> mutator = HFactory.createMutator(keyspace, DaoHelperBase.SER_BYTES);
mutator.addDeletion(_entityMeta.getFamilyNameBytes(), PersistenceManager.CF_IDXWAL, col.getName(), DaoHelperBase.SER_COMPOSITE);
mutator.execute();
_walRecoveryStats.incrNumCassandraOps(2);
}
start = columns.get(columns.size() - 1).getName();
}
}
_walRecoveryStats.addRecentTiming(System.nanoTime() - startTime);
return cnt;
}
public OperationStatistics getStats()
{
return _getHelper.stats();
}
public OperationStatistics deleteStats()
{
return _deleteHelper.stats();
}
public OperationStatistics putStats()
{
return _putHelper.stats();
}
public OperationStatistics putIndexStats()
{
return _putHelper.indexStats();
}
public OperationStatistics hashFindStats()
{
return _findHelper.hashFindStats();
}
public OperationStatistics hashFindIndexStats()
{
return _findHelper.hashFindIndexStats();
}
public OperationStatistics rangeFindStats()
{
return _findHelper.rangeFindStats();
}
public OperationStatistics rangeFindIndexStats()
{
return _findHelper.rangeFindIndexStats();
}
public OperationStatistics walRecoveryStats()
{
return _walRecoveryStats;
}
}