org.stockwatcher.data.cassandra.WatchListDAOImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.stockwatcher.data.cassandra.WatchListDAOImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.stockwatcher.data.cassandra;

import static com.datastax.driver.core.querybuilder.QueryBuilder.batch;
import static com.datastax.driver.core.querybuilder.QueryBuilder.decr;
import static com.datastax.driver.core.querybuilder.QueryBuilder.delete;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.in;
import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
import static com.datastax.driver.core.querybuilder.QueryBuilder.update;

import java.util.Date;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.stockwatcher.data.DAOException;
import org.stockwatcher.domain.Stock;
import org.stockwatcher.domain.WatchList;
import org.stockwatcher.domain.WatchListItem;

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.DriverException;
import com.datastax.driver.core.querybuilder.Batch;
import com.datastax.driver.core.utils.UUIDs;

/**
 * Implementation of the WatchListDAO interface for Apache Cassandra.
 * 
 * NOTE: There are a few places in this class where we perform a read before 
 * write. As a general rule this is something we want to avoid. In the cases 
 * where we have done this we made a conscious decision to validate the data
 * being passed in so we can throw an appropriate exception. 
 * 
 * @author Tony Piazza
 */
@Repository
public class WatchListDAOImpl extends CassandraDAO implements CassandraWatchListDAO {

    @Autowired
    private CassandraStockDAO stockDAO;

    private PreparedStatement insertWatchListItem;
    private PreparedStatement selectWatchListById;
    private PreparedStatement selectWatchListsByUserId;
    private PreparedStatement selectWatchListItems;
    private PreparedStatement selectWatchListItemCount;
    private PreparedStatement selectWatchListCountByUserId;
    private PreparedStatement updateWatchListCount;
    private PreparedStatement updateWatchListById;

    @PostConstruct
    public void init() {
        insertWatchListItem = prepare(
                "INSERT INTO WatchListItem (watchlist_id,stock_symbol, created, start_price) VALUES (?, ?, ?, ?) IF NOT EXISTS");
        selectWatchListById = prepare(
                "SELECT watchlist_id, user_id, display_name, visibility, active, updated FROM WatchList WHERE watchlist_id=?");
        selectWatchListsByUserId = prepare(
                "SELECT watchlist_id, user_id, display_name, visibility, active, updated FROM WatchList WHERE user_id=?");
        selectWatchListItems = prepare(
                "SELECT watchlist_id, stock_symbol, start_price, created FROM WatchListItem WHERE watchlist_id=?");
        selectWatchListItemCount = prepare("SELECT COUNT(*) FROM WatchListItem WHERE watchlist_id=?");
        selectWatchListCountByUserId = prepare("SELECT COUNT(*) FROM WatchList WHERE user_id=?");
        updateWatchListCount = prepare("UPDATE StockCount SET watch_count = watch_count+1 WHERE stock_symbol = ?");
        updateWatchListById = prepare(
                "UPDATE WatchList SET display_name=?, visibility=?, active=?, updated=? WHERE watchlist_id=?");

    }

    @Override
    public void deleteWatchList(UUID id) {
        deleteWatchList(getDefaultOptions(), id);
    }

    @Override
    public void deleteWatchList(StatementOptions options, UUID id) {
        if (id == null) {
            throw new IllegalArgumentException("id argument is null");
        }
        try {
            // Get the stock symbols before we delete them from the watch list
            SortedSet<String> symbols = getWatchListStockSymbols(options, id);
            RegularStatement statement1 = delete().from("WatchListItem").where(eq("watchlist_id", id));
            Batch batch = batch(statement1);
            RegularStatement statement2 = delete().from("WatchList").where(eq("watchlist_id", id));
            batch.add(statement2);
            execute(batch, options);
            // Statements with counter column families cannot be executed in a batch
            if (!symbols.isEmpty()) {
                Statement statement3 = update("StockCount").with(decr("watch_count"))
                        .where(in("stock_symbol", (Object[]) symbols.toArray()));
                execute(statement3, options);
            }
        } catch (DriverException e) {
            throw new DAOException(e);
        }
    }

    private int getWatchListItemCount(StatementOptions options, UUID id) {
        BoundStatement bs = selectWatchListItemCount.bind();
        bs.setUUID("watchlist_id", id);
        Row row = execute(bs, options).one();
        long count = row.getLong(0);
        if (count > Integer.MAX_VALUE) {
            throw new IllegalStateException("count is too big");
        }
        return (int) count;
    }

    @Override
    public WatchList getWatchList(UUID id) {
        return getWatchList(getDefaultOptions(), id);
    }

