package org.marketcetera.marketdata;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.marketcetera.module.DataFlowID;
import org.marketcetera.module.DataRequest;
import org.marketcetera.module.ExpectedFailure;
import org.marketcetera.module.IllegalRequestParameterValue;
import org.marketcetera.module.ModuleFactory;
import org.marketcetera.module.ModuleManager;
import org.marketcetera.module.ModuleState;
import org.marketcetera.module.ModuleTestBase;
import org.marketcetera.module.ModuleURN;
import org.marketcetera.module.SinkDataListener;
import org.marketcetera.module.UnsupportedRequestParameterType;
import org.marketcetera.module.ConfigurationProviderTest.MockConfigurationProvider;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.test.CollectionAssert;
/* $License$ */
/**
* Base class for market data provider <code>Module</code> tests.
*
* <p>Unit tests for market data provider modules should extend this class.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketDataModuleTestBase.java 16893 2014-04-25 18:20:56Z colin $
* @since 1.0.0
*/
public abstract class MarketDataModuleTestBase
extends ModuleTestBase
{
/**
* global singleton module manager
*/
protected ModuleManager moduleManager;
/**
* test destination of market data requests
*/
protected DataSink dataSink;
/**
* the factory to use to create the market data provider modules
*/
protected ModuleFactory factory;
/**
* configuration provider to use to set up the module to be tested, if necessary
*/
protected MockConfigurationProvider provider;
@Before
public void setup()
throws Exception
{
moduleManager = new ModuleManager();
provider = new MockConfigurationProvider();
populateConfigurationProvider(provider);
moduleManager.setConfigurationProvider(provider);
moduleManager.init();
dataSink = new DataSink();
moduleManager.addSinkListener(dataSink);
factory = getFactory();
startModule();
}
@After
public void cleanup()
throws Exception
{
stopModule();
moduleManager.stop();
moduleManager = null;
}
/**
* Sets up fake configuration files for module config.
*
* <p>Subclasses may extend this method to add their needed configuration values. The
* configuration will automatically get added to the <code>ModuleManager</code> used by
* these tests. This is typically necessary if the market data provider needs credentials
* to run. The default implementation does nothing.
*
* @param inProvider a <code>MockConfigurationProvider</code> value
*/
protected void populateConfigurationProvider(MockConfigurationProvider inProvider)
{
}
/**
* Indicates the expected capabilities for this feed.
*
* <p>Subclasses may override this method to indicate their expected capabilties. The
* default implementation returns an empty array.
*
* @return a <code>Capability[]</code> value
*/
protected Capability[] getExpectedCapabilities()
{
return new Capability[0];
}
/**
* Indicates a {@link Capability} that the feed does not support.
*
* <p>If possible, subclasses should override this method to indicate any unsupported
* capability. If not possible, retain this implementation which returns null.
*
* @return a <code>Capability</code> that is not supported or null if they are all supported
*/
protected Capability getUnexpectedCapability()
{
return null;
}
/**
* Returns an <code>MXBean</code> Proxy for the market data module being tested.
*
* @return an <code>AbstractMarketDataModuleMXBean</code> value
* @throws Exception if an error occurs
*/
protected final AbstractMarketDataModuleMXBean getMXBeanProxy()
throws Exception
{
ObjectName objectName = getInstanceURN().toObjectName();
MBeanServerConnection mMBeanServer = ModuleTestBase.getMBeanServer();
return JMX.newMXBeanProxy(mMBeanServer,
objectName,
AbstractMarketDataModuleMXBean.class,
true);
}
/**
* Returns a valid provider for this market data provider.
*
* @return a <code>String</code> value
*/
protected abstract String getProvider();
/**
* Tests that the feed's capabilities match the expected values.
*/
@Test
public void capabilities()
throws Exception
{
AbstractMarketDataModuleMXBean mBeanProxy = getMXBeanProxy();
CollectionAssert.assertArrayPermutation(getExpectedCapabilities(),
mBeanProxy.getCapabilities().toArray(new Capability[0]));
Capability unsupportedCapability = getUnexpectedCapability();
if(unsupportedCapability != null) {
assertFalse("The feed is not supposed to support " + unsupportedCapability,
mBeanProxy.getCapabilities().contains(unsupportedCapability));
}
}
@Test
public void badDataRequests()
throws Exception
{
// null request data
new ExpectedFailure<IllegalRequestParameterValue>(org.marketcetera.module.Messages.ILLEGAL_REQ_PARM_VALUE,
getInstanceURN().toString(),
null) {
protected void run()
throws Exception
{
moduleManager.createDataFlow(new DataRequest[] { new DataRequest(getInstanceURN(),
null) });
}
};
// inappropriate datatype
final Object invalidParam = new Object();
new ExpectedFailure<UnsupportedRequestParameterType>(org.marketcetera.module.Messages.UNSUPPORTED_REQ_PARM_TYPE,
getInstanceURN().toString(),
invalidParam.getClass().getName()) {
protected void run()
throws Exception
{
moduleManager.createDataFlow(new DataRequest[] { new DataRequest(getInstanceURN(),
invalidParam) });
}
};
// String, but not from a data request
final String invalidString = "There is no way you can make a data request from this, so there";
new ExpectedFailure<IllegalRequestParameterValue>(org.marketcetera.module.Messages.ILLEGAL_REQ_PARM_VALUE,
getInstanceURN().toString(),
invalidString) {
protected void run()
throws Exception
{
moduleManager.createDataFlow(new DataRequest[] { new DataRequest(getInstanceURN(),
invalidString) });
}
};
}
@Test
public void dataRequestFromString()
throws Exception
{
assertTrue(moduleManager.getDataFlows(true).isEmpty());
MarketDataRequest request = MarketDataRequestBuilder.newRequest().withSymbols("GOOG").withProvider(getProvider()).create();
final DataFlowID flowID = moduleManager.createDataFlow(new DataRequest[] { new DataRequest(getInstanceURN(),
request.toString()) });
// wait until some arbitrary number of ticks have been received
AbstractMarketDataFeedTest.wait(new Callable<Boolean>() {
@Override
public Boolean call()
throws Exception
{
return dataSink.getData(flowID).size() > 20;
}});
// cancel the flow
moduleManager.cancel(flowID);
}
@Test
public void dataRequestProducesData()
throws Exception
{
assertTrue(moduleManager.getDataFlows(true).isEmpty());
MarketDataRequest request = MarketDataRequestBuilder.newRequest().withSymbols("GOOG").create();
final DataFlowID flowID = moduleManager.createDataFlow(new DataRequest[] { new DataRequest(getInstanceURN(),
request) });
// wait until some arbitrary number of ticks have been received
AbstractMarketDataFeedTest.wait(new Callable<Boolean>() {
@Override
public Boolean call()
throws Exception
{
return dataSink.getData(flowID).size() > 20;
}});
// cancel the flow
moduleManager.cancel(flowID);
}
@Test@Ignore
public void reconnect()
throws Exception
{
MarketDataRequest request = MarketDataRequestBuilder.newRequest().withSymbols("GOOG").create();
final DataFlowID flowID = moduleManager.createDataFlow(new DataRequest[] { new DataRequest(getInstanceURN(),
request) });
// wait until some arbitrary number of ticks have been received
AbstractMarketDataFeedTest.wait(new Callable<Boolean>() {
@Override
public Boolean call()
throws Exception
{
return dataSink.getData(flowID).size() > 20;
}});
// reconnect and wait for more data
// TODO - need to figure out how to make this magic incantation work
// ObjectName objectName = getInstanceURN().toObjectName();
// MBeanServerConnection mMBeanServer = null;
// AbstractMarketDataModuleMXBean mMBeanProxy = JMX.newMXBeanProxy(mMBeanServer,
// objectName,
// AbstractMarketDataModuleMXBean.class,
// true);
}
/**
* Starts the module to be tested and verifies that it has started.
*
* @throws Exception if an error occurs
*/
protected void startModule()
throws Exception
{
moduleManager.start(getInstanceURN());
assertEquals(ModuleState.STARTED,
moduleManager.getModuleInfo(getInstanceURN()).getState());
}
/**
* Stops the module to be tested and verifies that it has stopped.
*
* @throws Exception if an error occurs
*/
protected void stopModule()
throws Exception
{
moduleManager.stop(getInstanceURN());
assertEquals(ModuleState.STOPPED,
moduleManager.getModuleInfo(getInstanceURN()).getState());
}
/**
* Returns a <code>ModuleFactory</code> instance appropriate for the module to be tested.
*
* @return a <code>ModuleFactory</code> value
*/
protected abstract ModuleFactory getFactory();
/**
* Returns a <code>ModuleURN</code> instance appropriate for the module to be tested.
*
* @return a <code>ModuleURN</code> value
*/
protected abstract ModuleURN getInstanceURN();
/**
* Sample <code>SinkDataListener</code> implementation that stores the objects it receives.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketDataModuleTestBase.java 16893 2014-04-25 18:20:56Z colin $
* @since 1.0.0
*/
public static class DataSink
implements SinkDataListener
{
/**
* data received by the <code>DataFlowID</code> that caused it to be delivered
*/
private final Map<DataFlowID,List<Object>> data = new HashMap<DataFlowID,List<Object>>();
/* (non-Javadoc)
* @see org.marketcetera.module.SinkDataListener#receivedData(org.marketcetera.module.DataFlowID, java.lang.Object)
*/
@Override
public void receivedData(DataFlowID inDataFlowID,
Object inData)
{
synchronized(data) {
SLF4JLoggerProxy.debug(this,
"Test DataSink received {}",
inData);
List<Object> dataForID = data.get(inDataFlowID);
if(dataForID == null) {
dataForID = new ArrayList<Object>();
data.put(inDataFlowID,
dataForID);
}
dataForID.add(inData);
}
}
/**
* Retrieves the data collected by this object.
*
* @param inDataFlowID a <code>DataFlowID</code> value
* @return a <code>List<Object></code> value
*/
public List<Object> getData(DataFlowID inDataFlowID)
{
synchronized(data) {
List<Object> result = data.get(inDataFlowID);
if(result == null) {
return new ArrayList<Object>();
}
return new ArrayList<Object>(result);
}
}
/**
* Gets all the data that has been collected by this object.
*
* @return a <code>Map<DataFlowID,List<Object>></code> value
*/
public Map<DataFlowID,List<Object>> getAllData()
{
synchronized(data) {
return new HashMap<DataFlowID,List<Object>>(data);
}
}
}
}