package org.marketcetera.marketdata.core.rpc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.net.DatagramSocket; import java.net.ServerSocket; import java.util.Deque; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.marketcetera.core.LoggerConfiguration; import org.marketcetera.core.notifications.ServerStatusListener; import org.marketcetera.event.Event; import org.marketcetera.event.EventTestBase; import org.marketcetera.marketdata.Capability; import org.marketcetera.marketdata.Content; import org.marketcetera.marketdata.MarketDataFeedTestBase; import org.marketcetera.marketdata.MarketDataRequestBuilder; import org.marketcetera.marketdata.core.webservice.PageRequest; import org.marketcetera.marketdata.core.webservice.impl.MarketDataContextClassProvider; import org.marketcetera.options.OptionUtils; import org.marketcetera.trade.Currency; import org.marketcetera.trade.Equity; import org.marketcetera.trade.Future; import org.marketcetera.trade.Instrument; import org.marketcetera.trade.Option; import org.marketcetera.trade.TradeContextClassProvider; import org.marketcetera.trade.utils.OrderHistoryManagerTest; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.marketcetera.util.rpc.RpcServer; import org.marketcetera.util.ws.stateful.SessionManager; import com.google.common.collect.Lists; /* $License$ */ /** * Tests {@link RpcClientImpl} and {@link RpcServer}. * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: MarketDataRpcClientServerTest.java 16901 2014-05-11 16:14:11Z colin $ * @since 2.4.0 */ public class MarketDataRpcClientServerTest { /** * Runs once before all tests. * * @throws Exception if an unexpected error occurs */ @BeforeClass public static void once() throws Exception { LoggerConfiguration.logSetup(); OrderHistoryManagerTest.once(); } /** * Runs before each test. * * @throws Exception if an unexpected error occurs */ @Before public void setup() throws Exception { stopClientServer(); hostname = "127.0.0.1"; port = -1; sessionManager = new SessionManager<MockSession>(); authenticator = new MockAuthenticator(); serviceAdapter = new MockMarketDataServiceAdapter(); startClientServer(); assertTrue(server.isRunning()); assertTrue(client.isRunning()); } /** * Runs after each test. * * @throws Exception if an unexpected error occurs */ @After public void after() throws Exception { stopClientServer(); } /** * Tests disconnection and reconnection. * * @throws Exception if an unexpected error occurs */ @Test public void testDisconnection() throws Exception { final AtomicBoolean status = new AtomicBoolean(false); ServerStatusListener statusListener = new ServerStatusListener() { @Override public void receiveServerStatus(boolean inStatus) { status.set(inStatus); } }; client.addServerStatusListener(statusListener); assertTrue(status.get()); // kill the server server.stop(); assertFalse(server.isRunning()); MarketDataFeedTestBase.wait(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return !client.isRunning(); } }); assertFalse(status.get()); server.start(); assertTrue(server.isRunning()); MarketDataFeedTestBase.wait(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return client.isRunning(); } }); assertTrue(status.get()); } /** * Tests {@link MarketDataRpcClient#request(org.marketcetera.marketdata.MarketDataRequest, boolean)}. * * @throws Exception if an unexpected error occurs */ @Test public void testRequest() throws Exception { assertTrue(serviceAdapter.getRequests().isEmpty()); long id = client.request(MarketDataRequestBuilder.newRequestFromString("SYMBOLS=METC"), true); assertTrue(id > 0); assertEquals(1, serviceAdapter.getRequests().size()); } /** * Tests {@link MarketDataRpcClient#getLastUpdate(long)}. * * @throws Exception if an unexpected error occurs */ @Test public void testGetLastUpdate() throws Exception { assertTrue(serviceAdapter.getLastUpdateRequests().isEmpty()); long timestamp = System.nanoTime(); long returnedTimestamp = client.getLastUpdate(timestamp); assertEquals(timestamp, returnedTimestamp); assertEquals(1, serviceAdapter.getLastUpdateRequests().size()); } /** * Tests {@link MarketDataRpcClient#cancel(long)}. * * @throws Exception if an unexpected error occurs */ @Test public void testCancel() throws Exception { assertTrue(serviceAdapter.getCanceledIds().isEmpty()); long timestamp = System.nanoTime(); client.cancel(timestamp); assertEquals(1, serviceAdapter.getCanceledIds().size()); } /** * Tests {@link MarketDataRpcClient#getEvents(long)}. * * @throws Exception if an unexpected error occurs */ @Test public void testGetEvents() throws Exception { assertTrue(serviceAdapter.getEventsRequests().isEmpty()); long timestamp = System.nanoTime(); Deque<Event> events = client.getEvents(timestamp); assertTrue(events.isEmpty()); assertEquals(1, serviceAdapter.getEventsRequests().size()); // add some events of each type to return Deque<Event> eventsToReturn = serviceAdapter.getEventsToReturn(); eventsToReturn.add(EventTestBase.generateDividendEvent()); Equity equity = new Equity("AAPL"); Option option = OptionUtils.getOsiOptionFromString("MSFT 001022P12345123"); Instrument[] testInstruments = new Instrument[] { equity,Future.fromString("AAPL-201306"),new Currency("USD/BTC"),option}; for(Instrument instrument : testInstruments) { if(instrument.equals(option)) { eventsToReturn.add(EventTestBase.generateOptionAskEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionBidEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionTradeEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionMarketstatEvent((Option)instrument, equity)); } else { eventsToReturn.add(EventTestBase.generateAskEvent(instrument)); eventsToReturn.add(EventTestBase.generateBidEvent(instrument)); eventsToReturn.add(EventTestBase.generateTradeEvent(instrument)); eventsToReturn.add(EventTestBase.generateMarketstatEvent(instrument)); } } events = client.getEvents(timestamp); assertEquals(eventsToReturn.size(), events.size()); } /** * Tests {@link MarketDataRpcClient#getAllEvents(java.util.List)}. * * @throws Exception if an unexpected error occurs */ @Test public void testGetAllEvents() throws Exception { assertTrue(serviceAdapter.getAllEventsRequests().isEmpty()); long timestamp = System.nanoTime(); List<Long> ids = Lists.newArrayList(); ids.add(timestamp); ids.add(timestamp+1); ids.add(timestamp+2); Map<Long,LinkedList<Event>> events = client.getAllEvents(ids); assertTrue(events.isEmpty()); assertEquals(1, serviceAdapter.getAllEventsRequests().size()); assertEquals(3, serviceAdapter.getAllEventsRequests().get(0).size()); // add some events of each type to return Map<Long,LinkedList<Event>> eventsToReturn = serviceAdapter.getAllEventsToReturn(); long id1 = System.nanoTime(); long id2 = System.nanoTime(); LinkedList<Event> events1 = new LinkedList<>(); LinkedList<Event> events2 = new LinkedList<>(); eventsToReturn.put(id1, events1); eventsToReturn.put(id2, events2); events1.add(EventTestBase.generateDividendEvent()); events2.add(EventTestBase.generateDividendEvent()); Equity equity = new Equity("AAPL"); Option option = OptionUtils.getOsiOptionFromString("MSFT 001022P12345123"); Instrument[] testInstruments = new Instrument[] { equity,Future.fromString("AAPL-201306"),new Currency("USD/BTC"),option}; for(Instrument instrument : testInstruments) { if(instrument.equals(option)) { events1.add(EventTestBase.generateOptionAskEvent((Option)instrument, equity)); events1.add(EventTestBase.generateOptionBidEvent((Option)instrument, equity)); events1.add(EventTestBase.generateOptionTradeEvent((Option)instrument, equity)); events1.add(EventTestBase.generateOptionMarketstatEvent((Option)instrument, equity)); events2.add(EventTestBase.generateOptionAskEvent((Option)instrument, equity)); events2.add(EventTestBase.generateOptionBidEvent((Option)instrument, equity)); events2.add(EventTestBase.generateOptionTradeEvent((Option)instrument, equity)); events2.add(EventTestBase.generateOptionMarketstatEvent((Option)instrument, equity)); } else { events1.add(EventTestBase.generateAskEvent(instrument)); events1.add(EventTestBase.generateBidEvent(instrument)); events1.add(EventTestBase.generateTradeEvent(instrument)); events1.add(EventTestBase.generateMarketstatEvent(instrument)); events2.add(EventTestBase.generateAskEvent(instrument)); events2.add(EventTestBase.generateBidEvent(instrument)); events2.add(EventTestBase.generateTradeEvent(instrument)); events2.add(EventTestBase.generateMarketstatEvent(instrument)); } } events = client.getAllEvents(Lists.newArrayList(id1,id2)); assertEquals(eventsToReturn.size(), events.size()); assertEquals(eventsToReturn.get(id1).size(), events.get(id1).size()); assertEquals(eventsToReturn.get(id2).size(), events.get(id2).size()); } /** * Tests {@link MarketDataRpcClient#getSnapshot(Instrument, org.marketcetera.marketdata.Content, String)}. * * @throws Exception if an unexpected error occurs */ @Test public void testGetSnapshot() throws Exception { Equity equity = new Equity("AAPL"); Deque<Event> events = serviceAdapter.getSnapshot(equity, Content.LATEST_TICK, null); assertTrue(events.isEmpty()); Deque<Event> eventsToReturn = serviceAdapter.getSnapshotEventsToReturn(); eventsToReturn.add(EventTestBase.generateDividendEvent()); Option option = OptionUtils.getOsiOptionFromString("MSFT 001022P12345123"); Instrument[] testInstruments = new Instrument[] { equity,Future.fromString("AAPL-201306"),new Currency("USD/BTC"),option}; for(Instrument instrument : testInstruments) { if(instrument.equals(option)) { eventsToReturn.add(EventTestBase.generateOptionAskEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionBidEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionTradeEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionMarketstatEvent((Option)instrument, equity)); } else { eventsToReturn.add(EventTestBase.generateAskEvent(instrument)); eventsToReturn.add(EventTestBase.generateBidEvent(instrument)); eventsToReturn.add(EventTestBase.generateTradeEvent(instrument)); eventsToReturn.add(EventTestBase.generateMarketstatEvent(instrument)); } events = client.getSnapshot(instrument, Content.LATEST_TICK, "provider"); assertEquals(eventsToReturn.size(), events.size()); } } /** * Tests {@link MarketDataRpcClient#getSnapshotPage(Instrument, Content, String, PageRequest)}. * * @throws Exception if an unexpected error occurs */ @Test public void testGetSnapshotPage() throws Exception { Equity equity = new Equity("AAPL"); Deque<Event> events = serviceAdapter.getSnapshotPage(equity, Content.LATEST_TICK, null, new PageRequest(1,1)); assertTrue(events.isEmpty()); Deque<Event> eventsToReturn = serviceAdapter.getSnapshotEventsToReturn(); eventsToReturn.add(EventTestBase.generateDividendEvent()); Option option = OptionUtils.getOsiOptionFromString("MSFT 001022P12345123"); Instrument[] testInstruments = new Instrument[] { equity,Future.fromString("AAPL-201306"),new Currency("USD/BTC"),option}; for(Instrument instrument : testInstruments) { if(instrument.equals(option)) { eventsToReturn.add(EventTestBase.generateOptionAskEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionBidEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionTradeEvent((Option)instrument, equity)); eventsToReturn.add(EventTestBase.generateOptionMarketstatEvent((Option)instrument, equity)); } else { eventsToReturn.add(EventTestBase.generateAskEvent(instrument)); eventsToReturn.add(EventTestBase.generateBidEvent(instrument)); eventsToReturn.add(EventTestBase.generateTradeEvent(instrument)); eventsToReturn.add(EventTestBase.generateMarketstatEvent(instrument)); } events = client.getSnapshotPage(instrument, Content.LATEST_TICK, "provider", new PageRequest(1,Integer.MAX_VALUE)); assertEquals(eventsToReturn.size(), events.size()); } } /** * Tests {@link MarketDataRpcClient#getAvailableCapability()}. * * @throws Exception if an unexpected error occurs */ @Test public void testGetAvailableCapability() throws Exception { assertEquals(0, serviceAdapter.getCapabilityRequests().get()); Set<Capability> capabilities = client.getAvailableCapability(); assertTrue(capabilities.isEmpty()); assertEquals(1, serviceAdapter.getCapabilityRequests().get()); serviceAdapter.getCapabilitiesToReturn().addAll(EnumSet.allOf(Capability.class)); capabilities = client.getAvailableCapability(); assertEquals(capabilities, serviceAdapter.getCapabilitiesToReturn()); assertEquals(2, serviceAdapter.getCapabilityRequests().get()); } /** * Stops the test client and server. * * @throws Exception if an unexpected error occurs */ private void stopClientServer() throws Exception { if(client != null && client.isRunning()) { try { client.stop(); } catch (Exception e) { SLF4JLoggerProxy.warn(this, e); } } client = null; if(server != null && server.isRunning()) { try { server.stop(); } catch (Exception e) { SLF4JLoggerProxy.warn(this, e); } } server = null; } /** * Starts the test client server and client. * * @throws Exception if an unexpected error occurs */ private void startClientServer() throws Exception { server = new RpcServer<MockSession>(); server.setHostname(hostname); if(port == -1) { port = assignPort(); } server.setPort(port); server.setSessionManager(sessionManager); server.setAuthenticator(authenticator); server.setContextClassProvider(TradeContextClassProvider.INSTANCE); MarketDataRpcService<MockSession> service = new MarketDataRpcService<>(); server.getServiceSpecs().add(service); service.setServiceAdapter(serviceAdapter); server.setContextClassProvider(MarketDataContextClassProvider.INSTANCE); server.start(); client = new MarketDataRpcClient("username", "password", hostname, port, null); client.setContextClassProvider(MarketDataContextClassProvider.INSTANCE); client.start(); } /** * Assigns a port value that is not in use. * * @return an <code>int</code> value */ private int assignPort() { for(int i=MIN_PORT_NUMBER;i<=MAX_PORT_NUMBER;i++) { try(ServerSocket ss = new ServerSocket(i)) { ss.setReuseAddress(true); try(DatagramSocket ds = new DatagramSocket(i)) { ds.setReuseAddress(true); return i; } } catch (IOException e) {} } throw new IllegalArgumentException("No available ports"); } /** * test service adapter value */ private MockMarketDataServiceAdapter serviceAdapter; /** * test authenticator value */ private MockAuthenticator authenticator; /** * test session manager value */ private SessionManager<MockSession> sessionManager; /** * test hostname */ private String hostname; /** * test port */ private int port; /** * minimum test port value */ private static final int MIN_PORT_NUMBER = 10000; /** * maximum test port value */ private static final int MAX_PORT_NUMBER = 65535; /** * test client value */ private MarketDataRpcClient client; /** * tests server value */ private RpcServer<MockSession> server; }