/** * 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.memory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; 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.Level; import java.util.logging.Logger; import kinetic.simulator.SimulatorConfiguration; 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.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; /** * * Memory store implementation for the Kinetic simulator. * <p> * All entries are stored in memory. When a Kinetic server is closed, the memory * tree is saved to the disk. When a kinetic server is crashed, all data updated * to memory store between start server to crash is lost. * * @author chiaming * */ public class MemoryStore implements Store<ByteString, ByteString, KVValue> { private final static java.util.logging.Logger logger = Logger .getLogger(MemoryStore.class.getName()); // memory tree map private TreeMap<byte[], byte[]> sortedMap = null; // file to store the tree private String dbFile = null; // server config private SimulatorConfiguration config = null; // persist folder private String persistFolder = null; /** * default constructor */ public MemoryStore() { ; } @Override public synchronized void put(ByteString key, ByteString oldVersion, KVValue value, PersistOption pOption) throws KVStoreException { ByteString version = null; byte[] keyArray = key.toByteArray(); KVValue obj = null; byte[] valueInStore = this.sortedMap.get(keyArray); if (valueInStore != null) { obj = new KVValue(valueInStore); version = obj.getVersion(); } checkVersion(version, oldVersion); value.setKeyOf(key); this.sortedMap.put(keyArray, value.toByteArray()); } @Override public synchronized void putForced(ByteString key, KVValue value, PersistOption pOption) throws KVStoreException { try { value.setKeyOf(key); this.sortedMap.put(key.toByteArray(), value.toByteArray()); } catch (Exception e) { throw new KVStoreException("DB internal exception"); } } @Override public synchronized void delete(ByteString key, ByteString oldVersion, PersistOption pOption) throws KVStoreException { ByteString prevVersion = getVersion(key); checkVersion(prevVersion, oldVersion); sortedMap.remove(key.toByteArray()); } @Override public synchronized void deleteForced(ByteString key, PersistOption pOption) throws KVStoreException { try { sortedMap.remove(key.toByteArray()); } catch (Exception e) { throw new KVStoreException("DB internal exception"); } } @Override public synchronized KVValue get(ByteString key) throws KVStoreException { byte[] object = this.sortedMap.get(key.toByteArray()); if (object == null) throw new KVStoreNotFound(); return new KVValue(object); } @Override public synchronized KVValue getPrevious(ByteString key) throws KVStoreException { byte[] key1 = sortedMap.lowerKey(key.toByteArray()); if (key1 == null) throw new KVStoreNotFound(); return new KVValue(sortedMap.get(key1)); } @Override public synchronized KVValue getNext(ByteString key) throws KVStoreException { // logger.info("getNext key=" + key.toStringUtf8()); byte[] key1 = sortedMap.higherKey(key.toByteArray()); if (key1 == null) { throw new KVStoreNotFound(); } return new KVValue(sortedMap.get(key1)); } @Override public synchronized SortedMap<?, ?> getRange(ByteString startKey, boolean startKeyInclusive, ByteString endKey, boolean endKeyInclusive, int n) throws KVStoreException { SortedMap<byte[], byte[]> bmap; if (endKey.size() == 0) { bmap = sortedMap.tailMap(startKey.toByteArray(), startKeyInclusive); } else { bmap = sortedMap.subMap(startKey.toByteArray(), startKeyInclusive, endKey.toByteArray(), endKeyInclusive); } // logger.fine("Number of entries: " + bmap.size() + " requesting " + // n); // convert type SortedMap<KVKey, KVValue> kvmap = new TreeMap<KVKey, KVValue>(); for (Entry<byte[], byte[]> e : bmap.entrySet()) { if (n-- > 0) { kvmap.put(new KVKey(e.getKey()), new KVValue(e.getValue())); } else { // return kvmap; break; } } return kvmap; } @Override public synchronized List<?> getRangeReversed(ByteString startKey, boolean startKeyInclusive, ByteString endKey, boolean endKeyInclusive, int n) throws KVStoreException { SortedMap<byte[], byte[]> bmap; if (endKey.size() == 0) { bmap = sortedMap.tailMap(startKey.toByteArray(), startKeyInclusive); } else { bmap = sortedMap.subMap(startKey.toByteArray(), startKeyInclusive, endKey.toByteArray(), endKeyInclusive); } // logger.fine("Number of entries: " + bmap.size() + " requesting " + // n); List<KVKey> kvKeyOfList = new ArrayList<KVKey>(); int i = 0, j = 0; int size = bmap.size() > n ? n : bmap.size(); byte[][] keys = new byte[size][]; if (bmap.size() > n) { for (Entry<byte[], byte[]> e : bmap.entrySet()) { if (i++ >= bmap.size() - n) keys[j++] = e.getKey(); } } else { for (Entry<byte[], byte[]> e : bmap.entrySet()) { keys[i++] = e.getKey(); } } for (i = size - 1; i >= 0; i--) { kvKeyOfList.add(new KVKey(keys[i])); } return kvKeyOfList; } @Override public void close() { FileOutputStream fos = null; ObjectOutputStream oos = null; try { // get out put stream fos = new FileOutputStream(dbFile); oos = new ObjectOutputStream(fos); // write to file. oos.writeObject(this.sortedMap); oos.flush(); fos.flush(); logger.info("saved memory file, path=" + dbFile + ", entry count=" + this.sortedMap.size()); } catch (Exception e) { logger.log(Level.WARNING, e.getMessage(), e); } finally { try { oos.close(); fos.close(); } catch (Exception e2) { ; } } logger.info("memory store closed ..."); } @Override public void reset() throws KVStoreException { // clean data this.sortedMap.clear(); // this will write the reset map to file. this.close(); // reopen the file this.init(config); } private static int mySize(ByteString s) { if (s == null) return 0; return s.size(); } public static 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"); } // private KVKey KvkOf(ByteString k) { // return new KVKey(k); // } 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(); } @SuppressWarnings("unchecked") @Override public void init(SimulatorConfiguration config) { this.config = config; // default home folder String defaultHome = System.getProperty("user.home") + File.separator + "kinetic"; // kinetic home String kineticHome = config.getProperty( SimulatorConfiguration.KINETIC_HOME, defaultHome); File lchome = new File(kineticHome); // make folder if not there if (lchome.exists() == false) { boolean created = lchome.mkdir(); logger.info("create kinetic home folder: " + kineticHome + ", created=" + created); } // persist home persistFolder = kineticHome + File.separator + config.getProperty(SimulatorConfiguration.PERSIST_HOME, "memory"); File f = new File(persistFolder); logger.info("Database file exists: " + f.exists() + ", name=" + persistFolder); // create persist folder if not existed if (f.exists() == false) { boolean created = f.mkdir(); logger.info("create persist folder: " + persistFolder + ", created=" + created); } // db file dbFile = persistFolder + "/memStore.ser"; FileInputStream fis = null; ObjectInputStream ois = null; try { // read memory tree fis = new FileInputStream(dbFile); ois = new ObjectInputStream(fis); this.sortedMap = (TreeMap<byte[], byte[]>) ois.readObject(); logger.info("loaded memory file, path=" + dbFile + ", size=" + this.sortedMap.size()); } catch (Exception e) { // logger.log(Level.WARNING, e.getMessage(), e); logger.fine("unable to load memory store from file, using a new store, path=" + dbFile); // start a new one if unable to read from one on disk this.sortedMap = new TreeMap<byte[], byte[]>(new KeyComparator()); } finally { try { ois.close(); fis.close(); } catch (Exception e2) { ; } } } @Override public BatchOperation<ByteString, KVValue> createBatchOperation() throws KVStoreException { throw new java.lang.UnsupportedOperationException(); } @Override public void flush() throws KVStoreException { // no op for mem store ; } @Override public void compactRange(ByteString startKey, ByteString endKey) throws KVStoreException { // no op ; } @Override public String getPersistStorePath() throws KVStoreException { return this.persistFolder; } }