Package com.opengamma.bbg.referencedata.cache

Source Code of com.opengamma.bbg.referencedata.cache.AbstractValueCachingReferenceDataProvider

/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.bbg.referencedata.cache;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeField;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.MutableFudgeMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
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.referencedata.impl.AbstractReferenceDataProvider;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;

/**
* Abstract reference data provider decorator that caches field values.
* <p>
* It is recommended to use a cache over the underlying provider to avoid excess queries on Bloomberg.
*/
public abstract class AbstractValueCachingReferenceDataProvider extends AbstractReferenceDataProvider {

  /** Logger. */
  private static final Logger s_logger = LoggerFactory.getLogger(AbstractValueCachingReferenceDataProvider.class);
  /**
   * Constant used when field not available.
   */
  private static final String FIELD_NOT_AVAILABLE_NAME = "NOT_AVAILABLE_FIELD";

  /**
   * The underlying provider.
   */
  private final ReferenceDataProvider _underlying;
  /**
   * The Fudge context.
   */
  private final FudgeContext _fudgeContext;

  /**
   * Creates an instance.
   *
   * @param underlying  the underlying provider, not null
   */
  protected AbstractValueCachingReferenceDataProvider(ReferenceDataProvider underlying) {
    this(underlying, OpenGammaFudgeContext.getInstance());
  }

  /**
   * Creates an instance.
   *
   * @param underlying  the underlying provider, not null
   * @param fudgeContext  the Fudge context, not null
   */
  protected AbstractValueCachingReferenceDataProvider(final ReferenceDataProvider underlying, final FudgeContext fudgeContext) {
    ArgumentChecker.notNull(underlying, "underlying");
    ArgumentChecker.notNull(fudgeContext, "fudgeContext");
    _underlying = underlying;
    _fudgeContext = fudgeContext;
  }

  //-------------------------------------------------------------------------
  /**
   * Gets the underlying provider.
   *
   * @return the underlying provider, not null
   */
  public ReferenceDataProvider getUnderlying() {
    return _underlying;
  }

  /**
   * Gets the Fudge context.
   *
   * @return the context, not null
   */
  protected FudgeContext getFudgeContext() {
    return _fudgeContext;
  }

  //-------------------------------------------------------------------------
  @Override
  protected ReferenceDataProviderGetResult doBulkGet(ReferenceDataProviderGetRequest request) {
    // if use-cache is false, then do not cache
    if (request.isUseCache() == false) {
      return getUnderlying().getReferenceData(request);
    }
   
    // load from cache
    Map<String, ReferenceData> cachedResults = loadFieldValues(request.getIdentifiers());
   
    // filter the request removing known invalid fields
    final Map<Set<String>, Set<String>> identifiersByFields = buildUnderlyingRequestGroups(request, cachedResults);
   
    // process everything that remains
    ReferenceDataProviderGetResult resolvedResults = loadAndPersistUnknownFields(cachedResults, identifiersByFields);
    resolvedResults = stripUnwantedFields(resolvedResults, request.getFields());
    return resolvedResults;
  }

  protected ReferenceDataProviderGetResult stripUnwantedFields(final ReferenceDataProviderGetResult resolvedResults, final Set<String> fields) {
    ReferenceDataProviderGetResult result = new ReferenceDataProviderGetResult();
    for (ReferenceData unstippedDataResult : resolvedResults.getReferenceData()) {
      String identifier = unstippedDataResult.getIdentifier();
      ReferenceData strippedDataResult = new ReferenceData(identifier);
      strippedDataResult.getErrors().addAll(unstippedDataResult.getErrors());
      MutableFudgeMsg strippedFields = getFudgeContext().newMessage();
      FudgeMsg unstrippedFieldData = unstippedDataResult.getFieldValues();
      // check requested fields
      for (String requestField : fields) {
        List<FudgeField> fudgeFields = unstrippedFieldData.getAllByName(requestField);
        for (FudgeField fudgeField : fudgeFields) {
          strippedFields.add(requestField, fudgeField.getValue());
        }
      }
      strippedDataResult.setFieldValues(strippedFields);
      result.addReferenceData(strippedDataResult);
    }
    return result;
  }

