package org.marketcetera.modules.cep.esper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import javax.management.JMX; import org.junit.BeforeClass; import org.junit.Test; import org.marketcetera.core.ExpectedTestFailure; import org.marketcetera.event.Event; import org.marketcetera.event.EventTestBase; import org.marketcetera.event.HasInstrument; import org.marketcetera.event.TradeEvent; import org.marketcetera.module.*; import org.marketcetera.modules.cep.system.CEPDataTypes; import org.marketcetera.modules.cep.system.CEPTestBase; import org.marketcetera.trade.Equity; import org.marketcetera.trade.Factory; import com.espertech.esper.client.EPStatement; import com.google.common.collect.Maps; /** * Test the Esper module functionality * @author toli@marketcetera.com * @version $Id: EsperModuleTest.java 16841 2014-02-20 19:59:04Z colin $ * @since 1.0.0 */ public class EsperModuleTest extends CEPTestBase { private static CEPEsperProcessorMXBean sEsperBean; private static ModuleURN TEST_URN = new ModuleURN(CEPEsperFactory.PROVIDER_URN, "toli"); @BeforeClass public static void setup() throws Exception { sFactory = Factory.getInstance(); sEsperBean = JMX.newMXBeanProxy( ModuleTestBase.getMBeanServer(), TEST_URN.toObjectName(), CEPEsperProcessorMXBean.class); } @Override protected ModuleURN getModuleURN() { return TEST_URN; } @Override protected Class<?> getIncorrectQueryException() { return IllegalRequestParameterValue.class; } /** * We have the following data flow: * CopierModule --> CEP --> Sink * Feed 3 events into copier (which just re-emits it), and then test that it goes through to the Sink via Esper * * @throws Exception if there were unexpected errors. */ @Test(timeout=120000) public void testBasicFlow() throws Exception { DataFlowID flowID = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), EventTestBase.generateEquityTradeEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")), EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")) }), // Esper -> Sink: only get IBM trade events new DataRequest(TEST_URN, "select * from trade where instrumentAsString='IBM'") }); Object obj = sSink.getNextData(); assertTrue("Didn't receive right trade event", obj instanceof TradeEvent); TradeEvent theTrade = (TradeEvent) obj; assertEquals("Didn't receive right symbol event", "IBM", theTrade.getInstrumentAsString()); assertEquals("Didn't receive right size event", new BigDecimal("200"), theTrade.getSize()); // check MXBean functionality assertEquals("Wrong number of received events", 3, sEsperBean.getNumEventsReceived()); assertEquals("Wrong number of emitted events", 1, sManager.getDataFlowInfo(flowID).getFlowSteps()[1].getNumEmitted()); assertEquals("Sink should only receive one event", 0, sSink.size()); // stop flow sManager.cancel(flowID); } /** Create a data flow where you subscribe to 2 types of events, but only the last one * should result in statements being received */ @Test(timeout=120000) public void testOnlyLastStatementGetsSubscriber() throws Exception { DataFlowID flowID = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityTradeEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), // Esper -> Sink: only get IBM trade events new DataRequest(TEST_URN, new String[]{"select * from trade where instrumentAsString='IBM'", "select * from trade where instrumentAsString='JAVA'"}) }); // verify that we only get the event for java, not IBM assertEquals("JAVA", ((HasInstrument)sSink.getNextData()).getInstrumentAsString()); assertEquals("wrong # of emitted events from Esper", 1, sManager.getDataFlowInfo(flowID).getFlowSteps()[1].getNumEmitted()); assertEquals("# of running statements", 2, sEsperBean.getStatementNames().length); sManager.cancel(flowID); } /** Create one data flow, send events, make sure they come through * Then cancel it, create similar data flow, send same events, but make sure * only the laste subscriptions get hits */ @Test(timeout=120000) public void testEsperCancel() throws Exception { DataFlowID flowID = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityTradeEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), // Esper -> Sink: only get IBM trade events new DataRequest(TEST_URN, "select * from trade where instrumentAsString='IBM'") }); // verify we get 1 trade and then cancel assertEquals("IBM", ((HasInstrument) sSink.getNextData()).getInstrumentAsString()); assertEquals("wrong # of emitted events from Esper", 1, sManager.getDataFlowInfo(flowID).getFlowSteps()[1].getNumEmitted()); assertEquals("# of running statements before cancel", 1, sEsperBean.getStatementNames().length); sManager.cancel(flowID); DataFlowID flowID2 = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityTradeEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), // Esper -> Sink: only get IBM trade events new DataRequest(TEST_URN, "select * from trade where instrumentAsString='JAVA'") }); // verify we only get 1 trade for Java assertEquals("JAVA", ((HasInstrument) sSink.getNextData()).getInstrumentAsString()); assertEquals("wrong # of emitted events from Esper", 1, sManager.getDataFlowInfo(flowID2).getFlowSteps()[1].getNumEmitted()); sManager.cancel(flowID2); } @Test(timeout=120000) /** Verify the statements are treated correctly */ public void testCreateStatements() throws Exception { CEPEsperProcessor esperPr = new CEPEsperProcessor(CEPEsperFactory.PROVIDER_URN); esperPr.preStart(); ArrayList<EPStatement> stmts = esperPr.createStatements("select * from ask where instrumentAsString = 'entourage'", "p:every(spike=ask(exchange='sunday'))"); org.junit.Assert.assertEquals(2, stmts.size()); assertFalse("Did not create a regular Esper statement", stmts.get(0).isPattern()); assertTrue("did not create a pattern statement", stmts.get(1).isPattern()); } @Test public void testJMX() throws Exception { sManager.createModule(CEPEsperFactory.PROVIDER_URN, TEST_URN); CEPEsperProcessorMXBean esperBean = JMX.newMXBeanProxy( ModuleTestBase.getMBeanServer(), TEST_URN.toObjectName(), CEPEsperProcessorMXBean.class); assertFalse("external time not set correctly", esperBean.isUseExternalTime()); sManager.stop(TEST_URN); esperBean.setUseExternalTime(true); sManager.start(TEST_URN); assertTrue("external time not set correctly", esperBean.isUseExternalTime()); sManager.stop(TEST_URN); sManager.deleteModule(TEST_URN); } @Test public void testUnknownAlias() throws Exception { new ExpectedTestFailure(IllegalRequestParameterValue.class, "bob") { protected void execute() throws Throwable { sManager.createDataFlow(new DataRequest[] { // Copier -> Esper new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("GOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), }), // ESPER -> Sink: invalid type name new DataRequest(TEST_URN, "select * from bob") }); } }.run(); } /** Verify that when you send a query of N steps, where a non-first step is invalid, all N statements are cleaned up */ @Test(timeout=120000) public void testAllStatementsCleanedUpIfOneHasError() throws Exception { // first create a valid statement DataFlowID flow = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("GOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), }), // ESPER -> Sink: invalid type name new DataRequest(TEST_URN, new String[] {"select * from trade"})}); CEPEsperProcessorMXBean esperBean = JMX.newMXBeanProxy(ModuleTestBase.getMBeanServer(), TEST_URN.toObjectName(), CEPEsperProcessorMXBean.class); assertEquals("invalid # of statements"+ Arrays.toString(esperBean.getStatementNames()), 1, esperBean.getStatementNames().length); new ExpectedTestFailure(IllegalRequestParameterValue.class, "bob") { protected void execute() throws Throwable { sManager.createDataFlow(new DataRequest[] { // Copier -> Esper new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("GOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), }), // ESPER -> Sink: invalid type name new DataRequest(TEST_URN, new String[] {"select * from trade", "select * from bob"}) }); } }.run(); assertEquals("invalid # of statements"+ Arrays.toString(esperBean.getStatementNames()), 1, esperBean.getStatementNames().length); sManager.cancel(flow); } /** do a pattern query and make sure we get something reasonable back p:every ask(symbol="IBM") where timer:within(10 seconds) */ @Test(timeout=120000) public void testPattern() throws Exception { long timeStart = System.currentTimeMillis(); DataFlowID flow = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper: send 2 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("GOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), EventTestBase.generateEquityAskEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("100"), new BigDecimal("100")), }), // ESPER -> Sink: pattern new DataRequest(TEST_URN, new String[] {"p: ask(instrumentAsString='IBM') -> timer:interval(10 seconds)"})}); // gets an empty hashmap back since we are not selecting anything sSink.getNextData(); long timeEnd = System.currentTimeMillis(); assertTrue("Didn't wait longer than 10 secs: "+(timeEnd-timeStart), timeEnd - timeStart > 10*1000); sManager.cancel(flow); } /** Create an explicit pattern (instead of using p:query that results in createPattern call) */ @Test public void testPattern_explicit() throws Exception { long timeStart = System.currentTimeMillis(); DataFlowID flow = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("GOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), EventTestBase.generateEquityAskEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("100"), new BigDecimal("100")), }), // ESPER -> Sink: explicit pattern new DataRequest(TEST_URN, new String[] {"select 1 as toli from pattern [ask(instrumentAsString='IBM') -> timer:interval(10 seconds)]"})}); // should get back an integer with value 1 assertEquals("received wrong object", 1, sSink.getNextData()); long timeEnd = System.currentTimeMillis(); assertTrue("Didn't wait longer than 10 secs: "+(timeEnd-timeStart), timeEnd - timeStart > 10*1000); sManager.cancel(flow); } /** * Verifies that map type is correctly registered such it's keys can * be referred via dynamic property syntax * * @throws Exception if there were unexpected failures */ @Test public void testDynamicMapProperties() throws Exception { Map<String,String> map1 = Maps.newHashMap(); map1.put("name","nap"); map1.put("game","tap"); Map<String,String> map2 = Maps.newHashMap(); map2.put("name","gap"); map2.put("game","kebap"); DataFlowID flow = sManager.createDataFlow(new DataRequest[] { // Copier -> Esper new DataRequest(CopierModuleFactory.INSTANCE_URN, new Map[] { map1,map2 }), // ESPER -> Fetch the name from the map new DataRequest(TEST_URN, new String[] {"select name? from map"})}); assertEquals("received wrong object", "nap", sSink.getNextData()); assertEquals("received wrong object", "gap", sSink.getNextData()); sManager.cancel(flow); } @Override public void testMap() throws Exception { //since maps are a special type in esper, they cannot be //matched as java.util.Map type, they can only be matched with //the alias flowTestHelper(CEPDataTypes.MAP, new Object[]{map1, map2}); } }