package com.alvazan.orm.layer5.nosql.cache;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alvazan.orm.api.z5api.NoSqlSession;
import com.alvazan.orm.api.z8spi.Cache;
import com.alvazan.orm.api.z8spi.CacheThreadLocal;
import com.alvazan.orm.api.z8spi.Key;
import com.alvazan.orm.api.z8spi.KeyValue;
import com.alvazan.orm.api.z8spi.MetaLookup;
import com.alvazan.orm.api.z8spi.NoSqlRawSession;
import com.alvazan.orm.api.z8spi.Row;
import com.alvazan.orm.api.z8spi.RowHolder;
import com.alvazan.orm.api.z8spi.ScanInfo;
import com.alvazan.orm.api.z8spi.action.Column;
import com.alvazan.orm.api.z8spi.action.IndexColumn;
import com.alvazan.orm.api.z8spi.conv.ByteArray;
import com.alvazan.orm.api.z8spi.iter.AbstractCursor;
import com.alvazan.orm.api.z8spi.iter.DirectCursor;
import com.alvazan.orm.api.z8spi.meta.DboTableMeta;
public class NoSqlReadCacheImpl implements NoSqlSession, Cache {
private static final Logger log = LoggerFactory.getLogger(NoSqlReadCacheImpl.class);
@Inject @Named("writecachelayer")
private NoSqlSession session;
private Map<TheKey, RowHolder<Row>> cache = new HashMap<TheKey, RowHolder<Row>>();
@Inject
private Provider<Row> rowProvider;
@Override
public void put(DboTableMeta colFamily, byte[] rowKey, List<Column> columns) {
session.put(colFamily, rowKey, columns);
RowHolder<Row> currentRow = fromCache(colFamily, rowKey);
if(currentRow == null) {
currentRow = new RowHolder<Row>(rowKey);
}
Row value = currentRow.getValue();
if(value == null)
value = rowProvider.get();
value.setKey(rowKey);
value.addColumns(columns);
cacheRow(colFamily, rowKey, value);
}
@Override
public void persistIndex(DboTableMeta colFamily, String indexColFamily, byte[] rowKey, IndexColumn columns) {
if(indexColFamily == null)
throw new IllegalArgumentException("indexcolFamily cannot be null");
else if(rowKey == null)
throw new IllegalArgumentException("rowKey cannot be null");
else if(columns == null)
throw new IllegalArgumentException("column cannot be null");
session.persistIndex(colFamily, indexColFamily, rowKey, columns);
}
@Override
public void remove(DboTableMeta colFamily, byte[] rowKey) {
session.remove(colFamily, rowKey);
cacheRow(colFamily, rowKey, null);
}
@Override
public void remove(DboTableMeta colFamily, byte[] rowKey, Collection<byte[]> columnNames) {
session.remove(colFamily, rowKey, columnNames);
RowHolder<Row> currentRow = fromCache(colFamily, rowKey);
if(currentRow == null) {
return;
}
Row value = currentRow.getValue();
if(value == null) {
return;
}
value.removeColumns(columnNames);
}
@Override
public Row find(DboTableMeta colFamily, byte[] rowKey) {
RowHolder<Row> result = fromCache(colFamily, rowKey);
if(result != null)
return result.getValue(); //This may return the cached null value!!
Row row = session.find(colFamily, rowKey);
cacheRow(colFamily, rowKey, row);
return row;
}
@Override
public AbstractCursor<KeyValue<Row>> find(DboTableMeta colFamily,
DirectCursor<byte[]> rowKeys, boolean skipCache, boolean cacheResults, Integer batchSize) {
Cache c = new EmptyCache(this, skipCache, cacheResults);
//NOTE: I would put a finally to clear out the threadlocal normally BUT sometimes log statements may
//cause further finds to be called which come in here as well and on their way BACK up the stack, they set
//the cache to NULL before the find actually happens(ie. very bad).
CacheThreadLocal.setCache(c);
//A layer below will read the thread local and pass it to lowest layer to use
AbstractCursor<KeyValue<Row>> rowsFromDb = session.find(colFamily, rowKeys, skipCache, cacheResults, batchSize);
return rowsFromDb;
}
public RowHolder<Row> fromCache(DboTableMeta colFamily, byte[] key) {
if(key == null)
throw new IllegalArgumentException("CF="+colFamily+" key is null and shouldn't be....(this should be trapped in higher level exception telling us which index is corrupt");
TheKey k = new TheKey(colFamily.getColumnFamily(), key);
RowHolder<Row> holder = cache.get(k);
if(holder != null && log.isInfoEnabled())
log.info("cache hit(need to optimize this even further)");
return holder;
}
public void cacheRow(DboTableMeta colFamily, byte[] key, Row r) {
//NOTE: We cache null rows as on a user.getYYYEntites(), the loaded entities may be null though the user
//get a List<YYYEntity> and all are there but he can check if they are really there with
//mgr.checkRowExists(entity) and that will just hit the cache
TheKey k = new TheKey(colFamily.getColumnFamily(), key);
RowHolder<Row> holder = new RowHolder<Row>(key, r); //r may be null so we are caching null here
cache.put(k, holder);
}
static class TheKey {
private String colFamily;
private ByteArray key;
public TheKey(String cf, byte[] key) {
colFamily = cf;
this.key = new ByteArray(key);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((colFamily == null) ? 0 : colFamily.hashCode());
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TheKey other = (TheKey) obj;
if (colFamily == null) {
if (other.colFamily != null)
return false;
} else if (!colFamily.equals(other.colFamily))
return false;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
}
@Override
public void flush() {
session.flush();
}
@Override
public NoSqlRawSession getRawSession() {
return session.getRawSession();
}
@Override
public void removeFromIndex(DboTableMeta cf, String indexColFamily, byte[] rowKeyBytes,
IndexColumn c) {
session.removeFromIndex(cf, indexColFamily, rowKeyBytes, c);
}
@Override
public void clearDb() {
session.clearDb();
}
@Override
public AbstractCursor<Column> columnSlice(DboTableMeta colFamily, byte[] rowKey, byte[] from, byte[] to, Integer batchSize) {
return session.columnSlice(colFamily, rowKey, from, to, batchSize);
}
@Override
public AbstractCursor<IndexColumn> scanIndex(ScanInfo info, Key from, Key to, Integer batchSize) {
return session.scanIndex(info, from, to, batchSize);
}
@Override
public void setOrmSessionForMeta(MetaLookup ormSession) {
session.setOrmSessionForMeta(ormSession);
}
@Override
public AbstractCursor<IndexColumn> scanIndex(ScanInfo scanInfo, List<byte[]> values) {
return session.scanIndex(scanInfo, values);
}
@Override
public void clear() {
cache.clear();
}
@Override
public void removeColumn(DboTableMeta colFamily, byte[] rowKey,
byte[] columnName) {
session.removeColumn(colFamily, rowKey, columnName);
cacheRow(colFamily, rowKey, null);
}
}