  protected ReferenceDataProviderGetResult loadAndPersistUnknownFields(
      Map<String, ReferenceData> cachedResults,
      Map<Set<String>, Set<String>> identifiersByFields) {
   
    // TODO kirk 2009-10-23 -- Also need to maintain securities we don't need to put back in the database.
    ReferenceDataProviderGetResult result = new ReferenceDataProviderGetResult();
    // REVIEW kirk 2009-10-23 -- Candidate for scatter/gather.
    for (Map.Entry<Set<String>, Set<String>> entry : identifiersByFields.entrySet()) {
      Set<String> requestedIdentifiers = entry.getValue();
      Set<String> requestedFields = entry.getKey();
      assert !requestedIdentifiers.isEmpty();
      if (entry.getKey().isEmpty()) {
        s_logger.debug("Satisfied entire request for securities {} from cache", requestedIdentifiers);
        for (String securityKey : requestedIdentifiers) {
          result.addReferenceData(cachedResults.get(securityKey));
        }
        continue;
      }
      s_logger.info("Loading {} fields for {} securities from underlying", entry.getKey().size(), requestedIdentifiers.size());
      final ReferenceDataProviderGetRequest underlyingRequest = ReferenceDataProviderGetRequest.createGet(requestedIdentifiers, requestedFields, false);
      ReferenceDataProviderGetResult loadedResult = getUnderlying().getReferenceData(underlyingRequest);
      for (String identifier : requestedIdentifiers) {
        ReferenceData cachedResult = cachedResults.get(identifier);
        ReferenceData freshResult = loadedResult.getReferenceDataOrNull(identifier);
        freshResult = (freshResult != null ? freshResult : new ReferenceData(identifier));
       
        ReferenceData resolvedResult = getCombinedResult(requestedFields, cachedResult, freshResult);
        saveFieldValues(resolvedResult);
        result.addReferenceData(resolvedResult);
      }
    }
    return result;
  }

  private ReferenceData getCombinedResult(Set<String> requestedFields, ReferenceData cachedResult, ReferenceData freshResult) {
    MutableFudgeMsg unionFieldData = null;
    if (cachedResult == null) {
      unionFieldData = getFudgeContext().newMessage();
    } else {
      unionFieldData = getFudgeContext().newMessage(cachedResult.getFieldValues());
    }
    Set<String> returnedFields = new HashSet<String>();
    for (FudgeField freshField : freshResult.getFieldValues().getAllFields()) {
      unionFieldData.add(freshField);
      returnedFields.add(freshField.getName());
    }
   
    // cache not available fields as well
    Set<String> notAvaliableFields = Sets.newTreeSet(requestedFields);
    notAvaliableFields.removeAll(returnedFields);
   
    // add list of not available fields
    for (String notAvailableField : notAvaliableFields) {
      unionFieldData.add(FIELD_NOT_AVAILABLE_NAME, notAvailableField);
    }
   
    // create combined result
    ReferenceData resolvedResult = new ReferenceData(freshResult.getIdentifier(), unionFieldData);
    for (ReferenceDataError error : freshResult.getErrors()) {
      if (resolvedResult.getErrors().contains(error) == false) {
        resolvedResult.getErrors().add(error);         
      }
    }
    return resolvedResult;
  }

  /**
   * Examines and groups the request using the known invalid fields.
   *
   * @param request  the request, not null
   * @param cachedResults  the cached results, keyed by identifier, not null
   * @return the map of field-set to identifier-set, not null
   */
  protected Map<Set<String>, Set<String>> buildUnderlyingRequestGroups(ReferenceDataProviderGetRequest request, Map<String, ReferenceData> cachedResults) {
    Map<Set<String>, Set<String>> result = Maps.newHashMap();
    for (String identifier : request.getIdentifiers()) {
      // select known invalid fields for the identifier
      ReferenceData cachedResult = cachedResults.get(identifier);
     
      // calculate the missing fields that must be queried from the underlying
      Set<String> missingFields = null;
      if (cachedResult == null) {
        missingFields = Sets.newHashSet(request.getFields());
      } else {
        missingFields = Sets.newHashSet(Sets.difference(request.getFields(), cachedResult.getFieldValues().getAllFieldNames()));
        // remove known not available fields from missingFields
        List<String> notAvailableFieldNames = getNotAvailableFields(cachedResult);
        for (String field : notAvailableFieldNames) {
          missingFields.remove(field);
        }
      }
     
      // build the grouped result map, keyed from field-set to identifier-set
      Set<String> resultIdentifiers = result.get(missingFields);
      if (resultIdentifiers == null) {
        resultIdentifiers = Sets.newTreeSet();
        result.put(missingFields, resultIdentifiers);
      }
      resultIdentifiers.add(identifier);
    }
    return result;
  }

