/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.bbg.livedata;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import net.sf.ehcache.CacheManager;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.MutableFudgeMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.bbg.referencedata.ReferenceData;
import com.opengamma.bbg.referencedata.ReferenceDataError;
import com.opengamma.bbg.referencedata.ReferenceDataProvider;
import com.opengamma.bbg.referencedata.ReferenceDataProviderGetRequest;
import com.opengamma.bbg.referencedata.ReferenceDataProviderGetResult;
import com.opengamma.bbg.util.BloombergDataUtils;
import com.opengamma.core.id.ExternalSchemes;
import com.opengamma.id.ExternalScheme;
import com.opengamma.livedata.normalization.StandardRuleResolver;
import com.opengamma.livedata.permission.PermissionUtils;
import com.opengamma.livedata.resolver.DefaultDistributionSpecificationResolver;
import com.opengamma.livedata.resolver.DistributionSpecificationResolver;
import com.opengamma.livedata.resolver.EHCachingDistributionSpecificationResolver;
import com.opengamma.livedata.resolver.IdResolver;
import com.opengamma.livedata.resolver.NormalizationRuleResolver;
import com.opengamma.livedata.server.StandardLiveDataServer;
import com.opengamma.livedata.server.Subscription;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;
/**
* Allows common functionality to be shared between the live and recorded Bloomberg data servers
*/
public abstract class AbstractBloombergLiveDataServer extends StandardLiveDataServer {
private static final Logger s_logger = LoggerFactory.getLogger(AbstractBloombergLiveDataServer.class);
private static final String DEFAULT_BBG_SUB_PREFIX = "/buid/";
private NormalizationRuleResolver _normalizationRules;
private IdResolver _idResolver;
private DistributionSpecificationResolver _defaultDistributionSpecificationResolver;
/**
* Creates an instance.
*
* @param cacheManager the cache manager, not null
*/
public AbstractBloombergLiveDataServer(CacheManager cacheManager) {
super(cacheManager);
}
//-------------------------------------------------------------------------
/**
* Gets the reference data provider.
*
* @return the reference data provider
*/
protected abstract ReferenceDataProvider getReferenceDataProvider();
@Override
protected ExternalScheme getUniqueIdDomain() {
return ExternalSchemes.BLOOMBERG_BUID;
}
@Override
protected boolean snapshotOnSubscriptionStartRequired(Subscription subscription) {
// As per Kirk, it is possible that you don't get all fields initially.
// Should we optimize this by asset type?
return true;
}
/**
*
* @return the prefix to use when making the subscription, including slashes.
* e.g. "/buid/"
*/
protected String getBloombergSubscriptionPathPrefix() {
return DEFAULT_BBG_SUB_PREFIX;
}
@Override
public Map<String, FudgeMsg> doSnapshot(Collection<String> uniqueIds) {
ArgumentChecker.notNull(uniqueIds, "Unique IDs");
if (uniqueIds.isEmpty()) {
return Collections.emptyMap();
}
Set<String> buids = Sets.newHashSetWithExpectedSize(uniqueIds.size());
for (String uniqueId : uniqueIds) {
String buid = getBloombergSubscriptionPathPrefix() + uniqueId;
buids.add(buid);
}
// caching ref data provider must not be used here
ReferenceDataProviderGetRequest refDataRequest = ReferenceDataProviderGetRequest.createGet(
buids, BloombergDataUtils.STANDARD_FIELDS_SET, false);
Map<String, FudgeMsg> snapshotValues = queryRefData(refDataRequest);
Map<String, FudgeMsg> returnValue = Maps.newHashMap();
for (String buid : buids) {
FudgeMsg fieldData = snapshotValues.get(buid);
if (fieldData == null) {
s_logger.error("Could not find result for {} in data snapshot, skipping", buid);
} else {
String securityUniqueId = buid.substring(getBloombergSubscriptionPathPrefix().length());
returnValue.put(securityUniqueId, fieldData);
}
}
return returnValue;
}
// broken out from AbstractReferenceDataProvider to handle errors differently
private Map<String, FudgeMsg> queryRefData(ReferenceDataProviderGetRequest request) {
Set<String> identifiers = ImmutableSet.copyOf(request.getIdentifiers()); // copy to avoid implementation bugs
Set<String> fields = ImmutableSet.copyOf(request.getFields()); // copy to avoid implementation bugs
ReferenceDataProviderGetResult result = getReferenceDataProvider().getReferenceData(request);
// extract identifier to field-values
Map<String, FudgeMsg> map = Maps.newHashMap();
for (String identifier : identifiers) {
ReferenceData data = result.getReferenceDataOrNull(identifier);
if (data != null) {
// filter results by error list
if (data.getErrors().isEmpty()) {
map.put(identifier, data.getFieldValues());
} else {
FudgeMsg fieldValues = handleRefDataError(data, fields);
if (fieldValues != null) {
map.put(identifier, fieldValues);
}
}
}
}
return map;
}
// handle errors in reference data, notably permission denied
protected FudgeMsg handleRefDataError(ReferenceData data, Set<String> fields) {
if (data.isIdentifierError()) {
// whole response is in error
// entitlement errors are handled, other errors will look like missing data
for (ReferenceDataError error : data.getErrors()) {
if (error.isEntitlementError()) {
String message = error.getMessage();
if (message.startsWith("Security Entitlement Check Failed! ")) {
message = message.substring("Security Entitlement Check Failed! ".length());
}
// need to communicate error message via the API available
// using LIVE_DATA_PERMISSION_DENIED_FIELD is not ideal but best avaiable option
// solution is a much larger rewrite
MutableFudgeMsg values = OpenGammaFudgeContext.getInstance().newMessage();
values.add(PermissionUtils.LIVE_DATA_PERMISSION_DENIED_FIELD, "Permission denied (Bloomberg): " + message);
// only return first entitlement error
return values;
}
}
// null used to remove result entirely, effectively indicating missing data
return null;
}
// some fields are in error, so ensure they are not present
MutableFudgeMsg values = OpenGammaFudgeContext.getInstance().newMessage(data.getFieldValues());
for (String field : fields) {
if (data.isError(field)) {
values.remove(field);
}
}
return values;
}
public synchronized NormalizationRuleResolver getNormalizationRules() {
if (_normalizationRules == null) {
_normalizationRules = new StandardRuleResolver(BloombergDataUtils.getDefaultNormalizationRules(getReferenceDataProvider(), getCacheManager(), getUniqueIdDomain()));
}
return _normalizationRules;
}
public synchronized IdResolver getIdResolver() {
if (_idResolver == null) {
_idResolver = new BloombergIdResolver(getReferenceDataProvider());
}
return _idResolver;
}
public synchronized DistributionSpecificationResolver getDefaultDistributionSpecificationResolver() {
if (_defaultDistributionSpecificationResolver == null) {
BloombergJmsTopicNameResolver topicResolver = new BloombergJmsTopicNameResolver(getReferenceDataProvider());
DefaultDistributionSpecificationResolver distributionSpecResolver = new DefaultDistributionSpecificationResolver(getIdResolver(), getNormalizationRules(), topicResolver);
return new EHCachingDistributionSpecificationResolver(distributionSpecResolver, getCacheManager(), "BBG");
}
return _defaultDistributionSpecificationResolver;
}
}