/**
* 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.Map;
import java.util.Map.Entry;
import java.util.Set;
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.referencedata.impl.AbstractReferenceDataProvider;
import com.opengamma.util.ArgumentChecker;
/**
* Abstract reference data provider decorator that caches permanent invalid field errors.
* <p>
* It is strongly recommended to always decorate the underlying provider with a permanent
* invalid field error caching provider. This avoids excess queries to Bloomberg.
*/
public abstract class AbstractInvalidFieldCachingReferenceDataProvider extends AbstractReferenceDataProvider {
// See BBG-72
/**
* The underlying provider.
*/
private final ReferenceDataProvider _underlying;
/**
* Creates an instance.
*
* @param underlying the underlying provider, not null
*/
protected AbstractInvalidFieldCachingReferenceDataProvider(ReferenceDataProvider underlying) {
ArgumentChecker.notNull(underlying, "underlying");
_underlying = underlying;
}
//-------------------------------------------------------------------------
/**
* Gets the underlying provider.
*
* @return the underlying provider, not null
*/
public ReferenceDataProvider getUnderlying() {
return _underlying;
}
//-------------------------------------------------------------------------
@Override
protected ReferenceDataProviderGetResult doBulkGet(ReferenceDataProviderGetRequest request) {
// this implementation always caches and ignore the use-cache flag in the request
// this is because the errors here really are permanent and there is no reason to avoid the cache
// load invalid fields from cache
final Map<String, Set<String>> cachedErrorsMap = loadInvalidFields(request.getIdentifiers());
// filter the request removing known invalid fields
final Map<Set<String>, Set<String>> requiredFields = buildUnderlyingRequestGroups(request, cachedErrorsMap);
// process everything that remains
final ReferenceDataProviderGetResult result = new ReferenceDataProviderGetResult();
for (Entry<Set<String>, Set<String>> group : requiredFields.entrySet()) {
if (group.getKey().isEmpty()) {
// all fields for these identifiers are invalid
for (String identifier : group.getValue()) {
result.addReferenceData(new ReferenceData(identifier));
}
} else {
// call the underlying with the filtered subset of identifiers and fields
final ReferenceDataProviderGetRequest underlyingRequest = ReferenceDataProviderGetRequest.createGet(group.getValue(), group.getKey(), false);
final ReferenceDataProviderGetResult underlyingResult = getUnderlying().getReferenceData(underlyingRequest);
for (ReferenceData refData : underlyingResult.getReferenceData()) {
String identifier = refData.getIdentifier();
checkAndSaveInvalidFields(refData, cachedErrorsMap.get(identifier));
result.addReferenceData(refData);
}
}
}
return result;
}
/**
* Examines and groups the request using the known invalid fields.
*
* @param request the request, not null
* @param invalidFieldsByIdentifier the invalid fields, 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, Set<String>> invalidFieldsByIdentifier) {
Map<Set<String>, Set<String>> result = Maps.newHashMap();
for (String identifier : request.getIdentifiers()) {
// select known invalid fields for the identifier
Set<String> invalidFields = invalidFieldsByIdentifier.get(identifier);
// calculate the missing fields that must be queried from the underlying
Set<String> missingFields = null;
if (invalidFields == null) {
missingFields = Sets.newHashSet(request.getFields());
} else {
missingFields = Sets.difference(request.getFields(), invalidFields);
}
// 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;
}
/**
* Checks the reference data and adds any extra permanent errors to the cache.
*
* @param refData the reference data to check, not null
* @param invalidFields the previously cached invalid fields for the identifier, may be null
*/
protected void checkAndSaveInvalidFields(ReferenceData refData, Set<String> invalidFields) {
// find all the new invalid fields
final Set<String> newPermanentErrors = Sets.newHashSet();
for (ReferenceDataError error : refData.getErrors()) {
if (isPermanent(error)) {
newPermanentErrors.add(error.getField());
}
}
// save the new invalid fields
if (newPermanentErrors.size() > 0) {
if (invalidFields != null) {
newPermanentErrors.addAll(invalidFields);
}
saveInvalidFields(refData.getIdentifier(), newPermanentErrors);
}
}
//-------------------------------------------------------------------------
/**
* Finds the fields which previously failed for the specified identifiers.
* <p>
* This loads from the cache.
*
* @param identifiers the identifiers to find errors for, not null
* @return the map of invalid fields keyed by identifier, not null
*/
protected abstract Map<String, Set<String>> loadInvalidFields(Set<String> identifiers);
/**
* Saves the permanent errors into the cache.
* <p>
* This stores into the cache.
*
* @param identifier the identifier to save errors for, not null
* @param invalidFields the invalid fields, not null
*/
protected abstract void saveInvalidFields(String identifier, Set<String> invalidFields);
//-------------------------------------------------------------------------
/**
* Checks whether the specified error is permanent or not.
*
* @param error the error object, not null
* @return true if error is permanent
*/
protected boolean isPermanent(ReferenceDataError error) {
return error.isFieldBased() && "BAD_FLD".equals(error.getCategory()) &&
"NOT_APPLICABLE_TO_REF_DATA".equals(error.getSubcategory());
}
}