  private List<String> getNotAvailableFields(ReferenceData cachedResult) {
    List<FudgeField> notAvailableFields = cachedResult.getFieldValues().getAllByName(FIELD_NOT_AVAILABLE_NAME);
    List<String> notAvailableFieldNames = new ArrayList<String>(notAvailableFields.size());
    for (FudgeField field : notAvailableFields) {
      notAvailableFieldNames.add((String) field.getValue());
    }
    return notAvailableFieldNames;
  }

  //-------------------------------------------------------------------------
  /**
   * Loads the field values from the cache.
   *
   * @param identifiers  the identifiers to find errors for, not null
   * @return the map of reference data keyed by identifier, not null
   */
  protected abstract Map<String, ReferenceData> loadFieldValues(Set<String> identifiers);

  /**
   * Saves the field value into the cache.
   *
   * @param result  the result to save, not null
   */
  protected abstract void saveFieldValues(ReferenceData result);

  //-------------------------------------------------------------------------
  /**
   * Refreshes the cache.
   *
   * @param identifiers  the identifiers, not null
   */
  public void refresh(Set<String> identifiers) {
    // TODO bulk queries
    Map<String, ReferenceData> cachedResults = loadFieldValues(identifiers);
   
    Map<Set<String>, Set<String>> identifiersByFields = Maps.newHashMap();
   
    for (String identifier : identifiers) {
      ReferenceData cachedResult = cachedResults.get(identifier);
      if (cachedResult == null) {
        continue; // nothing to refresh
      }
      Set<String> fields = new HashSet<String>();
      fields.addAll(cachedResult.getFieldValues().getAllFieldNames());
      fields.addAll(getNotAvailableFields(cachedResult));
      fields.remove(FIELD_NOT_AVAILABLE_NAME);
      Set<String> secsForTheseFields = identifiersByFields.get(fields);
      if (secsForTheseFields == null) {
        secsForTheseFields = new HashSet<String>();
        identifiersByFields.put(fields, secsForTheseFields);
      }
      secsForTheseFields.add(identifier);
    }
   
    for (Entry<Set<String>, Set<String>> entry : identifiersByFields.entrySet()) {
      Set<String> identifiersForTheseFields = entry.getValue();
      Set<String> fields = entry.getKey();
     
      ReferenceDataProviderGetRequest underlyingRequest = ReferenceDataProviderGetRequest.createGet(identifiersForTheseFields, fields, false);
      ReferenceDataProviderGetResult underlyingResult = _underlying.getReferenceData(underlyingRequest);
      for (ReferenceData refData : underlyingResult.getReferenceData()) {
        ReferenceData previousResult = cachedResults.get(refData.getIdentifier());
        ReferenceData resolvedResult = getCombinedResult(fields, new ReferenceData(refData.getIdentifier()), refData);
        if (differentCachedResult(previousResult, resolvedResult)) {
          saveFieldValues(resolvedResult);
        }
      }
    }
  }

  private boolean differentCachedResult(ReferenceData previousResult, ReferenceData resolvedResult) {
    if (previousResult.getIdentifier().equals(resolvedResult.getIdentifier()) == false) {
      throw new OpenGammaRuntimeException("Attempting to compare two different securities " + previousResult + " " + resolvedResult);
    }
    // TODO better, non ordered comparison
    if (previousResult.getFieldValues().toString().equals(resolvedResult.getFieldValues().toString())) {
      return false;
    }
    return true;
  }

}
TOP

Related Classes of com.opengamma.bbg.referencedata.cache.AbstractValueCachingReferenceDataProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.