/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.core.convention.impl; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import com.google.common.collect.MapMaker; import com.opengamma.DataNotFoundException; import com.opengamma.core.change.ChangeManager; import com.opengamma.core.convention.Convention; import com.opengamma.core.convention.ConventionSource; import com.opengamma.id.ExternalId; import com.opengamma.id.ExternalIdBundle; import com.opengamma.id.ObjectId; import com.opengamma.id.UniqueId; import com.opengamma.id.VersionCorrection; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.ehcache.EHCacheUtils; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * A cache decorating a {@code ConventionSource}. * <p> * The cache is implemented using {@code EHCache}. * <p> * Any requests with a "latest" version/correction or unversioned unique identifier are not cached * and will always hit the underlying. This should not be an issue in practice as the engine components * which use the convention source will always specify an exact version/correction and versioned * unique identifiers. * <p> * For the getSingle() methods, DataNotFoundExceptions are cached as this gives substantial * performance benefits in some situations. This should become the norm across all methods * and sources. */ public class EHCachingConventionSource implements ConventionSource { /** * Cache key for conventions. */ private static final String CONVENTION_CACHE = "convention"; /** * The underlying convention source. */ private final ConventionSource _underlying; /** * The cache manager. */ private final CacheManager _cacheManager; /** * The convention cache. */ private final Cache _conventionCache; /** * The front cache. */ private final ConcurrentMap<Object, Convention> _frontCache = new MapMaker().weakValues().makeMap(); /** * Creates the cache around an underlying convention source. * * @param underlying the underlying data, not null * @param cacheManager the cache manager, not null */ public EHCachingConventionSource(final ConventionSource underlying, final CacheManager cacheManager) { ArgumentChecker.notNull(underlying, "underlying"); ArgumentChecker.notNull(cacheManager, "cacheManager"); _underlying = underlying; _cacheManager = cacheManager; EHCacheUtils.addCache(cacheManager, CONVENTION_CACHE); _conventionCache = EHCacheUtils.getCacheFromManager(cacheManager, CONVENTION_CACHE); } //------------------------------------------------------------------------- /** * Gets the underlying source of conventions. * * @return the underlying source of conventions, not null */ protected ConventionSource getUnderlying() { return _underlying; } /** * Gets the cache manager. * * @return the cache manager, not null */ protected CacheManager getCacheManager() { return _cacheManager; } /** * For use by test methods only to control the front cache. */ void emptyFrontCache() { _frontCache.clear(); } /** * For use by test methods only to control the EH cache. */ void emptyEHCache() { EHCacheUtils.clear(getCacheManager(), CONVENTION_CACHE); } //------------------------------------------------------------------------- protected Convention addToFrontCache(Convention convention, VersionCorrection versionCorrection) { if (convention.getUniqueId().isLatest()) { return convention; } final Convention existing = _frontCache.putIfAbsent(convention.getUniqueId(), convention); if (existing != null) { return existing; } if (versionCorrection != null) { _frontCache.put(Pairs.of(convention.getExternalIdBundle(), versionCorrection), convention); _frontCache.put(Pairs.of(convention.getUniqueId().getObjectId(), versionCorrection), convention); } return convention; } protected Convention addToCache(Convention convention, VersionCorrection versionCorrection) { final Convention front = addToFrontCache(convention, null); if (front == convention) { if (convention.getUniqueId().isVersioned()) { _conventionCache.put(new Element(convention.getUniqueId(), convention)); } if (versionCorrection != null) { _conventionCache.put(new Element(Pairs.of(convention.getExternalIdBundle(), versionCorrection), convention)); _conventionCache.put(new Element(Pairs.of(convention.getUniqueId().getObjectId(), versionCorrection), convention)); } } return front; } //------------------------------------------------------------------------- @Override public Convention get(UniqueId uniqueId) { ArgumentChecker.notNull(uniqueId, "uniqueId"); // check cache, but not if latest if (uniqueId.isVersioned()) { Convention cached = _frontCache.get(uniqueId); if (cached != null) { return cached; } final Element e = _conventionCache.get(uniqueId); if (e != null) { cached = (Convention) e.getObjectValue(); return addToFrontCache(cached, null); } } // query underlying Convention convention = getUnderlying().get(uniqueId); return addToCache(convention, null); } @Override public <T extends Convention> T get(UniqueId uniqueId, Class<T> type) { ArgumentChecker.notNull(uniqueId, "uniqueId"); ArgumentChecker.notNull(type, "type"); return type.cast(get(uniqueId)); } @Override public Convention get(ObjectId objectId, VersionCorrection versionCorrection) { ArgumentChecker.notNull(objectId, "objectId"); ArgumentChecker.notNull(versionCorrection, "versionCorrection"); // latest not in cache, can cache only by uniqueId if (versionCorrection.containsLatest()) { return addToCache(getUnderlying().get(objectId, versionCorrection), null); } // check cache final Pair<ObjectId, VersionCorrection> key = Pairs.of(objectId, versionCorrection); Convention cached = _frontCache.get(key); if (cached != null) { return cached; } final Element e = _conventionCache.get(key); if (e != null) { cached = (Convention) e.getObjectValue(); return addToFrontCache(cached, versionCorrection); } // query underlying Convention convention = getUnderlying().get(objectId, versionCorrection); return addToCache(convention, versionCorrection); } @Override public <T extends Convention> T get(ObjectId objectId, VersionCorrection versionCorrection, Class<T> type) { ArgumentChecker.notNull(objectId, "objectId"); ArgumentChecker.notNull(versionCorrection, "versionCorrection"); ArgumentChecker.notNull(type, "type"); return type.cast(get(objectId, versionCorrection)); } //------------------------------------------------------------------------- @Override public Convention getSingle(ExternalId externalId) { ArgumentChecker.notNull(externalId, "externalId"); return getSingle(externalId.toBundle(), VersionCorrection.LATEST); } @Override public <T extends Convention> T getSingle(ExternalId externalId, Class<T> type) { ArgumentChecker.notNull(externalId, "externalId"); ArgumentChecker.notNull(type, "type"); return type.cast(getSingle(externalId.toBundle(), VersionCorrection.LATEST)); } @Override public Convention getSingle(ExternalIdBundle bundle) { ArgumentChecker.notNull(bundle, "bundle"); return getSingle(bundle, VersionCorrection.LATEST); } @Override public <T extends Convention> T getSingle(ExternalIdBundle bundle, Class<T> type) { ArgumentChecker.notNull(bundle, "bundle"); ArgumentChecker.notNull(type, "type"); return type.cast(getSingle(bundle)); } @Override public Convention getSingle(ExternalIdBundle bundle, VersionCorrection versionCorrection) { ArgumentChecker.notNull(bundle, "bundle"); ArgumentChecker.notNull(versionCorrection, "versionCorrection"); // latest not in cache, can cache only by uniqueId if (versionCorrection.containsLatest()) { return addToCache(getUnderlying().getSingle(bundle, versionCorrection), null); } // check cache final Pair<ExternalIdBundle, VersionCorrection> key = Pairs.of(bundle, versionCorrection); Convention cached = _frontCache.get(key); if (cached != null) { return cached; } final Element e = _conventionCache.get(key); if (e != null) { Object value = e.getObjectValue(); if (value instanceof DataNotFoundException) { throw (DataNotFoundException) value; } return addToFrontCache((Convention) value, versionCorrection); } // query underlying try { Convention convention = getUnderlying().getSingle(bundle, versionCorrection); return addToCache(convention, versionCorrection); } catch (DataNotFoundException ex) { // Only store the exception in the EH cache instance _conventionCache.put(new Element(Pairs.of(bundle, versionCorrection), ex)); throw ex; } } @Override public <T extends Convention> T getSingle(ExternalIdBundle bundle, VersionCorrection versionCorrection, Class<T> type) { ArgumentChecker.notNull(bundle, "bundle"); ArgumentChecker.notNull(versionCorrection, "versionCorrection"); ArgumentChecker.notNull(type, "type"); return type.cast(getSingle(bundle, versionCorrection)); } //------------------------------------------------------------------------- @Override @SuppressWarnings("deprecation") public Collection<Convention> get(ExternalIdBundle bundle) { return getUnderlying().get(bundle); } @Override public Collection<Convention> get(ExternalIdBundle bundle, VersionCorrection versionCorrection) { return getUnderlying().get(bundle, versionCorrection); } //------------------------------------------------------------------------- @Override public Map<UniqueId, Convention> get(Collection<UniqueId> uniqueIds) { Map<UniqueId, Convention> map = getUnderlying().get(uniqueIds); for (Entry<UniqueId, Convention> entry : map.entrySet()) { entry.setValue(addToCache(entry.getValue(), null)); } return map; } @Override public Map<ObjectId, Convention> get(Collection<ObjectId> objectIds, VersionCorrection versionCorrection) { Map<ObjectId, Convention> map = getUnderlying().get(objectIds, versionCorrection); for (Entry<ObjectId, Convention> entry : map.entrySet()) { entry.setValue(addToCache(entry.getValue(), versionCorrection)); } return map; } @Override public Map<ExternalIdBundle, Collection<Convention>> getAll(Collection<ExternalIdBundle> bundles, VersionCorrection versionCorrection) { return getUnderlying().getAll(bundles, versionCorrection); } @Override public Map<ExternalIdBundle, Convention> getSingle(Collection<ExternalIdBundle> bundles, VersionCorrection versionCorrection) { Map<ExternalIdBundle, Convention> map = getUnderlying().getSingle(bundles, versionCorrection); for (Entry<ExternalIdBundle, Convention> entry : map.entrySet()) { entry.setValue(addToCache(entry.getValue(), versionCorrection)); } return map; } //------------------------------------------------------------------------- @Override public ChangeManager changeManager() { return getUnderlying().changeManager(); } /** * Call this at the end of a unit test run to clear the state of EHCache. * It should not be part of a generic lifecycle method. */ protected void shutdown() { _cacheManager.removeCache(CONVENTION_CACHE); _frontCache.clear(); } @Override public String toString() { return getClass().getSimpleName() + "[" + getUnderlying() + "]"; } }