/*
* Copyright 2015 Edward Capriolo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.teknek.nibiru.engine;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.annotations.VisibleForTesting;
import io.teknek.nibiru.Store;
import io.teknek.nibiru.Keyspace;
import io.teknek.nibiru.Token;
import io.teknek.nibiru.engine.atom.AtomKey;
import io.teknek.nibiru.engine.atom.AtomValue;
import io.teknek.nibiru.engine.atom.ColumnKey;
import io.teknek.nibiru.engine.atom.ColumnValue;
import io.teknek.nibiru.engine.atom.TombstoneValue;
import io.teknek.nibiru.engine.atom.RowTombstoneKey;
import io.teknek.nibiru.metadata.StoreMetaData;
import io.teknek.nibiru.personality.ColumnFamilyPersonality;
public class DefaultColumnFamily extends Store implements ColumnFamilyPersonality, DirectSsTableWriter {
private final AtomicReference<AbstractMemtable> memtable;
private final MemtableFlusher memtableFlusher;
private final Set<SsTable> sstable = new ConcurrentSkipListSet<>();
private final StoreMetaData storeMetaData;
private ConcurrentMap<String, SsTableStreamWriter> streamSessions = new ConcurrentHashMap<>();
public DefaultColumnFamily(Keyspace keyspace, StoreMetaData cfmd){
super(keyspace, cfmd);
//It would be nice to move this into init but some things are dependent
CommitLog commitLog = new CommitLog(this);
try {
commitLog.open();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
storeMetaData = cfmd;
memtable = new AtomicReference<AbstractMemtable>(createFromConfiguration(this, commitLog));
memtableFlusher = new MemtableFlusher(this);
memtableFlusher.start();
}
public AbstractMemtable createFromConfiguration(DefaultColumnFamily defaultCf, CommitLog commitLog){
if (storeMetadata.getMemtableClass() == null){
return new VersionedMemtable(defaultCf, commitLog);
}
try {
Constructor<?> c = Class.forName(storeMetadata.getMemtableClass()).getConstructor(DefaultColumnFamily.class, CommitLog.class );
AbstractMemtable m = (AbstractMemtable) c.newInstance(defaultCf, commitLog);
return m;
} catch (NoSuchMethodException | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public void init() throws IOException {
File sstableDirectory = SsTableStreamWriter.pathToSsTableDataDirectory
(keyspace.getConfiguration(), storeMetadata);
for(File ssTable: sstableDirectory.listFiles()){
String [] parts = ssTable.getName().split("\\.");
if (parts.length == 2){
if ("ss".equalsIgnoreCase(parts[1])){
String id = parts[0];
SsTable toOpen = new SsTable(this);
toOpen.open(id, keyspace.getConfiguration());
sstable.add(toOpen);
}
}
}
for(File commitlog: CommitLog.getCommitLogDirectoryForColumnFamily(this).listFiles()){
String [] parts = commitlog.getName().split("\\.");
if (parts.length == 2){
if (CommitLog.EXTENSION.equalsIgnoreCase(parts[1])){
processCommitLog(parts[0]);
}
}
}
}
void processCommitLog(String id) throws IOException {
CommitLogReader r = new CommitLogReader(id, this);
r.open();
Token t;
while ((t = r.getNextToken()) != null){
SortedMap<AtomKey, AtomValue> x = r.readColumns();
for (Entry<AtomKey, AtomValue> col: x.entrySet()){
if (col.getKey() instanceof RowTombstoneKey){
TombstoneValue v = (TombstoneValue) col.getValue();
memtable.get().delete(t, v.getTime());
} else if (col.getKey() instanceof ColumnKey){
ColumnKey ck = (ColumnKey) col.getKey();
if (col.getValue() instanceof ColumnValue){
ColumnValue cv = (ColumnValue) col.getValue();
memtable.get().put(t, ck.getColumn(), cv.getValue(), cv.getTime(), cv.getTtl());
} else if (col.getValue() instanceof TombstoneValue){
memtable.get().delete(t, ck.getColumn(), col.getValue().getTime());
} else {
throw new RuntimeException("processing commit log "+id);
}
} else {
throw new RuntimeException("processing commit log "+id);
}
}
}
r.close();
r.delete();
}
public void shutdown(){
getMemtableFlusher().setGoOn(false);
for (SsTable s: sstable){
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//TODO should probably flush here
try {
memtable.get().getCommitLog().close();
} catch (IOException e) {
e.printStackTrace();
}
}
public StoreMetaData getStoreMetadata() {
return storeMetadata;
}
@Deprecated
public AbstractMemtable getMemtable() {
return memtable.get();
}
@Deprecated
public void setMemtable(Memtable memtable) {
this.memtable.set(memtable);
}
public static AtomValue applyRules(AtomValue lastValue, AtomValue thisValue){
if (thisValue == null){
return lastValue;
}
if (thisValue != null && lastValue == null){
return thisValue;
}
if (thisValue instanceof TombstoneValue &&
thisValue.getTime() >= lastValue.getTime()){
return thisValue;
}
if (lastValue instanceof TombstoneValue &&
lastValue.getTime() >= thisValue.getTime()){
return lastValue;
}
if (thisValue instanceof ColumnValue && lastValue instanceof ColumnValue){
if (thisValue.getTime() == lastValue.getTime()){
if ( ((ColumnValue) thisValue).getValue().compareTo(((ColumnValue) lastValue).getValue() ) > 0){
return thisValue;
} else {
return lastValue;
}
} else if (thisValue.getTime() > lastValue.getTime()){
return thisValue;
} else {
return lastValue;
}
}
throw new IllegalArgumentException ( "comparing " + thisValue + " " + lastValue);
}
public AtomValue get(String rowkey, String column){
AtomValue lastValue = memtable.get().get(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey), column);
for (AbstractMemtable m: memtableFlusher.getMemtables()){
AtomValue thisValue = m.get(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey), column);
lastValue = applyRules(lastValue, thisValue);
}
Token token = keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey);
for (SsTable sstable: this.getSstable()){
AtomValue thisValue = null;
try {
thisValue = sstable.get(token, column);
} catch (IOException e) {
throw new RuntimeException(e);
}
lastValue = applyRules(lastValue, thisValue);
}
return lastValue;
}
public void delete(String rowkey, String column, long time){
memtable.get().delete(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey), column, time);
//commit log
considerFlush();
}
public void put(String rowkey, String column, String value, long time, long ttl){
try {
memtable.get().getCommitLog().write(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey),
new ColumnKey(column), value, time, ttl);
} catch (IOException e) {
throw new RuntimeException(e);
}
memtable.get().put(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey), column, value, time, ttl);
considerFlush();
}
public void put(String rowkey, String column, String value, long time){
try {
memtable.get().getCommitLog().write(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey),
new ColumnKey(column), value, time, 0);
} catch (IOException e) {
throw new RuntimeException(e);
}
memtable.get().put(keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey), column, value, time, 0);
//commit log
considerFlush();
}
public void doFlush(){
AbstractMemtable now = memtable.get();
CommitLog commitLog = new CommitLog(this);
try {
commitLog.open();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
//VersionedMemtable aNewTable = new VersionedMemtable(this, commitLog);
AbstractMemtable aNewTable = this.createFromConfiguration(this, commitLog);
boolean success = memtableFlusher.add(now);
if (success){
boolean swap = memtable.compareAndSet(now, aNewTable);
if (!swap){
throw new RuntimeException("race detected");
}
}
}
void considerFlush(){
AbstractMemtable now = memtable.get();
if (storeMetadata.getFlushNumberOfRowKeys() != 0
&& now.size() >= storeMetadata.getFlushNumberOfRowKeys()){
doFlush();
}
}
public Keyspace getKeyspace() {
return keyspace;
}
public Set<SsTable> getSstable() {
return sstable;
}
public MemtableFlusher getMemtableFlusher() {
return memtableFlusher;
}
public SortedMap<AtomKey, AtomValue> slice(String rowkey, String start, String end){
Token t = keyspace.getKeyspaceMetaData().getPartitioner().partition(rowkey);
SortedMap<AtomKey, AtomValue> fromMemtable = memtable.get().slice(t, start, end);
for (SsTable table: this.sstable){
try {
Map<AtomKey, AtomValue> fromSs = table.slice(t, start, end);
for (Entry<AtomKey, AtomValue> each: fromSs.entrySet()){
if (!fromMemtable.containsKey(each.getKey())){
fromMemtable.put(each.getKey(), each.getValue());
} else {
//TODO use better rules that consider tombstones
AtomValue current = fromMemtable.get(each.getKey());
if (each.getValue().getTime() > current.getTime()){
fromMemtable.put(each.getKey(), each.getValue());
}
}
}
} catch (IOException e) {
throw new RuntimeException (e);
}
}
return fromMemtable;
}
@Override
public void open(String id) {
SsTableStreamWriter add = new SsTableStreamWriter(id, this);
try {
add.open();
streamSessions.put(id, add);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void write(Token token, SortedMap<AtomKey, AtomValue> columns, String id) {
try {
streamSessions.get(id).write(token, columns);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void close(String id) {
SsTableStreamWriter writer = streamSessions.get(id);
try {
writer.close();
SsTable table = new SsTable(this);
table.open(id, getKeyspace().getConfiguration());
sstable.add(table);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
this.streamSessions.remove(id);
}
}
@VisibleForTesting
public ConcurrentMap<String, SsTableStreamWriter> getStreamSessions() {
return streamSessions;
}
}