/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.livedata.cogda.server; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.fudgemsg.FudgeMsg; import org.fudgemsg.mapping.FudgeSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.Lifecycle; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.opengamma.id.ExternalId; import com.opengamma.livedata.LiveDataSpecification; import com.opengamma.livedata.LiveDataValueUpdateBean; import com.opengamma.livedata.LiveDataValueUpdateBeanFudgeBuilder; import com.opengamma.livedata.normalization.NormalizationRuleSet; import com.opengamma.livedata.server.FieldHistoryStore; import com.opengamma.livedata.server.LastKnownValueStore; import com.opengamma.livedata.server.LastKnownValueStoreProvider; import com.opengamma.transport.FudgeMessageSender; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.metric.MetricProducer; /** * Listens to a channel of raw data updates, normalizes, writes to the LKV store, and then * publishes all value updates (for the normalized results) to a different channel * for detection by the data servers. * <p/> * It has three ways that the list of active subscriptions can be built: * <ol> * <li>You can just wait for updates to come through. Whenever an update is received * where there is not a distribution order, one will be built.</li> * <li>By scanning the LKV store on startup.</li> * <li>You can explicitly add them (perhaps via startup configuration) * via calls to {@link #addDistribution(String)}.</li> * </ol> * <p/> * In general, if not bootstrapping for the first time, the first and second ways should be * sufficient. */ public abstract class CogdaDataDistributor implements Lifecycle, MetricProducer { private static final Logger s_logger = LoggerFactory.getLogger(CogdaDataDistributor.class); // Constructor injectors: private final String _externalIdScheme; private final LastKnownValueStoreProvider _lastKnownValueStoreProvider; private final Map<String, NormalizationRuleSet> _normalization; // TODO kirk 2012-08-13 -- Have support for multiple senders, one per normalization // rule. Otherwise there's way too much filtering. But that's a second-order effect. private FudgeMessageSender _normalizedMessageSender; // Internal state: private final ConcurrentMap<LiveDataSpecification, LastKnownValueStore> _valueStores = new ConcurrentHashMap<LiveDataSpecification, LastKnownValueStore>(); private final ConcurrentMap<LiveDataSpecification, FieldHistoryStore> _normalizationState = new ConcurrentHashMap<LiveDataSpecification, FieldHistoryStore>(); // Metrics: private Meter _tickMeter = new Meter(); public CogdaDataDistributor( String externalIdScheme, LastKnownValueStoreProvider lastKnownValueStoreProvider, String... normalizationSchemes) { ArgumentChecker.notNull(externalIdScheme, "externalIdScheme"); ArgumentChecker.notNull(lastKnownValueStoreProvider, "lastKnownValueStoreProvider"); _externalIdScheme = externalIdScheme; _lastKnownValueStoreProvider = lastKnownValueStoreProvider; _normalization = Collections.unmodifiableMap(constructNormalizationRules(normalizationSchemes)); } @Override public synchronized void registerMetrics(MetricRegistry summaryRegistry, MetricRegistry detailedRegistry, String namePrefix) { _tickMeter = summaryRegistry.meter(namePrefix + ".ticks"); } /** * Gets the externalIdScheme. * @return the externalIdScheme */ public String getExternalIdScheme() { return _externalIdScheme; } /** * Gets the normalizedMessageSender. * @return the normalizedMessageSender */ public FudgeMessageSender getNormalizedMessageSender() { return _normalizedMessageSender; } /** * Sets the normalizedMessageSender. * @param normalizedMessageSender the normalizedMessageSender */ public void setNormalizedMessageSender(FudgeMessageSender normalizedMessageSender) { _normalizedMessageSender = normalizedMessageSender; } /** * @param normalizationSchemes * @return */ private Map<String, NormalizationRuleSet> constructNormalizationRules(String[] normalizationSchemes) { Map<String, NormalizationRuleSet> normalization = new TreeMap<String, NormalizationRuleSet>(); for (String normalizationScheme : normalizationSchemes) { normalization.put(normalizationScheme, constructNormalizationRuleSet(normalizationScheme)); } return normalization; } /** * @param normalizationScheme name of the scheme to be generated. * @return the rule set for that scheme. */ protected abstract NormalizationRuleSet constructNormalizationRuleSet(String normalizationScheme); public void addDistribution(String uniqueIdentifier) { for (String normalizationScheme : _normalization.keySet()) { ensureLastKnownValueStore(ExternalId.of(_externalIdScheme, uniqueIdentifier), normalizationScheme); } } @Override public void start() { scanAllKeys(); } @Override public void stop() { } @Override public boolean isRunning() { return false; } /** * */ protected void scanAllKeys() { Set<String> allIdentifiers = null; try { allIdentifiers = _lastKnownValueStoreProvider.getAllIdentifiers(_externalIdScheme); } catch (UnsupportedOperationException uoe) { return; } for (String id: allIdentifiers) { for (String normalizationScheme : _normalization.keySet()) { ensureLastKnownValueStore(ExternalId.of(_externalIdScheme, id), normalizationScheme); } } } /** * Prepare the LKV store for the given specification and populate the normalization * state. * * @param id identifier for which to create store * @param normalizationScheme normalization scheme of store * @return The value store */ protected LastKnownValueStore ensureLastKnownValueStore(ExternalId id, String normalizationScheme) { LastKnownValueStore lkvStore = _lastKnownValueStoreProvider.newInstance(id, normalizationScheme); LiveDataSpecification ldspec = new LiveDataSpecification(normalizationScheme, id); if (_valueStores.putIfAbsent(ldspec, lkvStore) == null) { s_logger.debug("Created new LKV store and history state for {}", ldspec); // We actually did the creation. Also create the field history map. FieldHistoryStore historyStore = new FieldHistoryStore(lkvStore.getFields()); _normalizationState.put(ldspec, historyStore); return lkvStore; } return _valueStores.get(ldspec); } /** * Received raw, unnormalized values. * Will apply normalization, store results in the LKV store, and then rebroadcast * the normalized values. * * @param uniqueId the identifier for the updates * @param fields updated fields */ public void updateReceived(String uniqueId, FudgeMsg fields) { updateReceived(ExternalId.of(_externalIdScheme, uniqueId), fields); } /** * Received raw, unnormalized values. * Will apply normalization, store results in the LKV store, and then rebroadcast * the normalized values. * * @param id the identifier for the updates * @param fields updated fields */ public void updateReceived(ExternalId id, FudgeMsg fields) { _tickMeter.mark(); // Iterate over all normalization schemes. for (Map.Entry<String, NormalizationRuleSet> normalizationEntry : _normalization.entrySet()) { LiveDataSpecification ldspec = new LiveDataSpecification(normalizationEntry.getKey(), id); LastKnownValueStore lkvStore = ensureLastKnownValueStore(id, normalizationEntry.getKey()); NormalizationRuleSet ruleSet = normalizationEntry.getValue(); FudgeMsg normalizedFields = ruleSet.getNormalizedMessage(fields, id.getValue(), _normalizationState.get(ldspec)); // If nothing to update, this returns null. if ((normalizedFields != null) && !normalizedFields.isEmpty()) { // update the LKV store lkvStore.updateFields(normalizedFields); // Blast them out. distributeNormalizedUpdate(ldspec, normalizedFields); } } } /** * Distribute results, after normalization and LKV storage, to downstream channels. * * @param ldspec Specification of the data * @param normalizedFields Fully normalized field data for that specification */ protected void distributeNormalizedUpdate(LiveDataSpecification ldspec, FudgeMsg normalizedFields) { if (getNormalizedMessageSender() == null) { // Nothing to do here. return; } FudgeSerializer serializer = new FudgeSerializer(getNormalizedMessageSender().getFudgeContext()); LiveDataValueUpdateBean updateBean = new LiveDataValueUpdateBean(0, ldspec, normalizedFields); FudgeMsg msg = LiveDataValueUpdateBeanFudgeBuilder.toFudgeMsg(serializer, updateBean); getNormalizedMessageSender().send(msg); } }