/** * 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.bdb; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Logger; import kinetic.simulator.SimulatorConfiguration; import com.google.protobuf.ByteString; import com.seagate.kinetic.common.lib.Hmac; 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.KVKey; import com.seagate.kinetic.simulator.persist.KVValue; import com.sleepycat.bind.EntryBinding; import com.sleepycat.collections.StoredSortedMap; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.Durability; import com.sleepycat.je.Environment; import com.sleepycat.je.EnvironmentConfig; import com.sleepycat.je.Transaction; public final class KVStore { private final static java.util.logging.Logger logger = Logger .getLogger(KVStore.class.getName()); private SimulatorConfiguration config = null; // private KVStore me = new KVStore(); private final EnvironmentConfig envConfig = new EnvironmentConfig(); private Environment myDbEnvironment = null; private Database myDatabase = null; private final DatabaseConfig dbConfig = new DatabaseConfig(); private String kineticDbName = null; private String persistFolder = null; static DatabaseEntry dbe(String s) { ByteString bs = ByteString.copyFromUtf8(s); DatabaseEntry dbe = new DatabaseEntry(); dbe.setData(bs.toByteArray()); return dbe; } static String toString(DatabaseEntry dbe) { if (dbe.getData() == null) return new String(""); return new String(dbe.getData()); } private StoredSortedMap<KVKey, KVValue> v = null; public KVStore(SimulatorConfiguration config) { this.config = config; this.init(); } private void init() { 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, "bdb"); 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); } // allow fresh. envConfig.setAllowCreate(true); // conservative. envConfig.setDurability(Durability.COMMIT_SYNC); // Persistent (needed to make the data stay around) envConfig.setTransactional(true); // envConfig.setLockTimeout(10, java.util.concurrent.TimeUnit.SECONDS); myDbEnvironment = new Environment(new File(persistFolder), envConfig); // allow fresh. dbConfig.setAllowCreate(true); // Persistent (needed to make the data stay around) dbConfig.setTransactional(true); kineticDbName = "kineticDatabase"; myDatabase = myDbEnvironment .openDatabase(null, kineticDbName, dbConfig); v = new StoredSortedMap<KVKey, KVValue>(myDatabase, new KeyBinding(), new ValueBinding(), true); } // TODO should I put this in a destructor?!?! public void close() { logger.fine("Temporary == " + dbConfig.getTemporary()); myDatabase.close(); logger.fine("database closed1"); logger.fine("database closed2"); myDbEnvironment.close(); logger.fine("database closed3"); } public void closeEvn() { myDbEnvironment.close(); logger.fine("myDbEnvironment closed"); } public synchronized void removeDatabase() throws InterruptedException { myDatabase.close(); Transaction txn = myDbEnvironment.beginTransaction(null, null); myDbEnvironment.removeDatabase(txn, kineticDbName); txn.commit(); } private KVKey KvkOf(ByteString k) { return new KVKey(k); } static class KeyBinding implements EntryBinding<KVKey> { @Override public KVKey entryToObject(DatabaseEntry arg0) { return new KVKey(arg0.getData()); } @Override public void objectToEntry(KVKey arg0, DatabaseEntry arg1) { arg1.setData(arg0.getKey()); } } static class ValueBinding implements EntryBinding<KVValue> { ValueBinding() { } @Override public KVValue entryToObject(DatabaseEntry dbe) { return new KVValue(dbe.getData()); } @Override public void objectToEntry(KVValue kvv, DatabaseEntry dbe) { dbe.setData(kvv.toByteArray()); } } public synchronized KVValue get(ByteString key) throws KVStoreNotFound { KVValue object = v.get(KvkOf(key)); if (object == null) throw new KVStoreNotFound(); return object; } // 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(); } public synchronized KVValue getNext(ByteString key) throws KVStoreException { SortedMap<KVKey, KVValue> m = v.tailMap(KvkOf(key), false); KVKey key1 = m.firstKey(); if (key1 == null) throw new KVStoreNotFound(); return v.get(key1); } public synchronized SortedMap<KVKey, KVValue> getRange(ByteString k1, boolean i1, ByteString k2, boolean i2, int n) throws KVStoreException { logger.fine("Key 1: " + Hmac.toString(k1) + "Key 2: " + Hmac.toString(k2)); SortedMap<KVKey, KVValue> m; if (k2.size() == 0) m = v.tailMap(KvkOf(k1), i1); else m = v.subMap(KvkOf(k1), i1, KvkOf(k2), i2); logger.fine("Number of entries: " + m.size() + " requesting " + n); // if it is more entries than we need, copy only the first n. if (m.size() > n) { SortedMap<KVKey, KVValue> m1 = new TreeMap<KVKey, KVValue>(); for (Entry<KVKey, KVValue> e : m.entrySet()) { if (n-- > 0) m1.put(e.getKey(), e.getValue()); else return m1; } } return m; } public synchronized List<KVKey> getRangeReversed(ByteString k1, boolean i1, ByteString k2, boolean i2, int n) throws KVStoreException { logger.fine("Key 1: " + Hmac.toString(k1) + "Key 2: " + Hmac.toString(k2)); SortedMap<KVKey, KVValue> m; if (k2.size() == 0) m = v.tailMap(KvkOf(k1), i1); else m = v.subMap(KvkOf(k1), i1, KvkOf(k2), i2); logger.fine("Number of entries: " + m.size() + " requesting " + n); // if it is more entries than we need, copy only the first n. List<KVKey> kvKeyOfList = new ArrayList<KVKey>(); int i = 0, j = 0; int size = m.size() > n ? n : m.size(); KVKey[] keys = new KVKey[size]; if (m.size() > n) { for (Entry<KVKey, KVValue> e : m.entrySet()) { if (i++ >= m.size() - n) keys[j++] = e.getKey(); } } else { for (Entry<KVKey, KVValue> e : m.entrySet()) { keys[i++] = e.getKey(); } } for (i = size - 1; i >= 0; i--) { kvKeyOfList.add(keys[i]); } return kvKeyOfList; } public synchronized KVValue getPrevious(ByteString key) throws KVStoreException { SortedMap<KVKey, KVValue> m = v.headMap(KvkOf(key), false); KVKey key1 = m.lastKey(); if (key1 == null) throw new KVStoreNotFound(); return v.get(key1); } // This adds/replaces an entry. throw if there is a version mismatch. public synchronized void put(ByteString key, ByteString oldVersion, KVValue value) throws KVStoreVersionMismatch { ByteString version = null; KVValue obj = v.get(KvkOf(key)); if (obj != null) {// exists version = obj.getVersion(); } else { logger.fine("Key does not exist"); } checkVersion(version, oldVersion); SimulatorEngine.logBytes("put, key", KvkOf(key).getKey()); value.setKeyOf(key); v.put(KvkOf(key), value); } public synchronized void putForced(ByteString key, KVValue value) throws KVStoreException { try { SimulatorEngine.logBytes("put force, key", KvkOf(key).getKey()); value.setKeyOf(key); v.put(KvkOf(key), value); } catch (Exception e) { throw new KVStoreException("DB internal exception"); } } private int mySize(ByteString s) { if (s == null) return 0; return s.size(); } private void checkVersion(ByteString version, ByteString oldVersion) throws KVStoreVersionMismatch { logger.fine("Compare len: " + mySize(version) + ", " + 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"); } // TODO need to test the versions. public synchronized void delete(ByteString key, ByteString oldVersion) throws KVStoreException { ByteString prevVersion = getVersion(key); checkVersion(prevVersion, oldVersion); v.remove(KvkOf(key)); } public synchronized void deleteForced(ByteString key) throws KVStoreException { try { v.remove(KvkOf(key)); } catch (Exception e) { throw new KVStoreException("DB internal exception"); } } /** * Get persist store path. * * @return persist store path. * @throws KVStoreException * if any internal error occurred. */ public String getPersistStorePath() throws KVStoreException { return this.persistFolder; } }