All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openfeed.client.api.impl.websocket.OpenfeedClientWebSocket Maven / Gradle / Ivy

The newest version!
package org.openfeed.client.api.impl.websocket;

import com.google.common.base.Strings;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.openfeed.*;
import org.openfeed.SubscriptionRequest.Request.Builder;
import org.openfeed.client.api.*;
import org.openfeed.client.api.OpenfeedEvent.EventType;
import org.openfeed.client.api.impl.OpenfeedClientConfigImpl;
import org.openfeed.client.api.impl.PbUtil;
import org.openfeed.client.api.impl.Subscription;
import org.openfeed.client.api.impl.SubscriptionManagerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class OpenfeedClientWebSocket implements OpenfeedClient, Runnable {

    private static final Logger log = LoggerFactory.getLogger(OpenfeedClientWebSocket.class);
    private static final String OS = System.getProperty("os.name").toLowerCase();
    private static final int CONNECT_TIMEOUT_MSEC = 3000;
    private static final long LOGIN_WAIT_SEC = 15;
    private static final int BUF_SIZE_ENCODE = 1 * 1024;
    private static final int WSS_PORT = 443;


    private final OpenfeedClientConfigImpl config;
    private Bootstrap clientBootstrap;
    private EventLoopGroup clientEventLoopGroup;
    private OpenfeedWebSocketHandler webSocketHandler;
    private URI uri;
    private Channel channel;
    private ChannelPromise loginFuture;
    private ChannelPromise logoutFuture;
    // Session State
    private long correlationId = 1;
    private String token;
    private SubscriptionManagerImpl subscriptionManager = new SubscriptionManagerImpl();
    private ByteArrayOutputStream encodeBuf = new ByteArrayOutputStream(BUF_SIZE_ENCODE);
    private final OpenfeedClientEventHandler eventHandler;
    private final OpenfeedClientHandler clientHandler;
    private final OpenfeedClientMessageHandler messageHandler;
    //
    private AtomicBoolean running = new AtomicBoolean(true);
    private AtomicBoolean connected = new AtomicBoolean(false);
    private AtomicBoolean reconnectInProgress = new AtomicBoolean(false);
    private int numSuccessLogins = 0;
    private final String clientVersion;

    public OpenfeedClientWebSocket(OpenfeedClientConfigImpl config, OpenfeedClientEventHandler eventHandler,
                                   OpenfeedClientHandler clientHandler) {
        this(config, eventHandler, clientHandler, null);
    }

    public OpenfeedClientWebSocket(OpenfeedClientConfigImpl config, OpenfeedClientEventHandler eventHandler,
                                   OpenfeedClientMessageHandler messageHandler) {
        this(config, eventHandler, null, messageHandler);
    }

    public OpenfeedClientWebSocket(OpenfeedClientConfigImpl config,
                                   OpenfeedClientEventHandler eventHandler,
                                   OpenfeedClientHandler clientHandler,
                                   OpenfeedClientMessageHandler messageHandler) {
        this.config = config;
        this.eventHandler = eventHandler;
        this.clientHandler = clientHandler;
        this.messageHandler = messageHandler;
        this.clientVersion = getClientVersion();
    }


    private String getClientVersion() {
        Package jarPackage = this.getClass().getPackage();
        String version = jarPackage.getImplementationVersion() != null ? jarPackage.getImplementationVersion() : "1.0.0";
        StringBuilder sb = new StringBuilder();
        sb.append("sdk-java" + ":");
        sb.append(version + ":");
        sb.append(System.getProperty("java.version", "") + ":");
        sb.append(System.getProperty("java.vendor", "") + ":");
        sb.append(System.getProperty("java.name", "") + ":");
        sb.append(System.getProperty("os.name", "") + ":");
        sb.append(System.getProperty("os.version", "") + ":");
        sb.append(System.getProperty("os.arch", ""));
        return sb.toString();
    }

    @Override
    public void connectAndLogin() {
        log.info("{}: Starting Openfeed Client, user: {}", config.getClientId(), config.getUserName());
        init();
        attemptConnectAndLogin();
        // Start re-connection task
        new Thread(this).start();
    }

    @Override
    public long getCorrelationId() {
        return this.correlationId;
    }

    @Override
    public long getNextCorrelationId() {
        return this.correlationId++;
    }

    @Override
    public void run() {
        while (running.get()) {
            if (!connected.get()) {
                log.info("{}: Attempting reconnection in {} ms", config.getClientId(),
                        config.getReconnectDelayMs());
                try {
                    Thread.sleep(config.getReconnectDelayMs());
                } catch (InterruptedException ignore) {
                }
                init();
                attemptConnectAndLogin();
                if (numSuccessLogins > 1 && isLoggedIn()) {
                    resubscribe();
                    reconnectInProgress.set(false);
                }
            }
            // Wait until channel closes, if connected
            awaitChannelClose();
        }
    }

    private void resubscribe() {
        log.info("{} Resubscribing for {} subscriptions", config.getClientId(),
                subscriptionManager.getSubscriptions().size());
        for (Subscription sub : subscriptionManager.getSubscriptions()) {
            SubscriptionRequest subReq = sub.getRequest();
            if (subReq != null && this.token != null) {
                // Use new correlationId
                subReq = subReq.toBuilder().setToken(this.token).build();
                // Save new request on Subscription
                sub.setRequest(subReq);
                OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
                send(req);
            }
        }
    }

    private void init() {
        uri = null;
        final boolean ssl = config.getScheme().equalsIgnoreCase("wss");
        SslContext sslCtx = null;
        if (ssl) {
            try {
                sslCtx = SslContextBuilder.forClient()
                        .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
                config.setPort(WSS_PORT);
            } catch (SSLException e) {
                log.error("{}: Could not initialize SSL: {}", config.getClientId(), e.getMessage());
            }
        }
        try {
            uri = new URI(config.getScheme() + "://" + config.getHost() + ":" + config.getPort() + "/ws");
        } catch (URISyntaxException ex) {
            log.error("{}: Invalid URL err: {}", config.getClientId(), ex.getMessage());
        }

        log.info("{}: Initializing connection to: {} recBufSize: {}", config.getClientId(), uri, config.getReceiveBufferSize());
        // Connect with V13 (RFC 6455 aka HyBi-17).
        webSocketHandler = new OpenfeedWebSocketHandler(config, this, this.subscriptionManager, clientHandler, WebSocketClientHandshakerFactory
                .newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), config.getMaxFramePayloadSize()), messageHandler);
        boolean epoll = OS.indexOf("linux") >= 0 ? true : false;

        // Ensure previous event loop was shutdown
        shutdown();
        // Configure the event loop
        if (epoll) {
            clientEventLoopGroup = new EpollEventLoopGroup();
        } else {
            clientEventLoopGroup = new NioEventLoopGroup();
        }
        log.debug("{}: Using EventLoop: {}", config.getClientId(), clientEventLoopGroup.getClass());
        try {
            clientBootstrap = new Bootstrap();
            clientBootstrap.group(clientEventLoopGroup);
            if (epoll) {
                clientBootstrap.channel(EpollSocketChannel.class);
            } else {
                clientBootstrap.channel(NioSocketChannel.class);
            }
            final SslContext sslCtxFinal = sslCtx;
            clientBootstrap.option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MSEC)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    /*
                     * The default value is set by the /proc/sys/net/core/rmem_default file, and the
                     * maximum allowed value is set by the /proc/sys/net/core/rmem_max file.
                     */
                    .option(ChannelOption.SO_RCVBUF, config.getReceiveBufferSize())
                    //
                    .handler(new ChannelInitializer() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if (sslCtxFinal != null) {
                                p.addLast(sslCtxFinal.newHandler(ch.alloc(), config.getHost(), config.getPort()));
                            }
                            p.addLast(new HttpClientCodec());
                            p.addLast(new HttpObjectAggregator(config.getMaxFramePayloadSize()));
                            if (config.isLogWire()) {
                                p.addLast(new LoggingHandler(LogLevel.INFO));
                            }
                            p.addLast(webSocketHandler);
                        }
                    });
        } catch (Exception e) {
            log.error("{} Initialization error: {}", config.getClientId(), e.getMessage());
            throw new RuntimeException(config.getClientId() + ": Could not initialize environment", e);
        }
    }

    @Override
    public void scheduleAtFixedRate(Runnable task, long delay, long interval, TimeUnit timeUnit) {
        channel.eventLoop().scheduleAtFixedRate(task, delay, interval, timeUnit);
    }

    @Override
    public void schedule(Runnable task, long delay, TimeUnit timeUnit) {
        channel.eventLoop().schedule(task, delay, timeUnit);
    }

    private void attemptConnectAndLogin() {
        try {
            log.info("{}: Starting connection to: {}", config.getClientId(), uri);
            // Connect
            ChannelFuture connnectFuture = clientBootstrap.connect(config.getHost(), config.getPort()).sync();
            this.channel = connnectFuture.channel();
            // Wait for connect
            webSocketHandler.handshakeFuture().sync();
            this.connected.set(true);
            if (eventHandler != null) {
                eventHandler.onEvent(this, new OpenfeedEvent(EventType.Connected, "Connected to: " + uri));
            }
            // login
            login();

            log.info("{}: Successfully connected to: {} from: {}", config.getClientId(), uri, channel.localAddress());
        } catch (Exception e) {
            log.error("{}: Could not connect to uri {} err: {}", config.getClientId(), uri, e.getMessage());
            reconnectOrShutdown(config.isReconnect() ? false : true);
        }
    }

    private void reconnectOrShutdown(boolean shutdown) {
        closeConnection();
        if (!config.isReconnect() || shutdown) {
            if (!running.get()) {
                // Already shutdown
                return;
            }
            this.running.set(false);
            log.warn("{}: Closing and shutting down.", config.getClientId());
            shutdown();
        } else {
            log.info("{}: re-connecting in: {} ms", config.getClientId(), config.getReconnectDelayMs());
            reconnectInProgress.set(true);
        }
    }

    private void closeConnection() {
        if (this.channel != null && this.channel.isActive()) {
            this.channel.close();
        }
        if (eventHandler != null) {
            eventHandler.onEvent(this, new OpenfeedEvent(EventType.Disconnected, "Disconnected from: " + uri));
        }
        this.connected.set(false);
        this.token = null;
    }

    private void shutdown() {
        if (clientEventLoopGroup != null && !clientEventLoopGroup.isShutdown()) {
            log.info("{}: Shutting down event loop", config.getClientId());
            clientEventLoopGroup.shutdownGracefully();
        }
    }

    private void awaitChannelClose() {
        if (this.channel != null && channel.isActive()) {
            try {
                this.channel.closeFuture().sync();
                log.info("{}: Channel Closed", config.getClientId());
                this.channel = null;
                closeConnection();
                // For re-subscribe mark subscriptions as unsubsribed
                subscriptionManager.setAllSubscriptionsUnsubcribed();
            } catch (InterruptedException e) {
                log.error("{}: Channel Close Issue: {}", config.getClientId(), e.getMessage());
            }
        }
    }

    @Override
    public void disconnect() {
        this.connected.set(false);
        this.token = null;
        if (isLoggedIn()) {
            logout();
        }
        reconnectOrShutdown(config.isReconnect() ? false : true);
    }

    @Override
    public void close() {
        config.setReconnect(false);
        disconnect();
    }

    private void login() {
        LoginRequest.Builder request = LoginRequest.newBuilder().setCorrelationId(correlationId++)
                .setClientVersion(clientVersion).setProtocolVersion(config.getProtocolVersion());
        if (!Strings.isNullOrEmpty(config.getJwt())) {
            request.setJwt(config.getJwt());
        } else {
            request.setUsername(config.getUserName()).setPassword(config.getPassword());
        }
        OpenfeedGatewayRequest ofreq = request().setLoginRequest(request).build();
        send(ofreq);
        this.loginFuture = this.channel.newPromise();
        try {
            boolean ret = this.loginFuture.await(LOGIN_WAIT_SEC, TimeUnit.SECONDS);
            if (!ret) {
                log.error("{}: Login timeout for user: ", config.getClientId(), request.getUsername());
            } else {
                numSuccessLogins++;
                if (eventHandler != null) {
                    eventHandler.onEvent(this, new OpenfeedEvent(EventType.Login, "Logged In"));
                }
            }
        } catch (InterruptedException e) {
            log.error("{}: Login Timeout err: {}", config.getClientId(), e.getMessage());
        }
    }

    private void log(OpenfeedGatewayRequest ofreq) {
        if (config.isLogRequestResponse()) {
            log.info("{} > {}", config.getClientId(), PbUtil.toJson(ofreq));
        }
    }

    @Override
    public void logout() {
        LogoutRequest request = LogoutRequest.newBuilder().setCorrelationId(correlationId++).setToken(this.token)
                .build();
        OpenfeedGatewayRequest ofreq = request().setLogoutRequest(request).build();
        send(ofreq);
        this.logoutFuture = this.channel.newPromise();
        try {
            boolean ret = this.logoutFuture.await(LOGIN_WAIT_SEC, TimeUnit.SECONDS);
            if (!ret) {
                log.error("Logout Timeout");
                throw new RuntimeException("Logout timeout");
            }
        } catch (InterruptedException e) {
            log.error("Logout Timeout err: {}", e.getMessage());
            throw new RuntimeException("Logout timeout");
        }
    }

    @Override
    public void send(OpenfeedGatewayRequest req) {
        if (!isConnected()) {
            return;
        }
        log(req);

        // Binary
        ByteBuf outBuf = ByteBufAllocator.DEFAULT.buffer(1044,config.getMaxFramePayloadSize());
        try {
            this.encodeBuf.reset();
            req.writeTo(this.encodeBuf);
            byte [] bytes = encodeBuf.toByteArray();
            if(bytes.length >= config.getMaxFramePayloadSize()) {
                log.error("request size({}) is large than websocket frame size({}), not sending {}",bytes.length,config.getMaxFramePayloadSize(),PbUtil.toJson(req).substring(0,50));
                return;
            }
            outBuf.writeBytes(encodeBuf.toByteArray());
            BinaryWebSocketFrame frame = new BinaryWebSocketFrame(outBuf);
            this.channel.writeAndFlush(frame);
        } catch (IOException e) {
            log.error("{}: Send error: {}", config.getClientId(), e.getMessage());
        }
    }

    @Override
    public void listSubscriptionsRequest() {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        ListSubscriptionsRequest.Builder b = ListSubscriptionsRequest.newBuilder().setCorrelationId(correlationId++).setToken(this.token);
        OpenfeedGatewayRequest req = request().setListSubscriptionsRequest(b).build();
        send(req);
    }

    private OpenfeedGatewayRequest.Builder request() {
        return OpenfeedGatewayRequest.newBuilder();
    }

    public void setToken(String token) {
        this.token = token;
    }

    public void completeLogin(boolean success, String error) {
        if (success) {
            this.loginFuture.setSuccess();
        } else {
            this.loginFuture.setFailure(new RuntimeException(error));
        }
    }

    public boolean isLoggedIn() {
        return connected.get() && (token != null && token.length() > 0);
    }

    public void completeLogout(boolean success) {
        if (this.logoutFuture == null || logoutFuture.isDone()) {
            return;
        }
        if (success) {
            this.logoutFuture.setSuccess();
            this.token = null;
        } else {
            this.logoutFuture.setFailure(null);
        }
    }

    @Override
    public void instrument(String... symbols) {
        if (!isLoggedIn()) {
            return;
        }
        for (String s : symbols) {
            InstrumentRequest request = InstrumentRequest.newBuilder().setCorrelationId(correlationId++).setSymbol(s)
                    .setToken(token).build();
            OpenfeedGatewayRequest req = request().setInstrumentRequest(request).build();
            send(req);
        }
    }

    @Override
    public void instrument(InstrumentRequest request) {
        if (!isLoggedIn()) {
            return;
        }
        InstrumentRequest subReq = request.toBuilder().setCorrelationId(correlationId++).setToken(token).build();
        OpenfeedGatewayRequest req = request().setInstrumentRequest(subReq).build();
        send(req);
    }

    @Override
    public void instrumentMarketId(long... marketIds) {
        if (!isLoggedIn()) {
            return;
        }
        for (long id : marketIds) {
            InstrumentRequest request = InstrumentRequest.newBuilder().setCorrelationId(correlationId++).setToken(token)
                    .setMarketId(id).build();
            OpenfeedGatewayRequest req = request().setInstrumentRequest(request).build();
            send(req);
        }
    }

    @Override
    public ChannelPromise instrumentChannel(int channelId) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        InstrumentRequest request = InstrumentRequest.newBuilder().setCorrelationId(correlationId++).setToken(token)
                .setChannelId(channelId).build();
        OpenfeedGatewayRequest req = request().setInstrumentRequest(request).build();
        send(req);
        return channel.newPromise();
    }

    @Override
    public ChannelPromise instrumentExchange(String exchange) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        InstrumentRequest request = InstrumentRequest.newBuilder().setCorrelationId(correlationId++).setToken(token)
                .setExchange(exchange).build();
        OpenfeedGatewayRequest req = request().setInstrumentRequest(request).build();
        send(req);
        return channel.newPromise();
    }

    @Override
    public void instrumentReference(String... symbols) {
        if (!isLoggedIn()) {
            return;
        }
        for (String s : symbols) {
            log.debug("{}: instrumentRef: {}", config.getClientId(), s);
            InstrumentReferenceRequest request = InstrumentReferenceRequest.newBuilder()
                    .setCorrelationId(correlationId++).setSymbol(s).setToken(token).build();
            OpenfeedGatewayRequest req = request().setInstrumentReferenceRequest(request).build();
            send(req);
        }
    }

    @Override
    public void instrumentReferenceMarketId(long... marketIds) {
        if (!isLoggedIn()) {
            return;
        }
        for (long id : marketIds) {
            log.debug("{}: instrumentRef: {}", config.getClientId(), id);
            InstrumentReferenceRequest request = InstrumentReferenceRequest.newBuilder()
                    .setCorrelationId(correlationId++).setMarketId(id).setToken(token).build();
            OpenfeedGatewayRequest req = request().setInstrumentReferenceRequest(request).build();
            send(req);
        }
    }

    @Override
    public ChannelPromise instrumentReferenceChannel(int channelId) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        log.debug("{}: instrumentRef Channel: {}", config.getClientId(), channelId);
        InstrumentReferenceRequest request = InstrumentReferenceRequest.newBuilder().setCorrelationId(correlationId++)
                .setChannelId(channelId).setToken(token).build();
        OpenfeedGatewayRequest req = request().setInstrumentReferenceRequest(request).build();
        send(req);
        return channel.newPromise();
    }

    @Override
    public void exchangeRequest() {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        log.debug("{}: ExchangeReq : {}", config.getClientId());
        ExchangeRequest request = ExchangeRequest.newBuilder().setCorrelationId(correlationId++).
                setToken(token).build();
        OpenfeedGatewayRequest req = request().setExchangeRequest(request).build();
        send(req);
    }

    @Override
    public ChannelPromise instrumentReferenceExchange(String exchange) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        log.debug("{}: instrumentRef Exchange: {}", config.getClientId(), exchange);
        InstrumentReferenceRequest request = InstrumentReferenceRequest.newBuilder().setCorrelationId(correlationId++)
                .setExchange(exchange).setToken(token).build();
        OpenfeedGatewayRequest req = request().setInstrumentReferenceRequest(request).build();
        send(req);
        return channel.newPromise();
    }

    @Override
    public String subscribe(Service service, SubscriptionType subscriptionType, String[] symbols) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set syms = new HashSet<>();
        syms.addAll(Arrays.asList(symbols));

        SubscriptionType subType = subscriptionType != null ? subscriptionType : SubscriptionType.QUOTE;
        log.debug("{}: Subscribe Symbol: {}", config.getClientId(), Arrays.asList(syms.toArray()));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        for (String symbol : syms) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setSymbol(symbol);
            // Subscription Type
            subReq.addSubscriptionType(subType);
            request.addRequests(subReq);
        }
        if (request.getRequestsCount() == 0) {
            // already subscribed or no subscription items,  bail
            return null;
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, new SubscriptionType[]{subType}, syms.toArray(new String[0]));
        subscriptionManager.addSubscription(subscriptionId, subReq, symbols, correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribe(Service service, SubscriptionType[] subscriptionTypes, String[] symbols) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set syms = new HashSet<>();
        syms.addAll(Arrays.asList(symbols));

        log.debug("{}: Subscribe Symbol: {}", config.getClientId(), Arrays.asList(syms.toArray()));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        // Subcription Types
        Set subTypes = new HashSet<>();
        subTypes.addAll(Arrays.asList(subscriptionTypes));
        if (subTypes.size() == 0) {
            subTypes.add(SubscriptionType.QUOTE);
        }
        for (String symbol : syms) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setSymbol(symbol);
            // Subscription Types
            subTypes.forEach(type -> subReq.addSubscriptionType(type));
            request.addRequests(subReq);
        }
        if (request.getRequestsCount() == 0) {
            // already subscribed or no subscription items,  bail
            return null;
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, subTypes.toArray(new SubscriptionType[0]), syms.toArray(new String[0]));
        subscriptionManager.addSubscription(subscriptionId, subReq, syms.toArray(new String[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }


    private String createSubscriptionId(String userName, Service service, SubscriptionType[] subscriptionTypes, String[] symbols) {
        StringBuilder sb = new StringBuilder();
        sb.append(userName);
        sb.append(":");
        sb.append(service.getNumber());
        sb.append(":");
        sb.append(subscriptionTypes);
        sb.append(":");
        sb.append(symbols);
        return sb.toString();
    }

    private String createSubscriptionId(String userName, Service service, SubscriptionType[] subscriptionTypes, Long[] ids) {
        StringBuilder sb = new StringBuilder();
        sb.append(userName);
        sb.append(":");
        sb.append(service.getNumber());
        sb.append(":");
        sb.append(subscriptionTypes);
        sb.append(":");
        sb.append(ids);
        return sb.toString();
    }

    private String createSubscriptionId(String userName, Service service, SubscriptionRequest subscriptionRequest) {
        StringBuilder sb = new StringBuilder();
        sb.append(userName);
        sb.append(":");
        sb.append(service.getNumber());
        sb.append(":");
        sb.append(PbUtil.toJson(subscriptionRequest));
        return sb.toString();
    }

    private String createSubscriptionId(String userName, Service service, SubscriptionType[] subscriptionTypes, Integer[] ids) {
        StringBuilder sb = new StringBuilder();
        sb.append(userName);
        sb.append(":");
        sb.append(service.getNumber());
        sb.append(":");
        sb.append(subscriptionTypes);
        sb.append(":");
        sb.append(ids);
        return sb.toString();
    }

    private String createSubscriptionId(String userName, Service service, String[] symbols) {
        StringBuilder sb = new StringBuilder();
        sb.append(userName);
        sb.append(":");
        sb.append(service.getNumber());
        sb.append(":");
        sb.append(symbols);
        return sb.toString();
    }

    @Override
    public String subscribe(Service service, SubscriptionType subscriptionType, long marketId) {
        return subscribe(service, subscriptionType, new long[]{marketId});
    }


    @Override
    public String subscribe(Service service, SubscriptionType subscriptionType, long[] marketIds) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set ids = new HashSet();
        Arrays.stream(marketIds).forEach(id -> ids.add(id));

        SubscriptionType subType = subscriptionType != null ? subscriptionType : SubscriptionType.QUOTE;
        log.debug("{}: Subscribe Openfeed Id: {}", config.getClientId(), Arrays.asList(ids));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        for (Long id : ids) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setMarketId(id);
            subReq.addSubscriptionType(subType);
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, new SubscriptionType[]{subType}, ids.toArray(new Long[0]));
        subscriptionManager.addSubscription(subscriptionId, subReq, ids.toArray(new Long[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribe(Service service, SubscriptionType[] subscriptionTypes, long[] marketIds) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set ids = new HashSet();
        Arrays.stream(marketIds).forEach(id -> ids.add(id));

        log.debug("{}: Subscribe Openfeed Id: {}", config.getClientId(), Arrays.asList(ids));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        // Subscription Types
        Set subTypes = new HashSet<>();
        subTypes.addAll(Arrays.asList(subscriptionTypes));
        if (subTypes.size() == 0) {
            subTypes.add(SubscriptionType.QUOTE);
        }
        for (Long id : ids) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setMarketId(id);
            // Subscription Types
            subTypes.forEach(type -> subReq.addSubscriptionType(type));
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, subTypes.toArray(new SubscriptionType[0]), ids.toArray(new Long[0]));
        subscriptionManager.addSubscription(subscriptionId, subReq, ids.toArray(new Long[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public void subscribe(SubscriptionRequest request) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        String subscriptionId = createSubscriptionId(config.getUserName(), request.getService(), request);
        subscriptionManager.addSubscription(subscriptionId, request);
        send(req);
    }

    @Override
    public String subscribeExchange(Service service, String exchange) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        log.debug("{}: Subscribe Exchange: {}", config.getClientId(), exchange);
        Builder subReq = SubscriptionRequest.Request.newBuilder().setExchange(exchange);
        subReq.addSubscriptionType(SubscriptionType.ALL);
        request.addRequests(subReq);

        String subscriptionId = createSubscriptionId(config.getUserName(), service, request.build());
        subscriptionManager.addSubscriptionExchange(subscriptionId, request.build(), new String[] {exchange}, correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeExchange(Service service, SubscriptionType subscriptionType, String[] exchanges) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set exchs = new HashSet<>();
        exchs.addAll(Arrays.asList(exchanges));

        SubscriptionType subType = subscriptionType != null ? subscriptionType : SubscriptionType.QUOTE;
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        for (String exchange : exchs) {
            log.debug("{}: Subscribe Exchange: {}", config.getClientId(), exchange);
            Builder subReq = SubscriptionRequest.Request.newBuilder().setExchange(exchange);
            // Subscription Type
            subReq.addSubscriptionType(subType);
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, new SubscriptionType[]{subType}, exchs.toArray(new String[0]));
        subscriptionManager.addSubscriptionExchange(subscriptionId, subReq, exchs.toArray(new String[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeExchange(Service service, SubscriptionType[] subscriptionTypes, String[] exchanges) {
        return subscribeExchange(service, subscriptionTypes, new InstrumentDefinition.InstrumentType[0], exchanges, null);
    }

    @Override
    public String subscribeExchange(Service service, SubscriptionType[] subscriptionTypes, InstrumentDefinition.InstrumentType[] instrumentTypes, String[] exchanges, BulkSubscriptionFilter[] bulkSubscriptionFilters) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set exchs = new HashSet<>();
        exchs.addAll(Arrays.asList(exchanges));

        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        // Subscription Types
        Set subTypes = new HashSet<>();
        subTypes.addAll(Arrays.asList(subscriptionTypes));
        for (String exchange : exchs) {
            log.debug("{}: Subscribe Exchange: {}", config.getClientId(), exchange);
            Builder subReq = SubscriptionRequest.Request.newBuilder().setExchange(exchange);
            // Subscription Types
            subTypes.forEach(type -> subReq.addSubscriptionType(type));
            // Instrument Types
            Arrays.stream(instrumentTypes).forEach(type -> subReq.addInstrumentType(type));
            if (bulkSubscriptionFilters != null) {
                Arrays.stream(bulkSubscriptionFilters).forEach(f -> subReq.addBulkSubscriptionFilter(f));
            }
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, subTypes.toArray(new SubscriptionType[0]), exchs.toArray(new String[0]));
        subscriptionManager.addSubscriptionExchange(subscriptionId, subReq, exchs.toArray(new String[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeChannel(Service service, SubscriptionType subscriptionType, int[] channelIds) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set ids = new HashSet<>();
        Arrays.stream(channelIds).forEach(id -> ids.add(id));

        SubscriptionType subType = subscriptionType != null ? subscriptionType : SubscriptionType.QUOTE;
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        for (Integer id : ids) {
            log.debug("{}: Subscribe Channel: {}", config.getClientId(), id);
            Builder subReq = SubscriptionRequest.Request.newBuilder().setChannelId(id);
            if (subscriptionType != null) {
                subReq.addSubscriptionType(subscriptionType);
            }
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, new SubscriptionType[]{subType}, ids.toArray(new Integer[0]));
        subscriptionManager.addSubscriptionChannel(subscriptionId, subReq, ids.toArray(new Integer[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeChannel(Service service, SubscriptionType[] subscriptionTypes, int[] channelIds) {
        return subscribeChannel(service, subscriptionTypes, new InstrumentDefinition.InstrumentType[0], channelIds, null);
    }

    @Override
    public String subscribeChannel(Service service, SubscriptionType[] subscriptionTypes, InstrumentDefinition.InstrumentType[] instrumentTypes, int[] channelIds, BulkSubscriptionFilter[] bulkSubscriptionFilters) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        Set ids = new HashSet<>();
        Arrays.stream(channelIds).forEach(id -> ids.add(id));

        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);
        // Subscription Types
        Set subTypes = new HashSet<>();
        subTypes.addAll(Arrays.asList(subscriptionTypes));
        for (Integer id : ids) {
            log.debug("{}: Subscribe Channel: {}", config.getClientId(), id);
            Builder subReq = SubscriptionRequest.Request.newBuilder().setChannelId(id);
            subTypes.forEach(type -> subReq.addSubscriptionType(type));
            // Instrument Types
            Arrays.stream(instrumentTypes).forEach(type -> subReq.addInstrumentType(type));
            if (bulkSubscriptionFilters != null) {
                Arrays.stream(bulkSubscriptionFilters).forEach(f -> subReq.addBulkSubscriptionFilter(f));
            }
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), service, subTypes.toArray(new SubscriptionType[0]), ids.toArray(new Integer[0]));
        subscriptionManager.addSubscriptionChannel(subscriptionId, subReq, ids.toArray(new Integer[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeSnapshot(String[] symbols, int intervalSec) {
        return subscribeSnapshot(Service.REAL_TIME_SNAPSHOT, symbols, intervalSec);
    }

    @Override
    public String subscribeSnapshot(Service service, String[] symbols, int intervalSec) {
        return subscribeSnapshot(service, new SubscriptionType[0], symbols, intervalSec);
    }

    @Override
    public String subscribeSnapshot(Service service, SubscriptionType[] subscriptionTypes, String[] symbols, int intervalSec) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        List syms = new ArrayList<>();

        Arrays.stream(symbols).forEach(s -> syms.add(s));

        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);

        for (String symbol : syms) {
            log.debug("{}: Subscribe Snapshot: {}", config.getClientId(), symbol);
            Builder subReq = SubscriptionRequest.Request.newBuilder().setSymbol(symbol)
                    .setSnapshotIntervalSeconds(intervalSec);
            if (subscriptionTypes != null && subscriptionTypes.length > 0) {
                Arrays.stream(subscriptionTypes).forEach(st -> subReq.addSubscriptionType(st));
            }
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), Service.REAL_TIME_SNAPSHOT, syms.toArray(new String[0]));
        subscriptionManager.addSubscription(subscriptionId, subReq, syms.toArray(new String[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeSnapshot(Service service, SubscriptionType subscriptionType, String[] symbols, int intervalSec) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        // Eliminate dups
        List syms = new ArrayList<>();

        Arrays.stream(symbols).forEach(s -> syms.add(s));

        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);

        for (String symbol : syms) {
            log.debug("{}: Subscribe Snapshot: {}", config.getClientId(), symbol);
            Builder subReq = SubscriptionRequest.Request.newBuilder().setSymbol(symbol)
                    .setSnapshotIntervalSeconds(intervalSec);
            if (subscriptionType != null) {
                subReq.addSubscriptionType(subscriptionType);
            }
            request.addRequests(subReq);
        }
        SubscriptionRequest subReq = request.build();
        String subscriptionId = createSubscriptionId(config.getUserName(), Service.REAL_TIME_SNAPSHOT, syms.toArray(new String[0]));
        subscriptionManager.addSubscription(subscriptionId, subReq, syms.toArray(new String[0]), correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(subReq).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public String subscribeSnapshot(Service service, SubscriptionType subscriptionType, long marketId, int intervalSec) {
        if (!isLoggedIn()) {
            throw new RuntimeException("Not logged in.");
        }
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service);

        log.debug("{}: Subscribe Snapshot: {}", config.getClientId(), marketId);
        Builder subReq = SubscriptionRequest.Request.newBuilder().setMarketId(marketId).setSnapshotIntervalSeconds(intervalSec);
        if (subscriptionType != null) {
            subReq.addSubscriptionType(subscriptionType);
        }
        request.addRequests(subReq);
        String subscriptionId = createSubscriptionId(config.getUserName(), service, new SubscriptionType[]{subscriptionType}, new Long[]{marketId});
        subscriptionManager.addSubscription(subscriptionId, request.build(), new Long[]{marketId}, correlationId);
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        return subscriptionId;
    }

    @Override
    public void unSubscribe(Service service, String[] symbols) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Symbols: {}", config.getClientId(), Arrays.asList(symbols));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (String symbol : symbols) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setSymbol(symbol);
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscription(symbols);
    }

    @Override
    public void unSubscribe(Service service, SubscriptionType subscriptionType, String[] symbols) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Symbols: {} subType: {}", config.getClientId(), Arrays.asList(symbols), subscriptionType);
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (String symbol : symbols) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setSymbol(symbol);
            if (subscriptionType != null) {
                subReq.addSubscriptionType(subscriptionType);
            }
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscription(symbols);
    }

    @Override
    public void unSubscribe(Service service, long[] marketIds) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Ids: {}", config.getClientId(), Arrays.asList(marketIds));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (Long id : marketIds) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setMarketId(id);
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscription(marketIds);
    }

    @Override
    public void unSubscribe(Service service, SubscriptionType subscriptionType, long[] marketIds) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Ids: {}", config.getClientId(), Arrays.asList(marketIds));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (Long id : marketIds) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setMarketId(id);
            if (subscriptionType != null) {
                subReq.addSubscriptionType(subscriptionType);
            }
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscription(marketIds);
    }

    @Override
    public void unSubscribeExchange(Service service, String[] exchanges) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Exchanges: {}", config.getClientId(), Arrays.asList(exchanges));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (String exchange : exchanges) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setExchange(exchange);
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscriptionExchange(exchanges);
    }

    @Override
    public void unSubscribeExchange(Service service, SubscriptionType subscriptionType, String[] exchanges) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Exchanges: {}", config.getClientId(), Arrays.asList(exchanges));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (String exchange : exchanges) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setExchange(exchange);
            if (subscriptionType != null) {
                subReq.addSubscriptionType(subscriptionType);
            }
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscriptionExchange(exchanges);
    }

    @Override
    public void unSubscribeChannel(Service service, int[] channelIds) {
        if (!isLoggedIn()) {
            return;
        }
        log.debug("{}: Un Subscribe Channel: {}", config.getClientId(), Arrays.asList(channelIds));
        SubscriptionRequest.Builder request = SubscriptionRequest.newBuilder().setCorrelationId(correlationId++)
                .setToken(token).setService(service).setUnsubscribe(true);
        for (int channelId : channelIds) {
            Builder subReq = SubscriptionRequest.Request.newBuilder().setChannelId(channelId);
            request.addRequests(subReq);
        }
        OpenfeedGatewayRequest req = request().setSubscriptionRequest(request).build();
        send(req);
        subscriptionManager.removeSubscriptionChannel(channelIds);
    }

    @Override
    public boolean isConnected() {
        return this.connected.get();
    }

    @Override
    public boolean isReConnect() {
        return numSuccessLogins > 1 && this.reconnectInProgress.get();
    }

    public void setConnected(boolean b) {
        this.connected.set(b);
    }

    @Override
    public String getToken() {
        return this.token;
    }

    @Override
    public Collection getSubscriptions() {
        Collection subscriptions = subscriptionManager.getSubscriptions();
        return subscriptions;
    }

    @Override
    public Subscription getSubscription(String subscriptionId) {
        return this.subscriptionManager.getSubscription(subscriptionId);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy