/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.core.security.impl;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.fudgemsg.FudgeContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.common.base.Charsets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.core.change.DummyChangeManager;
import com.opengamma.core.security.Security;
import com.opengamma.core.security.SecuritySource;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.MutableUniqueIdentifiable;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.GUIDGenerator;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;
import com.opengamma.util.metric.MetricProducer;
/*
* REDIS DATA STRUCTURES:
* UniqueIds for each ExternalId:
* Key["EXT-"ExternalId] -> Set[UniqueId]
* Data for a particular security by UniqueId:
* Key["UNQ-"UniqueId] -> Hash
* Hash["DATA"] -> Fudge encoded security document
*
* While this data structure is more than necessary (in that you could cut out the hash for
* the security data), it allows future expansion if more data is required to be stored
* later without reformatting the Redis instance.
*
*/
/**
* A lightweight {@link SecuritySource} that cannot handle any versioning, and
* which stores all Security documents as a Fudge-encoded BLOB in Redis as a
* backing store.
*/
public class NonVersionedRedisSecuritySource implements SecuritySource, MetricProducer {
private static final Logger s_logger = LoggerFactory.getLogger(NonVersionedRedisSecuritySource.class);
private final JedisPool _jedisPool;
private final FudgeContext _fudgeContext;
private final String _redisPrefix;
private Timer _getTimer = new Timer();
private Timer _putTimer = new Timer();
private static final byte[] DATA_NAME_AS_BYTES = "DATA".getBytes(Charsets.UTF_8);
private static final byte[] CLASS_NAME_AS_BYTES = "CLASS".getBytes(Charsets.UTF_8);
/**
* The default scheme for unique identifiers.
*/
public static final String IDENTIFIER_SCHEME_DEFAULT = "RedisSec";
public NonVersionedRedisSecuritySource(JedisPool jedisPool) {
this(jedisPool, "");
}
public NonVersionedRedisSecuritySource(JedisPool jedisPool, String redisPrefix) {
this(jedisPool, redisPrefix, OpenGammaFudgeContext.getInstance());
}
public NonVersionedRedisSecuritySource(JedisPool jedisPool, String redisPrefix, FudgeContext fudgeContext) {
ArgumentChecker.notNull(jedisPool, "jedisPool");
ArgumentChecker.notNull(redisPrefix, "redisPrefix");
ArgumentChecker.notNull(fudgeContext, "fudgeContext");
_jedisPool = jedisPool;
_redisPrefix = redisPrefix;
_fudgeContext = fudgeContext;
}
/**
* Gets the jedisPool.
* @return the jedisPool
*/
protected JedisPool getJedisPool() {
return _jedisPool;
}
/**
* Gets the fudgeContext.
* @return the fudgeContext
*/
protected FudgeContext getFudgeContext() {
return _fudgeContext;
}
/**
* Gets the redisPrefix.
* @return the redisPrefix
*/
protected String getRedisPrefix() {
return _redisPrefix;
}
@Override
public void registerMetrics(MetricRegistry summaryRegistry, MetricRegistry detailRegistry, String namePrefix) {
_getTimer = summaryRegistry.timer(namePrefix + ".get");
_putTimer = summaryRegistry.timer(namePrefix + ".put");
}
// ---------------------------------------------------------------------
// REDIS KEY MANAGEMENT
// ---------------------------------------------------------------------
protected byte[] toRedisKey(UniqueId uniqueId) {
StringBuilder sb = new StringBuilder();
if (!getRedisPrefix().isEmpty()) {
sb.append(getRedisPrefix());
sb.append("-");
}
sb.append("UNQ-");
sb.append(uniqueId);
String keyText = sb.toString();
byte[] bytes = keyText.getBytes(Charsets.UTF_8);
return bytes;
}
protected byte[] toRedisKey(ObjectId objectId) {
return toRedisKey(UniqueId.of(objectId, null));
}
protected String toRedisKey(ExternalId externalId) {
StringBuilder sb = new StringBuilder();
if (!getRedisPrefix().isEmpty()) {
sb.append(getRedisPrefix());
sb.append("-");
}
sb.append("EXT-");
sb.append(externalId);
return sb.toString();
}
// ---------------------------------------------------------------------
// DATA SETTING/UPDATING OPERATIONS
// UNIQUE TO THIS CLASS
// ---------------------------------------------------------------------
public UniqueId put(Security security) {
ArgumentChecker.notNull(security, "security");
//ArgumentChecker.notNull(security.getUniqueId(), "security uniqueId");
UniqueId uniqueId = security.getUniqueId();
if (uniqueId == null) {
uniqueId = UniqueId.of(IDENTIFIER_SCHEME_DEFAULT, GUIDGenerator.generate().toString());
}
if (uniqueId.getVersion() != null) {
uniqueId = UniqueId.of(uniqueId.getObjectId(), null);
}
if (security instanceof MutableUniqueIdentifiable) {
MutableUniqueIdentifiable mutableSecurity = (MutableUniqueIdentifiable) security;
mutableSecurity.setUniqueId(uniqueId);
}
try (Timer.Context context = _putTimer.time()) {
byte[] securityData = SecurityFudgeUtil.convertToFudge(getFudgeContext(), security);
Jedis jedis = getJedisPool().getResource();
try {
for (ExternalId externalId : security.getExternalIdBundle()) {
String redisKey = toRedisKey(externalId);
jedis.sadd(redisKey, uniqueId.toString());
if (jedis.scard(redisKey) > 1) {
s_logger.warn("Multiple securities with same ExternalId {}. Probable misuse.", externalId);
}
}
byte[] redisKey = toRedisKey(uniqueId);
jedis.hset(redisKey, DATA_NAME_AS_BYTES, securityData);
jedis.hset(redisKey, CLASS_NAME_AS_BYTES, security.getClass().getName().getBytes(Charsets.UTF_8));
getJedisPool().returnResource(jedis);
} catch (Exception e) {
s_logger.error("Unable to put security " + security, e);
getJedisPool().returnBrokenResource(jedis);
throw new OpenGammaRuntimeException("Unable to put security " + security, e);
}
}
return uniqueId;
}
// ---------------------------------------------------------------------
// IMPLEMENTATION OF SECURITYSOURCE
// ---------------------------------------------------------------------
private interface GetWorker<T> {
T query(Jedis jedis);
}
protected <T> T executeGet(GetWorker<T> getWorker) {
try (Timer.Context context = _getTimer.time()) {
Jedis jedis = getJedisPool().getResource();
T result = null;
try {
result = getWorker.query(jedis);
getJedisPool().returnResource(jedis);
} catch (Exception e) {
s_logger.error("Unable to execute get", e);
getJedisPool().returnBrokenResource(jedis);
throw new OpenGammaRuntimeException("Unable to execute get()", e);
}
return result;
}
}
@Override
public Collection<Security> get(ExternalIdBundle bundle, VersionCorrection versionCorrection) {
return get(bundle);
}
@Override
public Map<ExternalIdBundle, Collection<Security>> getAll(Collection<ExternalIdBundle> bundles, VersionCorrection versionCorrection) {
Map<ExternalIdBundle, Collection<Security>> result = new HashMap<ExternalIdBundle, Collection<Security>>();
for (ExternalIdBundle bundle : bundles) {
result.put(bundle, get(bundle));
}
return result;
}
@Override
public Collection<Security> get(ExternalIdBundle bundle) {
Security security = getSingle(bundle);
if (security == null) {
return Collections.emptySet();
} else {
return Collections.singleton(security);
}
}
@Override
public Security getSingle(ExternalIdBundle bundle) {
ArgumentChecker.notNull(bundle, "bundle");
if (bundle.size() != 1) {
s_logger.warn("Possible bad use of NonVersionedRedisSecuritySource: bundle size {} not equal to 1.", bundle);
}
final ExternalId externalId = bundle.iterator().next();
Security result = executeGet(new GetWorker<Security>() {
@Override
public Security query(Jedis jedis) {
Set<String> uniqueIds = jedis.smembers(toRedisKey(externalId));
if (uniqueIds.isEmpty()) {
return null;
}
if (uniqueIds.size() > 1) {
s_logger.info("Following unique IDs for externalId {} : {}. Choosing randomly.", externalId, uniqueIds);
}
UniqueId uniqueId = UniqueId.parse(uniqueIds.iterator().next());
return getInJedis(jedis, uniqueId);
}
});
return result;
}
@Override
public Security getSingle(ExternalIdBundle bundle, VersionCorrection versionCorrection) {
return getSingle(bundle);
}
@Override
public Map<ExternalIdBundle, Security> getSingle(Collection<ExternalIdBundle> bundles, VersionCorrection versionCorrection) {
Map<ExternalIdBundle, Security> result = new HashMap<ExternalIdBundle, Security>();
for (ExternalIdBundle bundle : bundles) {
Security security = getSingle(bundle);
result.put(bundle, security);
}
return result;
}
@Override
public Security get(final UniqueId uniqueId) {
ArgumentChecker.notNull(uniqueId, "uniqueId");
Security result = executeGet(new GetWorker<Security>() {
public Security query(Jedis jedis) {
return getInJedis(jedis, uniqueId);
}
});
return result;
}
@Override
public Security get(final ObjectId objectId, VersionCorrection versionCorrection) {
ArgumentChecker.notNull(objectId, "objectId");
Security result = executeGet(new GetWorker<Security>() {
public Security query(Jedis jedis) {
return getInJedis(jedis, UniqueId.of(objectId, null));
}
});
return result;
}
@Override
public Map<UniqueId, Security> get(Collection<UniqueId> uniqueIds) {
Map<UniqueId, Security> result = new HashMap<UniqueId, Security>();
for (UniqueId uniqueId : uniqueIds) {
result.put(uniqueId, get(uniqueId));
}
return result;
}
@Override
public Map<ObjectId, Security> get(Collection<ObjectId> objectIds, VersionCorrection versionCorrection) {
Map<ObjectId, Security> result = new HashMap<ObjectId, Security>();
for (ObjectId objectId : objectIds) {
result.put(objectId, get(UniqueId.of(objectId, null)));
}
return result;
}
@Override
public ChangeManager changeManager() {
return DummyChangeManager.INSTANCE;
}
protected Security getInJedis(Jedis jedis, UniqueId uniqueId) {
byte[] redisKey = toRedisKey(uniqueId);
byte[] securityData = jedis.hget(redisKey, DATA_NAME_AS_BYTES);
byte[] classNameData = jedis.hget(redisKey, CLASS_NAME_AS_BYTES);
if (securityData == null) {
s_logger.warn("No data for security unique ID {}", uniqueId);
return null;
} else {
String className = Charsets.UTF_8.decode(ByteBuffer.wrap(classNameData)).toString();
Security security = null;
try {
security = SecurityFudgeUtil.convertFromFudge(getFudgeContext(), className, securityData);
} catch (Exception ex) {
s_logger.warn("Unable to convert from fudge for security unique ID " + uniqueId, ex);
}
return security;
}
}
}