package org.marketcetera.core.position.impl;
import java.math.BigDecimal;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.marketcetera.core.position.MarketDataSupport;
import org.marketcetera.core.position.Trade;
import org.marketcetera.trade.Equity;
import org.marketcetera.trade.Future;
import org.marketcetera.trade.Instrument;
import org.marketcetera.trade.Option;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.util.concurrent.Lock;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/* $License$ */
/**
* Test {@link PositionRowUpdater}. It simulates market data in one thread and creating/disposing
* PositionRowUpdater objects in another thread.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: PositionRowUpdaterConcurrencyTest.java 16604 2013-06-26 14:49:42Z colin $
* @since 1.5.0
*/
public class PositionRowUpdaterConcurrencyTest {
private final Random mGenerator = new Random();
private static final Instrument INSTRUMENT = new Equity("METC");
private static final String ACCOUNT = "A1";
private static final String TRADER = "1";
private PositionRowImpl mRow;
private BasicEventList<Trade<?>> mTrades;
private final Object mSimulatedDataFlowLock = new Object();
private MockMarketData mMockMarketData;
private Lock mLock;
@Before
public void before() {
// BasicConfigurator.configure();
// Logger.getLogger("MarketData").setLevel(Level.TRACE);
// Logger.getLogger("ListUpdate").setLevel(Level.TRACE);
mTrades = new BasicEventList<Trade<?>>();
mLock = mTrades.getReadWriteLock().writeLock();
mRow = new PositionRowImpl(INSTRUMENT, "METC", ACCOUNT, TRADER, new BigDecimal(100));
mMockMarketData = new MockMarketData();
}
@Test(timeout = 10000)
public void testConcurrentRun() throws Exception {
mListUpdateThread.start();
mMarketDataThread.start();
Thread.sleep(4000);
checkFailure();
mListUpdateThread.interrupt();
mMarketDataThread.interrupt();
checkFailure();
mListUpdateThread.join();
mMarketDataThread.join();
}
private Thread mMarketDataThread = new ReportingThread("MarketData") {
@Override
protected void runWithReporting() throws Exception {
while (true) {
if (isInterrupted()) return;
SLF4JLoggerProxy.trace(getName(), "Firing event");
mMockMarketData.fireEvent();
try {
sleep(300);
} catch (InterruptedException e) {
return;
}
}
}
};
private Thread mListUpdateThread = new ReportingThread("ListUpdate") {
private final List<PositionRowUpdater> mListeners = Lists.newLinkedList();
@Override
protected void runWithReporting() throws Exception {
while (true) {
if (isInterrupted()) return;
if (mGenerator.nextBoolean()) {
mLock.lock();
try {
SLF4JLoggerProxy.trace(getName(), "Adding updater");
mListeners.add(new PositionRowUpdater(mRow, mTrades, mMockMarketData));
try {
sleep(26);
} catch (InterruptedException e) {
return;
}
} finally {
mLock.unlock();
}
}
if (!mListeners.isEmpty() && mGenerator.nextInt(3) == 1) {
mLock.lock();
try {
SLF4JLoggerProxy.trace(getName(), "Disposing updater");
mListeners.remove(getRandomElement(mListeners)).dispose();
try {
sleep(17);
} catch (InterruptedException e) {
return;
}
} finally {
mLock.unlock();
}
}
}
}
};
private int getRandomElement(List<?> list) {
return list.size() == 1 ? 0 : mGenerator.nextInt(list.size() - 1);
}
class MockMarketData implements MarketDataSupport {
private final Set<InstrumentMarketDataListener> mListeners = Sets.newHashSet();
@Override
public void addInstrumentMarketDataListener(Instrument instrument, InstrumentMarketDataListener listener) {
synchronized (mSimulatedDataFlowLock) {
try {
mListeners.add(listener);
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void fireEvent() {
synchronized (mSimulatedDataFlowLock) {
InstrumentMarketDataEvent event = new InstrumentMarketDataEvent(
this, new BigDecimal(mGenerator.nextInt(5)));
int type = mGenerator.nextInt(4);
for (InstrumentMarketDataListener listener : mListeners) {
switch (type) {
case 0:
listener.symbolTraded(event);
break;
case 1:
listener.closePriceChanged(event);
break;
case 2:
listener.optionMultiplierChanged(event);
break;
case 3:
listener.futureMultiplierChanged(event);
break;
default:
throw new AssertionError();
}
}
}
}
@Override
public BigDecimal getClosingPrice(Instrument instrument) {
return null;
}
@Override
public BigDecimal getLastTradePrice(Instrument instrument) {
return null;
}
@Override
public BigDecimal getOptionMultiplier(Option option) {
return null;
}
@Override
public BigDecimal getFutureMultiplier(Future future) {
return null;
}
@Override
public void removeInstrumentMarketDataListener(Instrument instrument, InstrumentMarketDataListener listener) {
synchronized (mSimulatedDataFlowLock) {
try {
mListeners.remove(listener);
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@Override
public void dispose() {
}
}
// code below is duplicated from org.marketcetera.photon.test.MultiThreadedTestBase
/**
* Caches the first reported exception.
*/
private Throwable mFailure;
/**
* Records an exception to report to JUnit. Only the first reported exception is stored.
*
* @param failure
* the exception to record
*/
protected synchronized void setFailure(Throwable failure) {
if (this.mFailure == null) this.mFailure = failure;
}
/**
* If an exception was recorded, it will be wrapped and thrown as a {@link RuntimeException}.
* This should be called from the main JUnit thread so the exception will be reported.
*
* @throws RuntimeException
* if an exception was recorded by this class
*/
protected synchronized void checkFailure() {
if (mFailure != null) throw new RuntimeException(mFailure);
}
/**
* Convenience thread that reports any exceptions thrown by a runnable.
*/
protected abstract class ReportingThread extends Thread {
public ReportingThread() {
super();
}
public ReportingThread(String name) {
super(name);
}
@Override
public final void run() {
try {
runWithReporting();
} catch (Exception e) {
setFailure(e);
}
}
/**
* Hook for subclass code to run. Any thrown exception will be reported.
*/
protected abstract void runWithReporting() throws Exception;
}
}