package com.datastax.dse.demo.dao; import java.nio.charset.CharacterCodingException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.cassandra.cql.jdbc.CassandraResultSet; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.utils.ByteBufferUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlParameterValue; import org.springframework.stereotype.Component; import com.datastax.dse.demo.domain.Portfolio; import com.datastax.dse.demo.domain.Position; import com.datastax.dse.demo.domain.Stock; import com.datastax.dse.demo.domain.StockHistory; /** * Some examples of how to wire up a DAO with the jdbc-pool data source * and JdbcTemplate. See individual methods for details. * * @author zznate */ @Component public class PortfolioDao { @Autowired private JdbcTemplate jdbcTemplate; /** * Return a List of Portfolio objects with just the name set * * @param startPortfolioName * @param count * @return */ public List<Portfolio> getPortfoliosLazy(String startPortfolioName, int count) { // TODO hack setObject proxy in jdbc-pool String portfoliosCql = "select FIRST 1000 ''..'' FROM Portfolios WHERE KEY >= ? LIMIT "+count; List<Portfolio> portfolios = jdbcTemplate.query(portfoliosCql, new SqlParameterValue[]{new SqlParameterValue(Types.VARCHAR, startPortfolioName)}, new RowMapper<Portfolio>() { public Portfolio mapRow(ResultSet rs, int arg1) throws SQLException { CassandraResultSet crs = (CassandraResultSet)rs; Portfolio portfolio = new Portfolio(); portfolio.setName(new String(crs.getKey())); return portfolio; } }); return portfolios; } /** * Load a single {@link Portfolio} object from the provided portfolioName. * This load method populates the related positions and sets their prices, * effectively fully building out he {@link Portfolio} for display on the * front end. * * @param portfolioName * @return */ public Portfolio loadPortfolio(String portfolioName) { Portfolio portfolio = jdbcTemplate.queryForObject("select FIRST 1000 ''..'' FROM Portfolios WHERE KEY = ?", new SqlParameterValue[]{new SqlParameterValue(Types.VARCHAR, portfolioName)}, new RowMapper<Portfolio>() { public Portfolio mapRow(ResultSet rs, int arg1) throws SQLException { CassandraResultSet crs = (CassandraResultSet)rs; Portfolio portfolio = new Portfolio(); portfolio.setName(new String(crs.getKey())); List<String> tickers = new ArrayList<String>(); ResultSetMetaData rsmd = rs.getMetaData(); for (int i = 1; i <= rsmd.getColumnCount(); i++) { // gnarly hack around off-by-one in crs.getColumn(i) Column column = crs.getColumn(rsmd.getColumnName(i)).getRawColumn(); Position position = null; try { position = new Position(ByteBufferUtil.string(column.name), 0.0, ByteBufferUtil.toLong(column.value)); portfolio.addToConstituents(position); tickers.add(position.getTicker()); // TODO add stockHist data as well } catch (CharacterCodingException cce) { cce.printStackTrace(); } } portfolio.applyStockPrices(loadStocks(tickers.toArray(new String[]{}))); return portfolio; } }); return portfolio; } /** * Return a List of {@link StockHistory} objects - one for each ticker. * The days argument limits the number of column * @param tickers * @return */ public List<StockHistory> loadHistoricals(long days, String ...tickers) { List<StockHistory> results = jdbcTemplate.query(String.format("select FIRST %d ''..'' FROM StockHist where key in (%s)", days, appendKeys(tickers).toString()), new RowMapper<StockHistory>() { public StockHistory mapRow(ResultSet rs, int row) throws SQLException { CassandraResultSet crs = (CassandraResultSet)rs; ResultSetMetaData rsmd = rs.getMetaData(); StockHistory stockHistory = new StockHistory(new String(crs.getKey())); for (int i = 1; i <= rsmd.getColumnCount(); i++) { // gnarly hack around off-by-one in crs.getColumn(i) Column column = crs.getColumn(rsmd.getColumnName(i)).getRawColumn(); try { stockHistory.addDate(ByteBufferUtil.string(column.bufferForName()), ByteBufferUtil.toDouble(column.bufferForValue())); } catch (CharacterCodingException e) { e.printStackTrace(); } } return stockHistory; } }); return results; } /** * Load a list of {@link Stock} objects - one per provided ticker. This is assembled as a * 'WHERE KEY IN (?,?,?...) clause which uses a similar approach to multiget_slice thrift method. * Ie. It parallelizes the selects on the coordinator. * * @param tickers * @return */ public List<Stock> loadStocks(String ...tickers) { // TODO Gnarly hack until we can get array params working List<Stock> stocks = jdbcTemplate.query(String.format("select price FROM Stocks WHERE KEY IN (%s)", appendKeys(tickers).toString()), new RowMapper<Stock>() { public Stock mapRow(ResultSet rs, int row) throws SQLException { CassandraResultSet crs = (CassandraResultSet)rs; Stock stock = new Stock(); stock.setTicker(new String(crs.getKey())); stock.setPrice(crs.getDouble("price")); return stock; } }); return stocks; } private StringBuilder appendKeys(String... tickers) { StringBuilder keys = new StringBuilder(); for (int i = 0; i < tickers.length; i++) { if ( i > 0 ) keys.append(","); keys.append(tickers[i]); } return keys; } }