/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.core.config.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import com.opengamma.core.AbstractEHCachingSource;
import com.opengamma.core.change.ChangeEvent;
import com.opengamma.core.change.ChangeListener;
import com.opengamma.core.config.ConfigSource;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.ehcache.EHCacheUtils;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
import com.opengamma.util.tuple.Triple;
/**
* EHCache backed {@link ConfigSource}.
* <p>
* The current implementation of {@link #getSingle} is incorrect; it will cache values queried with a version/correction containing "latest". This should only be used if the issues that such caching
* will cause are of less concern than the performance penalty of not caching the calls at all.
*/
public class EHCachingConfigSource extends AbstractEHCachingSource<ConfigItem<?>, ConfigSource> implements ConfigSource {
private final String _nameCacheName = getClass().getName() + "-name-cache";
private final String _classCacheName = getClass().getName() + "-class-cache";
private final Cache _nameCache;
private final Cache _classCache;
public EHCachingConfigSource(final ConfigSource underlying, final CacheManager cacheManager) {
super(underlying, cacheManager);
EHCacheUtils.addCache(cacheManager, _nameCacheName);
EHCacheUtils.addCache(cacheManager, _classCacheName);
_nameCache = EHCacheUtils.getCacheFromManager(cacheManager, _nameCacheName);
_classCache = EHCacheUtils.getCacheFromManager(cacheManager, _classCacheName);
// this is not nice, but it's better than a stale cache.
getUnderlying().changeManager().addChangeListener(new ChangeListener() {
@Override
public void entityChanged(ChangeEvent event) {
switch (event.getType()) {
case ADDED:
break;
case CHANGED:
_nameCache.flush();
_classCache.flush();
flush();
break;
case REMOVED:
_nameCache.flush();
_classCache.flush();
flush();
break;
default:
break;
}
}
});
}
private synchronized <R> void cacheNameHit(final Triple<Class<R>, String, VersionCorrection> key, final R value) {
Element element = _nameCache.get(key);
if (element == null) {
// Don't cache the single form if a collection (or another single) has already been written
_nameCache.put(new Element(key, ConfigItem.of(value, key.getSecond(), key.getFirst())));
}
}
private synchronized <R> void cacheNameHit(final Triple<Class<R>, String, VersionCorrection> key, final Collection<ConfigItem<R>> values) {
_nameCache.put(new Element(key, values));
}
private synchronized <R> void cacheNameMiss(final Triple<Class<R>, String, VersionCorrection> key) {
Element element = _nameCache.get(key);
if (element == null) {
_nameCache.put(new Element(key, Collections.<ConfigItem<?>>emptySet()));
}
}
@SuppressWarnings("unchecked")
@Override
public <R> Collection<ConfigItem<R>> get(final Class<R> clazz, final String configName, final VersionCorrection versionCorrection) {
if (versionCorrection.containsLatest()) {
// Not cacheable
return getUnderlying().get(clazz, configName, versionCorrection);
}
final Triple<Class<R>, String, VersionCorrection> nameKey = Triple.of(clazz, configName, versionCorrection);
Element element = _nameCache.get(nameKey);
if (element != null) {
if (element.getObjectValue() instanceof Collection) {
return (Collection<ConfigItem<R>>) element.getObjectValue();
}
}
final Collection<ConfigItem<R>> result;
final Pair<Class<R>, VersionCorrection> classKey = Pairs.of(clazz, versionCorrection);
element = _classCache.get(classKey);
if (element != null) {
result = new ArrayList<ConfigItem<R>>();
for (ConfigItem<?> item : (Collection<ConfigItem<?>>) element.getObjectValue()) {
if (configName.equals(item.getName()) && clazz.isAssignableFrom(item.getValue().getClass())) {
result.add((ConfigItem<R>) item);
}
}
} else {
result = getUnderlying().get(clazz, configName, versionCorrection);
}
cacheNameHit(nameKey, result);
return result;
}
@SuppressWarnings("unchecked")
@Override
public <R> Collection<ConfigItem<R>> getAll(final Class<R> clazz, final VersionCorrection versionCorrection) {
if ((versionCorrection == null) || versionCorrection.containsLatest()) {
// Not cacheable
return getUnderlying().getAll(clazz, versionCorrection);
}
final Pair<Class<R>, VersionCorrection> key = Pairs.of(clazz, versionCorrection);
final Element element = _classCache.get(key);
if (element != null) {
return (Collection<ConfigItem<R>>) element.getObjectValue();
}
Collection<ConfigItem<R>> result = getUnderlying().getAll(clazz, versionCorrection);
if (result == null) {
result = Collections.<ConfigItem<R>>emptySet();
}
_classCache.put(new Element(key, result));
return result.isEmpty() ? null : result;
}
@SuppressWarnings("unchecked")
@Override
public <R> R getConfig(final Class<R> clazz, final UniqueId uniqueId) {
final Object value = get(uniqueId).getValue();
if (clazz.isAssignableFrom(value.getClass())) {
return (R) value;
} else {
throw new IllegalArgumentException("The requested object is " + value.getClass() + ", not " + clazz);
}
}
@SuppressWarnings("unchecked")
@Override
public <R> R getConfig(final Class<R> clazz, final ObjectId objectId, final VersionCorrection versionCorrection) {
final Object value = get(objectId, versionCorrection).getValue();
if (clazz.isAssignableFrom(value.getClass())) {
return (R) value;
} else {
throw new IllegalArgumentException("The requested object is " + value.getClass() + ", not " + clazz);
}
}
@SuppressWarnings("unchecked")
@Override
public <R> R getSingle(final Class<R> clazz, final String configName, final VersionCorrection versionCorrection) {
// TODO: This isn't strictly cacheable, if versionCorrection contains latest, but performance can be terrible if we don't
final Triple<Class<R>, String, VersionCorrection> nameKey = Triple.of(clazz, configName, versionCorrection);
Element element = _nameCache.get(nameKey);
if (element == null) {
final Pair<Class<R>, VersionCorrection> classKey = Pairs.of(clazz, versionCorrection);
element = _classCache.get(classKey);
if (element != null) {
for (ConfigItem<?> item : (Collection<ConfigItem<?>>) element.getObjectValue()) {
if (configName.equals(item.getName()) && clazz.isAssignableFrom(item.getValue().getClass())) {
final R result = (R) item.getValue();
cacheNameHit(nameKey, result);
return result;
}
}
cacheNameMiss(nameKey);
return null;
}
final R result = getUnderlying().getSingle(clazz, configName, versionCorrection);
if (result != null) {
cacheNameHit(nameKey, result);
} else {
cacheNameMiss(nameKey);
}
return result;
}
Object value = element.getObjectValue();
if (value instanceof ConfigItem) {
final Object result = ((ConfigItem<?>) value).getValue();
if (clazz.isAssignableFrom(result.getClass())) {
return (R) result;
}
}
final Collection<ConfigItem<?>> results = (Collection<ConfigItem<?>>) value;
if (results.isEmpty()) {
return null;
}
value = results.iterator().next().getValue();
if (clazz.isAssignableFrom(value.getClass())) {
return (R) value;
} else {
throw new IllegalArgumentException("The requested object is " + value.getClass() + ", not " + clazz);
}
}
@Override
public <R> R getLatestByName(final Class<R> clazz, final String name) {
return getSingle(clazz, name, VersionCorrection.LATEST);
}
}