/**
* Copyright 2013-2015 Seagate Technology LLC.
*
* This Source Code Form is subject to the terms of the Mozilla
* Public License, v. 2.0. If a copy of the MPL was not
* distributed with this file, You can obtain one at
* https://mozilla.org/MP:/2.0/.
*
* This program is distributed in the hope that it will be useful,
* but is provided AS-IS, WITHOUT ANY WARRANTY; including without
* the implied warranty of MERCHANTABILITY, NON-INFRINGEMENT or
* FITNESS FOR A PARTICULAR PURPOSE. See the Mozilla Public
* License for more details.
*
* See www.openkinetic.org for more project information
*/
package com.seagate.kinetic.simulator.persist.kyoto;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import kinetic.simulator.SimulatorConfiguration;
import kyotocabinet.Cursor;
import kyotocabinet.DB;
import com.google.protobuf.ByteString;
import com.seagate.kinetic.simulator.internal.KVStoreException;
import com.seagate.kinetic.simulator.internal.KVStoreNotFound;
import com.seagate.kinetic.simulator.internal.KVStoreVersionMismatch;
import com.seagate.kinetic.simulator.internal.SimulatorEngine;
import com.seagate.kinetic.simulator.persist.BatchOperation;
import com.seagate.kinetic.simulator.persist.KVKey;
import com.seagate.kinetic.simulator.persist.KVValue;
import com.seagate.kinetic.simulator.persist.PersistOption;
import com.seagate.kinetic.simulator.persist.Store;
/**
*
* Kyoto Cabinet store implementation.
*
* Prototype quality.
*
* XXX chiaming 12/24/2013: support PersistOption
*
* @author chiaming
*
*/
public class KyotoCabinet implements Store<ByteString, ByteString, KVValue> {
private final static java.util.logging.Logger logger = Logger
.getLogger(KyotoCabinet.class.getName());
private DB db = null;
private String persistFolder = null;
public KyotoCabinet() {
;
}
@Override
public void init(SimulatorConfiguration config) {
String defaultHome = System.getProperty("user.home") + File.separator
+ "kinetic";
String kineticHome = config.getProperty(
SimulatorConfiguration.KINETIC_HOME, defaultHome);
File lchome = new File(kineticHome);
if (lchome.exists() == false) {
boolean created = lchome.mkdir();
logger.info("create kinetic home folder: " + kineticHome
+ ", created=" + created);
}
persistFolder = kineticHome
+ File.separator
+ config.getProperty(SimulatorConfiguration.PERSIST_HOME,
"kyoto");
File f = new File(persistFolder);
logger.info("Database file exists: " + f.exists() + ", name="
+ persistFolder);
if (f.exists() == false) {
boolean created = f.mkdir();
logger.info("create persist folder: " + persistFolder
+ ", created=" + created);
}
// db file
String dbFile = persistFolder + "/kyoto.kct";
db = new DB();
// open the database ( file tree database)
if (!db.open(dbFile, DB.OWRITER | DB.OCREATE | DB.OAUTOTRAN)) {
logger.info("open error: " + db.error() + ", db file=" + dbFile);
} else {
logger.info("opened kyoto cabinet db, file=" + dbFile);
}
}
@Override
public void put(ByteString key, ByteString oldVersion, KVValue value,
PersistOption option) throws KVStoreException {
ByteString version = null;
byte[] keyArray = key.toByteArray();
byte[] data = db.get(keyArray);
KVValue obj = null;
logger.finest("put versioned ...., data =" + data);
if (data != null) {
obj = new KVValue(data);
version = obj.getVersion();
// checkVersion(version, oldVersion);
}
SimulatorEngine.logBytes("put, key", KvkOf(key).getKey());
checkVersion(version, oldVersion);
value.setKeyOf(key);
db.set(keyArray, value.toByteArray());
}
@Override
public void putForced(ByteString key, KVValue value, PersistOption option)
throws KVStoreException {
byte[] keyArray = key.toByteArray();
value.setKeyOf(key);
db.set(keyArray, value.toByteArray());
}
@Override
public void delete(ByteString key, ByteString oldVersion,
PersistOption option) throws KVStoreException {
ByteString prevVersion = getVersion(key);
checkVersion(prevVersion, oldVersion);
db.remove(key.toByteArray());
}
@Override
public void deleteForced(ByteString key, PersistOption option)
throws KVStoreException {
db.remove(key.toByteArray());
}
@Override
public KVValue get(ByteString key) throws KVStoreException {
byte[] keyArray = key.toByteArray();
byte[] data = db.get(keyArray);
if (data == null) {
throw new KVStoreNotFound();
}
return new KVValue(data);
}
@Override
public KVValue getPrevious(ByteString key) throws KVStoreException {
Cursor cursor = null;
KVValue value = null;
try {
cursor = db.cursor();
boolean exist = cursor.jump(key.toByteArray());
exist = cursor.step_back();
if (exist) {
byte[] data = cursor.get_value(false);
value = new KVValue(data);
// logger.info("get previous has value ...");
} else {
throw new KVStoreNotFound();
}
} catch (KVStoreNotFound kvnf) {
throw kvnf;
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
throw new KVStoreException(e.getMessage());
} finally {
cursor.disable();
}
return value;
}
@Override
public KVValue getNext(ByteString key) throws KVStoreException {
// TODO Auto-generated method stub
Cursor cursor = null;
KVValue value = null;
byte[] bkey = key.toByteArray();
try {
cursor = db.cursor();
// if (db.check(bkey) < 0) {
// return this.scanFindNext(cursor, bkey);
// }
boolean exist = cursor.jump(bkey);
exist = cursor.step();
if (exist) {
byte[] key2 = cursor.get_key(false);
logger.finest("get next, key= " + key.toStringUtf8()
+ ", store key2="
+ ByteString.copyFrom(key2).toStringUtf8());
byte[] data = cursor.get_value(false);
logger.finest("get next store data ="
+ ByteString.copyFrom(data).toStringUtf8());
value = new KVValue(data);
} else {
throw new KVStoreNotFound();
}
} catch (KVStoreNotFound kvsnf) {
throw kvsnf;
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
throw new KVStoreException(e.getMessage());
} finally {
cursor.disable();
}
return value;
}
@Override
public SortedMap<?, ?> getRange(ByteString startKey,
boolean startKeyInclusive, ByteString endKey,
boolean endKeyInclusive, int max) throws KVStoreException {
SortedMap<KVKey, KVValue> map = new TreeMap<KVKey, KVValue>();
byte[] start = startKey.toByteArray();
byte[] end = endKey.toByteArray();
Cursor cursor = null;
try {
cursor = db.cursor();
boolean exist = cursor.jump(start);
if (startKeyInclusive == false) {
exist = cursor.step();
}
// first key there?
boolean more = exist;
while (more) {
byte[][] pair = cursor.get(true);
if (pair != null) {
// check should we put the pair in the map
if (shouldInclude(pair[0], end, endKeyInclusive, false)) {
map.put(new KVKey(pair[0]), new KVValue(pair[1]));
} else {
// exit loop
more = false;
}
} else {
more = false;
}
// check if reached max size
if (map.size() == max) {
more = false;
}
}
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
throw new KVStoreException(e.getMessage());
} finally {
cursor.disable();
}
return map;
}
@Override
public List<?> getRangeReversed(ByteString startKey,
boolean startKeyInclusive, ByteString endKey,
boolean endKeyInclusive, int maxReturned) throws KVStoreException {
byte[] start = startKey.toByteArray();
byte[] end = endKey.toByteArray();
List<KVKey> listOfKVKey = new ArrayList<KVKey>();
Cursor cursor = null;
try {
cursor = db.cursor();
boolean exist = cursor.jump_back(end);
if (endKeyInclusive == false) {
exist = cursor.step_back();
}
// last key there?
boolean more = exist;
while (more) {
byte[] key = cursor.get_key(false);
if (key != null) {
// check should we put the pair in the map
if (shouldInclude(key, start, startKeyInclusive, true)) {
listOfKVKey.add(new KVKey(key));
} else {
// exit loop
more = false;
}
} else {
more = false;
}
// check if reached max size
if (listOfKVKey.size() >= maxReturned) {
if (listOfKVKey.size() > maxReturned) {
listOfKVKey.remove(listOfKVKey.size() - 1);
} else {
more = false;
}
} else {
more = cursor.step_back();
}
}
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
throw new KVStoreException(e.getMessage());
} finally {
cursor.disable();
}
return listOfKVKey;
}
@Override
public void close() {
this.db.close();
logger.info("Kyoto db close ...");
}
@Override
public void reset() throws KVStoreException {
this.db.clear();
}
private KVKey KvkOf(ByteString k) {
return new KVKey(k);
}
private void checkVersion(ByteString version, ByteString oldVersion)
throws KVStoreVersionMismatch {
logger.finest("Compare len, version size= " + mySize(version)
+ ", ols version size=" + mySize(oldVersion));
if (mySize(version) != mySize(oldVersion)) {
throw new KVStoreVersionMismatch("Length mismatch");
}
if (mySize(version) == 0)
return;
if (!version.equals(oldVersion)) {
throw new KVStoreVersionMismatch("Compare mismatch");
}
}
private int mySize(ByteString s) {
if (s == null)
return 0;
return s.size();
}
// returns the version if it is in the db. Null otherwise.
ByteString getVersion(ByteString key) throws KVStoreException {
KVValue obj = get(key);
if (obj == null)
throw new KVStoreNotFound();
if (!obj.hasVersion())
return ByteString.EMPTY;
return obj.getVersion();
}
private static boolean shouldInclude(byte[] k1, byte[] endKey,
boolean inclusive, boolean reverse) {
int cv = compare(k1, endKey);
if (cv == 0) {
if (inclusive) {
return true;
}
}
return reverse ? (cv > 0) : (cv < 0);
}
/**
*
* @param left
* @param right
* @return
*/
public static int compare(byte[] left, byte[] right) {
int l1 = left.length;
int l2 = right.length;
int len = Math.min(l1, l2);
for (int i = 0; i < len; i++) {
int a = (left[i] & 0xff);
int b = (right[i] & 0xff);
if (a != b) {
return a - b;
}
}
return left.length - right.length;
}
@Override
public BatchOperation<ByteString, KVValue> createBatchOperation()
throws KVStoreException {
// TODO Auto-generated method stub
throw new java.lang.UnsupportedOperationException();
}
@Override
public void flush() throws KVStoreException {
logger.warning("Flush is not yet implemented.");
}
@Override
public void compactRange(ByteString startKey, ByteString endKey)
throws KVStoreException {
logger.warning("method is not yet implemented.");
}
@Override
public String getPersistStorePath() throws KVStoreException {
return this.persistFolder;
}
}