/******************************************************************************* * Copyright (c) 2013 Luigi Sgro. All rights reserved. This * program and the accompanying materials are made available under the terms of * the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Luigi Sgro - initial API and implementation ******************************************************************************/ package com.quantcomponents.series.jdbc; import java.sql.SQLException; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; import com.quantcomponents.core.model.IContract; import com.quantcomponents.core.model.ISeriesListener; import com.quantcomponents.core.model.ISeriesPoint; import com.quantcomponents.marketdata.IMutableOHLCTimeSeries; import com.quantcomponents.marketdata.IMutableTickTimeSeries; import com.quantcomponents.marketdata.IOHLCPoint; import com.quantcomponents.marketdata.IStockDatabase; import com.quantcomponents.marketdata.IStockDatabaseContainer; import com.quantcomponents.marketdata.ITickPoint; import com.quantcomponents.marketdata.StockDatabase; public class JdbcStockDatabaseContainer implements IStockDatabaseContainer { private static final Logger logger = Logger.getLogger(JdbcStockDatabaseContainer.class.getName()); private enum EventType { ADD, UPDATE }; private class OHLCEvent { final String stockDbId; final EventType type; final IOHLCPoint existingOhlc; final IOHLCPoint newOhlc; public OHLCEvent(String stockDbId, EventType type, IOHLCPoint existingOhlc, IOHLCPoint newOhlc) { this.stockDbId = stockDbId; this.type = type; this.existingOhlc = existingOhlc; this.newOhlc = newOhlc; } } private class TickEvent { public TickEvent(String stockDbId, ITickPoint tick) { this.stockDbId = stockDbId; this.tick = tick; } final String stockDbId; final ITickPoint tick; } private class OHLCTimeSeriesListener implements ISeriesListener<Date, Double> { private final String stockDbId; OHLCTimeSeriesListener(String stockDbId) { this.stockDbId = stockDbId; } @Override public void onItemUpdated(ISeriesPoint<Date, Double> existingItem, ISeriesPoint<Date, Double> updatedItem) { ohlcEventsQueue.add(new OHLCEvent(stockDbId, EventType.UPDATE, (IOHLCPoint) existingItem, (IOHLCPoint) updatedItem)); } @Override public void onItemAdded(ISeriesPoint<Date, Double> newItem) { ohlcEventsQueue.add(new OHLCEvent(stockDbId, EventType.ADD, null, (IOHLCPoint) newItem)); } } private class TickTimeSeriesListener implements ISeriesListener<Date, Double> { private final String stockDbId; TickTimeSeriesListener(String stockDbId) { this.stockDbId = stockDbId; } @Override public void onItemUpdated(ISeriesPoint<Date, Double> existingItem, ISeriesPoint<Date, Double> updatedItem) { throw new UnsupportedOperationException(); } @Override public void onItemAdded(ISeriesPoint<Date, Double> newItem) { tickEventsQueue.add(new TickEvent(stockDbId, (ITickPoint) newItem)); } } private static class StockDbCacheInfo { public StockDbCacheInfo(String id, IStockDatabase stockDatabase, OHLCTimeSeriesListener ohlcListener, TickTimeSeriesListener tickListener) { this.id = id; this.stockDatabase = stockDatabase; this.ohlcListener = ohlcListener; this.tickListener = tickListener; } String id; IStockDatabase stockDatabase; OHLCTimeSeriesListener ohlcListener; TickTimeSeriesListener tickListener; } private final Map<String, StockDbCacheInfo> cacheById = new HashMap<String, StockDbCacheInfo>(); private final Map<IStockDatabase, StockDbCacheInfo> cacheByStockDb = new HashMap<IStockDatabase, StockDbCacheInfo>(); private final BlockingQueue<OHLCEvent> ohlcEventsQueue = new LinkedBlockingQueue<OHLCEvent>(); private final BlockingQueue<TickEvent> tickEventsQueue = new LinkedBlockingQueue<TickEvent>(); private final IStockDatabaseHeaderDao stockDbHeaderDao; private final IOHLCPointDao ohlcPointDao; private final ITickPointDao tickPointDao; private volatile boolean interrupt; private volatile boolean asyncPersistence = false; private volatile Thread asyncOhlcPersisterThread; private volatile Thread asyncTickPersisterThread; private final Runnable asyncOhlcPersister = new Runnable() { @Override public void run() { try { while (!interrupt) { OHLCEvent event = ohlcEventsQueue.take(); if (event.type == EventType.ADD) { ohlcPointDao.save(event.stockDbId, event.newOhlc); } else if (event.type == EventType.UPDATE) { ohlcPointDao.update(event.stockDbId, event.existingOhlc, event.newOhlc); } if (!asyncPersistence) { // for testing: do not call startAsynchronousPersisters() break; } } } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while persisting OHLCPoint", e); } catch (InterruptedException e) { logger.log(Level.WARNING, "OHLC persisting thread interrupted"); } }}; private final Runnable asyncTickPersister = new Runnable() { @Override public void run() { try { while (!interrupt) { TickEvent event = tickEventsQueue.take(); tickPointDao.save(event.stockDbId, event.tick); if (!asyncPersistence) { // for testing: do not call startAsynchronousPersisters() break; } } } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while persisting TickPoint", e); } catch (InterruptedException e) { logger.log(Level.WARNING, "Tick persisting thread interrupted"); } }}; public JdbcStockDatabaseContainer(IStockDatabaseHeaderDao stockDbHeaderDao, IOHLCPointDao ohlcPointDao, ITickPointDao tickPointDao) { this.stockDbHeaderDao = stockDbHeaderDao; this.ohlcPointDao = ohlcPointDao; this.tickPointDao = tickPointDao; } public void start() throws SQLException { init(); allStockDatabases(); startAsynchronousPersisters(); } public void stop() throws SQLException { stopAsynchronousPersisters(); } public void init() throws SQLException { stockDbHeaderDao.initDb(); ohlcPointDao.initDb(); tickPointDao.initDb(); } private void startAsynchronousPersisters() { asyncPersistence = true; asyncOhlcPersisterThread = new Thread(getAsyncOhlcPersister(), "OHLC persister"); asyncTickPersisterThread = new Thread(getAsyncTickPersister(), "Tick persister"); asyncOhlcPersisterThread.start(); asyncTickPersisterThread.start(); } private void stopAsynchronousPersisters() { interrupt = true; asyncOhlcPersisterThread.interrupt(); asyncTickPersisterThread.interrupt(); } public Runnable getAsyncOhlcPersister() { return asyncOhlcPersister; } public Runnable getAsyncTickPersister() { return asyncTickPersister; } @Override public synchronized void addStockDatabase(IStockDatabase stockDatabase) { StockDatabaseHeader hdr = StockDatabaseHeader.fromStockDatabase(stockDatabase); try { stockDbHeaderDao.save(hdr); for (IOHLCPoint ohlc : stockDatabase.getOHLCTimeSeries()) { ohlcPointDao.save(hdr.id, ohlc); } for (ITickPoint tick : stockDatabase.getTickTimeSeries()) { tickPointDao.save(hdr.id, tick); } } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while saving stock DB", e); return; } addToCache(stockDatabase, hdr.id); } @Override public synchronized Collection<IStockDatabase> allStockDatabases() { Collection<IStockDatabase> result = new LinkedList<IStockDatabase>(); try { Set<StockDatabaseHeader> headerSet = null; headerSet = stockDbHeaderDao.findAll(); for (StockDatabaseHeader hdr : headerSet) { result.add(getOrCreate(hdr)); } } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while finding all stock DBs", e); return result; } return result; } @Override public Collection<IStockDatabase> findStockDatabases(IContract contract) { Collection<IStockDatabase> result = new LinkedList<IStockDatabase>(); try { Set<StockDatabaseHeader> headerSet = null; headerSet = stockDbHeaderDao.findByContract(contract); for (StockDatabaseHeader hdr : headerSet) { result.add(getOrCreate(hdr)); } } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while finding stock DBs for contract: " + contract, e); return result; } return result; } @Override public synchronized boolean removeStockDatabase(IStockDatabase stockDatabase) { StockDbCacheInfo cacheItem = cacheByStockDb.get(stockDatabase); if (cacheItem != null) { stockDatabase.getOHLCTimeSeries().removeSeriesListener(cacheItem.ohlcListener); stockDatabase.getTickTimeSeries().removeSeriesListener(cacheItem.tickListener); cacheByStockDb.remove(stockDatabase); cacheById.remove(cacheItem.id); try { stockDbHeaderDao.delete(cacheItem.id); ohlcPointDao.deleteAll(cacheItem.id); tickPointDao.deleteAll(cacheItem.id); } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while deleting stock DB", e); return false; } return true; } else { return false; } } private void addToCache(IStockDatabase stockDatabase, String id) { OHLCTimeSeriesListener ohlcListener = new OHLCTimeSeriesListener(id); stockDatabase.getOHLCTimeSeries().addSeriesListener(ohlcListener); TickTimeSeriesListener tickListener = new TickTimeSeriesListener(id); stockDatabase.getTickTimeSeries().addSeriesListener(tickListener); StockDbCacheInfo cacheItem = new StockDbCacheInfo(id, stockDatabase, ohlcListener, tickListener); cacheById.put(id, cacheItem); cacheByStockDb.put(stockDatabase, cacheItem); } private IStockDatabase getOrCreate(StockDatabaseHeader hdr) throws SQLException { IStockDatabase stockDatabase; StockDbCacheInfo cacheItem = cacheById.get(hdr.id); if (cacheItem != null) { stockDatabase = cacheItem.stockDatabase; } else { stockDatabase = new StockDatabase(hdr.contract, hdr.dataType, hdr.barSize, hdr.includeAfterHours, hdr.timeZone); IMutableOHLCTimeSeries ohlcTimeSeries = stockDatabase.getOHLCTimeSeries(); for (IOHLCPoint ohlc : ohlcPointDao.find(hdr.id)) { ohlcTimeSeries.addLast(ohlc); } IMutableTickTimeSeries tickTimeSeries = stockDatabase.getTickTimeSeries(); for (ITickPoint tick : tickPointDao.find(hdr.id)) { tickTimeSeries.addLast(tick); } addToCache(stockDatabase, hdr.id); } return stockDatabase; } @Override public int size() { int size = -1; try { size = stockDbHeaderDao.countAll(); } catch (SQLException e) { logger.log(Level.SEVERE, "Exception while counting all stock DBs", e); } return size; } @Override public IStockDatabase getStockDatabase(String ID) { StockDbCacheInfo info = cacheById.get(ID); if (info == null) { logger.log(Level.SEVERE, "Stock database not found for ID: " + ID); return null; } return cacheById.get(ID).stockDatabase; } }