/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.provider.historicaltimeseries.impl; import java.util.Collections; import java.util.HashSet; import java.util.Set; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import org.joda.beans.JodaBeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.id.ExternalIdBundle; import com.opengamma.provider.historicaltimeseries.HistoricalTimeSeriesProvider; import com.opengamma.provider.historicaltimeseries.HistoricalTimeSeriesProviderGetRequest; import com.opengamma.provider.historicaltimeseries.HistoricalTimeSeriesProviderGetResult; import com.opengamma.timeseries.date.localdate.ImmutableLocalDateDoubleTimeSeries; import com.opengamma.timeseries.date.localdate.LocalDateDoubleTimeSeries; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.ehcache.EHCacheUtils; import com.opengamma.util.time.LocalDateRange; /** * A cache decorating a time-series provider. * <p> * The cache is implemented using {@code EHCache}. */ public class EHCachingHistoricalTimeSeriesProvider extends AbstractHistoricalTimeSeriesProvider { /** Logger. */ private static final Logger s_logger = LoggerFactory.getLogger(EHCachingHistoricalTimeSeriesProvider.class); /** * The cache name. */ private static final String DATA_CACHE_NAME = "HistoricalTimeSeriesProviderCache"; /** * The object representing a cache miss. */ private static final LocalDateDoubleTimeSeries NO_HTS = ImmutableLocalDateDoubleTimeSeries.EMPTY_SERIES; /** * The underlying provider. */ private final HistoricalTimeSeriesProvider _underlying; /** * The cache. */ private final Cache _cache; /** * Creates an instance. * * @param underlying the underlying source, not null * @param cacheManager the cache manager, not null */ public EHCachingHistoricalTimeSeriesProvider(HistoricalTimeSeriesProvider underlying, CacheManager cacheManager) { ArgumentChecker.notNull(underlying, "underlying"); ArgumentChecker.notNull(cacheManager, "Cache Manager"); _underlying = underlying; EHCacheUtils.addCache(cacheManager, DATA_CACHE_NAME); _cache = EHCacheUtils.getCacheFromManager(cacheManager, DATA_CACHE_NAME); } //------------------------------------------------------------------------- /** * Gets the underlying provider. * * @return the underlying provider, not null */ public HistoricalTimeSeriesProvider getUnderlying() { return _underlying; } /** * Gets the cache. * * @return the cache, not null */ public Cache getCache() { return _cache; } //------------------------------------------------------------------------- @Override protected HistoricalTimeSeriesProviderGetResult doBulkGet(HistoricalTimeSeriesProviderGetRequest request) { HistoricalTimeSeriesProviderGetResult result = new HistoricalTimeSeriesProviderGetResult(); // find in cache Set<ExternalIdBundle> remainingIds = new HashSet<ExternalIdBundle>(); for (ExternalIdBundle bundle : request.getExternalIdBundles()) { HistoricalTimeSeriesProviderGetRequest key = createCacheKey(request, bundle, false); LocalDateDoubleTimeSeries cached = doSingleGetInCache(key); if (cached != null) { if (cached == NO_HTS) { result.getResultMap().put(bundle, null); } else { result.getResultMap().put(bundle, cached); } } else { remainingIds.add(bundle); } } // find in underlying if (remainingIds.size() > 0) { HistoricalTimeSeriesProviderGetRequest underlyingAllRequest = JodaBeanUtils.clone(request); underlyingAllRequest.setExternalIdBundles(remainingIds); underlyingAllRequest.setDateRange(LocalDateRange.ALL); underlyingAllRequest.setMaxPoints(null); HistoricalTimeSeriesProviderGetResult underlyingAllResult = _underlying.getHistoricalTimeSeries(underlyingAllRequest); // cache result for whole time-series for (ExternalIdBundle bundle : remainingIds) { LocalDateDoubleTimeSeries underlyingWholeHts = underlyingAllResult.getResultMap().get(bundle); if (underlyingWholeHts == null) { underlyingWholeHts = NO_HTS; } HistoricalTimeSeriesProviderGetRequest wholeHtsKey = createCacheKey(underlyingAllRequest, bundle, true); _cache.put(new Element(wholeHtsKey, underlyingWholeHts)); } // cache result for requested time-series HistoricalTimeSeriesProviderGetResult fiteredResult = filterResult(underlyingAllResult, request.getDateRange(), request.getMaxPoints()); for (ExternalIdBundle bundle : remainingIds) { LocalDateDoubleTimeSeries filteredHts = fiteredResult.getResultMap().get(bundle); result.getResultMap().put(bundle, filteredHts); if (filteredHts == null) { filteredHts = NO_HTS; } HistoricalTimeSeriesProviderGetRequest key = createCacheKey(request, bundle, false); _cache.put(new Element(key, filteredHts)); } } return result; } /** * Lookup when there is only one bundle in the request. * * @param requestKey the request suitable for use as the cache key, not null * @return the result, not null */ protected LocalDateDoubleTimeSeries doSingleGetInCache(HistoricalTimeSeriesProviderGetRequest requestKey) { // find in cache Element cacheElement = _cache.get(requestKey); if (cacheElement != null) { s_logger.debug("Found time-series in cache: {}", requestKey); return (LocalDateDoubleTimeSeries) cacheElement.getObjectValue(); } // find whole time-series in cache if (requestKey.getMaxPoints() != null || requestKey.getDateRange().equals(LocalDateRange.ALL) == false) { HistoricalTimeSeriesProviderGetRequest wholeHtsKey = createCacheKey(requestKey, null, true); cacheElement = _cache.get(wholeHtsKey); if (cacheElement != null) { if (cacheElement.getObjectValue() == NO_HTS) { return NO_HTS; } LocalDateDoubleTimeSeries wholeHts = (LocalDateDoubleTimeSeries) cacheElement.getObjectValue(); LocalDateDoubleTimeSeries filteredHts = filterResult(wholeHts, requestKey.getDateRange(), requestKey.getMaxPoints()); _cache.put(new Element(requestKey, filteredHts)); // re-cache under filtered values s_logger.debug("Derived time-series from cache: {}", requestKey); return filteredHts; } } // not in cache return null; } /** * Creates a cache key. * * @param request the base request object, not null * @param bundle the bundle to set, null to leave as is (already one key) * @param allDataPoints true to create a key for all data points * @return a clone of the request with the bundle set, not null */ protected HistoricalTimeSeriesProviderGetRequest createCacheKey(HistoricalTimeSeriesProviderGetRequest request, ExternalIdBundle bundle, boolean allDataPoints) { HistoricalTimeSeriesProviderGetRequest key = JodaBeanUtils.clone(request); if (bundle != null) { key.setExternalIdBundles(Collections.singleton(bundle)); } if (allDataPoints) { key.setDateRange(LocalDateRange.ALL); key.setMaxPoints(null); } return key; } //------------------------------------------------------------------------- @Override public String toString() { return getClass().getSimpleName() + "[" + getUnderlying() + "]"; } }