package focusedCrawler.util.persistence.rocksdb; import java.io.Closeable; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.output.ByteArrayOutputStream; import org.rocksdb.Options; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import focusedCrawler.util.persistence.HashtableDb; import focusedCrawler.util.persistence.Tuple; import focusedCrawler.util.persistence.TupleIterator; public class RocksDBHashtable<T> implements HashtableDb<T>, Closeable { private static final ThreadLocal<Kryo> KRYOS = new ThreadLocal<Kryo>() { protected Kryo initialValue() { Kryo kryo = new Kryo(); return kryo; }; }; private Class<T> contentClass; private Options options; private RocksDB db; static { RocksDB.loadLibrary(); } public RocksDBHashtable(String path, Class<T> contentClass) { this.contentClass = contentClass; this.options = new Options(); this.options.setCreateIfMissing(true); try { this.db = RocksDB.open(options, path); } catch (RocksDBException e) { throw new RuntimeException("Failed to open database at "+path, e); } } @Override public void put(List<Tuple<T>> tuples) { if(tuples == null || tuples.size() == 0){ return; } for(Tuple<T> tuple : tuples) { put(tuple.getKey(), tuple.getValue()); } } @Override public void put(String key, T value) { try { byte[] valueBytes = serializeObject(value); db.put(key.getBytes(), valueBytes); } catch (RocksDBException e) { throw new RuntimeException("failed to write value to database: "+key); } } private byte[] serializeObject(T value) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Output output = new Output(baos); KRYOS.get().writeObject(output, value); output.flush(); return baos.toByteArray(); } private T unserializeObject(byte[] value) { Input input = new Input(value); return KRYOS.get().readObject(input, contentClass); } @Override public List<Tuple<T>> listElements() { List<Tuple<T>> items = new ArrayList<>();; try(RocksDBIterator it = new RocksDBIterator()) { while(it.hasNext()) { items.add(it.next()); } return items; } } @Override public T get(String key) { byte[] bytes = key.getBytes(); try { byte[] value = db.get(bytes); if(value == null) { return null; } return unserializeObject(value); } catch (RocksDBException e) { throw new RuntimeException("Failed to get value from database for key: "+key, e); } } @Override public void close() { synchronized(db) { if (db != null) { db.close(); db = null; options.close(); options = null; } } } @Override public TupleIterator<T> iterator() { return new RocksDBIterator(); } public class RocksDBIterator implements TupleIterator<T>, Closeable { final private RocksIterator cursor; private boolean hasNext; private boolean isOpen; private byte[] value; private byte[] key; public RocksDBIterator() { this.cursor = db.newIterator(); this.cursor.seekToFirst(); this.isOpen = true; readNextTuple(true); } private void readNextTuple(boolean firstEntry) { if(!firstEntry) { cursor.next(); } if(cursor.isValid()) { this.hasNext = true; this.key = cursor.key(); this.value = cursor.value(); } else { this.close(); } } @Override public void close() { if(this.isOpen) { cursor.close(); this.isOpen = false; this.hasNext = false; } } @Override public boolean hasNext() { return hasNext; } @Override public Tuple<T> next() { if (!hasNext) { return null; } T value = unserializeObject(this.value); Tuple<T> tuple = new Tuple<T>(new String(key), value); readNextTuple(false); return tuple; } public void remove() { throw new UnsupportedOperationException("remove() not yet supported by "+getClass().getName()); } } }