    @Override
    public WatchList getWatchList(StatementOptions options, UUID id) {
        if (id == null) {
            throw new IllegalArgumentException("id argument is null");
        }
        try {
            BoundStatement bs = selectWatchListById.bind();
            bs.setUUID("watchlist_id", id);
            Row watchListRow = execute(bs, options).one();
            if (watchListRow == null) {
                throw new DAOException("no watch list found for specified id");
            }
            return WatchListHelper.createWatchList(watchListRow, getWatchListItemCount(options, id));
        } catch (DriverException e) {
            throw new DAOException(e);
        }
    }

    @Override
    public int getWatchListCountByUserId(UUID userId) {
        return getWatchListCountByUserId(getDefaultOptions(), userId);
    }

    @Override
    public int getWatchListCountByUserId(StatementOptions options, UUID userId) {
        try {
            BoundStatement bs = selectWatchListCountByUserId.bind();
            bs.setUUID("user_id", userId);
            Row row = execute(bs, options).one();
            long count = row.getLong(0);
            if (count > Integer.MAX_VALUE) {
                throw new IllegalStateException("count is too big");
            }
            return (int) count;
        } catch (DriverException e) {
            throw new DAOException(e);
        }
    }

    @Override
    public SortedSet<WatchList> getWatchListsByUserId(UUID userId) {
        return getWatchListsByUserId(getDefaultOptions(), userId);
    }

    @Override
    public SortedSet<WatchList> getWatchListsByUserId(StatementOptions options, UUID userId) {
        if (userId == null) {
            throw new IllegalArgumentException("userId argument is null");
        }
        SortedSet<WatchList> watchLists = new TreeSet<WatchList>();
        try {
            BoundStatement bs = selectWatchListsByUserId.bind();
            bs.setUUID("user_id", userId);
            for (Row row : execute(bs, options)) {
                UUID id = row.getUUID("watchlist_id");
                watchLists.add(WatchListHelper.createWatchList(row, getWatchListItemCount(options, id)));
            }
        } catch (DriverException e) {
            throw new DAOException(e);
        }
        return watchLists;
    }

    @Override
    public SortedSet<String> getWatchListStockSymbols(UUID id) {
        return getWatchListStockSymbols(getDefaultOptions(), id);
    }

    @Override
    public SortedSet<String> getWatchListStockSymbols(StatementOptions options, UUID id) {
        if (id == null) {
            throw new IllegalArgumentException("id argument is null");
        }

        // We assume that the watchlist id is valid
        SortedSet<String> stocks = new TreeSet<String>();
        try {
            Statement statement = select().column("stock_symbol").from("WatchListItem")
                    .where(eq("watchlist_id", id));
            for (Row row : execute(statement, options)) {
                stocks.add(row.getString("stock_symbol"));
            }
        } catch (DriverException e) {
            throw new DAOException(e);
        }
        return stocks;
    }

    @Override
    public SortedSet<WatchListItem> getWatchListItems(UUID id) {
        return getWatchListItems(getDefaultOptions(), id);
    }

    @Override
    public SortedSet<WatchListItem> getWatchListItems(StatementOptions options, UUID id) {
        if (id == null) {
            throw new IllegalArgumentException("id argument is null");
        }

        // We assume that the watchlist id is valid
        SortedSet<WatchListItem> items = new TreeSet<WatchListItem>();
        try {
            BoundStatement bs = selectWatchListItems.bind();
            bs.setUUID("watchlist_id", id);
            for (Row row : execute(bs, options)) {
                items.add(WatchListHelper.createWatchListItem(row,
                        stockDAO.getStockBySymbol(options, row.getString("stock_symbol"))));
            }
        } catch (DriverException e) {
            throw new DAOException(e);
        }
        return items;
    }

    @Override
    public void insertWatchList(WatchList watchList) {
        insertWatchList(getDefaultOptions(), watchList);
    }

    @Override
    public void insertWatchList(StatementOptions options, WatchList watchList) {
        if (watchList == null) {
            throw new IllegalArgumentException("watchList argument is null");
        }
        UUID userId = watchList.getUserId();
        if (userId == null) {
            throw new IllegalArgumentException("userId property is null");
        }
        UUID id = UUIDs.timeBased();
        Date now = new Date(UUIDs.unixTimestamp(id));
        try {
            Statement statement = insertInto("WatchList").value("watchlist_id", id)
                    .value("user_id", watchList.getUserId()).value("display_name", watchList.getDisplayName())
                    .value("visibility", watchList.getVisibility().name()).value("active", watchList.isActive())
                    .value("updated", now);
            execute(statement, options);
        } catch (DriverException e) {
            throw new DAOException(e);
        }
        watchList.setId(id);
        watchList.setCreated(now);
        watchList.setUpdated(now);
    }

    @Override
    public void updateWatchList(WatchList watchList) {
        updateWatchList(getDefaultOptions(), watchList);
    }

