package org.marketcetera.modules.remote.emitter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Deque; import java.util.LinkedList; import javax.management.AttributeChangeNotification; import javax.management.JMX; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.Notification; import javax.management.NotificationListener; import org.junit.Test; import org.marketcetera.client.ClientTest; import org.marketcetera.event.EventTestBase; import org.marketcetera.event.LogEvent; import org.marketcetera.event.LogEventLevel; import org.marketcetera.event.impl.LogEventBuilder; import org.marketcetera.module.BlockingSinkDataListener; import org.marketcetera.module.CopierModuleFactory; import org.marketcetera.module.DataFlowID; import org.marketcetera.module.DataRequest; import org.marketcetera.module.ExpectedFailure; import org.marketcetera.module.MockConfigProvider; import org.marketcetera.module.ModuleException; import org.marketcetera.module.ModuleInfo; import org.marketcetera.module.ModuleState; import org.marketcetera.module.ModuleURN; import org.marketcetera.modules.remote.receiver.ReceiverFactory; import org.marketcetera.modules.remote.receiver.ReceiverModuleMXBean; import org.marketcetera.trade.Equity; import org.marketcetera.trade.ExecutionReport; import org.marketcetera.trade.FIXOrder; import org.marketcetera.trade.OrderCancel; import org.marketcetera.trade.OrderCancelReject; import org.marketcetera.trade.OrderReplace; import org.marketcetera.trade.OrderSingle; import org.marketcetera.trade.TypesTestBase; import org.marketcetera.util.log.I18NMessage0P; import org.marketcetera.util.misc.ClassVersion; /* $License$ */ /** * Tests {@link EmitterModule} & its integration with * {@link org.marketcetera.modules.remote.receiver.ReceiverModule}. * This test also verifies * {@link org.marketcetera.modules.remote.receiver.ReceiverModule} * functionality. * * @author anshul@marketcetera.com * @version $Id: EmitterModuleTest.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: EmitterModuleTest.java 16154 2012-07-14 16:34:05Z colin $") public class EmitterModuleTest extends RemoteEmitterTestBase { /** * Tests provider and module info values. * * @throws Exception if there were errors. */ @Test public void info() throws Exception { initManager(); assertProviderInfo(mManager, EmitterFactory.PROVIDER_URN, new String[]{String.class.getName()}, new Class[]{String.class}, Messages.PROVIDER_DESCRIPTION.getText(), false, true); final String myModule = "mine"; //Attempt to create the instance fails as it fails to start, //but the module gets created. new ExpectedFailure<ModuleException>(Messages.START_FAIL_NO_URL){ @Override protected void run() throws Exception { mManager.createModule(EmitterFactory.PROVIDER_URN, myModule); } }; //Verify module instance info ModuleInfo info = assertModuleInfo(mManager, new ModuleURN(EmitterFactory.PROVIDER_URN, myModule), ModuleState.START_FAILED, null, null, false, true, false, true, false); assertEquals(Messages.START_FAIL_NO_URL.getText(), info.getLastStartFailure()); assertNull(info.getLastStopFailure()); } /** * Tests the MXBean interface. The semantics around attribute changes * and their impact on module lifecycle operations. * * @throws Exception if there were errors. */ @Test public void jmx() throws Exception { initManager(); mManager.createModule(EmitterFactory.PROVIDER_URN, TEST_INSTANCE_URN.instanceName()); assertModuleInfo(mManager, TEST_INSTANCE_URN, ModuleState.STARTED, null, null, false, true, false, true, false); //verify bean info MBeanInfo beanInfo = getMBeanServer().getMBeanInfo( TEST_INSTANCE_URN.toObjectName()); verifyBeanInfo(beanInfo); assertEquals(1, beanInfo.getNotifications().length); assertEquals(new MBeanNotificationInfo( new String[]{AttributeChangeNotification.ATTRIBUTE_CHANGE}, AttributeChangeNotification.class.getName(), Messages.ATTRIB_CHANGE_NOTIFICATION.getText()), beanInfo.getNotifications()[0]); //Get the MBean final EmitterModuleMXBean bean = JMX.newMXBeanProxy(getMBeanServer(), TEST_INSTANCE_URN.toObjectName(), EmitterModuleMXBean.class); //Verify attribute values. assertEquals(null, bean.getLastFailure()); assertEquals(DEFAULT_URL, bean.getURL()); assertEquals(DEFAULT_CREDENTIAL, bean.getUsername()); assertEquals(true, bean.isConnected()); //Test for failures when setting the attributes new ExpectedFailure<IllegalStateException>( Messages.ILLEGAL_STATE_CHANGE_PASSWORD.getText()){ @Override protected void run() throws Exception { bean.setPassword("value"); } }; new ExpectedFailure<IllegalStateException>( Messages.ILLEGAL_STATE_CHANGE_URL.getText()){ @Override protected void run() throws Exception { bean.setURL("tcp://myurl"); } }; new ExpectedFailure<IllegalStateException>( Messages.ILLEGAL_STATE_CHANGE_USERNAME.getText()){ @Override protected void run() throws Exception { bean.setUsername("myuser"); } }; //Stop the module mManager.stop(TEST_INSTANCE_URN); assertEquals(false, bean.isConnected()); //Test failures when attributes are not set correctly //Set the URL to an incorrect value String newURL = "tcp://127.0.0.1:50000"; bean.setURL(newURL); assertEquals(newURL, bean.getURL()); verifyStartFailure(bean); //Try NULL url bean.setURL(null); assertEquals(null, bean.getURL()); verifyStartFailure(bean, Messages.START_FAIL_NO_URL); //Fix the URL bean.setURL(DEFAULT_URL); //Test failures when the user name doesn't match. String newUser = "who?"; bean.setUsername(newUser); assertEquals(newUser, bean.getUsername()); verifyStartFailure(bean); //Try null value bean.setUsername(null); assertEquals(null, bean.getUsername()); verifyStartFailure(bean); //fix the user name bean.setUsername(DEFAULT_CREDENTIAL); //mess up the password String newPass = "pass"; bean.setPassword(newPass); verifyStartFailure(bean); //try null value bean.setPassword(null); verifyStartFailure(bean); //fix the password bean.setPassword(DEFAULT_CREDENTIAL); //Should now connect. mManager.start(TEST_INSTANCE_URN); assertEquals(true, bean.isConnected()); //Stop and delete the module mManager.stop(TEST_INSTANCE_URN); mManager.deleteModule(TEST_INSTANCE_URN); } /** * Tests data flows from the remote receiver to the emitter to sink. * * @throws Exception if there were errors. */ @Test(timeout = 10000) public void flows() throws Exception { initManager(); //Create the emitter mManager.createModule(EmitterFactory.PROVIDER_URN, TEST_INSTANCE_URN.instanceName()); //Add a sink listener BlockingSinkDataListener listener = new BlockingSinkDataListener(); mManager.addSinkListener(listener); //Setup the emitter flow DataFlowID eFlowID = mManager.createDataFlow(new DataRequest[]{ new DataRequest(TEST_INSTANCE_URN)}, true); //Wait for some time to ensure that we do not receive any events yet. Thread.sleep(1000); assertEquals(0, listener.size()); //The data to send Object [] data = { EventTestBase.generateEquityAskEvent(1, 2, new Equity("asym"), "ex", BigDecimal.ONE, BigDecimal.TEN), EventTestBase.generateEquityBidEvent(3, 4, new Equity("bsym"), "ex", BigDecimal.ONE, BigDecimal.TEN), EventTestBase.generateEquityTradeEvent(5, 6, new Equity("csym"), "ex", BigDecimal.ONE, BigDecimal.TEN), ClientTest.createOrderSingle(), ClientTest.createOrderReplace(), ClientTest.createOrderCancel(), ClientTest.createOrderFIX(), ClientTest.createCancelReject(), ClientTest.createExecutionReport(), org.marketcetera.core.notifications.Notification.high( "Subject", "body", "test.notification"), BigInteger.ONE, "Test String" }; //Now setup a data flow into the receiver. DataFlowID rFlowID = mManager.createDataFlow(new DataRequest[]{ new DataRequest(CopierModuleFactory.INSTANCE_URN, data), new DataRequest(ReceiverFactory.INSTANCE_URN) }, false); Object actual; for(Object expected: data) { actual = listener.getNextData(); if(expected instanceof OrderSingle) { TypesTestBase.assertOrderSingleEquals((OrderSingle)expected, (OrderSingle)actual); } else if(expected instanceof OrderReplace) { TypesTestBase.assertOrderReplaceEquals((OrderReplace)expected, (OrderReplace)actual); } else if(expected instanceof OrderCancel) { TypesTestBase.assertOrderCancelEquals((OrderCancel)expected, (OrderCancel)actual); } else if(expected instanceof FIXOrder) { TypesTestBase.assertOrderFIXEquals((FIXOrder)expected, (FIXOrder)actual); } else if(expected instanceof OrderCancelReject) { TypesTestBase.assertCancelRejectEquals((OrderCancelReject)expected, (OrderCancelReject)actual); } else if(expected instanceof ExecutionReport) { TypesTestBase.assertExecReportEquals((ExecutionReport)expected, (ExecutionReport)actual); } else if(expected instanceof org.marketcetera.core.notifications.Notification) { assertEquals(expected.toString(), actual.toString()); } else { assertEquals(expected, actual); } } //cancel the data flows mManager.cancel(rFlowID); mManager.cancel(eFlowID); } /** * Verifies log event filtering carried out by the * receiver. * * @throws Exception if there's an error. */ @Test public void logEventFiltering() throws Exception { initManager(); //Create the emitter mManager.createModule(EmitterFactory.PROVIDER_URN, TEST_INSTANCE_URN.instanceName()); //Add a sink listener BlockingSinkDataListener listener = new BlockingSinkDataListener(); mManager.addSinkListener(listener); final ReceiverModuleMXBean bean = JMX.newMXBeanProxy(getMBeanServer(), ReceiverFactory.INSTANCE_URN.toObjectName(), ReceiverModuleMXBean.class); //verify the default level assertEquals(LogEventLevel.WARN, bean.getLogLevel()); //test the default log level runLogFilterFlow(listener, LogEventLevel.WARN); //test out each of the log levels for(LogEventLevel level: LogEventLevel.values()) { bean.setLogLevel(level); assertEquals(level, bean.getLogLevel()); runLogFilterFlow(listener, level); } } /** * Tests attribute change notifications sent during lifecycle changes * of the module. * * @throws Exception if there were errors. */ @Test public void connectionFailureAndJMXNotifications() throws Exception { initManager(); mManager.createModule(EmitterFactory.PROVIDER_URN, TEST_INSTANCE_URN.instanceName()); final EmitterModuleMXBean bean = JMX.newMXBeanProxy(getMBeanServer(), TEST_INSTANCE_URN.toObjectName(), EmitterModuleMXBean.class); //Register for attrib change notifications. BeanNotificationListener listener = new BeanNotificationListener(); getMBeanServer().addNotificationListener( TEST_INSTANCE_URN.toObjectName(), listener, null, new Object()); assertEquals(true, bean.isConnected()); assertNull(bean.getLastFailure()); assertEquals(0, listener.size()); //Stop the receiver to terminate the connection mManager.stop(ReceiverFactory.INSTANCE_URN); //Give it some time to disconnect Thread.sleep(3000); assertEquals(false, bean.isConnected()); assertNotNull(bean.getLastFailure()); assertEquals(1, listener.size()); assertNotification(listener.getLastNotification(), true, false); //Stop the emitter mManager.stop(TEST_INSTANCE_URN); //No extra notifications assertEquals(1, listener.size()); //Restart the receiver mManager.start(ReceiverFactory.INSTANCE_URN); //verify that we can restart the module mManager.start(TEST_INSTANCE_URN); assertEquals(true, bean.isConnected()); assertNull(bean.getLastFailure()); //verify that start sends a notification assertEquals(2, listener.size()); assertNotification(listener.getLastNotification(), false, true); //Now stop the module and verify that it sends notifications mManager.stop(TEST_INSTANCE_URN); assertEquals(false, bean.isConnected()); assertNull(bean.getLastFailure()); assertEquals(3, listener.size()); assertNotification(listener.getLastNotification(), true, false); //Now unregister the listener getMBeanServer().removeNotificationListener( TEST_INSTANCE_URN.toObjectName(), listener); //verify that no notifications are received when module //is started or stopped. mManager.start(TEST_INSTANCE_URN); assertEquals(true, bean.isConnected()); assertEquals(3, listener.size()); mManager.stop(TEST_INSTANCE_URN); assertEquals(false, bean.isConnected()); assertEquals(3, listener.size()); //Delete the module mManager.deleteModule(TEST_INSTANCE_URN); //no extra notifications assertEquals(3, listener.size()); } /** * Runs the log filtering data flow test. * * @param inListener the sink listener instance. * @param inCurrentLevel the current log filtering level. * * @throws Exception if there were errors. */ private void runLogFilterFlow(BlockingSinkDataListener inListener, LogEventLevel inCurrentLevel) throws Exception { //Setup the emitter flow DataFlowID eFlowID = mManager.createDataFlow(new DataRequest[]{ new DataRequest(TEST_INSTANCE_URN) }, true); //Wait for some time to ensure that we do not receive any events yet. Thread.sleep(1000); assertEquals(0, inListener.size()); I18NMessage0P[] msgs = { new I18NMessage0P(Messages.LOGGER, "debug"), new I18NMessage0P(Messages.LOGGER, "info"), new I18NMessage0P(Messages.LOGGER, "warn"), new I18NMessage0P(Messages.LOGGER, "error") }; //The data to send LogEvent [] data = { LogEventBuilder.debug().withMessage(msgs[0]).create(), LogEventBuilder.info().withMessage(msgs[1]).create(), LogEventBuilder.warn().withMessage(msgs[2]).create(), LogEventBuilder.error().withMessage(msgs[3]).create() }; //Now setup a data flow into the receiver. DataFlowID rFlowID = mManager.createDataFlow(new DataRequest[]{ new DataRequest(CopierModuleFactory.INSTANCE_URN, data), new DataRequest(ReceiverFactory.INSTANCE_URN) }, false); int numEvents = data.length - inCurrentLevel.ordinal(); int idx = inCurrentLevel.ordinal(); while(numEvents-- > 0) { assertEquals(data[idx++].getMessage(), ((LogEvent)inListener.getNextData()).getMessage()); } //cancel the data flows mManager.cancel(rFlowID); mManager.cancel(eFlowID); } @Override protected MockConfigProvider configProviderWithURLValue(String inUrl) { MockConfigProvider prov = super.configProviderWithURLValue(inUrl); prov.addDefault(EmitterModuleTest.TEST_INSTANCE_URN, "URL", inUrl); prov.addDefault(EmitterModuleTest.TEST_INSTANCE_URN, "Username", DEFAULT_CREDENTIAL); prov.addDefault(EmitterModuleTest.TEST_INSTANCE_URN, "Password", DEFAULT_CREDENTIAL); return prov; } /** * Verifies module start failure. * * @param inBean the MXBean proxy for the module. * * @throws Exception if there were errors. */ private void verifyStartFailure(EmitterModuleMXBean inBean) throws Exception { verifyStartFailure(inBean, Messages.ERROR_STARTING_MODULE); } /** * Verifies module start failure. * * @param inBean the MXBean proxy for the module. * @param inMessage the expected failure message. * * @throws Exception if there were errors. */ private void verifyStartFailure(EmitterModuleMXBean inBean, I18NMessage0P inMessage) throws Exception { new ExpectedFailure<ModuleException>(inMessage){ @Override protected void run() throws Exception { mManager.start(TEST_INSTANCE_URN); } }; assertEquals(false, inBean.isConnected()); assertNull(inBean.getLastFailure()); } /** * Verifies the supplied notification as an attribute change notification. * * @param inNotify the notification instance. * @param inOldValue the old attribute value. * @param inNewValue the new attribute value. * * @throws Exception if there were errors. */ private static void assertNotification(Notification inNotify, boolean inOldValue, boolean inNewValue) throws Exception { assertEquals(AttributeChangeNotification.ATTRIBUTE_CHANGE, inNotify.getType()); assertEquals(TEST_INSTANCE_URN.toString(), inNotify.getSource()); assertTrue(inNotify.toString(), inNotify instanceof AttributeChangeNotification); AttributeChangeNotification note = (AttributeChangeNotification) inNotify; assertEquals("Connected", note.getAttributeName()); assertEquals("boolean", note.getAttributeType()); assertEquals(inOldValue, note.getOldValue()); assertEquals(inNewValue, note.getNewValue()); } private static final ModuleURN TEST_INSTANCE_URN = new ModuleURN(EmitterFactory.PROVIDER_URN, "test"); /** * A notification listener to listen for MBean notifications. */ private static class BeanNotificationListener implements NotificationListener { @Override public synchronized void handleNotification(Notification notification, Object handback) { mNotifications.addLast(notification); } /** * The number of notifications received so far. * * @return number of notifications. */ synchronized int size() { return mNotifications.size(); } /** * The last notification received. * * @return the last notification. */ synchronized Notification getLastNotification() { return mNotifications.getLast(); } private final Deque<Notification> mNotifications = new LinkedList<Notification>(); } }