package org.openlca.core.database;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.openlca.core.model.AbstractEntity;
import org.openlca.core.model.descriptors.ActorDescriptor;
import org.openlca.core.model.descriptors.BaseDescriptor;
import org.openlca.core.model.descriptors.CategoryDescriptor;
import org.openlca.core.model.descriptors.CurrencyDescriptor;
import org.openlca.core.model.descriptors.DQSystemDescriptor;
import org.openlca.core.model.descriptors.FlowDescriptor;
import org.openlca.core.model.descriptors.FlowPropertyDescriptor;
import org.openlca.core.model.descriptors.ImpactCategoryDescriptor;
import org.openlca.core.model.descriptors.ImpactMethodDescriptor;
import org.openlca.core.model.descriptors.LocationDescriptor;
import org.openlca.core.model.descriptors.ParameterDescriptor;
import org.openlca.core.model.descriptors.ProcessDescriptor;
import org.openlca.core.model.descriptors.ProductSystemDescriptor;
import org.openlca.core.model.descriptors.ProjectDescriptor;
import org.openlca.core.model.descriptors.SocialIndicatorDescriptor;
import org.openlca.core.model.descriptors.SourceDescriptor;
import org.openlca.core.model.descriptors.UnitGroupDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
/**
* A loading cache for entities and descriptors. This cache is intended to be
* used for caching entities that are used very often (like unit groups or flow
* properties) and descriptors.
*/
public class EntityCache {
private Logger log = LoggerFactory.getLogger(getClass());
private LoadingCache<Key, Object> cache;
public static EntityCache create(IDatabase database) {
return new EntityCache(database);
}
private EntityCache(IDatabase database) {
cache = CacheBuilder.newBuilder().build(new Loader(database));
}
public <T> T get(Class<T> clazz, long id) {
try {
Object obj = cache.get(Key.get(clazz, id));
if (obj instanceof Optional)
return null;
return clazz.cast(obj);
} catch (Exception e) {
log.error("failed to get from cache " + clazz + " with id " + id,
e);
return null;
}
}
public <T> Map<Long, T> getAll(Class<T> clazz, Collection<Long> ids) {
List<Key> keys = new ArrayList<>(ids.size());
for (long id : ids) {
keys.add(Key.get(clazz, id));
}
try {
Map<Key, Object> values = cache.getAll(keys);
Map<Long, T> result = new HashMap<>(values.size());
for (Key key : values.keySet()) {
Object obj = values.get(key);
if (obj instanceof Optional)
continue;
result.put(key.id, clazz.cast(obj));
}
return result;
} catch (Exception e) {
log.error("failed to get entities from cache: " + clazz, e);
return Collections.emptyMap();
}
}
public void invalidate(Class<?> clazz, long id) {
cache.invalidate(Key.get(clazz, id));
}
public void refresh(Class<?> clazz, long id) {
cache.refresh(Key.get(clazz, id));
}
public void invalidateAll() {
cache.invalidateAll();
}
public void invalidateAll(Class<?> clazz, Collection<Long> ids) {
List<Key> keys = new ArrayList<>(ids.size());
for (long id : ids) {
keys.add(Key.get(clazz, id));
}
cache.invalidateAll(keys);
}
private static class Key {
private final Class<?> clazz;
private final long id;
private static Key get(Class<?> clazz, long id) {
return new Key(clazz, id);
}
private Key(Class<?> clazz, long id) {
this.clazz = clazz;
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null)
return false;
if (!(obj instanceof Key))
return false;
Key other = (Key) obj;
return this.id == other.id
&& Objects.equals(this.clazz, other.clazz);
}
@Override
public int hashCode() {
return Objects.hash(id, clazz);
}
}
private static class Loader extends CacheLoader<Key, Object> {
private Logger log = LoggerFactory.getLogger(getClass());
private final IDatabase database;
private final HashMap<Class<?>, BaseDao<?>> daos = new HashMap<>();
private final HashMap<Class<?>, RootEntityDao<?, ?>> descriptorDaos = new HashMap<>();
public Loader(IDatabase database) {
this.database = database;
registerDescriptorDaos(database);
}
/** Registers the DAOs for the descriptor types. */
private void registerDescriptorDaos(IDatabase db) {
log.trace("register descriptor DAOs");
HashMap<Class<?>, RootEntityDao<?, ?>> m = descriptorDaos;
m.put(ActorDescriptor.class, new ActorDao(db));
m.put(SourceDescriptor.class, new SourceDao(db));
m.put(UnitGroupDescriptor.class, new UnitGroupDao(db));
m.put(FlowPropertyDescriptor.class, new FlowPropertyDao(db));
m.put(FlowDescriptor.class, new FlowDao(db));
m.put(ProcessDescriptor.class, new ProcessDao(db));
m.put(ProductSystemDescriptor.class, new ProductSystemDao(db));
m.put(ImpactMethodDescriptor.class, new ImpactMethodDao(db));
m.put(ProjectDescriptor.class, new ProjectDao(db));
m.put(ImpactCategoryDescriptor.class, new ImpactCategoryDao(db));
m.put(SocialIndicatorDescriptor.class, new SocialIndicatorDao(db));
m.put(CurrencyDescriptor.class, new CurrencyDao(db));
m.put(LocationDescriptor.class, new LocationDao(db));
m.put(ParameterDescriptor.class, new ParameterDao(db));
m.put(DQSystemDescriptor.class, new DQSystemDao(db));
m.put(CategoryDescriptor.class, new CategoryDao(db));
}
@Override
public Map<Key, Object> loadAll(Iterable<? extends Key> keys)
throws Exception {
Multimap<Class<?>, Long> idMap = ArrayListMultimap.create();
for (Key key : keys)
idMap.put(key.clazz, key.id);
Set<Class<?>> classes = idMap.keySet();
HashMap<Key, Object> result = new HashMap<>();
for (Class<?> clazz : classes) {
Collection<Long> ids = idMap.get(clazz);
if (ids.isEmpty())
continue;
if (BaseDescriptor.class.isAssignableFrom(clazz))
loadDescriptors(clazz, ids, result);
else
loadFullEntities(clazz, ids, result);
}
for (Key key : keys) {
if (!result.containsKey(key))
result.put(key, Optional.absent());
}
return result;
}
private void loadFullEntities(Class<?> clazz, Collection<Long> ids,
HashMap<Key, Object> result) {
BaseDao<?> dao = getDao(clazz);
List<?> entities = dao.getForIds(new HashSet<>(ids));
for (Object obj : entities) {
AbstractEntity entity = (AbstractEntity) obj;
result.put(Key.get(clazz, entity.getId()), entity);
}
}
private void loadDescriptors(Class<?> clazz, Collection<Long> ids,
HashMap<Key, Object> result) {
RootEntityDao<?, ?> dao = descriptorDaos.get(clazz);
if (dao == null) {
log.error("unknown descriptor class {}, returning null", clazz);
return;
}
List<? extends BaseDescriptor> descriptors = dao
.getDescriptors(new HashSet<>(ids));
for (BaseDescriptor descriptor : descriptors)
result.put(Key.get(clazz, descriptor.getId()), descriptor);
}
@Override
public Object load(Key key) throws Exception {
if (key == null || key.clazz == null)
return null;
Object obj = null;
if (BaseDescriptor.class.isAssignableFrom(key.clazz))
obj = loadDescriptor(key);
else
obj = loadFull(key);
return obj != null ? obj : Optional.absent();
}
private Object loadDescriptor(Key key) {
RootEntityDao<?, ?> dao = descriptorDaos.get(key.clazz);
if (dao == null) {
log.error("unknown descriptor class {}, returning null",
key.clazz);
return null;
}
return dao.getDescriptor(key.id);
}
private Object loadFull(Key key) {
BaseDao<?> dao = getDao(key.clazz);
return dao.getForId(key.id);
}
private BaseDao<?> getDao(Class<?> clazz) {
BaseDao<?> dao = daos.get(clazz);
if (dao == null) {
log.trace("register class {}", clazz);
dao = new BaseDao<>(clazz, database);
daos.put(clazz, dao);
}
return dao;
}
}
}