/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.livedata.client;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.livedata.LiveDataListener;
import com.opengamma.livedata.LiveDataSpecification;
import com.opengamma.livedata.LiveDataValueUpdate;
import com.opengamma.livedata.LiveDataValueUpdateBean;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.livedata.msg.LiveDataSubscriptionResponse;
import com.opengamma.livedata.msg.LiveDataSubscriptionResult;
import com.opengamma.livedata.msg.SubscriptionType;
import com.opengamma.util.ArgumentChecker;
/**
* A subscription handle is kept by the client while a subscription is being established.
* After the subscription has been established, it is no longer needed.
*
* @author kirk
*/
public class SubscriptionHandle {
private static final Logger s_logger = LoggerFactory.getLogger(SubscriptionHandle.class);
private final UserPrincipal _user;
private final SubscriptionType _subscriptionType;
private final LiveDataSpecification _requestedSpecification;
private final LiveDataListener _listener;
private final List<LiveDataValueUpdateBean> _ticksOnHold = new ArrayList<LiveDataValueUpdateBean>();
private LiveDataValueUpdateBean _snapshotOnHold; // = null;
public SubscriptionHandle(
UserPrincipal user,
SubscriptionType subscriptionType,
LiveDataSpecification requestedSpecification,
LiveDataListener listener) {
ArgumentChecker.notNull(user, "User credentials");
ArgumentChecker.notNull(subscriptionType, "Subscription type");
ArgumentChecker.notNull(requestedSpecification, "Requested Specification");
ArgumentChecker.notNull(listener, "Live Data Listener");
_user = user;
_subscriptionType = subscriptionType;
_requestedSpecification = requestedSpecification;
_listener = listener;
}
/**
* @return the user principal
*/
public UserPrincipal getUser() {
return _user;
}
public SubscriptionType getSubscriptionType() {
return _subscriptionType;
}
/**
* @return the requestedSpecification
*/
public LiveDataSpecification getRequestedSpecification() {
return _requestedSpecification;
}
/**
* @return the listener
*/
public LiveDataListener getListener() {
return _listener;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
/**
* Informs the client listener about the response received from the server
*
* @param response Response received, not null
*/
public void subscriptionResultReceived(LiveDataSubscriptionResponse response) {
if (s_logger.isDebugEnabled()) {
if (_subscriptionType == SubscriptionType.SNAPSHOT) {
if (response.getSubscriptionResult() == LiveDataSubscriptionResult.SUCCESS) {
s_logger.debug("Got snapshot {}", getRequestedSpecification());
} else {
s_logger.debug("Failed to snapshot {}. Result was {}, msg = {}",
new Object[] {getRequestedSpecification(), response.getSubscriptionResult(), response.getUserMessage() });
}
} else {
if (response.getSubscriptionResult() == LiveDataSubscriptionResult.SUCCESS) {
s_logger.debug("Established subscription to {}", getRequestedSpecification());
} else if (response.getSubscriptionResult() == LiveDataSubscriptionResult.INTERNAL_ERROR) {
s_logger.debug("Failed to establish subscription, {} {}, request = {}",
new Object[] {response.getSubscriptionResult(), response.getUserMessage(), getRequestedSpecification() });
} else {
s_logger.debug("Failed to establish subscription, {} {}, request = {}",
new Object[] {response.getSubscriptionResult(), response.getUserMessage(), getRequestedSpecification() });
}
}
}
getListener().subscriptionResultReceived(response);
}
/**
* In a two-phase subscription procedure (see LIV-18), after a subscription is established,
* the client needs to get a snapshot from the server.
* Between establishing the subscription and getting the snapshot, all ticks
* must be kept in memory and only released after the snapshot is received.
*
* @param tick Tick to add to temporary memory store
*/
public synchronized void addTickOnHold(LiveDataValueUpdateBean tick) {
_ticksOnHold.add(tick);
}
/**
* In a two-phase subscription procedure (see LIV-18), after a subscription is established,
* the client needs to get a snapshot from the server. This method is used
* to store that snapshot.
*
* @param snapshot The snapshot to be placed on hold
*/
public synchronized void addSnapshotOnHold(LiveDataValueUpdateBean snapshot) {
if (_snapshotOnHold != null) {
throw new IllegalStateException("Snapshot has already been set");
}
_snapshotOnHold = snapshot;
}
/**
* Releases the snapshot and ticks stored in memory.
* For an explanation of why we need to do this, see LIV-18.
* The method copes with server restarts during the subscription process
* by assuming that the server sends a full image to the client
* when it restarts.
*/
public synchronized void releaseTicksOnHold() {
if (_snapshotOnHold == null) {
// this will happen if the snapshot failed.
s_logger.debug("No ticks to send to {}. {}", getListener(), getRequestedSpecification());
return;
}
long snapshotSequenceNo = _snapshotOnHold.getSequenceNumber();
// Find the LAST reset (in theory, there could be multiple resets although
// this is a highly theoretical case)
Integer resetIndex = null;
for (int i = 0; i < _ticksOnHold.size(); i++) {
LiveDataValueUpdateBean tick = _ticksOnHold.get(i);
if (tick.getSequenceNumber() == LiveDataValueUpdate.SEQUENCE_START) {
resetIndex = i;
}
}
if (resetIndex == null) {
s_logger.debug("{}: Sending snapshot and {} ticks on hold to {}",
new Object[] {getRequestedSpecification(), _ticksOnHold.size(), getListener() });
// No resets. This is the normal case. Use the snapshot
// and any subsequent ticks. The subsequent ticks
// are not sorted, but are played back in the order received,
// which hopefully should be the sequence number order (i.e., no sorting necessary).
_listener.valueUpdate(_snapshotOnHold);
for (LiveDataValueUpdateBean tick : _ticksOnHold) {
if (tick.getSequenceNumber() > snapshotSequenceNo) {
_listener.valueUpdate(tick);
}
}
} else {
s_logger.debug("{}: Reset detected. Sending {} ticks on hold to {}",
new Object[] {getRequestedSpecification(), _ticksOnHold.size() - resetIndex, getListener() });
// This happens when the server is reset (rebooted/migrated) while subscribing.
// We assume that the tick with sequence number = 0
// is a full update (as LiveDataValueUpdate.getSequenceNumber() specifies).
// Using this assumption, we first use the tick with sequence number = 0, which
// acts as the snapshot, and then simply send any subsequent ticks in order.
for (int i = resetIndex; i < _ticksOnHold.size(); i++) {
LiveDataValueUpdateBean tick = _ticksOnHold.get(i);
_listener.valueUpdate(tick);
}
}
_ticksOnHold.clear();
_snapshotOnHold = null;
}
}