    @Override
    public void updateWatchList(StatementOptions options, WatchList watchList) {
        if (watchList == null) {
            throw new IllegalArgumentException("watchList is null");
        }
        final UUID id = watchList.getId();
        if (id == null) {
            throw new IllegalArgumentException("id property is null");
        }

        // We assume that the watchlist id is valid
        Date now = new Date();
        try {
            BoundStatement bs = updateWatchListById.bind();
            bs.setString("display_name", watchList.getDisplayName());
            bs.setString("visibility", watchList.getVisibility().name());
            bs.setBool("active", watchList.isActive());
            bs.setDate("updated", now);
            bs.setUUID("watchlist_id", id);
            execute(bs, options);
        } catch (DriverException e) {
            throw new DAOException(e);
        }
        watchList.setUpdated(now);
    }

    @Override
    public void addWatchListStock(UUID id, String stockSymbol) {
        addWatchListStock(getDefaultOptions(), id, stockSymbol);
    }

    /**
     * Add a stock to a watch list.  We assume that the passed
     * in watch list id and the stock symbol are valid and exist
     * 
     * @param id  The watch list id
     * @param stockSymbol The stock symbol to remove 
     */
    @Override
    public void addWatchListStock(StatementOptions options, UUID id, String stockSymbol) {
        if (id == null) {
            throw new IllegalArgumentException("id argument is null");
        }

        Stock stock = stockDAO.getStockBySymbol(options, stockSymbol);
        try {
            /*******
             * Business requirement: Make sure the stock is not already on the 
             * watch list, otherwise we upsert and lose the original created date.
             * We do this using CAS in the prepared statement (using IF NOT EXISTS on the INSERT)
             *******/
            Date now = new Date();
            BoundStatement bs = insertWatchListItem.bind();
            bs.setUUID("watchlist_id", id);
            bs.setString("stock_symbol", stockSymbol);
            bs.setDate("created", now);
            bs.setDecimal("start_price", stock.getCurrentPrice());

            /*******
             * Statements with counter column families cannot be executed in a batch.
             * However, we need to make sure that we only update the counter if 
             * the insert succeeds - accordingly we get the result of the CAS [applied] 
             * column which will be true if the update succeeded.
             */
            Row insertResult = execute(bs, options).one();
            boolean applied = insertResult.getBool("[applied]");
            System.out.println("WLDAOImp.addWatchListStock applied = " + applied);
            if (applied) {
                BoundStatement bs2 = updateWatchListCount.bind();
                bs2.setString("stock_symbol", stockSymbol);
                execute(bs2, options);
            }
        } catch (DriverException e) {
            throw new DAOException(e);
        }
    }

    @Override
    public void removeWatchListStock(UUID id, String stockSymbol) {
        removeWatchListStock(getDefaultOptions(), id, stockSymbol);
    }

    /**
     * Remove a stock from a watch list.  We assume that the passed
     * in watch list id, and the stock symbol are valid and exist
     * 
     * @param id  The watch list id
     * @param stockSymbol The stock symbol to remove 
     */
    @Override
    public void removeWatchListStock(StatementOptions options, UUID id, String stockSymbol) {
        if (id == null) {
            throw new IllegalArgumentException("id argument is null");
        }

        try {
            // We need to know if the stock was actually on the watch list 
            // before we decrement the counters.
            Statement statement1 = select().column("watchlist_id").column("stock_symbol").column("start_price")
                    .column("created").from("WatchListItem").where(eq("watchlist_id", id))
                    .and(eq("stock_symbol", stockSymbol));
            Row row = execute(statement1, options).one();
            if (row == null) {
                throw new IllegalArgumentException("stock symbol is not on the specified watchList");
            }
            Statement statement2 = delete().from("WatchListItem").where(eq("watchlist_id", id))
                    .and(eq("stock_symbol", stockSymbol));
            execute(statement2, options);
            // Statements with counter column families cannot be executed in a batch
            Statement statement3 = update("StockCount").with(decr("watch_count"))
                    .where(eq("stock_symbol", stockSymbol));
            execute(statement3, options);
        } catch (DriverException e) {
            throw new DAOException(e);
        }
    }

    @Override
    public int getWatchCount(String symbol) {
        return getWatchCount(getDefaultOptions(), symbol);
    }

    @Override
    public int getWatchCount(StatementOptions options, String symbol) {
        try {
            Statement statement = select().column("watch_count").from("StockCount")
                    .where(eq("stock_symbol", symbol));
            Row row = execute(statement, options).one();
            long result = row == null ? 0 : row.getLong("watch_count");
            if (result > Integer.MAX_VALUE) {
                throw new IllegalStateException("watch count is too big");
            }
            return (int) result;
        } catch (DriverException e) {
            throw new DAOException(e);
        }
    }
}