package kvstore;
import static kvstore.KVConstants.ERROR_NO_SUCH_KEY;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import kvstore.xml.KVPairType;
import kvstore.xml.KVStoreType;
import kvstore.xml.ObjectFactory;
/**
* This is a basic key-value store. Ideally this would go to disk, or some other
* backing store.
*/
public class KVStore implements KeyValueInterface {
public ConcurrentHashMap<String, String> store;
/**
* Construct a new KVStore.
*/
public KVStore() {
resetStore();
}
public void resetStore() {
this.store = new ConcurrentHashMap<String, String>();
}
/**
* Insert key, value pair into the store.
*
* @param key String key
* @param value String value
*/
@Override
public void put(String key, String value) {
store.put(key, value);
}
/**
* Retrieve the value corresponding to the provided key
* @param key String key
* @throws KVException with ERROR_NO_SUCH_KEY if key does not exist in store
*/
@Override
public String get(String key) throws KVException {
String retVal = this.store.get(key);
if (retVal == null) {
KVMessage msg = new KVMessage(KVConstants.RESP, ERROR_NO_SUCH_KEY);
throw new KVException(msg);
}
return retVal;
}
/**
* Delete the value corresponding to the provided key.
*
* @param key String key
* @throws KVException with ERROR_NO_SUCH_KEY if key does not exist in store
*/
@Override
public void del(String key) throws KVException {
if(key != null) {
if (!this.store.containsKey(key)) {
KVMessage msg = new KVMessage(KVConstants.RESP, ERROR_NO_SUCH_KEY);
throw new KVException(msg);
}
this.store.remove(key);
}
}
private synchronized JAXBElement<KVStoreType> getXMLRoot() throws JAXBException {
ObjectFactory factory = new ObjectFactory();
KVStoreType xmlStore = factory.createKVStoreType();
for (Entry<String, String> e : store.entrySet()) {
KVPairType kvPair = factory.createKVPairType();
kvPair.setKey(e.getKey());
kvPair.setValue(e.getValue());
xmlStore.getKVPair().add(kvPair);
}
return factory.createKVStore(xmlStore);
}
private void marshalTo(OutputStream os) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(KVStoreType.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty("com.sun.xml.internal.bind.xmlHeaders", "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
marshaller.marshal(getXMLRoot(), os);
}
private KVStoreType unmarshal(File f) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
KVStoreType xmlStore = ((JAXBElement<KVStoreType>) unmarshaller.unmarshal(f)).getValue();
return xmlStore;
}
/**
* Serialize this store to XML. See the spec for specific output format.
* This method is best effort. Any exceptions that appear can be dropped.
*/
public String toXML() {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
marshalTo(os);
} catch (JAXBException e) {
e.printStackTrace();
}
return os.toString();
}
@Override
public String toString() {
return this.toXML();
}
/**
* Serialize to XML and write the output to a file.
* This method is best effort. Any exceptions that arise can be dropped.
*
* @param fileName the file to write the serialized store
*/
public void dumpToFile(String fileName) {
try {
File f = new File(fileName);
FileOutputStream os = new FileOutputStream(f);
marshalTo(os);
} catch (Exception e) {}
}
/**
* Replaces the contents of the store with the contents of a file
* written by dumpToFile; the previous contents of the store are lost.
* The store is cleared even if the file does not exist.
* This method is best effort. Any exceptions that arise can be dropped.
*
* @param fileName the file containing the serialized store data
*/
public void restoreFromFile(String fileName) {
resetStore();
try {
KVStoreType kvst = unmarshal(new File(fileName));
for(KVPairType pair : kvst.getKVPair())
put(pair.getKey(), pair.getValue());
} catch (Exception e) {}
}
}