package be.bagofwords.db.leveldb; import be.bagofwords.db.CoreDataInterface; import be.bagofwords.db.combinator.Combinator; import be.bagofwords.iterator.CloseableIterator; import be.bagofwords.util.DataLock; import be.bagofwords.util.KeyValue; import be.bagofwords.util.SerializationUtils; import org.fusesource.leveldbjni.internal.JniDB; import org.fusesource.leveldbjni.internal.JniKeyDBIterator; import org.iq80.leveldb.*; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.Map; import static org.fusesource.leveldbjni.JniDBFactory.factory; public class LevelDBDataInterface<T> extends CoreDataInterface<T> { private DB db; private File databaseDir; private DataLock dataLock; public LevelDBDataInterface(String directory, String name, Class<T> objectClass, Combinator<T> combinator, boolean isTemporaryDataInterface) { super(name, objectClass, combinator, isTemporaryDataInterface); try { databaseDir = new File(directory + File.separator + name); if (!databaseDir.exists()) { databaseDir.mkdirs(); } db = factory.open(databaseDir, createOptions()); } catch (Exception exp) { throw new RuntimeException(exp); } dataLock = new DataLock(); } private Options createOptions() { Options options = new Options(); options.createIfMissing(true); return options; } @Override public void optimizeForReading() { db.compactRange(null, null); } @Override public T read(long key) { return SerializationUtils.bytesToObject(db.get(SerializationUtils.longToBytes(key)), getObjectClass()); } @Override public void write(long key, T value) { dataLock.lockWrite(key); if (value == null) { db.delete(SerializationUtils.longToBytes(key)); } else { byte[] keyInBytes = SerializationUtils.longToBytes(key); byte[] currentValueInBytes = db.get(keyInBytes); T valueToWrite; if (currentValueInBytes == null) { valueToWrite = value; } else { valueToWrite = getCombinator().combine(SerializationUtils.bytesToObject(currentValueInBytes, getObjectClass()), value); } db.put(keyInBytes, SerializationUtils.objectToBytes(valueToWrite, getObjectClass())); } dataLock.unlockWrite(key); } @Override public CloseableIterator<Long> keyIterator() { final JniKeyDBIterator iterator = ((JniDB) db).keyIterator(new ReadOptions()); iterator.seekToFirst(); return new CloseableIterator<Long>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Long next() { byte[] entry = iterator.next(); return SerializationUtils.bytesToLong(entry); } @Override public void remove() { throw new RuntimeException("Not implemented"); } @Override public void closeInt() { iterator.close(); } }; } @Override public CloseableIterator<KeyValue<T>> iterator() { final DBIterator iterator = db.iterator(); iterator.seekToFirst(); return new CloseableIterator<KeyValue<T>>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public KeyValue<T> next() { Map.Entry<byte[], byte[]> entry = iterator.next(); return new KeyValue<>(SerializationUtils.bytesToLong(entry.getKey()), SerializationUtils.bytesToObject(entry.getValue(), getObjectClass())); } @Override public void remove() { throw new RuntimeException("Not implemented"); } @Override public void closeInt() { //Closing the iterator here results in 'pthread lock: Invalid argument' and termination of the JVM. //Is there a big memory leak because we don't close the iterator now? // try { // iterator.close(); // } catch (IOException e) { // throw new RuntimeException(e); // } } }; } @Override public void dropAllData() { dataLock.lockWriteAll(); Options options = new Options(); try { db.close(); factory.destroy(databaseDir, options); options = new Options(); options.createIfMissing(true); db = factory.open(databaseDir, options); } catch (IOException e) { throw new RuntimeException(e); } dataLock.unlockWriteAll(); } @Override public void flush() { //Always flushed } @Override protected void doClose() { try { db.close(); } catch (IOException e) { throw new RuntimeException(e); } db = null; } @Override public long apprSize() { //TODO: find faster method. Tried already using db.getApproximateSizes() but gave bad results... return exactSize(); } @Override public synchronized void write(Iterator<KeyValue<T>> entries) { WriteBatch writeBatch = db.createWriteBatch(); while (entries.hasNext()) { KeyValue<T> entry = entries.next(); long key = entry.getKey(); byte[] keyInBytes = SerializationUtils.longToBytes(key); byte[] currentValueInBytes = db.get(keyInBytes); T valueToWrite; if (currentValueInBytes == null || entry.getValue() == null) { valueToWrite = entry.getValue(); } else { valueToWrite = getCombinator().combine(SerializationUtils.bytesToObject(currentValueInBytes, getObjectClass()), entry.getValue()); } if (valueToWrite == null) { writeBatch.delete(keyInBytes); } else { writeBatch.put(keyInBytes, SerializationUtils.objectToBytes(valueToWrite, getObjectClass())); } } db.write(writeBatch); try { writeBatch.close(); } catch (IOException e) { throw new RuntimeException(e); } } }