com.opengamma.bbg.referencedata.cache.AbstractInvalidFieldCachingReferenceDataProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.bbg.referencedata.cache.AbstractInvalidFieldCachingReferenceDataProvider.java

Source

/**
 * 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());
    }

}