/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.marketdata;
import static com.opengamma.sesame.marketdata.DefaultLiveDataManagerTest.WaitState.DONE;
import static com.opengamma.sesame.marketdata.DefaultLiveDataManagerTest.WaitState.INITIAL;
import static com.opengamma.sesame.marketdata.DefaultLiveDataManagerTest.WaitState.WAITING;
import static com.opengamma.sesame.marketdata.MarketDataTestUtils.buildFailureResponse;
import static com.opengamma.sesame.marketdata.MarketDataTestUtils.buildSuccessResponse;
import static com.opengamma.sesame.marketdata.MarketDataTestUtils.createBundle;
import static com.opengamma.sesame.marketdata.MarketDataTestUtils.createLiveDataManager;
import static com.opengamma.sesame.marketdata.MarketDataTestUtils.createLiveDataSpec;
import static com.opengamma.sesame.marketdata.MarketDataTestUtils.createMockLiveDataClient;
import static com.opengamma.util.result.FailureStatus.MISSING_DATA;
import static com.opengamma.util.result.FailureStatus.PENDING_DATA;
import static com.opengamma.util.result.FailureStatus.PERMISSION_DENIED;
import static com.opengamma.util.result.SuccessStatus.SUCCESS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.fudgemsg.FudgeContext;
import org.fudgemsg.MutableFudgeMsg;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.livedata.LiveDataClient;
import com.opengamma.livedata.LiveDataListener;
import com.opengamma.livedata.LiveDataValueUpdateBean;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.livedata.msg.LiveDataSubscriptionResponse;
import com.opengamma.livedata.permission.PermissionUtils;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.auth.PermissiveSecurityManager;
import com.opengamma.util.result.ResultStatus;
import com.opengamma.util.test.TestGroup;
/**
* Tests for the DefaultLiveDataManager.
*/
@Test(groups = TestGroup.UNIT)
public class DefaultLiveDataManagerTest {
public static final FieldName MARKET_VALUE_FIELD = FieldName.of("Market_Value");
public static final FieldName ANOTHER_FIELD = FieldName.of("Another");
private DefaultLiveDataManager _manager;
private FudgeContext _fudgeContext;
private LiveDataClient _mockLiveDataClient;
enum WaitState {INITIAL, WAITING, DONE}
@BeforeMethod
public void setUp() {
// Set authorization to allow everything for most tests
ThreadContext.bind(new PermissiveSecurityManager());
_fudgeContext = new FudgeContext();
_mockLiveDataClient = createMockLiveDataClient();
_manager = createLiveDataManager(_mockLiveDataClient);
}
@AfterMethod
public void tearDown() {
// This is necessary as once the subject has been set, the security manager is
// ignored. But we want to be able to change the security manager for tests.
ThreadContext.unbindSubject();
}
@Test(expectedExceptions = IllegalStateException.class)
public void testUnknownClientGivesError() {
LiveDataManager manager = _manager;
manager.snapshot(mock(LDListener.class));
}
@Test
public void testUnfulfilledSubsReturnPending() {
LDListener client = mock(LDListener.class);
String[] tickers = {"T1", "T2", "T3"};
_manager.subscribe(client, createIdBundles(tickers));
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(3));
for (String ticker : tickers) {
checkSnapshotEntry(snapshot, ticker, PENDING_DATA);
}
}
private void checkSnapshotEntry(LiveDataResults snapshot,
String ticker, ResultStatus status) {
ExternalIdBundle bundle = createBundle(ticker);
// Check we actually hold an entry
assertThat(snapshot.containsTicker(bundle), is(true));
if (status == SUCCESS) {
// Check object is what we expect
assertThat(snapshot.get(bundle), is(instanceOf(DefaultLiveDataResult.class)));
} else {
assertThat(snapshot.get(bundle).getValue(FieldName.of("any field")).getStatus(), is(status));
}
}
@Test
public void testFailedSubsReturnMissing() {
LDListener client = mock(LDListener.class);
String[] tickers = {"T1", "T2", "T3"};
_manager.subscribe(client, createIdBundles(tickers));
// Now simulate market data server returning its response
// indicating data is not available
Set<LiveDataSubscriptionResponse> responses = new HashSet<>();
for (String ticker : tickers) {
LiveDataSubscriptionResponse response = buildFailureResponse(ticker);
responses.add(response);
}
_manager.subscriptionResultsReceived(responses);
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(3));
for (String ticker : tickers) {
checkSnapshotEntry(snapshot, ticker, MISSING_DATA);
}
}
@Test
public void testMixedResults() {
LDListener client = mock(LDListener.class);
String[] tickers = {
"T1", // Available but not yet received
"T2", // Missing
"T3", // Pending
"T4" // Available and received
};
_manager.subscribe(client, createIdBundles(tickers));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T1"), buildFailureResponse("T2"), buildSuccessResponse("T4")));
// Now send the value update for T4
MutableFudgeMsg msg = _fudgeContext.newMessage();
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T4"), msg));
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(4));
// Data is available but not sent so still pending
checkSnapshotEntry(snapshot, "T1", PENDING_DATA);
checkSnapshotEntry(snapshot, "T2", MISSING_DATA);
checkSnapshotEntry(snapshot, "T3", PENDING_DATA);
checkSnapshotEntry(snapshot, "T4", SUCCESS);
}
@Test
public void testMultipleSubscribes() {
LDListener client = mock(LDListener.class);
_manager.subscribe(client, createIdBundles("T1", "T2", "T3"));
_manager.subscriptionResultsReceived(ImmutableSet.of(buildSuccessResponse("T1"), buildFailureResponse("T2")));
// Now send the value update for T1
MutableFudgeMsg msg = _fudgeContext.newMessage();
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg));
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(3));
// Data is available but not sent so still pending
checkSnapshotEntry(snapshot, "T1", SUCCESS);
checkSnapshotEntry(snapshot, "T2", MISSING_DATA);
checkSnapshotEntry(snapshot, "T3", PENDING_DATA);
// Now subscribe to more
_manager.subscribe(client, createIdBundles("T4", "T5"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T3"), buildSuccessResponse("T4"), buildFailureResponse("T5")));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T3"), msg));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T4"), msg));
LiveDataResults snapshot2 = _manager.snapshot(client);
assertThat(snapshot2.size(), is(5));
// Data is available but not sent so still pending
checkSnapshotEntry(snapshot2, "T1", SUCCESS);
checkSnapshotEntry(snapshot2, "T2", MISSING_DATA);
checkSnapshotEntry(snapshot2, "T3", SUCCESS);
checkSnapshotEntry(snapshot2, "T4", SUCCESS);
checkSnapshotEntry(snapshot2, "T5", MISSING_DATA);
}
/**
* Test that if client opts to wait for results they are held until
* all results are available for them. For failure this means
* don't need to wait for data to be received.
*/
@Test
public void testAwaitingResultsAllFailures() throws InterruptedException {
WaitingClient client = new WaitingClient(_manager);
String[] tickers = {"T1", "T2", "T3"};
_manager.subscribe(client, createIdBundles(tickers));
// Start in another thread and wait on data
new Thread(client).start();
waitForStateChange(client, INITIAL, WAITING);
_manager.subscriptionResultsReceived
(ImmutableSet.of(buildFailureResponse("T1"), buildFailureResponse("T2")));
// No response for T3 so still waiting
assertThat(client.getWaitState(), is(WAITING));
_manager.subscriptionResultsReceived(ImmutableSet.of(buildFailureResponse("T3")));
waitForStateChange(client, WAITING, WaitState.DONE);
}
/**
* Test that if client opts to wait for results they are held until
* all results are available for them. For successes we have to wait
* for data to arrive.
*/
@Test
public void testAwaitingResults() throws InterruptedException {
WaitingClient client = new WaitingClient(_manager);
String[] tickers = {"T1", "T2", "T3"};
_manager.subscribe(client, createIdBundles(tickers));
// Start in another thread and wait on data
new Thread(client).start();
waitForStateChange(client, INITIAL, WAITING);
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildFailureResponse("T1"), buildSuccessResponse("T2"), buildSuccessResponse("T3")));
// No data for T2 or T3 so still waiting
assertThat(client.getWaitState(), is(WAITING));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T2"), _fudgeContext.newMessage()));
// No data for T3 so still waiting
assertThat(client.getWaitState(), is(WAITING));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T3"), _fudgeContext.newMessage()));
waitForStateChange(client, WAITING, WaitState.DONE);
}
private void waitForStateChange(WaitingClient client, WaitState state1, WaitState state2) throws InterruptedException {
// Add time check so tests don't hang
long start = System.currentTimeMillis();
ArgumentChecker.isTrue(state1.compareTo(state2) < 0, "state: {} comes after state: {}", state1, state2);
while (client.getWaitState().compareTo(state1) <= 0 && (System.currentTimeMillis() - start < 5000)) {
Thread.sleep(1);
}
assertThat(client.getWaitState(), is(state2));
}
/**
* Set security manager such that every data request is turned down.
*/
@Test
public void testUnauthorizedUserGetsPermissionDenied() {
// By default this will deny any authorization request
ThreadContext.bind(new DefaultSecurityManager(new SimpleAccountRealm()));
LDListener client = mock(LDListener.class);
String[] tickers = {"T1", "T2", "T3"};
_manager.subscribe(client, createIdBundles(tickers));
Set<LiveDataSubscriptionResponse> responses = new HashSet<>();
responses.add(buildSuccessResponse("T1"));
responses.add(buildSuccessResponse("T2"));
responses.add(buildSuccessResponse("T3"));
_manager.subscriptionResultsReceived(responses);
// Now send the value updates for all
MutableFudgeMsg msg = buildPermissionedMsg("somePerm");
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg));
_manager.valueUpdate(new LiveDataValueUpdateBean(2, createLiveDataSpec("T2"), msg));
_manager.valueUpdate(new LiveDataValueUpdateBean(3, createLiveDataSpec("T3"), msg));
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(3));
// Data is available but not sent so still pending
checkSnapshotEntry(snapshot, "T1", PERMISSION_DENIED);
checkSnapshotEntry(snapshot, "T2", PERMISSION_DENIED);
checkSnapshotEntry(snapshot, "T3", PERMISSION_DENIED);
}
// test permissions stops only some tickers and allows others through
@Test
public void testUserMayBePartiallyDeniedTickers() {
ThreadContext.bind(createSecurityManagerAllowing("T3-Perm"));
LDListener client = mock(LDListener.class);
String[] tickers = {"T1", "T2", "T3"};
_manager.subscribe(client, createIdBundles(tickers));
Set<LiveDataSubscriptionResponse> responses = new HashSet<>();
responses.add(buildSuccessResponse("T1"));
responses.add(buildSuccessResponse("T2"));
responses.add(buildSuccessResponse("T3"));
_manager.subscriptionResultsReceived(responses);
// Now send the value updates for all, with individual permissioning
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), buildPermissionedMsg("T1-Perm")));
_manager.valueUpdate(new LiveDataValueUpdateBean(2, createLiveDataSpec("T2"), buildPermissionedMsg("T2-Perm")));
_manager.valueUpdate(new LiveDataValueUpdateBean(3, createLiveDataSpec("T3"), buildPermissionedMsg("T3-Perm")));
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(3));
checkSnapshotEntry(snapshot, "T1", PERMISSION_DENIED);
checkSnapshotEntry(snapshot, "T2", PERMISSION_DENIED);
checkSnapshotEntry(snapshot, "T3", SUCCESS);
}
// test permissions only affects some clients - 1 client allowed all, 1 allowed some, 1 allowed none
@Test
public void testUsersCanHaveDifferentResultsForSameTickers() throws InterruptedException {
PermissionedClient client1 = new PermissionedClient(_manager, "T1-Perm", "T2-Perm", "T3-Perm");
PermissionedClient client2 = new PermissionedClient(_manager, "T1-Perm", "T2-Perm");
PermissionedClient client3 = new PermissionedClient(_manager);
Set<ExternalIdBundle> subscriptionRequest = createIdBundles("T1", "T2", "T3");
_manager.subscribe(client1, subscriptionRequest);
_manager.subscribe(client2, subscriptionRequest);
_manager.subscribe(client3, subscriptionRequest);
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T1"),
buildSuccessResponse("T2"),
buildSuccessResponse("T3")));
// Now send the value updates for all, with individual permissioning
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), buildPermissionedMsg("T1-Perm")));
_manager.valueUpdate(new LiveDataValueUpdateBean(2, createLiveDataSpec("T2"), buildPermissionedMsg("T2-Perm")));
_manager.valueUpdate(new LiveDataValueUpdateBean(3, createLiveDataSpec("T3"), buildPermissionedMsg("T3-Perm")));
for (PermissionedClient client : new PermissionedClient[]{client1, client2, client3}) {
new Thread(client).start();
}
// Delay to allow the clients to pick up their data
Thread.sleep(50);
LiveDataResults snapshot1 = client1.getSnapshot();
assertThat(snapshot1.size(), is(3));
checkSnapshotEntry(snapshot1, "T1", SUCCESS);
checkSnapshotEntry(snapshot1, "T2", SUCCESS);
checkSnapshotEntry(snapshot1, "T3", SUCCESS);
LiveDataResults snapshot2 = client2.getSnapshot();
assertThat(snapshot2.size(), is(3));
checkSnapshotEntry(snapshot2, "T1", SUCCESS);
checkSnapshotEntry(snapshot2, "T2", SUCCESS);
checkSnapshotEntry(snapshot2, "T3", PERMISSION_DENIED);
LiveDataResults snapshot3 = client3.getSnapshot();
assertThat(snapshot3.size(), is(3));
checkSnapshotEntry(snapshot3, "T1", PERMISSION_DENIED);
checkSnapshotEntry(snapshot3, "T2", PERMISSION_DENIED);
checkSnapshotEntry(snapshot3, "T3", PERMISSION_DENIED);
}
// test multiple clients get their results
@Test
public void testClientsGetOnlyTheirResults() throws InterruptedException {
WaitingClient client1 = new WaitingClient(_manager);
WaitingClient client2 = new WaitingClient(_manager);
WaitingClient client3 = new WaitingClient(_manager);
_manager.subscribe(client1, createIdBundles("T1", "T2", "T3"));
_manager.subscribe(client2, createIdBundles("T3", "T4", "T5"));
_manager.subscribe(client3, createIdBundles("T1", "T4", "T6"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildFailureResponse("T1"), buildSuccessResponse("T2"), buildSuccessResponse("T3"),
buildFailureResponse("T4"), buildSuccessResponse("T5"), buildSuccessResponse("T6")));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T2"), _fudgeContext.newMessage()));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T3"), _fudgeContext.newMessage()));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T5"), _fudgeContext.newMessage()));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T6"), _fudgeContext.newMessage()));
// Start in another thread and wait on data
new Thread(client1).start();
new Thread(client2).start();
new Thread(client3).start();
waitForStateChange(client1, WAITING, WaitState.DONE);
waitForStateChange(client2, WAITING, WaitState.DONE);
waitForStateChange(client3, WAITING, WaitState.DONE);
checkSnapshotContainsTickers(client1, "T1", "T2", "T3");
checkSnapshotContainsTickers(client2, "T3", "T4", "T5");
checkSnapshotContainsTickers(client3, "T1", "T4", "T6");
}
// test multiple clients stop waiting as their results arrive
@Test
public void testClientsStopWaitingAsTheyGetTheirResults() throws InterruptedException {
WaitingClient client1 = new WaitingClient(_manager);
WaitingClient client2 = new WaitingClient(_manager);
WaitingClient client3 = new WaitingClient(_manager);
_manager.subscribe(client1, createIdBundles("T1", "T2", "T3"));
_manager.subscribe(client2, createIdBundles("T3", "T4", "T5"));
_manager.subscribe(client3, createIdBundles("T1", "T4", "T6"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildFailureResponse("T1"), buildSuccessResponse("T2"), buildSuccessResponse("T3"),
buildFailureResponse("T4"), buildSuccessResponse("T5"), buildSuccessResponse("T6")));
// Start in another thread and wait on data
new Thread(client1).start();
new Thread(client2).start();
new Thread(client3).start();
Thread.sleep(10);
// Client 1 first
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T2"), _fudgeContext.newMessage()));
assertThat(client1.getWaitState(), is(WAITING));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T3"), _fudgeContext.newMessage()));
waitForStateChange(client1, WAITING, WaitState.DONE);
// Now client 2
assertThat(client2.getWaitState(), is(WAITING));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T5"), _fudgeContext.newMessage()));
waitForStateChange(client2, WAITING, WaitState.DONE);
// Now client 3
assertThat(client3.getWaitState(), is(WAITING));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T6"), _fudgeContext.newMessage()));
waitForStateChange(client2, WAITING, WaitState.DONE);
checkSnapshotContainsTickers(client1, "T1", "T2", "T3");
checkSnapshotContainsTickers(client2, "T3", "T4", "T5");
checkSnapshotContainsTickers(client3, "T1", "T4", "T6");
}
private void checkSnapshotContainsTickers(LDListener client, String... tickers) {
for (String ticker : tickers) {
assertThat(_manager.snapshot(client).containsTicker(createBundle(ticker)), is(true));
assertThat(_manager.snapshot(client).tickerSet().contains(createBundle(ticker)), is(true));
}
}
@Test
public void testMappingOfTickers() {
LDListener client = mock(LDListener.class);
_manager.subscribe(client, createIdBundles("T1", "T2", "T3"));
// Server responds with a different id for the ticker - all
// messages it sends will be against the new id
Set<LiveDataSubscriptionResponse> responses = ImmutableSet.of(
buildSuccessResponse("T1", "M1"),
buildFailureResponse("T2", "M2"),
buildSuccessResponse("T3", "M3"));
_manager.subscriptionResultsReceived(responses);
// Now send the value updates against the mapped id
MutableFudgeMsg msg = _fudgeContext.newMessage();
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("M1"), msg));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("M2"), msg));
// Use the original ticker - should end up still PENDING
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T3"), msg));
LiveDataResults snapshot = _manager.snapshot(client);
assertThat(snapshot.size(), is(3));
checkSnapshotEntry(snapshot, "T1", SUCCESS);
checkSnapshotEntry(snapshot, "T2", SUCCESS);
checkSnapshotEntry(snapshot, "T3", PENDING_DATA);
}
@Test
public void testReceivingMappedTickerUpdatesClient() throws InterruptedException {
WaitingClient client = new WaitingClient(_manager);
_manager.subscribe(client, createIdBundles("T1", "T2"));
// Server responds with a different id for the ticker - all
// messages it sends will be against the new id
Set<LiveDataSubscriptionResponse> responses = ImmutableSet.of(
buildSuccessResponse("T1", "M1"), buildSuccessResponse("T2", "M2"));
_manager.subscriptionResultsReceived(responses);
new Thread(client).start();
MutableFudgeMsg msg = _fudgeContext.newMessage();
// Now send the value updates against the mapped id
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("M1"), msg));
waitForStateChange(client, INITIAL, WAITING);
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("M2"), msg));
waitForStateChange(client, WAITING, DONE);
}
@Test
@SuppressWarnings("unchecked")
public void testSubscribeIsOnlySentOnce() throws InterruptedException {
LDListener client1 = mock(LDListener.class);
LDListener client2 = mock(LDListener.class);
LDListener client3 = mock(LDListener.class);
// Subscribe for 2 tickers
_manager.subscribe(client1, createIdBundles("T1", "T2"));
// Subscribe for 1 additional ticker
_manager.subscribe(client2, createIdBundles("T1", "T3"));
// No subscription required
_manager.subscribe(client3, createIdBundles("T2", "T3"));
// Sleep as the subscribe/unsubscribe is happening on another thread
Thread.sleep(500);
verify(_mockLiveDataClient, times(2))
.subscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
verifyNoMoreInteractions(_mockLiveDataClient);
}
@Test
@SuppressWarnings("unchecked")
public void testUnubscribeFromTickerDoesNothingIfTickerIsUsedByOthers() throws InterruptedException {
LDListener client1 = mock(LDListener.class);
LDListener client2 = mock(LDListener.class);
_manager.subscribe(client1, createIdBundles("T1", "T2"));
_manager.subscribe(client2, createIdBundles("T1", "T3"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T1", "M1"), buildSuccessResponse("T2", "M2"), buildSuccessResponse("T3", "M3")));
// Client 2 also has subscription
_manager.unsubscribe(client1, createIdBundles("T1"));
// Sleep as the subscribe/unsubscribe is happening on another thread
Thread.sleep(10);
verify(_mockLiveDataClient, never())
.unsubscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
// Only client 1 subscribed, so should go through
_manager.unsubscribe(client1, createIdBundles("T2"));
Thread.sleep(10);
verify(_mockLiveDataClient)
.unsubscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
// Client 2 is only one with subs
_manager.unsubscribe(client2, createIdBundles("T1", "T3"));
Thread.sleep(10);
// Times(2) as mockito keeps track of all calls - we had 1 previously so now we have 2
verify(_mockLiveDataClient, times(2))
.unsubscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
}
@Test
@SuppressWarnings("unchecked")
public void testCannotUnubscribeSomeoneElsesTicker() throws InterruptedException {
LDListener client1 = mock(LDListener.class);
LDListener client2 = mock(LDListener.class);
_manager.subscribe(client1, createIdBundles("T1", "T2"));
_manager.subscribe(client2, createIdBundles("T1", "T3"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T1", "M1"), buildSuccessResponse("T2", "M2"), buildSuccessResponse("T3", "M3")));
// Client 2 does not have subscription to T2
_manager.unsubscribe(client2, createIdBundles("T2"));
// Sleep as the subscribe/unsubscribe is happening on another thread
Thread.sleep(10);
verify(_mockLiveDataClient, never())
.unsubscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
}
@Test
@SuppressWarnings("unchecked")
public void testClientUnregisteringWithUnusedTickers() throws InterruptedException {
LDListener client1 = mock(LDListener.class);
LDListener client2 = mock(LDListener.class);
_manager.subscribe(client1, createIdBundles("T1", "T2"));
_manager.subscribe(client2, createIdBundles("T1", "T3"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T1", "M1"), buildSuccessResponse("T2", "M2"), buildSuccessResponse("T3", "M3")));
_manager.unregister(client1);
// Sleep as the subscribe/unsubscribe is happening on another thread
Thread.sleep(500);
// Unsubscribe from the T2 ticker should happen
verify(_mockLiveDataClient)
.unsubscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
}
@Test
@SuppressWarnings("unchecked")
public void testClientUnregisteringWithNoUnusedTickers() throws InterruptedException {
LDListener client1 = mock(LDListener.class);
LDListener client2 = mock(LDListener.class);
// All client1's tickers are wanted by client2
_manager.subscribe(client1, createIdBundles("T1", "T2"));
_manager.subscribe(client2, createIdBundles("T1", "T2"));
_manager.subscriptionResultsReceived(ImmutableSet.of(
buildSuccessResponse("T1", "M1"), buildSuccessResponse("T2", "M2"), buildSuccessResponse("T3", "M3")));
_manager.unregister(client1);
// Sleep as the subscribe/unsubscribe is happening on another thread
Thread.sleep(10);
// Unsubscribe from the T2 ticker should happen
verify(_mockLiveDataClient, never())
.unsubscribe(any(UserPrincipal.class), any(Collection.class), any(LiveDataListener.class));
}
@Test
public void testDataCanBeRetrieved() {
LDListener client = mock(LDListener.class);
_manager.subscribe(client, createIdBundles("T1"));
_manager.subscriptionResultsReceived(ImmutableSet.of(buildSuccessResponse("T1")));
MutableFudgeMsg msg = _fudgeContext.newMessage();
msg.add("Market_Value", 1.23);
msg.add("Another", "testIt");
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg));
_manager.waitForAllData(client);
LiveDataResult t1 = _manager.snapshot(client).get(createBundle("T1"));
assertThat(t1.getValue(MARKET_VALUE_FIELD).getValue(), is((Object) 1.23));
assertThat(t1.getValue(ANOTHER_FIELD).getValue(), is((Object) "testIt"));
}
@Test
public void testMultipleUpdatesCanBeReceived() {
LDListener client = mock(LDListener.class);
_manager.subscribe(client, createIdBundles("T1"));
_manager.subscriptionResultsReceived(ImmutableSet.of(buildSuccessResponse("T1")));
MutableFudgeMsg msg = _fudgeContext.newMessage();
msg.add("Market_Value", 1.23);
msg.add("Another", "testIt");
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg));
// Send the same update again
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg));
_manager.waitForAllData(client);
LiveDataResult t1 = _manager.snapshot(client).get(createBundle("T1"));
assertThat(t1.getValue(MARKET_VALUE_FIELD).getValue(), is((Object) 1.23));
assertThat(t1.getValue(ANOTHER_FIELD).getValue(), is((Object) "testIt"));
}
@Test
public void testMultipleUpdatesCanBeReceivedAndGetMerged() {
LDListener client = mock(LDListener.class);
_manager.subscribe(client, createIdBundles("T1"));
_manager.subscriptionResultsReceived(ImmutableSet.of(buildSuccessResponse("T1")));
MutableFudgeMsg msg1 = _fudgeContext.newMessage();
msg1.add("Market_Value", 1.23);
msg1.add("Another", "testIt");
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg1));
_manager.waitForAllData(client);
LiveDataResult t1_1 = _manager.snapshot(client).get(createBundle("T1"));
assertThat(t1_1.getValue(MARKET_VALUE_FIELD).getValue(), is((Object) 1.23));
assertThat(t1_1.getValue(ANOTHER_FIELD).getValue(), is((Object) "testIt"));
MutableFudgeMsg msg2 = _fudgeContext.newMessage();
msg2.add("Market_Value", 2.34);
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg2));
LiveDataResult t1_2 = _manager.snapshot(client).get(createBundle("T1"));
assertThat(t1_2.getValue(MARKET_VALUE_FIELD).getValue(), is((Object) 2.34)); // updated
assertThat(t1_2.getValue(ANOTHER_FIELD).getValue(), is((Object) "testIt")); // unchanged even though not sent
}
@Test
public void testPermissionsGetMerged() {
// Created to prevent regression of issue raised in SSM-320
ThreadContext.bind(createSecurityManagerAllowing("T1-Perm"));
LDListener client = mock(LDListener.class);
_manager.subscribe(client, createIdBundles("T1", "T2"));
_manager.subscriptionResultsReceived(ImmutableSet.of(buildSuccessResponse("T1"), buildSuccessResponse("T2")));
// Permitted
MutableFudgeMsg msg1 = buildPermissionedMsg("T1-Perm");
msg1.add("Market_Value", 1.23);
// Denied
MutableFudgeMsg msg2 = buildPermissionedMsg("T2-Perm");
msg2.add("Market_Value", 2.34);
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg1));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T2"), msg2));
_manager.waitForAllData(client);
LiveDataResults snapshot1 = _manager.snapshot(client);
assertThat(snapshot1.size(), is(2));
checkSnapshotEntry(snapshot1, "T1", SUCCESS);
checkSnapshotEntry(snapshot1, "T2", PERMISSION_DENIED);
// Now send an update but without permission field
// as only deltas are sent
msg1 = _fudgeContext.newMessage();
msg1.add("Market_Value", 1.13);
msg2 = _fudgeContext.newMessage();
msg2.add("Market_Value", 2.54);
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T1"), msg1));
_manager.valueUpdate(new LiveDataValueUpdateBean(1, createLiveDataSpec("T2"), msg2));
_manager.waitForAllData(client);
// Again we shouldn't see T2
LiveDataResults snapshot2 = _manager.snapshot(client);
assertThat(snapshot2.size(), is(2));
checkSnapshotEntry(snapshot2, "T1", SUCCESS);
checkSnapshotEntry(snapshot2, "T2", PERMISSION_DENIED);
}
// TODO - additional tests:
// test multiple threads for one client all waiting on same latch
// test multiple clients all on separate threads and data ticking in
private SecurityManager createSecurityManagerAllowing(final String... permissions) {
final AuthorizingRealm realm = new AuthorizingRealm() {
Set<String> perms = ImmutableSet.copyOf(permissions);
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// this method should possibly be simpler, but this certainly works
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (String perm : perms) {
info.addObjectPermission(createPermission(perm));
}
return info;
}
private Permission createPermission(final String perm) {
return new Permission() {
@Override
public boolean implies(Permission p) {
return perm.toLowerCase().equals(p.toString());
}
};
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// Not concerned with authentication for these tests
return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
}
};
return new PermissioningSecurityManager(realm);
}
private MutableFudgeMsg buildPermissionedMsg(String perm) {
MutableFudgeMsg msg = _fudgeContext.newMessage();
msg.add(PermissionUtils.LIVE_DATA_PERMISSION_FIELD, perm);
return msg;
}
public class PermissioningSecurityManager extends DefaultWebSecurityManager {
/**
* Creates an instance.
*/
public PermissioningSecurityManager(Realm realm) {
setRealm(realm);
}
//-------------------------------------------------------------------------
@Override
protected SubjectContext copy(SubjectContext subjectContext) {
// this is the only way to trick the superclass into believing subject is always authenticated
UsernamePasswordToken token = new UsernamePasswordToken("permissive", "nopassword");
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(),
token.getCredentials(),
"Permissive");
subjectContext.setAuthenticated(true);
subjectContext.setAuthenticationToken(token);
subjectContext.setAuthenticationInfo(info);
return subjectContext;
}
}
private Set<ExternalIdBundle> createIdBundles(String... tickers) {
Set<ExternalIdBundle> subs = new HashSet<>();
for (String ticker : tickers) {
subs.add(createBundle(ticker));
}
return subs;
}
/**
* Helper class that will run on another thread and immediately wait
* for the manager to indicate that data is available. The state can
* be monitored to check the behaviour is as expected.
*/
private static class WaitingClient implements LDListener, Runnable {
private final LiveDataManager _manager;
private WaitState _waitState = INITIAL;
public WaitingClient(LiveDataManager manager) {
_manager = manager;
}
@Override
public void run() {
_waitState = WAITING;
_manager.waitForAllData(this);
_waitState = WaitState.DONE;
}
public WaitState getWaitState() {
return _waitState;
}
@Override
public void valueUpdated() {
}
}
/**
* Helper class that will run on another thread and has different
* permissions to to indicate that data is available. The state can
* be monitored to check the behaviour is as expected.
*/
private class PermissionedClient implements LDListener, Runnable {
private final LiveDataManager _manager;
private final String[] _allowedPerms;
private LiveDataResults _snapshot;
public PermissionedClient(LiveDataManager manager, String... allowedPerms) {
_manager = manager;
_allowedPerms = allowedPerms;
}
@Override
public void run() {
// This will set the permissions for the thread local and will not
// affect other clients
ThreadContext.bind(createSecurityManagerAllowing(_allowedPerms));
_manager.waitForAllData(this);
_snapshot = _manager.snapshot(this);
}
public LiveDataResults getSnapshot() {
return _snapshot;
}
@Override
public void valueUpdated() {
}
}
}