/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeFieldType;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeRuntimeException;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.fudgemsg.wire.FudgeSize;
import org.fudgemsg.wire.types.FudgeWireType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.fudgemsg.WriteReplaceHelper;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* An implementation of {@link ViewComputationCache} which backs value storage on a pair of {@link IdentifierMap} and {@link FudgeMessageStore}.
*/
public class DefaultViewComputationCache implements ViewComputationCache,
Iterable<Pair<ValueSpecification, FudgeMsg>> {
private static final Logger s_logger = LoggerFactory.getLogger(DefaultViewComputationCache.class);
/**
* Callback to locate missing data.
*/
public interface MissingValueLoader {
FudgeMsg findMissingValue(long identifier);
Map<Long, FudgeMsg> findMissingValues(Collection<Long> identifiers);
};
private static final int NATIVE_FIELD_INDEX = -1;
private final IdentifierMap _identifierMap;
private final FudgeMessageStore _privateDataStore;
private final FudgeMessageStore _sharedDataStore;
private final FudgeContext _fudgeContext;
private MissingValueLoader _missingValueLoader;
/**
* The size of recent values that have gone into or come out of this cache.
*/
@SuppressWarnings({"rawtypes", "unchecked" })
private final ThreadLocal<Map<ValueSpecification, Integer>> _valueSizeCache = new ThreadLocal(); //NOTE: this being thread local is dangerous, but avoids blocking
private Map<ValueSpecification, Integer> getValueSizeCache() {
Map<ValueSpecification, Integer> c = _valueSizeCache.get();
if (c == null) {
c = new HashMap<ValueSpecification, Integer>();
_valueSizeCache.set(c);
}
return c;
}
/**
* The size of classes which will always have the same size
*/
private final Map<Class<?>, Integer> _valueSizeByClassCache;
private void cacheValueSize(final ValueSpecification specification, final FudgeMsg data, final Object value) {
if (value != null && _valueSizeByClassCache.containsKey(value.getClass())) {
return;
}
final int calculateMessageSize = FudgeSize.calculateMessageSize(data);
getValueSizeCache().put(specification, calculateMessageSize);
}
protected DefaultViewComputationCache(final IdentifierMap identifierMap, final FudgeMessageStore dataStore,
final FudgeContext fudgeContext) {
this(identifierMap, dataStore, dataStore, fudgeContext);
}
public DefaultViewComputationCache(final IdentifierMap identifierMap, final FudgeMessageStore privateDataStore,
final FudgeMessageStore sharedDataStore, final FudgeContext fudgeContext) {
ArgumentChecker.notNull(identifierMap, "Identifier map");
ArgumentChecker.notNull(privateDataStore, "Private data store");
ArgumentChecker.notNull(sharedDataStore, "Shared data store");
ArgumentChecker.notNull(fudgeContext, "Fudge context");
_identifierMap = identifierMap;
_privateDataStore = privateDataStore;
_sharedDataStore = sharedDataStore;
_fudgeContext = fudgeContext;
_valueSizeByClassCache = buildValueSizeByClassMap();
}
private Map<Class<?>, Integer> buildValueSizeByClassMap() {
//All of these classes must be have consistent sizes
final ArrayList<Object> templates = Lists.<Object>newArrayList(Double.valueOf(12.0), MissingInput.MISSING_MARKET_DATA);
final Map<Class<?>, Integer> valueSizeByClass = new HashMap<Class<?>, Integer>(templates.size());
for (final Object obj : templates) {
final FudgeSerializer serializer = new FudgeSerializer(getFudgeContext());
final FudgeMsg data = serializeValue(serializer, obj);
final int size = FudgeSize.calculateMessageSize(data);
valueSizeByClass.put(obj.getClass(), size);
}
return valueSizeByClass;
}
public void setMissingValueLoader(final MissingValueLoader missingValueLoader) {
_missingValueLoader = missingValueLoader;
}
public MissingValueLoader getMissingValueLoader() {
return _missingValueLoader;
}
/**
* Gets the identifierSource field.
*
* @return the identifierSource
*/
public IdentifierMap getIdentifierMap() {
return _identifierMap;
}
/**
* Gets the private / local data store.
*
* @return the dataStore
*/
public FudgeMessageStore getPrivateDataStore() {
return _privateDataStore;
}
public FudgeMessageStore getSharedDataStore() {
return _sharedDataStore;
}
/**
* Gets the fudgeContext field.
*
* @return the fudgeContext
*/
public FudgeContext getFudgeContext() {
return _fudgeContext;
}
@Override
public Object getValue(final ValueSpecification specification) {
ArgumentChecker.notNull(specification, "Specification");
final long identifier = getIdentifierMap().getIdentifier(specification);
FudgeMsg data = getPrivateDataStore().get(identifier);
if (data == null) {
data = getSharedDataStore().get(identifier);
}
if (data == null) {
final MissingValueLoader loader = getMissingValueLoader();
if (loader == null) {
return null;
}
data = loader.findMissingValue(identifier);
if (data == null) {
return null;
}
}
final FudgeDeserializer deserializer = new FudgeDeserializer(getFudgeContext());
final Object obj = deserializeValue(deserializer, data);
cacheValueSize(specification, data, obj);
return obj;
}
@Override
public Object getValue(final ValueSpecification specification, final CacheSelectHint filter) {
ArgumentChecker.notNull(specification, "Specification");
final long identifier = getIdentifierMap().getIdentifier(specification);
final boolean isPrivate = filter.isPrivateValue(specification);
final FudgeMsg data = (isPrivate ? getPrivateDataStore() : getSharedDataStore()).get(identifier);
if (data == null) {
return null;
}
final FudgeDeserializer deserializer = new FudgeDeserializer(getFudgeContext());
final Object obj = deserializeValue(deserializer, data);
cacheValueSize(specification, data, obj);
return obj;
}
@Override
public Collection<Pair<ValueSpecification, Object>> getValues(final Collection<ValueSpecification> specifications) {
ArgumentChecker.notNull(specifications, "specifications");
final Map<ValueSpecification, Long> identifiers = getIdentifierMap().getIdentifiers(specifications);
final Collection<Pair<ValueSpecification, Object>> returnValues = new ArrayList<Pair<ValueSpecification, Object>>(specifications.size());
final Collection<Long> identifierValues = identifiers.values();
final FudgeDeserializer deserializer = new FudgeDeserializer(getFudgeContext());
Map<Long, FudgeMsg> rawValues = getPrivateDataStore().get(identifierValues);
if (!rawValues.isEmpty()) {
final Iterator<Map.Entry<ValueSpecification, Long>> identifierIterator = identifiers.entrySet().iterator();
while (identifierIterator.hasNext()) {
final Map.Entry<ValueSpecification, Long> identifier = identifierIterator.next();
final FudgeMsg data = rawValues.get(identifier.getValue());
if (data != null) {
final Object value = deserializeValue(deserializer, data);
cacheValueSize(identifier.getKey(), data, value);
returnValues.add(Pairs.of(identifier.getKey(), value));
identifierIterator.remove();
}
}
if (identifiers.isEmpty()) {
return returnValues;
}
}
rawValues = getSharedDataStore().get(identifierValues);
if (!rawValues.isEmpty()) {
final Iterator<Map.Entry<ValueSpecification, Long>> identifierIterator = identifiers.entrySet().iterator();
while (identifierIterator.hasNext()) {
final Map.Entry<ValueSpecification, Long> identifier = identifierIterator.next();
final FudgeMsg data = rawValues.get(identifier.getValue());
if (data != null) {
final Object value = deserializeValue(deserializer, data);
cacheValueSize(identifier.getKey(), data, value);
returnValues.add(Pairs.of(identifier.getKey(), value));
identifierIterator.remove();
}
}
if (identifiers.isEmpty()) {
return returnValues;
}
}
final MissingValueLoader loader = getMissingValueLoader();
if (loader != null) {
rawValues = loader.findMissingValues(identifierValues);
if (!rawValues.isEmpty()) {
final Iterator<Map.Entry<ValueSpecification, Long>> identifierIterator = identifiers.entrySet().iterator();
while (identifierIterator.hasNext()) {
final Map.Entry<ValueSpecification, Long> identifier = identifierIterator.next();
final FudgeMsg data = rawValues.get(identifier.getValue());
if (data != null) {
final Object value = deserializeValue(deserializer, data);
cacheValueSize(identifier.getKey(), data, value);
returnValues.add(Pairs.of(identifier.getKey(), value));
identifierIterator.remove();
}
}
}
}
return returnValues;
}
@Override
public Collection<Pair<ValueSpecification, Object>> getValues(final Collection<ValueSpecification> specifications, final CacheSelectHint filter) {
ArgumentChecker.notNull(specifications, "specifications");
final Map<ValueSpecification, Long> identifiers = getIdentifierMap().getIdentifiers(specifications);
final Collection<Pair<ValueSpecification, Object>> returnValues = new ArrayList<Pair<ValueSpecification, Object>>(specifications.size());
List<Long> privateIdentifiers = null;
List<Long> sharedIdentifiers = null;
for (final ValueSpecification specification : specifications) {
if (filter.isPrivateValue(specification)) {
if (privateIdentifiers == null) {
privateIdentifiers = new ArrayList<Long>(specifications.size());
}
privateIdentifiers.add(identifiers.get(specification));
} else {
if (sharedIdentifiers == null) {
sharedIdentifiers = new ArrayList<Long>(specifications.size());
}
sharedIdentifiers.add(identifiers.get(specification));
}
}
final Map<Long, FudgeMsg> rawValues = new HashMap<Long, FudgeMsg>();
// TODO Can we overlay the fetch of shared and private data?
if (sharedIdentifiers != null) {
if (sharedIdentifiers.size() == 1) {
final FudgeMsg data = getSharedDataStore().get(sharedIdentifiers.get(0));
rawValues.put(sharedIdentifiers.get(0), data);
} else {
rawValues.putAll(getSharedDataStore().get(sharedIdentifiers));
}
}
if (privateIdentifiers != null) {
if (privateIdentifiers.size() == 1) {
final FudgeMsg data = getPrivateDataStore().get(privateIdentifiers.get(0));
rawValues.put(privateIdentifiers.get(0), data);
} else {
rawValues.putAll(getPrivateDataStore().get(privateIdentifiers));
}
}
final FudgeDeserializer deserializer = new FudgeDeserializer(getFudgeContext());
for (final Map.Entry<ValueSpecification, Long> identifier : identifiers.entrySet()) {
final FudgeMsg data = rawValues.get(identifier.getValue());
if (data != null) {
final Object value = deserializeValue(deserializer, data);
cacheValueSize(identifier.getKey(), data, value);
returnValues.add(Pairs.of(identifier.getKey(), value));
} else {
returnValues.add(Pairs.of(identifier.getKey(), null));
}
}
return returnValues;
}
protected void putValue(final ComputedValue value, final FudgeMessageStore dataStore) {
ArgumentChecker.notNull(value, "value");
final long identifier = getIdentifierMap().getIdentifier(value.getSpecification());
final FudgeSerializer serializer = new FudgeSerializer(getFudgeContext());
final Object obj = value.getValue();
final FudgeMsg data = serializeValue(serializer, obj);
cacheValueSize(value.getSpecification(), data, obj);
dataStore.put(identifier, data);
}
@Override
public void putPrivateValue(final ComputedValue value) {
putValue(value, getPrivateDataStore());
}
@Override
public void putSharedValue(final ComputedValue value) {
putValue(value, getSharedDataStore());
}
@Override
public void putValue(final ComputedValue value, final CacheSelectHint filter) {
AbstractViewComputationCache.putValue(this, value, filter);
}
protected void putValues(final Collection<? extends ComputedValue> values, final FudgeMessageStore dataStore) {
ArgumentChecker.notNull(values, "values");
final Collection<ValueSpecification> specifications = new ArrayList<ValueSpecification>(values.size());
for (final ComputedValue value : values) {
specifications.add(value.getSpecification());
}
final Map<ValueSpecification, Long> identifiers = getIdentifierMap().getIdentifiers(specifications);
final Map<Long, FudgeMsg> data = new HashMap<Long, FudgeMsg>();
final FudgeSerializer serializer = new FudgeSerializer(getFudgeContext());
for (final ComputedValue value : values) {
final Object obj = value.getValue();
final FudgeMsg valueData = serializeValue(serializer, obj);
cacheValueSize(value.getSpecification(), valueData, obj);
data.put(identifiers.get(value.getSpecification()), valueData);
}
dataStore.put(data);
}
@Override
public void putPrivateValues(final Collection<? extends ComputedValue> values) {
putValues(values, getPrivateDataStore());
}
@Override
public void putSharedValues(final Collection<? extends ComputedValue> values) {
putValues(values, getSharedDataStore());
}
@Override
public void putValues(final Collection<? extends ComputedValue> values, final CacheSelectHint filter) {
ArgumentChecker.notNull(values, "values");
final Collection<ValueSpecification> specifications = new ArrayList<ValueSpecification>(values.size());
for (final ComputedValue value : values) {
specifications.add(value.getSpecification());
}
final Map<ValueSpecification, Long> identifiers = getIdentifierMap().getIdentifiers(specifications);
final FudgeSerializer serializer = new FudgeSerializer(getFudgeContext());
Map<Long, FudgeMsg> privateData = null;
Map<Long, FudgeMsg> sharedData = null;
for (final ComputedValue value : values) {
final Object obj = value.getValue();
final FudgeMsg valueData = serializeValue(serializer, obj);
cacheValueSize(value.getSpecification(), valueData, value.getValue());
if (filter.isPrivateValue(value.getSpecification())) {
if (privateData == null) {
privateData = new HashMap<Long, FudgeMsg>();
}
privateData.put(identifiers.get(value.getSpecification()), valueData);
} else {
if (sharedData == null) {
sharedData = new HashMap<Long, FudgeMsg>();
}
sharedData.put(identifiers.get(value.getSpecification()), valueData);
}
}
// TODO 2010-08-31 Andrew -- can we overlay the shared and private puts ?
if (sharedData != null) {
getSharedDataStore().put(sharedData);
}
if (privateData != null) {
getPrivateDataStore().put(privateData);
}
}
protected static FudgeMsg serializeValue(final FudgeSerializer serializer, final Object value) {
if (value instanceof Double) {
//Make sure fudge doesn't faff around with reflection
final MutableFudgeMsg newMessage = serializer.newMessage();
final FudgeFieldType doubleFieldType = FudgeWireType.DOUBLE;
newMessage.add(null, NATIVE_FIELD_INDEX, doubleFieldType, value);
return newMessage;
} else if (value instanceof FudgeMsg) {
final MutableFudgeMsg newMessage = serializer.newMessage();
final FudgeFieldType messageFieldType = FudgeWireType.SUB_MESSAGE;
newMessage.add(null, NATIVE_FIELD_INDEX, messageFieldType, value);
return newMessage;
}
serializer.reset();
final MutableFudgeMsg message = serializer.newMessage();
try {
serializer.addToMessageWithClassHeaders(message, null, NATIVE_FIELD_INDEX, WriteReplaceHelper.writeReplace(value));
} catch (FudgeRuntimeException e) {
s_logger.error("Can't encode value {}", value);
s_logger.warn("Caught exception", e);
}
// Optimize the "value encoded as sub-message" case to reduce space requirement
final Object svalue = message.getValue(NATIVE_FIELD_INDEX);
if (svalue instanceof FudgeMsg) {
return (FudgeMsg) svalue;
} else {
return message;
}
}
protected static Object deserializeValue(final FudgeDeserializer deserializer, final FudgeMsg message) {
deserializer.reset();
if (message.getNumFields() == 1) {
final Object value = message.getValue(NATIVE_FIELD_INDEX);
if (value != null) {
return value;
}
}
return deserializer.fudgeMsgToObject(message);
}
@Override
public Integer estimateValueSize(final ComputedValue value) {
if (value.getValue() == null) {
return null;
}
final Integer classSize = _valueSizeByClassCache.get(value.getValue().getClass());
if (classSize != null) {
return classSize;
}
return getValueSizeCache().get(value.getSpecification());
}
@Override
public Iterator<Pair<ValueSpecification, FudgeMsg>> iterator() {
// TODO 2008-08-09 Implement this; iterate over the values in the data store
throw new UnsupportedOperationException();
}
/**
* Remove any underlying resources from the data stores and make the size cache available for garbage collection.
*/
public void delete() {
_valueSizeCache.remove(); //TODO this is not right
getPrivateDataStore().delete();
if (getSharedDataStore() != getPrivateDataStore()) {
getSharedDataStore().delete();
}
}
}