/*
* Concept profile generation tool suite
* Copyright (C) 2015 Biosemantics Group, Erasmus University Medical Center,
* Rotterdam, The Netherlands
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package org.erasmusmc.storecaching;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
public class SleepyCatStoreCache<K,V> extends StoreMapCaching<K, V> implements Iterable<Map.Entry<K, V>>{
private String databaseName = "cache";
private EnvironmentConfig environmentConfig;
private Environment environment;
private DatabaseConfig databaseConfig;
private Database database;
private int cachesize = 1000000;
private Shutdown sh;
protected TupleBinding keyBinding;
protected TupleBinding valueBinding;
private File datadir;
protected V notInDatabase = null;
@Override
protected Map<K, V> getEntriesFromStoreWithIDs(Collection<K> ids) {
Map<K, V> result = new HashMap<K, V>();
for (K id : ids)
result.put(id, getEntryFromStoreWithID(id));
return result;
}
public void clearCache() {
closeDatabase();
for (File file : datadir.listFiles())
file.delete();
try {
initialize();
} catch (DatabaseException e) {
e.printStackTrace();
}
}
public void flush(){
try {
database.sync();
} catch (DatabaseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void closeDatabase() {
try {
if (database != null) {
database.close();
}
if (environment != null) {
environment.cleanLog(); // Clean the log before closing
environment.close();
}
} catch (DatabaseException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
@Override
protected V getEntryFromStoreWithID(K id) {
DatabaseEntry theKey = new DatabaseEntry();
keyBinding.objectToEntry(id, theKey);
DatabaseEntry theValue = new DatabaseEntry();
try {
database.get(null, theKey, theValue, LockMode.DEFAULT);
} catch (DatabaseException e) {
e.printStackTrace();
}
if (theValue.getSize() != 0)
return (V) valueBinding.entryToObject(theValue);
else
return notInDatabase;
}
@Override
protected void setEntryInStore(K id, V value) {
try {
DatabaseEntry theKey = new DatabaseEntry();
keyBinding.objectToEntry(id, theKey);
DatabaseEntry theValue = new DatabaseEntry();
valueBinding.objectToEntry(value, theValue);
database.put(null, theKey,theValue);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setSkippingCache(K id, V value){
setEntryInStore(id, value);
}
@Override
public int size() {
System.err.println("Calling unimplemented method size()");
return 0;
}
public SleepyCatStoreCache(String cacheFolder) throws DatabaseException{
datadir = new File(cacheFolder);
if( !datadir.exists())
datadir.mkdir();
initialize();
}
private void initialize() throws DatabaseException{
environmentConfig = new EnvironmentConfig();
environmentConfig.setAllowCreate(true);
environmentConfig.setTransactional(false);
environmentConfig.setCacheSize(cachesize);
// perform other environment configurations
environment = new Environment(datadir, environmentConfig);
databaseConfig = new DatabaseConfig();
databaseConfig.setAllowCreate(true);
databaseConfig.setTransactional(false);
// perform other database configurations
openDB();
// add a shutdown hook to close the database when the VM is terminated
sh = new Shutdown();
sh.cache = this;
Runtime.getRuntime().addShutdownHook(sh);
}
public void close(){
this.closeDatabase();
Runtime.getRuntime().removeShutdownHook(sh);
}
private void openDB() {
try {
database = environment.openDatabase(null, databaseName, databaseConfig);
} catch (DatabaseException e) {
e.printStackTrace();
}
}
public Iterator<Map.Entry<K, V>> iterator() {
return new CacheIterator();
}
public Iterator<V> valueIterator(){
return new ValueIterator();
}
private class ValueIterator implements Iterator<V>{
private CacheIterator cacheIterator;
public ValueIterator(){
cacheIterator = new CacheIterator();
}
public boolean hasNext() {
return cacheIterator.hasNext();
}
public V next() {
return cacheIterator.next().getValue();
}
public void remove() {
System.err.println("Method not implemented");
}
}
private class CacheIterator implements Iterator<Map.Entry<K, V>> {
Cursor myCursor;
DatabaseEntry foundKey = new DatabaseEntry();
DatabaseEntry foundData = new DatabaseEntry();
Map.Entry<K, V> next = null;
Boolean toggle = false;
public CacheIterator() {
try {
myCursor = database.openCursor(null, null);
} catch (DatabaseException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
public boolean hasNext() {
Boolean result = false;
if (myCursor != null) {
try {
if (!toggle) {
if (myCursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
K key = (K) keyBinding.entryToObject(foundKey);
V value = (V) valueBinding.entryToObject(foundData);
next = new Entry(key, value);
toggle = true;
result = true;
}
else {
toggle = true;
next = null;
myCursor.close();
}
}
else {
result = true;
}
} catch (DatabaseException e) {
e.printStackTrace();
}
}
return result;
}
@SuppressWarnings("unchecked")
public Map.Entry<K, V> next() {
Map.Entry<K, V> result = null;
if (myCursor != null) {
if (toggle) {
result = next;
}
else {
try {
if (myCursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
K key = (K) keyBinding.entryToObject(foundKey);
V value = (V) valueBinding.entryToObject(foundData);
result = new Entry(key, value);
}
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
toggle = false;
return result;
}
public void remove() {
System.out.println("Remove is not implemented for iterator!");
}
private class Entry implements Map.Entry<K, V>{
private K key;
private V value;
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
this.value = value;
return value;
}
public Entry(K key, V value){
this.key = key;
this.value = value;
}
}
}
protected class Shutdown extends Thread {
public SleepyCatStoreCache<K, V> cache;
public void run() {
cache.closeDatabase();
System.out.println("Shutdown hook called!");
}
}
}