/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.bbg.referencedata.impl; import static com.opengamma.bbg.BloombergConstants.BLOOMBERG_FIELDS_REQUEST; import static com.opengamma.bbg.BloombergConstants.BLOOMBERG_REFERENCE_DATA_REQUEST; import static com.opengamma.bbg.BloombergConstants.BLOOMBERG_SECURITIES_REQUEST; import static com.opengamma.bbg.BloombergConstants.EID_DATA; import static com.opengamma.bbg.BloombergConstants.ERROR_INFO; import static com.opengamma.bbg.BloombergConstants.FIELD_DATA; import static com.opengamma.bbg.BloombergConstants.FIELD_EXCEPTIONS; import static com.opengamma.bbg.BloombergConstants.FIELD_ID; import static com.opengamma.bbg.BloombergConstants.RESPONSE_ERROR; import static com.opengamma.bbg.BloombergConstants.SECURITY; import static com.opengamma.bbg.BloombergConstants.SECURITY_DATA; import static com.opengamma.bbg.BloombergConstants.SECURITY_ERROR; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import org.apache.commons.lang.StringUtils; import org.fudgemsg.FudgeMsg; import org.fudgemsg.MutableFudgeMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.helpers.MessageFormatter; import org.springframework.context.Lifecycle; import com.bloomberglp.blpapi.Element; import com.bloomberglp.blpapi.Request; import com.google.common.base.CharMatcher; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.bbg.AbstractBloombergStaticDataProvider; import com.opengamma.bbg.BloombergConnector; import com.opengamma.bbg.BloombergConstants; import com.opengamma.bbg.referencedata.ReferenceData; import com.opengamma.bbg.referencedata.ReferenceDataError; import com.opengamma.bbg.referencedata.ReferenceDataProviderGetRequest; import com.opengamma.bbg.referencedata.ReferenceDataProviderGetResult; import com.opengamma.bbg.referencedata.statistics.BloombergReferenceDataStatistics; import com.opengamma.bbg.util.BloombergDataUtils; import com.opengamma.util.ArgumentChecker; /** * Provider of reference-data from the Bloomberg data source. */ public class BloombergReferenceDataProvider extends AbstractReferenceDataProvider implements Lifecycle { /** Logger. */ private static final Logger s_logger = LoggerFactory.getLogger(BloombergReferenceDataProvider.class); /** * Implementation class. */ private final BloombergReferenceDataRequestService _refDataService; /** * Creates an instance. * <p> * This will use the statistics tool in the connector. * * @param bloombergConnector the bloomberg connector, not null */ public BloombergReferenceDataProvider(BloombergConnector bloombergConnector) { this(ArgumentChecker.notNull(bloombergConnector, "bloombergConnector"), bloombergConnector.getReferenceDataStatistics()); } /** * Creates an instance with statistics gathering. * * @param bloombergConnector the Bloomberg connector, not null * @param statistics the statistics to collect, not null */ public BloombergReferenceDataProvider(BloombergConnector bloombergConnector, BloombergReferenceDataStatistics statistics) { _refDataService = new BloombergReferenceDataRequestService(bloombergConnector, statistics); } //------------------------------------------------------------------------- @Override protected ReferenceDataProviderGetResult doBulkGet(ReferenceDataProviderGetRequest request) { return _refDataService.doBulkGet(request); } //------------------------------------------------------------------------- @Override public void start() { _refDataService.start(); } @Override public void stop() { _refDataService.stop(); } @Override public boolean isRunning() { return _refDataService.isRunning(); } //------------------------------------------------------------------------- /** * Loads reference-data from Bloomberg. */ static class BloombergReferenceDataRequestService extends AbstractBloombergStaticDataProvider { /** * Bloomberg statistics. */ private final BloombergReferenceDataStatistics _statistics; BloombergReferenceDataRequestService(BloombergConnector bloombergConnector) { this(ArgumentChecker.notNull(bloombergConnector, "bloombergConnector"), bloombergConnector.getReferenceDataStatistics()); } /** * Creates an instance. * * @param bloombergConnector the bloomberg connector, not null * @param statistics the bloomberg reference data statistics, not null * @param applicationName the bpipe application name if applicable * @param reAuthorizationScheduleTime the identity re authorization schedule time in hours */ BloombergReferenceDataRequestService(BloombergConnector bloombergConnector, BloombergReferenceDataStatistics statistics) { super(bloombergConnector, BloombergConstants.REF_DATA_SVC_NAME); ArgumentChecker.notNull(statistics, "statistics"); _statistics = statistics; } //------------------------------------------------------------------------- @Override protected Logger getLogger() { return s_logger; } //------------------------------------------------------------------------- /** * Get reference-data from Bloomberg. * * @param request the request, not null * @return the reference-data result, not null */ ReferenceDataProviderGetResult doBulkGet(ReferenceDataProviderGetRequest request) { Set<String> identifiers = request.getIdentifiers(); Set<String> dataFields = request.getFields(); validateIdentifiers(identifiers); validateFields(dataFields); ensureStarted(); getLogger().debug("Getting reference data for {}, fields {}", identifiers, dataFields); Request bbgRequest = createRequest(identifiers, dataFields); _statistics.recordStatistics(identifiers, dataFields); ReferenceDataProviderGetResult result = new ReferenceDataProviderGetResult(); try { List<Element> resultElements = submitRequest(bbgRequest).get(); if (resultElements == null || resultElements.isEmpty()) { getLogger().warn("Unable to get a Bloomberg response for {} fields for {}", dataFields, identifiers); } else { result = parse(identifiers, dataFields, resultElements); } } catch (InterruptedException | ExecutionException ex) { getLogger().warn(String.format("Unable to get a Bloomberg response fields:[%s] for security[%s]", dataFields, identifiers), ex); } return result; } //------------------------------------------------------------------------- /** * Checks that all the identifiers are valid. * * @param identifiers the set of identifiers, not null */ private void validateIdentifiers(Set<String> identifiers) { Set<String> excluded = new HashSet<String>(); for (String identifier : identifiers) { if (StringUtils.isEmpty(identifier)) { throw new IllegalArgumentException("Must not have any null or empty identifiers"); } if (CharMatcher.ASCII.matchesAllOf(identifier) == false) { //[BBG-93] - The C++ interface is declared as UChar, so this just enforces that restriction excluded.add(identifier); } } if (excluded.size() > 0) { String message = MessageFormatter.format("Request contains invalid identifiers {} from ({})", excluded, identifiers).getMessage(); getLogger().error(message); throw new OpenGammaRuntimeException(message); } } /** * Checks that all the fields are valid. * * @param fields the set of fields, not null */ private void validateFields(Set<String> fields) { Set<String> excluded = new HashSet<String>(); for (String field : fields) { if (StringUtils.isEmpty(field)) { throw new IllegalArgumentException("Must not have any null or empty fields"); } if (CharMatcher.ASCII.matchesAllOf(field) == false) { excluded.add(field); } } if (excluded.size() > 0) { String message = MessageFormatter.format("Request contains invalid fields {} from ({})", excluded, fields).getMessage(); getLogger().error(message); throw new OpenGammaRuntimeException(message); } } //------------------------------------------------------------------------- /** * Creates the Bloomberg request. * * @param identifiers the identifiers, not null * @param dataFields the datafields, not null * @return the bloomberg request, not null */ private Request createRequest(Set<String> identifiers, Set<String> dataFields) { // create request Request request = getService().createRequest(BLOOMBERG_REFERENCE_DATA_REQUEST); Element securitiesElem = request.getElement(BLOOMBERG_SECURITIES_REQUEST); // identifiers for (String identifier : identifiers) { if (StringUtils.isEmpty(identifier)) { throw new IllegalArgumentException("Must not have any null or empty securities"); } securitiesElem.appendValue(identifier); } // fields Element fieldElem = request.getElement(BLOOMBERG_FIELDS_REQUEST); for (String dataField : dataFields) { if (StringUtils.isEmpty(dataField)) { throw new IllegalArgumentException("Must not have any null or empty fields"); } if (dataField.equals(BloombergConstants.FIELD_EID_DATA) == false) { fieldElem.appendValue(dataField); } } request.set("returnEids", true); return request; } /** * Performs the main work to parse the result from Bloomberg. * <p> * This is part of {@link #getFields(Set, Set)}. * * @param securityKeys the set of securities, not null * @param fields the set of fields, not null * @param resultElements the result elements from Bloomberg, not null * @return the parsed result, not null */ private ReferenceDataProviderGetResult parse(Set<String> securityKeys, Set<String> fields, List<Element> resultElements) { ReferenceDataProviderGetResult result = new ReferenceDataProviderGetResult(); for (Element resultElem : resultElements) { if (resultElem.hasElement(RESPONSE_ERROR)) { Element responseError = resultElem.getElement(RESPONSE_ERROR); String category = responseError.getElementAsString(BloombergConstants.CATEGORY); if ("LIMIT".equals(category)) { getLogger().error("Limit reached {}", responseError); } throw new OpenGammaRuntimeException("Unable to get a Bloomberg response for " + fields + " fields for " + securityKeys + ": " + responseError); } Element securityDataArray = resultElem.getElement(SECURITY_DATA); int numSecurities = securityDataArray.numValues(); for (int iSecurityElem = 0; iSecurityElem < numSecurities; iSecurityElem++) { Element securityElem = securityDataArray.getValueAsElement(iSecurityElem); String securityKey = securityElem.getElementAsString(SECURITY); ReferenceData refData = new ReferenceData(securityKey); if (securityElem.hasElement(SECURITY_ERROR)) { Element securityError = securityElem.getElement(SECURITY_ERROR); parseIdentifierError(refData, securityKey, securityError); } if (securityElem.hasElement(FIELD_DATA)) { parseFieldData(refData, securityElem.getElement(FIELD_DATA)); } if (securityElem.hasElement(FIELD_EXCEPTIONS)) { Element fieldExceptions = securityElem.getElement(FIELD_EXCEPTIONS); parseFieldExceptions(refData, fieldExceptions); } if (securityElem.hasElement(EID_DATA)) { parseEidData(refData, securityElem.getElement(EID_DATA)); } result.addReferenceData(refData); } } return result; } /** * Processes an error affecting the whole identifier. * * @param refData the per identifier reference data result, not null * @param securityKey the security identifier, not null * @param element the bloomberg element, not null */ private void parseIdentifierError(ReferenceData refData, String securityKey, Element element) { ReferenceDataError error = buildError(null, element); if (error.isEntitlementError()) { getLogger().warn("Bloomberg referenceData security error: {} {}", securityKey, error.getMessage()); } else { getLogger().warn("Bloomberg referenceData security error: {} {}", securityKey, element); } refData.addError(error); } /** * Processes the field data. * * @param refData the per identifier reference data result, not null * @param element the bloomberg element, not null */ private void parseFieldData(ReferenceData refData, Element element) { FudgeMsg fieldData = BloombergDataUtils.parseElement(element); refData.setFieldValues(fieldData); } /** * Processes the an error affecting a single field on a one identifier. * * @param refData the per identifier reference data result, not null * @param fieldExceptionArray the bloomberg data, not null */ private void parseFieldExceptions(ReferenceData refData, Element fieldExceptionArray) { int numExceptions = fieldExceptionArray.numValues(); if (numExceptions > 0) { getLogger().warn("Bloomberg referenceData field exceptions: {}", fieldExceptionArray); } for (int i = 0; i < numExceptions; i++) { Element exceptionElem = fieldExceptionArray.getValueAsElement(i); String fieldId = exceptionElem.getElementAsString(FIELD_ID); ReferenceDataError error = buildError(fieldId, exceptionElem.getElement(ERROR_INFO)); refData.addError(error); } } /** * Processes the EID data. * * @param refData the per identifier reference data result, not null * @param eidElement the bloomberg element, not null */ private void parseEidData(ReferenceData refData, Element eidElement) { for (int i = 0; i < eidElement.numValues(); i++) { refData.getEidValues().add(eidElement.getValueAsInt32(i)); } if (refData.getFieldValues() instanceof MutableFudgeMsg) { MutableFudgeMsg fieldValues = (MutableFudgeMsg) refData.getFieldValues(); for (Integer eid : refData.getEidValues()) { fieldValues.add(BloombergConstants.EID_DATA.toString(), eid); } } } /** * Creates an instance from a Bloomberg element. * * @param field the field, null if linked to the identifier rather than a field * @param element the element, not null * @return the error, not null */ private ReferenceDataError buildError(String field, Element element) { return new ReferenceDataError( field, element.getElementAsInt32(BloombergConstants.CODE), element.getElementAsString(BloombergConstants.CATEGORY), element.getElementAsString(BloombergConstants.SUBCATEGORY), element.getElementAsString(BloombergConstants.MESSAGE)); } } }