com.quotemedia.streamer.client.impl.StompStreamImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of streamerclient-java-core Show documentation
Show all versions of streamerclient-java-core Show documentation
Java streaming client that provides easy-to-use client APIs to connect and subscribe to QuoteMedia's market data streaming services. https://quotemedia.com/
The newest version!
package com.quotemedia.streamer.client.impl;
import com.google.common.collect.Lists;
import com.quotemedia.datacache.messaging.GenericDataMessage;
import com.quotemedia.integration.Response;
import com.quotemedia.streamer.client.*;
import com.quotemedia.streamer.client.auth.AuthClient;
import com.quotemedia.streamer.client.auth.impl.HttpPostJson;
import com.quotemedia.streamer.client.auth.json.ObjectMapperFactory;
import com.quotemedia.streamer.client.factory.AuthHeader;
import com.quotemedia.streamer.client.factory.StompFactory;
import com.quotemedia.streamer.client.util.Datatypes;
import com.quotemedia.streamer.messages.MimeTypes;
import com.quotemedia.streamer.messages.control.*;
import com.quotemedia.streamer.messages.market.DataMessage;
import com.quotemedia.streamer.messages.qmci.QmciMessage;
import com.quotemedia.streamer.messages.smessage.UShortId;
import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.websocket.WsWebSocketContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandler;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import javax.websocket.WebSocketContainer;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.ConnectException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.base.Preconditions.checkNotNull;
public class StompStreamImpl implements Stream {
private static final Logger log = LoggerFactory.getLogger(StompStreamImpl.class);
private final Authenticate auth;
private final StompFactory stomp;
private final JsonMessageMapper jsnMapper;
private final StreamStats stats;
private final SymbolLocator symbols;
private final RequestResponseTracker tracker;
private SubscribeMessageTracker subscribeMessageTracker;
private HttpPostJson http;
private StompSession session;
private String connectionId;
private StreamCfg streamCfg;
private AuthenticateMessage msg;
public StompStreamImpl(final AuthClient auth,
final StompFactory stomp,
final StompPayloadDecoder decoder,
final StompPayloadEncoder encoder,
final JsonMessageMapper jsnMapper,
final StreamStats stats,
final SymbolLocator symbols
) {
this.auth = new Authenticate(checkNotNull(auth));
this.stomp = checkNotNull(stomp);
this.decoder = checkNotNull(decoder);
this.encoder = checkNotNull(encoder);
this.jsnMapper = checkNotNull(jsnMapper);
this.stats = checkNotNull(stats);
this.symbols = checkNotNull(symbols);
this.tracker = new RequestResponseTracker();
this.subscribeMessageTracker = new SubscribeMessageTracker();
this.http = new HttpPostJson(new ObjectMapperFactory().create());
}
/**
* May be used to monitor stream statistics.
* The returned instance is shared and 'live' reflecting any updates instantly.
*
* @return the statistics.
*/
public final StreamStats stats() {
return this.stats;
}
private AtomicReference> onmessage = new AtomicReference<>();
@Override
public final void onmessage(final OnMessage> callback) {
this.onmessage.set(callback);
}
private AtomicReference onerror = new AtomicReference();
private AtomicReference onReconnect = new AtomicReference<>();
@Override
public void onReconnect(OnReconnect callback) {
this.onReconnect.set(callback);
}
@Override
public final void onerror(final OnError callback) {
this.onerror.set(callback);
}
private AtomicReference> onctrlmessage = new AtomicReference<>();
@Override
public final void onctrlmessage(final OnMessage> callback) {
onctrlmessage.set(callback);
}
private final AtomicReference _state = new AtomicReference<>(STATE.NEW);
@Override
public final STATE state() {
return this._state.get();
}
private final void state(final STATE val) {
final STATE oldval = this._state.getAndSet(val);
log.debug("State change {} -> {}", oldval, val);
}
private final AtomicBoolean _doReconnect = new AtomicBoolean();
private boolean doReconnect() {
return this._doReconnect.get();
}
private void doReconnect(boolean val) {
this._doReconnect.set(val);
}
//private Socket socket;
private final AtomicReference> connectresponse
= new AtomicReference<>();
private final AtomicReference> reconnectresponse
= new AtomicReference<>();
volatile private String format = StreamCfg.FORMAT_DEFAULT;
volatile private OnMarketDataMessage ondatamessage;
volatile private StompPayloadDecoder decoder;
volatile private StompPayloadEncoder encoder;
volatile private MessageProcessor messageProcessor;
private int maxEntitlementsPerSubscription;
private int maxReconnectAttempts;
private boolean checkServerOnReconnect;
// synchronous
@Override
public final ConnectResponse open(final StreamCfg cfg) throws StreamException {
this.streamCfg = cfg;
this.maxReconnectAttempts = cfg.getReopenMaxAttempts();
if (maxReconnectAttempts > StreamCfg.MAX_PERMITTED_ATTEMPTS) {
throw new StreamException("Max permitted attempts for reconnection is " + StreamCfg.MAX_PERMITTED_ATTEMPTS
+ ". Please reduce the amount. Amount entered: " + maxReconnectAttempts);
}
synchronized (this._state) {
this.checkopen();
this.state(STATE.OPENING);
}
this.format = cfg.format();
this.maxReconnectAttempts = cfg.getReopenMaxAttempts();
this.checkServerOnReconnect = cfg.isCheckServerOnReconnect();
if (!MimeTypes.QITCH.equals(this.format)) {
this.ondatamessage = new OnMarketDataMessage() {
@Override
public void on(final long timestamp, final Object msg) {
assert msg instanceof GenericDataMessage || msg instanceof QmciMessage || msg instanceof DataMessage;
firedatamessage(timestamp, jsnMapper.map(msg));
}
};
} else {
throw new StreamException("Format is not supported");
}
final FutureValue futureresponse = new FutureValue<>();
this.connectresponse.set(futureresponse);
ConnectResponse response;
try {
if (cfg.checkversion()) {
String versionEndpoint = cfg.uri().replace("/stream/connect", "/version/v1");
String httpsEndpoint;
if (versionEndpoint.contains("wss:")) {
httpsEndpoint = versionEndpoint.replace("wss:", "https:");
} else {
httpsEndpoint = versionEndpoint.replace("ws:", "http:");
}
this.http.get(new URL(httpsEndpoint), null, HttpPostJson.REQUEST_METHOD_GET, Response.class);
}
} catch (Exception e) {
log.warn("Error making server version call Reason {}: {}", e.getClass().getSimpleName(), e.getMessage());
}
try {
final AuthHeader authheader = this.auth.authenticate(cfg.auth());
this.msg = stomp.prepareAuthRequests(cfg, authheader);
this.session = this.opensocket(cfg);
this.session.send("/stream/message", encoder.encode(this.msg));
if (cfg.opentimeout_s() == null) {
response = futureresponse.get();
} else {
response = futureresponse.get(cfg.opentimeout_s(), TimeUnit.SECONDS);
}
if (ResponseCodes.OK_CODE == response.getCode()) {
initializeMessageProcessor(cfg);
} else {
throw new Exception(response.getReason());
}
} catch (final Exception e) {
synchronized (this._state) {
state(STATE.BROKEN);
}
futureresponse.fail(e);
throw new StreamException(e);
}
maxEntitlementsPerSubscription = response.getMaxEntitlementsPerSubscription();
return response;
}
private ConnectResponse reopen() throws StreamException {
synchronized (this._state) {
this.checkReopen();
this.state(STATE.REOPENING);
}
final FutureValue connectFutureResponse = new FutureValue<>();
this.connectresponse.set(connectFutureResponse);
ConnectResponse connectResponse;
OnReconnect onReconnect = this.onReconnect.get();
try {
if (this.connectionId == null) {
log.error("Unable to reopen connection. Socket or connection id is null");
this.doReconnect(false);
this.closeimpl();
StreamException streamException = new StreamException("Unable to reopen connection. Socket or connection id is null");
if (onReconnect != null) {
onReconnect.error(streamException);
}
throw streamException;
}
this.session = this.opensocket(this.streamCfg);
this.session.send("/stream/message", encoder.encode(this.msg));
connectResponse = (this.streamCfg.reopentimeout_ms() == null) ? connectFutureResponse.get() : connectFutureResponse.get(this.streamCfg.reopentimeout_ms(), TimeUnit.SECONDS);
if (ResponseCodes.OK_CODE == connectResponse.getCode()) {
initializeMessageProcessor(this.streamCfg);
if (onReconnect != null) {
onReconnect.onConnectionSuccess(connectResponse);
}
} else {
if (onReconnect != null) {
onReconnect.error(new StreamException("Connection response returned" + connectResponse.toString()));
}
}
} catch (final Exception e) {
connectFutureResponse.fail(e);
if (onReconnect != null) {
onReconnect.error(e);
}
throw new StreamException(e);
}
maxEntitlementsPerSubscription = connectResponse.getMaxEntitlementsPerSubscription();
return connectResponse;
}
/**
* Needs to check if the current state of the stream is either broken or closed
*/
private void checkReopen() {
final STATE state = this.state();
if (!(STATE.CLOSED.equals(state) || STATE.BROKEN.equals(state))) {
throw new IllegalStateException(String.format("Cannot trigger re-open for stream in state '%state'", state));
}
}
private void initializeMessageProcessor(StreamCfg cfg) {
if (cfg.isSynchronousMessageProcessing()) {
this.messageProcessor = new SynchronousMessageProcessor();
} else {
this.messageProcessor = new AsynchronousMessageProcessor(cfg.queueCapacity());
}
this.messageProcessor.start(ondatamessage);
}
private final void checkopen() {
final STATE s = this.state();
if (!(STATE.NEW.equals(s) || STATE.CLOSED.equals(s) || STATE.REOPENING.equals(s))) {
throw new IllegalStateException(String.format("Cannot open stream in state '%s'.", s));
}
}
private final StompSession opensocket(final StreamCfg cfg) throws TimeoutException, ExecutionException, InterruptedException {
StompSession stompSession;
WebSocketContainer container = new WsWebSocketContainer();
container.setDefaultMaxTextMessageBufferSize(131072);
WebSocketClient webSocketClient = new StandardWebSocketClient(container);
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
StompSessionHandler sessionHandler = new MyStompSessionHandler();
WebSocketHttpHeaders handshakeHeaders = stomp.setHeaders(cfg, connectionId);
if (cfg.opentimeout_s() == null) {
stompSession = stompClient.connect(cfg.uri(), handshakeHeaders, new StompHeaders(), sessionHandler).get();
} else {
stompSession = stompClient.connect(cfg.uri(), handshakeHeaders, new StompHeaders(), sessionHandler).get(cfg.opentimeout_s(), TimeUnit.SECONDS);
}
stompSession.subscribe("/user/queue/messages", sessionHandler);
return stompSession;
}
// Flag to check if the connection is successfully reconnected
private boolean isReconnected = false;
// This flag is mainly to check if we should stop reconnecting regardless, i.e 401 returned from server. if this is true at some point, it won't be set back to false
private boolean stopReconnection = false;
// handle socket close event
private final void onSocketClose() {
OnError on = this.onerror.get();
if (messageProcessor != null) {
messageProcessor.stop();
}
final STATE state = this._state.get();
// logic is needed to unify WEBSOCKET and STREAMING
if (STATE.CLOSING.equals(state)) {
this.closeimpl();
} else { // unexpected close
synchronized (this._state) {
this.state(STATE.BROKEN);
}
if (this.streamCfg.isReconnectActive() && !doReconnect()) {
this.doReconnect(true);
}
if (on != null) {
on.error(new StreamException(new ConnectException("Unexpected connection close.")));
}
}
if (doReconnect() && !stopReconnection) {
try {
this.tryReopen();
} catch (StreamException streamException) {
log.error("There was an error while reconnecting. {}", streamException.toString());
if (on != null) {
on.error(new StreamException("Error while trying re-connection. Reason: " + streamException.getMessage()));
}
}
}
}
protected void tryReopen() throws StreamException {
if (!isServerUp()) {
log.debug("Unable to reconnect, server is down");
throw new StreamException("Unable to reconnect, server is down");
}
if (isReconnected) {
log.debug("Connection state is {}, won't try reconnection", this.state());
throw new StreamException(String.format("Connection state is {}, won't try reconnection", this.state()));
}
if (!doReconnect() || stopReconnection) {
log.debug("The reconnect is not active and won't try to reconnect");
throw new StreamException("The reconnect is not active and won't try to reconnect");
}
startReconnection();
}
private void startReconnection() throws StreamException {
if (maxReconnectAttempts <= 0) {
throw new StreamException("Max reconnect attempts reached and won't try to reconnect");
} else {
try {
int attempt = 1;
while (!stopReconnection && doReconnect() && !isReconnected && attempt <= maxReconnectAttempts) {
ConnectResponse connectResponse = this.reopen();
if (connectResponse.getCode() == ResponseCodes.OK_CODE && this.state() == STATE.OPEN) {
isReconnected = true;
maxReconnectAttempts--;
this.doReconnect(false);
break;
} else if (connectResponse.getCode() == ResponseCodes.UNAUTHORIZED_CODE) {
log.warn("Unauthorized response. Won't try to reconnect");
stopReconnection = true; // need to stop right here since there is no point of continuing trying to reconnect if 401 was sent
} else {
log.warn("Reconnection attempt: {} failed. Reason: {}", attempt, connectResponse.getReason());
}
attempt++;
}
} catch (StreamException e) {
log.error("Exception was thrown while reconnecting. Reason: {}", e.getMessage());
doReconnect(false);
this.closeimpl();
throw e;
}
}
}
protected boolean isServerUp() {
if (!this.checkServerOnReconnect) {
return true;
}
//make a ping request to server
String pingEndpoint = this.streamCfg.uri().replace("/stream/connect", "/ping/v1");
String httpsEndpoint;
if (pingEndpoint.contains("wss:")) {
httpsEndpoint = pingEndpoint.replace("wss:", "https:");
} else {
httpsEndpoint = pingEndpoint.replace("ws:", "http:");
}
try {
this.http.get(new URL(httpsEndpoint), null, HttpPostJson.REQUEST_METHOD_GET, Response.class);
return true;
} catch (IOException e) {
log.warn("Unable to get ping response from server. Reason {}: {}", e.getClass(), e.getMessage());
this.doReconnect(false);
stopReconnection = true;
}
return false;
}
// handle socket error event
private final void onSocketError(final Throwable t) {
OnError on;
synchronized (this._state) {
this.state(STATE.BROKEN);
on = this.onerror.get();
}
if (on != null) {
on.error(new StreamException(t));
}
}
@Override
public final void subscribe(final Subscription sub, final OnResponse callback) {
List subscriptionRequests = prepareRequests(sub, Action.SUBSCRIBE);
synchronized (this._state) {
for (SubscribeMessage subscriptionRequest : subscriptionRequests) {
sendRequest(subscriptionRequest, callback);
}
}
}
@Override
public final void unsubscribe(final Subscription sub, final OnResponse callback) {
List subscriptionRequests = prepareRequests(sub, Action.UNSUBSCRIBE);
synchronized (this._state) {
for (SubscribeMessage subscriptionRequest : subscriptionRequests) {
sendRequest(subscriptionRequest, callback);
}
}
}
private List prepareRequests(Subscription sub, Action action) {
int currentApproximateNumberOfEntitlements = 0;
int lastAddedSymbolIndex = -1;
int numberOfSymbols = sub.symbols().length;
boolean isOrderbookSubscription = Arrays.asList(sub.types()).contains(Datatype.ORDERBOOK);
List requests = Lists.newArrayList();
for (int i = 0; i < numberOfSymbols; i++) {
currentApproximateNumberOfEntitlements = getUpdatedNumberOfEntitlements(sub.types().length,
currentApproximateNumberOfEntitlements, sub.symbols()[i], isOrderbookSubscription);
if (currentApproximateNumberOfEntitlements >= maxEntitlementsPerSubscription || i == numberOfSymbols - 1) {
requests.add(buildRequest(Arrays.copyOfRange(sub.symbols(), lastAddedSymbolIndex + 1, i + 1), sub, action));
lastAddedSymbolIndex = i;
currentApproximateNumberOfEntitlements = 0;
}
}
return requests;
}
private SubscribeMessage buildRequest(String[] symbols, Subscription sub, Action action) {
final SubscribeMessage msg = new SubscribeMessage();
msg.setAction(action);
msg.setSymbols(symbols);
msg.setTypes(Datatypes.map(sub.types()));
msg.setSkipHeavyInitialLoad(sub.skipHeavyInitialLoad());
msg.setMimetype(this.format);
msg.setConflation(sub.conflation());
msg.setIntervalPeriod(sub.intervalPeriod());
return msg;
}
private void sendRequest(final SubscribeMessage msg, final OnResponse extends BaseResponse> callback) {
this.checkfire();
this.fire(msg, callback);
}
@Override
public void subscribeExchange(final ExchangeSubscription sub, final OnResponse callback) {
ExchangeSubscribeMessage exchangeSubscribeMessage = new ExchangeSubscribeMessage();
exchangeSubscribeMessage.setExchange(sub.getExchange());
exchangeSubscribeMessage.setMimetype(this.format);
exchangeSubscribeMessage.setConflation(sub.getConflation());
exchangeSubscribeMessage.setAction(Action.SUBSCRIBE);
this.checkfire();
this.fire(exchangeSubscribeMessage, callback);
}
@Override
public void unsubscribeExchange(ExchangeSubscription request, OnResponse callback) {
ExchangeSubscribeMessage exchangeSubscribeMessage = new ExchangeSubscribeMessage();
exchangeSubscribeMessage.setExchange(request.getExchange());
exchangeSubscribeMessage.setMimetype(this.format);
exchangeSubscribeMessage.setConflation(request.getConflation());
exchangeSubscribeMessage.setAction(Action.UNSUBSCRIBE);
this.checkfire();
this.fire(exchangeSubscribeMessage, callback);
}
@Override
public void subUnsubAlert(AlertSubscription request, OnResponse callback) {
SubUnsubAlertMessage subUnsubAlertMessage = new SubUnsubAlertMessage();
subUnsubAlertMessage.setOperation(request.getOperation());
subUnsubAlertMessage.setMimetype(this.format);
this.checkfire();
this.fire(subUnsubAlertMessage, callback);
}
@Override
public void subscribeCorporateEvent(CorpEventSubscription request, OnResponse callback) {
CorpEventSubscribeMessage corpEventSubscribeMessage = new CorpEventSubscribeMessage();
corpEventSubscribeMessage.setSymbols(request.getSymbols());
corpEventSubscribeMessage.setAllCorpEvents(request.isAllCorpEvents());
corpEventSubscribeMessage.setAction(Action.SUBSCRIBE);
corpEventSubscribeMessage.setCorpEventTypes(request.getTypes());
corpEventSubscribeMessage.setMimeType(this.format);
this.checkfire();
this.fire(corpEventSubscribeMessage, callback);
}
@Override
public void unsubscribeCorporateEvent(CorpEventSubscription request, OnResponse callback) {
CorpEventSubscribeMessage corpEventSubscribeMessage = new CorpEventSubscribeMessage();
corpEventSubscribeMessage.setSymbols(request.getSymbols());
corpEventSubscribeMessage.setAllCorpEvents(request.isAllCorpEvents());
corpEventSubscribeMessage.setAction(Action.UNSUBSCRIBE);
corpEventSubscribeMessage.setCorpEventTypes(request.getTypes());
corpEventSubscribeMessage.setMimeType(this.format);
this.checkfire();
this.fire(corpEventSubscribeMessage, callback);
}
//TODO add subscribe news client interface when java client is ready
// public void subscribeNews(NewsSubscription request, OnResponse callback){
// NewsSubscribeMessage newsSubscribeMessage = new NewsSubscribeMessage();
// newsSubscribeMessage.setNewsFilters(request.getNewsFilters());
// newsSubscribeMessage.setSkipHeavyInitialLoad(request.skipHeavyInitialLoad());
// this.checkfire();
// this.fire(newsSubscribeMessage, callback);
// }
@Override
public void getSessionStats(OnResponse callback) {
this.checkfire();
final StatsMessage msg = new StatsMessage();
this.fire(msg, callback);
}
private boolean isConsolidatedSymbol(String symbol) {
return StringUtils.endsWith(symbol, CONSOLIDATED_SYMBOL_SUFFIX);
}
private int getUpdatedNumberOfEntitlements(int numberOfSubscriptionTypes, int currentApproximateNumberOfEntitlements, String symbol, boolean isSubscribeToOrderbook) {
int result = currentApproximateNumberOfEntitlements;
if (isSubscribeToOrderbook && isConsolidatedSymbol(symbol)) {
result += CONSOLIDATED_SYMBOL_ENTITLEMENTS_COEFFICIENT * numberOfSubscriptionTypes;
} else {
result += numberOfSubscriptionTypes;
}
return result;
}
private void checkfire() {
final STATE s = this.state();
if (!STATE.OPEN.equals(s)) {
throw new IllegalStateException(String.format("Cannot fire message over stream in state '%s'.", s));
}
}
private void fire(final CtrlMessage sub, final OnResponse extends BaseResponse> callback) {
Integer id;
if (callback != null) {
id = this.tracker.add(callback);
} else {
id = UShortId.NULL_VALUE;
}
try {
if (sub instanceof BaseRequest) {
((BaseRequest) sub).setId(id);
if (sub instanceof SubscribeMessage && ((SubscribeMessage) sub).getAction().equals(Action.SUBSCRIBE)) {
this.subscribeMessageTracker.add(id);
}
}
final String msg = this.encoder.encode(sub);
this.send(msg);
} catch (final IOException e) {
this.tracker.remove(id);
this.subscribeMessageTracker.remove(id);
// exception handling through callback
if (callback != null) {
callback.error(new StreamException(e));
}
}
}
private final void send(final String msg) {
session.send("/stream/message", msg);
this.stats.incmsgout();
}
@Override
public void performReconnect() throws StreamException {
if (!streamCfg.isReconnectActive()) {
log.debug("Reconnect flag is set to {}. Won't try to reconnect", streamCfg.isReconnectActive());
throw new StreamException(String.format("Reconnect flag is set to {}", streamCfg.isReconnectActive()));
}
this.doReconnect(true);
try {
this.tryReopen();
} catch (Exception exception) {
OnReconnect onReconnect = this.onReconnect.get();
if (onReconnect != null) {
onReconnect.error(new StreamException(exception));
}
}
}
private final AtomicReference onclose = new AtomicReference();
@Override
public final void close(final OnClose callback) {
synchronized (this._state) {
if (!this.canclose()) {
return;
}
this.onclose.set(callback);
state(STATE.CLOSING);
}
if (this.session != null) {
if (this.session.isConnected()) {
this.session.disconnect();
}
}
onSocketClose();
}
private boolean canclose() {
final STATE s = this.state();
if (STATE.CLOSED.equals(s) || STATE.CLOSING.equals(s)) {
return false;
}
return true;
}
private void closeimpl() {
OnClose on;
synchronized (_state) {
on = onclose.getAndSet(null);
state(STATE.CLOSED);
isReconnected = false;
}
if (on != null) {
on.closed();
}
}
private void handlepayload(final Object msg) {
Object payload;
try {
payload = this.decoder.decode(msg);
} catch (final IOException e) {
payload = null;
log.warn("Problem decoding message. Reason {}: {}", e.getClass().getSimpleName(), e.getMessage());
log.debug("Problem decoding message.", e);
}
if (payload == null) {
return;
}
this.onMessage(System.currentTimeMillis(), payload);
}
private void onMessage(final long timestamp, final Object msg) {
if (msg instanceof OpenFlowMessage) {
this.fire((OpenFlowMessage) msg, null);
} else if (!(msg instanceof CtrlMessage)) {
this.messageProcessor.process(timestamp, msg);
} else {
this.stats.incctrlmsgin();
this.stats.incctrlmsglag_ms(System.currentTimeMillis() - timestamp);
this.onCtrlMessage((CtrlMessage) msg);
}
}
private final void firedatamessage(final long timestamp, final DataMessage msg) {
log.trace("Received MarketData Message {}", msg.toString());
this.stats.incdatamsgin();
this.stats.incdatamsglag_ms(System.currentTimeMillis() - timestamp);
final OnMessage on = this.onmessage.get();
if (on != null) {
on.message(msg);
}
}
private final void firectrlmessage(final CtrlMessage msg) {
final OnMessage on = this.onctrlmessage.get();
if (on != null) {
on.message(msg);
}
}
private final void onCtrlMessage(final CtrlMessage msg) {
log.debug("Received control message. {}", msg);
if (msg instanceof Heartbeat) {
return; // do not forward
}
if (msg instanceof ReconnectResponse) {
this.onReconnectResponse((ReconnectResponse) msg);
}
if (msg instanceof ConnectResponse) {
this.onConnectResponse((ConnectResponse) msg);
} else if (msg instanceof SubscribeResponse) {
this.onSubscribeResponse((SubscribeResponse) msg);
} else if (msg instanceof UnsubscribeResponse) {
this.onUnsubscribeResponse((UnsubscribeResponse) msg);
} else if (msg instanceof ConnectionClose) {
this.onConnectionClose((ConnectionClose) msg);
} else if (msg instanceof SlowConnection) {
this.onSlowConnectionMessage((SlowConnection) msg);
} else if (msg instanceof StatsResponse) {
this.onStatsResponse((StatsResponse) msg);
} else if (msg instanceof InitialDataSent) {
this.onInitialDataSent((InitialDataSent) msg);
} else if (msg instanceof ResubscribeMessage) {
this.onResubscribeMessage((ResubscribeMessage) msg);
} else if (msg instanceof ExchangeSubscribeResponse) {
this.onExchangeSubscribeResponse((ExchangeSubscribeResponse) msg);
} else if (msg instanceof ExchangeUnsubscribeResponse) {
this.onExchangeUnsubscribeResponse((ExchangeUnsubscribeResponse) msg);
} else if (msg instanceof AlertsSubUnsubResponse) {
this.onAlertSubUnsubResponse((AlertsSubUnsubResponse) msg);
} else if (msg instanceof NewsSubscribeResponse) {
this.onNewsSubscribeResponse((NewsSubscribeResponse) msg);
} else if (msg instanceof MissedDataSent) {
this.onMissedDataSent((MissedDataSent) msg);
} else if (msg instanceof CorpEventResponse) {
this.onCorporateEventResponse((CorpEventResponse) msg);
} else if (msg != null) {
log.warn("Received unsupported control message. {}", msg);
}
}
private void onExchangeSubscribeResponse(ExchangeSubscribeResponse msg) {
OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
private void onExchangeUnsubscribeResponse(ExchangeUnsubscribeResponse msg) {
OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
private void onAlertSubUnsubResponse(AlertsSubUnsubResponse msg) {
OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
private void onNewsSubscribeResponse(NewsSubscribeResponse msg) {
OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
private void onCorporateEventResponse(CorpEventResponse msg) {
OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
volatile private String serverversion = null;
private void onConnectResponse(final ConnectResponse msg) {
this.serverversion = msg.getVersion();
synchronized (this._state) {
final FutureValue response = this.connectresponse.getAndSet(null);
if (STATE.OPENING.equals(this._state.get()) || STATE.REOPENING.equals(this._state.get())) {
this.state(STATE.OPEN);
response.complete(msg);
}
}
}
private void onReconnectResponse(ReconnectResponse msg) {
this.serverversion = msg.getVersion();
OnReconnect callback;
synchronized (this._state) {
final FutureValue response = this.reconnectresponse.getAndSet(null);
callback = this.onReconnect.get();
if (STATE.REOPENING.equals(this._state.get())) {
this.state(STATE.OPEN);
response.complete(msg);
}
if (callback != null) {
callback.onReconnectResponse(msg);
}
}
if (msg.getCode() == 450) {
this.doReconnect(false);
this.stopReconnection = true;
log.warn("A slow connection response was sent by the server. Another reopen won't be attempted");
}
log.debug("Received reconnect response for id: {} ", msg.getRequestId());
}
private final void onSubscribeResponse(final SubscribeResponse msg) {
this.subscribeMessageTracker.combineResponse(msg);
log.debug("Received subscribe response for request {}", msg.getRequestId());
final OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
log.debug("Callback is not null");
if (msg.getCode() != ResponseCodes.OK_CODE) {
callback.response(msg);
this.subscribeMessageTracker = new SubscribeMessageTracker();
} else if (this.subscribeMessageTracker.pending.isEmpty()) {
callback.response(this.subscribeMessageTracker.combinedResponse);
this.subscribeMessageTracker = new SubscribeMessageTracker();
}
}
}
private final void onUnsubscribeResponse(final UnsubscribeResponse msg) {
final OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
private final void onStatsResponse(final StatsResponse msg) {
final OnResponse callback = (OnResponse) this.tracker.remove(msg.getRequestId());
if (callback != null) {
callback.response(msg);
}
}
/**
* Last message before close the connection.
*
* @param msg
*/
private final void onConnectionClose(final ConnectionClose msg) {
firectrlmessage(msg);
}
/**
* Messages informing that client is lagging behind.
*
* @param msg
*/
private final void onSlowConnectionMessage(final SlowConnection msg) {
firectrlmessage(msg);
}
/**
* Messages informing that Initial data for subscription was sent.
*
* @param msg
*/
private final void onInitialDataSent(final InitialDataSent msg) {
firectrlmessage(msg);
}
/**
* Messages informing that Resubscription has occur after a datasource reset.
*
* @param msg
*/
private final void onResubscribeMessage(final ResubscribeMessage msg) {
firectrlmessage(msg);
}
/**
* Messages informing that Missed data for reconnection subscription was sent.
*
* @param msg
*/
private void onMissedDataSent(final MissedDataSent msg) {
firectrlmessage(msg);
}
/**
*
*/
private static final class RequestResponseTracker {
private final UShortId ids;
private final Map> pending;
public RequestResponseTracker() {
this.ids = new UShortId();
this.pending = new HashMap<>();
}
public final Integer add(final OnResponse> r) {
synchronized (this.pending) {
final int id = this.ids.next();
this.pending.put(id, r);
return id;
}
}
public final OnResponse> remove(final Integer id) {
synchronized (this.pending) {
return this.pending.remove(id);
}
}
}
private static final class SubscribeMessageTracker {
private final List pending;
private SubscribeResponse combinedResponse;
public SubscribeMessageTracker() {
this.pending = new ArrayList<>();
this.combinedResponse = new SubscribeResponse();
this.combinedResponse.setEntitlements(new ArrayList());
this.combinedResponse.setInvalidSymbols(new ArrayList());
this.combinedResponse.setRejectedSymbols(new ArrayList());
}
public final void add(final Integer id) {
synchronized (this.pending) {
this.pending.add(id);
}
}
public final void remove(Integer id) {
synchronized (this.pending) {
this.pending.remove(id);
}
}
private void combineResponse(final SubscribeResponse response) {
synchronized (this.combinedResponse) {
if (response != null) {
log.debug("Combining response");
if (response.getCode() == ResponseCodes.OK_CODE) {
this.combinedResponse.setCode(ResponseCodes.OK_CODE);
this.combinedResponse.getEntitlements().addAll(response.getEntitlements());
this.combinedResponse.getInvalidSymbols().addAll(response.getInvalidSymbols());
this.combinedResponse.getRejectedSymbols().addAll(response.getRejectedSymbols());
this.combinedResponse.setReason(response.getReason());
this.combinedResponse.setRequestId(response.getRequestId());
}
remove(response.getRequestId());
}
}
}
}
public class MyStompSessionHandler implements StompSessionHandler {
private final Logger logger = LoggerFactory.getLogger(MyStompSessionHandler.class);
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
if (streamCfg.isReconnectActive() && connectedHeaders.containsKey("user-name")) {
connectionId = connectedHeaders.get("user-name").get(0);
}
logger.debug("Connection is established for connectionId " + connectedHeaders.get("user-name"));
}
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
onSocketError(exception);
logger.error("Client error: exception {}, command {}, payload {}, headers {}",
exception.getMessage(), command, payload, headers);
}
@Override
public void handleTransportError(StompSession session, Throwable exception) {
onSocketError(exception);
logger.error("Client transport error: error {}", exception.getMessage());
}
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
@Override
public void handleFrame(StompHeaders headers, Object data) {
handlepayload(data);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy