/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.masterdb.config;
import java.io.ByteArrayInputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeObjectReader;
import org.fudgemsg.mapping.FudgeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.support.SqlLobValue;
import org.springframework.jdbc.support.lob.LobHandler;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.opengamma.core.config.impl.ConfigItem;
import com.opengamma.elsql.ElSqlBundle;
import com.opengamma.id.IdUtils;
import com.opengamma.id.MutableUniqueIdentifiable;
import com.opengamma.id.ObjectId;
import com.opengamma.id.ObjectIdentifiable;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.master.AbstractHistoryRequest;
import com.opengamma.master.AbstractHistoryResult;
import com.opengamma.master.config.ConfigDocument;
import com.opengamma.master.config.ConfigHistoryRequest;
import com.opengamma.master.config.ConfigHistoryResult;
import com.opengamma.master.config.ConfigMetaDataRequest;
import com.opengamma.master.config.ConfigMetaDataResult;
import com.opengamma.master.config.ConfigSearchRequest;
import com.opengamma.master.config.ConfigSearchResult;
import com.opengamma.master.config.ConfigSearchSortOrder;
import com.opengamma.masterdb.AbstractDocumentDbMaster;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.ClassUtils;
import com.opengamma.util.db.DbConnector;
import com.opengamma.util.db.DbDateUtils;
import com.opengamma.util.db.DbMapSqlParameterSource;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;
import com.opengamma.util.paging.Paging;
import com.opengamma.util.paging.PagingRequest;
/**
*
*/
/*package*/class DbConfigWorker extends AbstractDocumentDbMaster<ConfigDocument> {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(DbConfigWorker.class);
/**
* The Fudge context.
*/
protected static final FudgeContext s_fudgeContext = OpenGammaFudgeContext.getInstance();
// -----------------------------------------------------------------
// TIMERS FOR METRICS GATHERING
// By default these do nothing. Registration will replace them
// so that they actually do something.
// -----------------------------------------------------------------
private Timer _searchTimer = new Timer();
private Timer _metaDataTimer = new Timer();
private Timer _insertTimer = new Timer();
/**
* SQL order by.
*/
protected static final EnumMap<ConfigSearchSortOrder, String> ORDER_BY_MAP = new EnumMap<ConfigSearchSortOrder, String>(ConfigSearchSortOrder.class);
static {
ORDER_BY_MAP.put(ConfigSearchSortOrder.OBJECT_ID_ASC, "oid ASC");
ORDER_BY_MAP.put(ConfigSearchSortOrder.OBJECT_ID_DESC, "oid DESC");
ORDER_BY_MAP.put(ConfigSearchSortOrder.VERSION_FROM_INSTANT_ASC, "ver_from_instant ASC");
ORDER_BY_MAP.put(ConfigSearchSortOrder.VERSION_FROM_INSTANT_DESC, "ver_from_instant DESC");
ORDER_BY_MAP.put(ConfigSearchSortOrder.NAME_ASC, "name ASC");
ORDER_BY_MAP.put(ConfigSearchSortOrder.NAME_DESC, "name DESC");
}
/**
* Creates an instance.
*
* @param dbConnector the database connector, not null
* @param defaultScheme the default scheme, not null
*/
public DbConfigWorker(DbConnector dbConnector, String defaultScheme) {
super(dbConnector, defaultScheme);
setElSqlBundle(ElSqlBundle.of(dbConnector.getDialect().getElSqlConfig(), DbConfigMaster.class));
}
@Override
public void registerMetrics(MetricRegistry summaryRegistry, MetricRegistry detailedRegistry, String namePrefix) {
super.registerMetrics(summaryRegistry, detailedRegistry, namePrefix);
_insertTimer = summaryRegistry.timer(namePrefix + ".insert");
_metaDataTimer = summaryRegistry.timer(namePrefix + ".metaData");
_searchTimer = summaryRegistry.timer(namePrefix + ".search");
}
//-------------------------------------------------------------------------
@Override
public ConfigDocument get(UniqueId uniqueId) {
return doGet(uniqueId, new ConfigDocumentExtractor(), "Config");
}
@Override
public ConfigDocument get(ObjectIdentifiable objectId, VersionCorrection versionCorrection) {
return doGetByOidInstants(objectId, versionCorrection, new ConfigDocumentExtractor(), "Config");
}
@Override
protected void mergeNonUpdatedFields(ConfigDocument newDocument, ConfigDocument oldDocument) {
if (newDocument.getConfig() == null) {
ConfigDocument hackGenerics = newDocument;
hackGenerics.setConfig(oldDocument.getConfig());
}
}
@Override
protected ConfigDocument insert(ConfigDocument document) {
ArgumentChecker.notNull(document.getName(), "document.name");
ArgumentChecker.notNull(document.getConfig(), "document.value");
ArgumentChecker.notNull(document.getType(), "document.type");
Timer.Context context = _insertTimer.time();
try {
final Object value = document.getConfig().getValue();
final long docId = nextId("cfg_config_seq");
final long docOid = (document.getUniqueId() != null ? extractOid(document.getUniqueId()) : docId);
// set the uniqueId
final UniqueId uniqueId = createUniqueId(docOid, docId);
document.setUniqueId(uniqueId);
if (value instanceof MutableUniqueIdentifiable) {
((MutableUniqueIdentifiable) value).setUniqueId(uniqueId);
}
byte[] bytes = serializeToFudge(value);
// the arguments for inserting into the config table
final DbMapSqlParameterSource docArgs = new DbMapSqlParameterSource()
.addValue("doc_id", docId)
.addValue("doc_oid", docOid)
.addTimestamp("ver_from_instant", document.getVersionFromInstant())
.addTimestampNullFuture("ver_to_instant", document.getVersionToInstant())
.addTimestamp("corr_from_instant", document.getCorrectionFromInstant())
.addTimestampNullFuture("corr_to_instant", document.getCorrectionToInstant())
.addValue("name", document.getName())
.addValue("config_type", document.getType().getName())
.addValue("config", new SqlLobValue(bytes, getDialect().getLobHandler()), Types.BLOB);
final String sqlDoc = getElSqlBundle().getSql("Insert", docArgs);
getJdbcTemplate().update(sqlDoc, docArgs);
return document;
} finally {
context.stop();
}
}
private byte[] serializeToFudge(final Object configObj) {
// serialize the configuration value
FudgeSerializer serializer = new FudgeSerializer(s_fudgeContext);
MutableFudgeMsg objectToFudgeMsg = serializer.objectToFudgeMsg(configObj);
return s_fudgeContext.toByteArray(objectToFudgeMsg);
}
//-------------------------------------------------------------------------
public ConfigMetaDataResult metaData(ConfigMetaDataRequest request) {
ArgumentChecker.notNull(request, "request");
Timer.Context context = _metaDataTimer.time();
try {
ConfigMetaDataResult result = new ConfigMetaDataResult();
if (request.isConfigTypes()) {
final String sql = getElSqlBundle().getSql("SelectTypes");
List<String> configTypes = getJdbcTemplate().getJdbcOperations().queryForList(sql, String.class);
for (String configType : configTypes) {
try {
result.getConfigTypes().add(ClassUtils.loadClass(configType));
} catch (ClassNotFoundException ex) {
s_logger.warn("Unable to load class", ex);
}
}
}
return result;
} finally {
context.stop();
}
}
//-------------------------------------------------------------------------
public <T> ConfigSearchResult<T> search(ConfigSearchRequest<T> request) {
ArgumentChecker.notNull(request, "request");
ArgumentChecker.notNull(request.getType(), "request.type");
ArgumentChecker.notNull(request.getPagingRequest(), "request.pagingRequest");
ArgumentChecker.notNull(request.getVersionCorrection(), "request.versionCorrection");
s_logger.debug("search {}", request);
Timer.Context context = _searchTimer.time();
try {
final VersionCorrection vc = request.getVersionCorrection().withLatestFixed(now());
final ConfigSearchResult<T> result = new ConfigSearchResult<T>(vc);
final List<ObjectId> objectIds = request.getConfigIds();
if (objectIds != null && objectIds.size() == 0) {
result.setPaging(Paging.of(request.getPagingRequest(), 0));
return result;
}
final DbMapSqlParameterSource args = new DbMapSqlParameterSource()
.addTimestamp("version_as_of_instant", vc.getVersionAsOf())
.addTimestamp("corrected_to_instant", vc.getCorrectedTo())
.addValueNullIgnored("name", getDialect().sqlWildcardAdjustValue(request.getName()));
if (!request.getType().isInstance(Object.class)) {
args.addValue("config_type", request.getType().getName());
}
if (objectIds != null) {
StringBuilder buf = new StringBuilder(objectIds.size() * 10);
for (ObjectId objectId : objectIds) {
checkScheme(objectId);
buf.append(extractOid(objectId)).append(", ");
}
buf.setLength(buf.length() - 2);
args.addValue("sql_search_object_ids", buf.toString());
}
args.addValue("sort_order", ORDER_BY_MAP.get(request.getSortOrder()));
args.addValue("paging_offset", request.getPagingRequest().getFirstItem());
args.addValue("paging_fetch", request.getPagingRequest().getPagingSize());
String[] sql = {getElSqlBundle().getSql("Search", args), getElSqlBundle().getSql("SearchCount", args) };
final NamedParameterJdbcOperations namedJdbc = getDbConnector().getJdbcTemplate();
ConfigDocumentExtractor configDocumentExtractor = new ConfigDocumentExtractor();
if (request.equals(PagingRequest.ALL)) {
List<ConfigDocument> queryResult = namedJdbc.query(sql[0], args, configDocumentExtractor);
for (ConfigDocument configDocument : queryResult) {
if (request.getType().isInstance(configDocument.getConfig().getValue())) {
result.getDocuments().add(configDocument);
}
}
result.setPaging(Paging.of(request.getPagingRequest(), result.getDocuments()));
} else {
final int count = namedJdbc.queryForObject(sql[1], args, Integer.class);
result.setPaging(Paging.of(request.getPagingRequest(), count));
if (count > 0 && request.getPagingRequest().equals(PagingRequest.NONE) == false) {
List<ConfigDocument> queryResult = namedJdbc.query(sql[0], args, configDocumentExtractor);
for (ConfigDocument configDocument : queryResult) {
if (request.getType().isInstance(configDocument.getConfig().getValue())) {
result.getDocuments().add(configDocument);
}
}
}
}
return result;
} finally {
context.stop();
}
}
//-------------------------------------------------------------------------
public <T> ConfigHistoryResult<T> history(ConfigHistoryRequest<T> request) {
ArgumentChecker.notNull(request, "request");
//ArgumentChecker.notNull(request.getType(), "request.type");
ArgumentChecker.notNull(request.getObjectId(), "request.objectId");
checkScheme(request.getObjectId());
s_logger.debug("history {}", request);
ConfigHistoryResult<T> result = new ConfigHistoryResult<T>();
ConfigDocumentExtractor extractor = new ConfigDocumentExtractor();
final DbMapSqlParameterSource args = argsHistory(request);
final String[] sql = {getElSqlBundle().getSql("History", args), getElSqlBundle().getSql("HistoryCount", args) };
final NamedParameterJdbcOperations namedJdbc = getDbConnector().getJdbcTemplate();
if (request.getPagingRequest().equals(PagingRequest.ALL)) {
List<ConfigDocument> queryResult = namedJdbc.query(sql[0], args, extractor);
for (ConfigDocument configDocument : queryResult) {
if (request.getType() == null || request.getType().isInstance(configDocument.getConfig().getValue())) {
result.getDocuments().add(configDocument);
}
}
result.setPaging(Paging.of(request.getPagingRequest(), result.getDocuments()));
} else {
final int count = namedJdbc.queryForObject(sql[1], args, Integer.class);
result.setPaging(Paging.of(request.getPagingRequest(), count));
if (count > 0 && request.getPagingRequest().equals(PagingRequest.NONE) == false) {
List<ConfigDocument> queryResult = namedJdbc.query(sql[0], args, extractor);
for (ConfigDocument configDocument : queryResult) {
if (request.getType() == null || request.getType().isInstance(configDocument.getConfig().getValue())) {
result.getDocuments().add(configDocument);
}
}
}
}
return result;
}
//-------------------------------------------------------------------------
/**
* Mapper from SQL rows to a ConfigDocument.
*/
private final class ConfigDocumentExtractor implements ResultSetExtractor<List<ConfigDocument>> {
private long _lastDocId = -1;
private List<ConfigDocument> _documents = new ArrayList<ConfigDocument>();
@Override
public List<ConfigDocument> extractData(ResultSet rs) throws SQLException, DataAccessException {
while (rs.next()) {
final long docId = rs.getLong("DOC_ID");
if (_lastDocId != docId) {
_lastDocId = docId;
buildConfig(rs, docId);
}
}
return _documents;
}
private void buildConfig(final ResultSet rs, final long docId) throws SQLException {
final long docOid = rs.getLong("DOC_OID");
final Timestamp versionFrom = rs.getTimestamp("VER_FROM_INSTANT");
final Timestamp versionTo = rs.getTimestamp("VER_TO_INSTANT");
final Timestamp correctionFrom = rs.getTimestamp("CORR_FROM_INSTANT");
final Timestamp correctionTo = rs.getTimestamp("CORR_TO_INSTANT");
final String name = rs.getString("NAME");
final String configType = rs.getString("CONFIG_TYPE");
LobHandler lob = getDialect().getLobHandler();
byte[] bytes = lob.getBlobAsBytes(rs, "CONFIG");
Class<?> reifiedType = null;
try {
reifiedType = ClassUtils.loadClass(configType);
} catch (ClassNotFoundException ex) {
s_logger.warn("ConfigType: {} class can not be found for docOid: {}", configType, docOid);
return;
}
FudgeObjectReader objReader = s_fudgeContext.createObjectReader(new ByteArrayInputStream(bytes));
FudgeMsg fudgeMsg = objReader.getMessageReader().nextMessage();
try {
FudgeDeserializer deserializer = new FudgeDeserializer(s_fudgeContext);
Object configObj = deserializer.fudgeMsgToObject(reifiedType, fudgeMsg);
ConfigItem<?> item = ConfigItem.of(configObj);
item.setName(name);
item.setType(reifiedType);
ConfigDocument doc = new ConfigDocument(item);
UniqueId uniqueId = createUniqueId(docOid, docId);
doc.setUniqueId(uniqueId);
IdUtils.setInto(configObj, uniqueId);
doc.setVersionFromInstant(DbDateUtils.fromSqlTimestamp(versionFrom));
doc.setVersionToInstant(DbDateUtils.fromSqlTimestampNullFarFuture(versionTo));
doc.setCorrectionFromInstant(DbDateUtils.fromSqlTimestamp(correctionFrom));
doc.setCorrectionToInstant(DbDateUtils.fromSqlTimestampNullFarFuture(correctionTo));
_documents.add(doc);
} catch (Exception ex) {
s_logger.warn("Bad fudge message in database, unable to deserialise docOid:" + docOid + " " + fudgeMsg +
" to " + configType, ex);
}
}
}
@SuppressWarnings({"rawtypes", "unchecked" })
@Override
protected AbstractHistoryResult<ConfigDocument> historyByVersionsCorrections(AbstractHistoryRequest request) {
ConfigHistoryRequest historyRequest = new ConfigHistoryRequest();
historyRequest.setCorrectionsFromInstant(request.getCorrectionsFromInstant());
historyRequest.setCorrectionsToInstant(request.getCorrectionsToInstant());
historyRequest.setVersionsFromInstant(request.getVersionsFromInstant());
historyRequest.setVersionsToInstant(request.getVersionsToInstant());
historyRequest.setObjectId(request.getObjectId());
return (AbstractHistoryResult) history(historyRequest);
}
}