package io.conducive.server.db.impl;
import com.google.common.collect.Lists;
import io.conducive.server.bind.DBFile;
import org.mapdb.DB;
import org.mapdb.Fun;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.File;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.util.*;
import static com.google.common.base.Preconditions.*;
/**
* @author Reuben Firmin
*/
public class AbstractMapDBDao {
final Logger logger = LoggerFactory.getLogger(getClass());
final File dbFile;
final DB db;
@Inject
public AbstractMapDBDao(@DBFile final File dbFile, final DB db) {
this.dbFile = dbFile;
this.db = db;
}
protected void commit() {
db.commit();
}
public boolean enoughDiskSpace() {
try {
FileStore fs = Files.getFileStore(dbFile.toPath());
// we want 20% of the partition free
return ((double) fs.getTotalSpace() / (double) fs.getUsableSpace()) > 1.25;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
/**
* Create / retrieve a one to many collection.
*/
protected <S, T> NavigableSet<Fun.Tuple2<S, T>> oneToMany(final String name) {
return db.getTreeSet(name);
}
/**
* Create / retrieve a one to one collection.
*/
protected <S, T> NavigableMap<S, T> oneToOne(final String name) {
return db.getTreeMap(name);
}
/**
* Get items associated with this key.
*/
protected <S, T> Iterable<T> get(NavigableSet<Fun.Tuple2<S, T>> oneToMany, S key) {
return Fun.filter(oneToMany, key);
}
/**
* Get single item associated with this key.
*/
protected <S, T> T get(NavigableMap<S, T> oneToOne, S key) {
return oneToOne.get(key);
}
/**
* Put an item into the oneToMany.
*/
protected <S, T> void put(NavigableSet<Fun.Tuple2<S, T>> oneToMany, S key, T item) {
remove(oneToMany, key, item);
oneToMany.add(Fun.t2(key, item));
}
/**
* Put an item into the oneToOne.
*/
protected <S, T> void put(NavigableMap<S, T> oneToOne, S key, T item) {
oneToOne.put(key, item);
}
/**
* Remove an item.
*/
protected <S, T> void remove(NavigableSet<Fun.Tuple2<S, T>> oneToMany, S key, T item) {
oneToMany.remove(Fun.t2(key, item));
}
/**
* Remove all items mapped to this key, and the key itself.
*/
protected <S, T> void remove(NavigableSet<Fun.Tuple2<S, T>> oneToMany, S key) {
List<T> ts = Lists.newLinkedList(get(oneToMany, key));
for (T t : ts) {
remove(oneToMany, key, t);
}
}
/**
* Remove an item.
*/
protected <S, T> void remove(NavigableMap<S, T> oneToOne, S key) {
oneToOne.remove(key);
}
/**
* Check if a mapping exists.
* @return
*/
protected <S, T> boolean exists(NavigableSet<Fun.Tuple2<S, T>> oneToMany, S key, T item) {
Iterable<T> mapped = get(oneToMany, key);
if (mapped != null) {
for (T mappedItem : mapped) {
if (item.equals(mappedItem)) {
return true;
}
}
}
return false;
}
protected <S> boolean exists(NavigableMap<S, ?> oneToOne, S key) {
return oneToOne.get(key) != null;
}
// ------------------ EXPERIMENTAL STUFF BELOW -------------------------------------- //
/**
* TODO - in progress (experimental)
* Create / retrieve a model store. This is a map of id -> entity.
*/
protected <E extends Entity> LookupTable<String, E> table(final Class<E> entityClazz) {
return new LookupTable<String, E>() {
Map<String, E> map = oneToOne(entityClazz.getName());
@Override
public E get(String s) {
checkNotNull(s);
return map.get(s);
}
@Override
public void put(String s, E e) {
checkNotNull(s);
checkNotNull(e);
checkNotNull(e.getId());
map.put(s, e);
}
};
}
/**
* TODO - in progress
* Create / retrieve a structure that maps keys to models.
* @return
*/
protected <K, E extends Entity> LookupTable<K, E> lookup(final Class<E> entityClazz, final Class<K> keyClazz) {
if (Entity.class.isAssignableFrom(keyClazz)) {
throw new RuntimeException("Use foreignKey instead");
}
return new LookupTable<K, E>() {
Map<K, String> map = oneToOne(entityClazz.getName() + "_" + keyClazz.getName());
@Override
public E get(K k) {
checkNotNull(k);
return table(entityClazz).get(map.get(k));
}
@Override
public void put(K k, E e) {
checkNotNull(k);
checkNotNull(e.getId());
// make sure we're storing it in the main datastore
checkNotNull(AbstractMapDBDao.this.<E>table(entityClazz).get(e.getId()));
map.put(k, e.getId());
}
};
}
/**
* TODO - in progress
* Create / retrieve a structure that maps one entity to another.
* @return
*/
protected <K extends Entity, E extends Entity> LookupTable<K, E> foreignKey(final Class<K> keyEntityClass, final Class<E> entityClass) {
return new LookupTable<K, E>() {
// key id -> entity id
Map<String, String> map = oneToOne(keyEntityClass.getName() + "_" + entityClass.getName());
@Override
public E get(K k) {
checkNotNull(k.getId());
if (AbstractMapDBDao.this.<K>table(keyEntityClass).get(k.getId()) == null) {
throw new RuntimeException("Must store key prior to using it for lookup");
}
return AbstractMapDBDao.this.<E>table(entityClass).get(map.get(k.getId()));
}
@Override
public void put(K k, E e) {
if (AbstractMapDBDao.this.<K>table(keyEntityClass).get(k.getId()) == null) {
throw new RuntimeException("Must store key prior to using it for mapping");
}
map.put(k.getId(), e.getId());
}
};
}
}