Package com.barchart.feed.ddf.datalink.provider

Source Code of com.barchart.feed.ddf.datalink.provider.FeedClientDDF

/**
* Copyright (C) 2011-2012 Barchart, Inc. <http://www.barchart.com/>
*
* All rights reserved. Licensed under the OSI BSD License.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package com.barchart.feed.ddf.datalink.provider;

import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.logging.Slf4JLoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.barchart.feed.client.api.FeedStateListener;
import com.barchart.feed.client.enums.FeedState;
import com.barchart.feed.ddf.datalink.api.CommandFuture;
import com.barchart.feed.ddf.datalink.api.DDF_FeedClient;
import com.barchart.feed.ddf.datalink.api.DDF_MessageListener;
import com.barchart.feed.ddf.datalink.api.DDF_SocksProxy;
import com.barchart.feed.ddf.datalink.api.DummyFuture;
import com.barchart.feed.ddf.datalink.api.EventPolicy;
import com.barchart.feed.ddf.datalink.api.Subscription;
import com.barchart.feed.ddf.datalink.enums.DDF_FeedEvent;
import com.barchart.feed.ddf.message.api.DDF_BaseMessage;
import com.barchart.feed.ddf.settings.api.DDF_Server;
import com.barchart.feed.ddf.settings.api.DDF_Settings;
import com.barchart.feed.ddf.settings.enums.DDF_ServerType;
import com.barchart.feed.ddf.settings.provider.DDF_SettingsService;
import com.barchart.feed.ddf.util.FeedDDF;

class FeedClientDDF implements DDF_FeedClient {

  private static final int PORT = 7500;
  private static final int LOGIN_DELAY = 3000;

  /** use slf4j for internal NETTY LoggingHandler facade */
  static {
    final InternalLoggerFactory defaultFactory = new Slf4JLoggerFactory();
    InternalLoggerFactory.setDefaultFactory(defaultFactory);
  }

  private static final Logger log = LoggerFactory
      .getLogger(FeedClientDDF.class);

  /** channel operation time out */
  private static final long TIMEOUT = 2 * 1000;
  private static final String TIMEOUT_OPTION = "connectTimeoutMillis";
  private static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS;

  private static final long HEARTBEAT_TIMEOUT = 30 * 1000;

  //

  private final Map<DDF_FeedEvent, EventPolicy> eventPolicy = new ConcurrentHashMap<DDF_FeedEvent, EventPolicy>();

  private final Map<String, Subscription> subscriptions = new ConcurrentHashMap<String, Subscription>();

  //

  private final LoginHandler loginHandler = new LoginHandler();

  //

  private final BlockingQueue<DDF_FeedEvent> eventQueue = new LinkedBlockingQueue<DDF_FeedEvent>();

  private final BlockingQueue<DDF_BaseMessage> messageQueue = new LinkedBlockingQueue<DDF_BaseMessage>();

  private final AtomicLong lastHeartbeat = new AtomicLong(0);

  //

  private volatile DDF_MessageListener msgListener = null;

  private final CopyOnWriteArrayList<FeedStateListener> feedListeners = new CopyOnWriteArrayList<FeedStateListener>();

  //

  private ClientBootstrap boot;

  private Channel channel;

  //

  private String username;
  private String password;
  private DDF_ServerType serverType = DDF_ServerType.STREAM;
  private Executor executor;

  // SOCKS5

  private DDF_SocksProxy proxySettings = null;
  private final BlockingQueue<Boolean> socksConnectResult = new LinkedBlockingQueue<Boolean>();

  //

  FeedClientDDF(final String username, final String password,
      final Executor executor) {

    startup(username, password, executor, null);

  }

  public FeedClientDDF(String username, String password, Executor executor,
      DDF_SocksProxy proxySettings) {

    startup(username, password, executor, proxySettings);

  }

  private void startup(final String username, final String password,
      final Executor exec, final DDF_SocksProxy proxy) {

    this.username = username;
    this.password = password;
    this.executor = exec;

    this.proxySettings = proxy;

    final ChannelFactory channelFactory = new NioClientSocketChannelFactory(
        executor, executor);

    boot = new ClientBootstrap(channelFactory);

    if (proxySettings == null) {

      /*
       * The vector for data leaving the netty channel and entering the
       * business application logic.
       */
      final SimpleChannelHandler ddfHandler = new ChannelHandlerDDF(
          eventQueue, messageQueue);

      final ChannelPipelineFactory pipelineFactory = new PipelineFactoryDDF(
          ddfHandler);

      boot.setPipelineFactory(pipelineFactory);

    } else {

      final ChannelPipelineFactory socksPipelineFactory = new PipelineFactorySocks(
          executor, this, proxy);

      boot.setPipelineFactory(socksPipelineFactory);
      boot.setOption("child.tcpNoDelay", true);
      boot.setOption("child.keepAlive", true);
      boot.setOption("child.reuseAddress", true);
      boot.setOption("readWriteFair", true);

    }

    boot.setOption(TIMEOUT_OPTION, TIMEOUT);

    /* Initialize event policy with event policies that do nothing. */
    for (final DDF_FeedEvent event : DDF_FeedEvent.values()) {
      eventPolicy.put(event, new EventPolicy() {

        @Override
        public void newEvent() {
          /* Do nothing */
        }
      });
    }

    /* Add DefaultReloginPolicy to selected events */
    eventPolicy.put(DDF_FeedEvent.LOGIN_FAILURE, reconnectionPolicy);

    eventPolicy.put(DDF_FeedEvent.LINK_DISCONNECT, reconnectionPolicy);

    //no
    eventPolicy.put(DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE,
        reconnectionPolicy);

    eventPolicy.put(DDF_FeedEvent.CHANNEL_CONNECT_FAILURE,
        reconnectionPolicy);
    eventPolicy.put(DDF_FeedEvent.CHANNEL_CONNECT_TIMEOUT,
        reconnectionPolicy);
    eventPolicy.put(DDF_FeedEvent.LINK_CONNECT_PROXY_TIMEOUT,
        reconnectionPolicy);

    /* Add SubscribeAfterLogin to LOGIN_SUCCESS */
    eventPolicy.put(DDF_FeedEvent.LOGIN_SUCCESS, new SubscribeAfterLogin());

    /* Add HeartbeatPolicy to HEART_BEAT */
    eventPolicy.put(DDF_FeedEvent.HEART_BEAT, new HeartbeatPolicy());

    /* Start heart beat listener */
    executor.execute(heartbeatTask);

    executor.execute(eventTask);
    executor.execute(messageTask);
  }

  private final DefaultReloginPolicy reconnectionPolicy = new DefaultReloginPolicy();

  private boolean loginProxy(String username, String password,
      DDF_Server feedServers) {

    terminate();

    initialize();

    // do socks connection

    log.error("connect to proxy - address {} port {}",
        proxySettings.getProxyAddress(), proxySettings.getProxyPort());

    final InetSocketAddress address = new InetSocketAddress(
        proxySettings.getProxyAddress(), proxySettings.getProxyPort());

    final ChannelFuture futureConnect = boot.connect(address);

    channel = futureConnect.getChannel();

    final boolean isGoodConnect = futureConnect
        .awaitUninterruptibly(TIMEOUT);

    if (!isGoodConnect) {
      log.error("proxy connect error {}", futureConnect.getCause());
      log.error("proxy; {}:{} ", proxySettings.getProxyAddress(),
          proxySettings.getProxyPort());

      postEvent(DDF_FeedEvent.LINK_CONNECT_PROXY_TIMEOUT);

      channel.close();
      return false;
    }

    log.debug("server = {}", feedServers.getPrimary());

    // set the ddf servers

    proxySettings.setFeedServer(feedServers);

    // block until we get proxy_connect_command result

    Boolean proxyResult = false;

    try {
      proxyResult = socksConnectResult.take();
    } catch (InterruptedException e) {
    }

    if (proxyResult == false) {

      log.error("Socks connect error");
      // postEvent(DDF_FeedEvent.LINK_CONNECT_PROXY_TIMEOUT);

      return false;
    }

    /* Send login command to JERQ */
    DDF_FeedEvent writeEvent = blockingWrite(FeedDDF.tcpLogin(username,
        password));

    if (writeEvent == DDF_FeedEvent.COMMAND_WRITE_FAILURE) {
      log.error("error sending login command to jerq");
      return false;
    }

    /* Send VERSION 3 command to JERQ */
    writeEvent = blockingWrite(FeedDDF.tcpVersion(FeedDDF.VERSION_3));

    if (writeEvent == DDF_FeedEvent.COMMAND_WRITE_FAILURE) {
      log.error("error sending VERSION 3 command to jerq");
      return false;
    }

    /* Send timestamp command to JERQ */
    writeEvent = blockingWrite(FeedDDF.tcpGo(FeedDDF.SYMBOL_TIMESTAMP));

    if (writeEvent == DDF_FeedEvent.COMMAND_WRITE_FAILURE) {
      log.error("error sending login GO TIMESTAMP to jerq");
      return false;
    }

    // all is good

    return true;

  }

  /*
   * This policy ensures that all subscribed instruments are requested from
   * JERQ upon login or relogin after a dicsonnect
   */
  private class SubscribeAfterLogin implements EventPolicy {

    @Override
    public void newEvent() {
      if (subscriptions.size() > 0) {
        log.debug("Requesting current subscriptions");
        final Set<Subscription> subs = new HashSet<Subscription>();
        for(final Entry<String, Subscription> e : subscriptions.entrySet()) {
          subs.add(e.getValue());
        }
        subscribe(subs);
      } else {
        log.warn("subscriptions set is empty.");
      }
    }
  }

  /*
   * This policy pauses a runner thread for a specified time interval and then
   * attempts to log in.
   */
  private class DefaultReloginPolicy implements EventPolicy {

    @Override
    public void newEvent() {
      synchronized (loginHandler) {
        loginHandler.login(LOGIN_DELAY);
      }
    }
  }

  /*
   * This policy updates the lastHeartbeat to the current local clock on every
   * heart beat event received.
   */
  private class HeartbeatPolicy implements EventPolicy {
    @Override
    public void newEvent() {
      lastHeartbeat.set(System.currentTimeMillis());
    }
  }

  private final RunnerDDF eventTask = new RunnerDDF() {

    @Override
    protected void runCore() {

      Thread.currentThread().setName("# EVENT TASK");

      log.info("# starting ddf-EventTask thread");

      while (!Thread.currentThread().isInterrupted()) {

        try {

          final DDF_FeedEvent event = eventQueue.take();

          if (DDF_FeedEvent.isConnectionError(event)) {

            log.debug("Setting feed state to logged out");
            updateFeedStateListeners(FeedState.LOGGED_OUT);

          } else if (event == DDF_FeedEvent.LOGIN_SUCCESS) {

            log.debug("Login success, feed state updated");
            updateFeedStateListeners(FeedState.LOGGED_IN);

          } else if (event == DDF_FeedEvent.LOGOUT) {

            log.debug("Setting feed state to logged out");
            updateFeedStateListeners(FeedState.LOGGED_OUT);

          }

          log.info("Enacting policy for :{}", event.name());
          eventPolicy.get(event).newEvent();

        } catch (final InterruptedException e) {
          log.warn("# ddf-EventTask thread InterruptedException");

          log.info("Setting feed state to logged out");
          updateFeedStateListeners(FeedState.LOGGED_OUT);

          return;
        } catch (final Throwable e) {
          log.error("event delivery failed", e);
        }
      }

      log.error("# ddf-EventTask thread death");
    }
  };

  private final RunnerDDF messageTask = new RunnerDDF() {
    @Override
    protected void runCore() {

      Thread.currentThread().setName("# DDF MessageTask");

      log.warn("# started ddf-MessageTask ");

      while (!Thread.currentThread().isInterrupted()) {
        try {
          final DDF_BaseMessage message = messageQueue.take();
          if (msgListener != null) {
            msgListener.handleMessage(message);
          }
        } catch (final InterruptedException e) {
          log.warn("# ddf-MessageTask thread InterruptedException");
          return;
        } catch (final Throwable e) {
          log.error("message delivery failed", e);
        }
      }

      log.warn("# ddf-MessageTask thread death");
    }
  };

  @Override
  public void setPolicy(final DDF_FeedEvent event, final EventPolicy policy) {
    log.debug("Setting policy for :{}", event.name());
    eventPolicy.put(event, policy);
  }

  //

  void postEvent(final DDF_FeedEvent event) {
    try {
      eventQueue.put(event);
    } catch (final InterruptedException e) {
      log.error("could not post event - interrupted");
    }
  }

  /*
   * the calls to initialize() and terminate were causing the threading issue,
   * as the runnables were never getting shutdown...
   */
  private void initialize() {

    log.warn("initialize called");
    final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
    for(final StackTraceElement e : trace) {
      log.debug(e.getClassName() + ":" + e.getLineNumber());
    }

    try {
      executor.execute(heartbeatTask);
    } catch (Exception e) {
      log.error("error starting DDF_Heartbeat Thread: {} ", e);

      hardRestart();
      return;
    }
    try {
      executor.execute(eventTask);
    } catch (Exception e) {
      log.error("error starting DDF_Event Thread: {} ", e);

      hardRestart();
      return;
    }
    try {
      executor.execute(messageTask);
    } catch (Exception e) {
      log.error("error starting DDF_Message Thread: {} ", e);

      hardRestart();
      return;
    }

  }

  private void terminate() {

    // did not work, maybe because the while(true)

    log.warn("terminate called");

    eventQueue.clear();
    messageQueue.clear();

    // kill all threads

    if (heartbeatTask != null) {
      heartbeatTask.interrupt();
    }

    if (messageTask != null) {
      messageTask.interrupt();
    }

    if (eventTask != null) {
      eventTask.interrupt();
    }

    log.warn("terminate - closing channel, channel = {}", channel);

    if (channel != null) {

      log.warn("channel NOT null, isOpen {}", channel.isOpen());

      channel.close();

      log.warn("called channel.close(), channel isOpen() {}",
          channel.isOpen());

      channel = null;
    }

  }

  /**
   * blocks
   *
   */
  private void hardRestart() {

    log.error("#### interupt logins");

    loginHandler.disableLogins();

    /* Interrupts login thread if login is active */
    loginHandler.interruptLogin();

    log.error("#### terminate");
    terminate();

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
    }

    // login

    log.error("#### enabling logins");

    loginHandler.enableLogins();
    loginHandler.login(0);

  }

  /*
   * Can post to the FeedEventHandler the following events: <p>
   * CHANNEL_CONNECT_TIMEOUT {@link DDF_FeedEvent.CHANNEL_CONNECT_TIMEOUT} <p>
   * CHANNEL_CONNECT_FAILURE {@link DDF_FeedEvent.CHANNEL_CONNECT_FAILURE} <p>
   * SETTINGS_RETRIEVAL_FAILURE {@link
   * DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE} <p> LOGIN_FAILURE {@link
   * DDF_FeedEvent.LOGIN_FAILURE} <p> LOGIN_SUCCESS {@link
   * DDF_FeedEvent.LOGIN_SUCCESS}
   */
  @Override
  public synchronized void startup() {

    log.debug("Public login called");
    loginHandler.enableLogins();
    loginHandler.login(0);

  }

  private DDF_FeedEvent blockingWrite(final CharSequence message) {
    final ChannelFuture futureWrite = channel.write(message);

    futureWrite.awaitUninterruptibly(TIMEOUT, TIME_UNIT);

    if (futureWrite.isSuccess()) {
      return DDF_FeedEvent.COMMAND_WRITE_SUCCESS;
    } else {
      return DDF_FeedEvent.COMMAND_WRITE_FAILURE;
    }
  }

  @Override
  public synchronized void shutdown() {

    log.warn("public shutdown() has been called, shutting down now.");

    /* Clear subscriptions, Jerq will stop sending data when we disconnect */
    subscriptions.clear();

    postEvent(DDF_FeedEvent.LOGOUT);

    // Do we need to specifically tell JERQ we're logging out?
    // blockingWrite(FeedDDF.tcpLogout());

    terminate();

  }

  private boolean isConnected() {
    if (channel == null) {
      return false;
    }
    return channel.isConnected();
  }

  /*
   * Adds a COMMAND_WRITE_ERROR to the event queue if an attempt to write to
   * the channel fails.
   */
  private class CommandFailureListener implements ChannelFutureListener {

    @Override
    public void operationComplete(final ChannelFuture future)
        throws Exception {
      if (!future.isSuccess()) {
        postEvent(DDF_FeedEvent.COMMAND_WRITE_FAILURE);
      }
    }

  }

  /* Asynchronous write to the channel, future returns true on success */
  private Future<Boolean> writeAsync(final String message) {
    log.debug("Attempting to send reqeust to JERQ : {}", message);
    final ChannelFuture future = channel.write(message + "\n");
    future.addListener(new CommandFailureListener());
    return new CommandFuture(future);
  }

  @Override
  public Future<Boolean> subscribe(final Set<Subscription> subs) {

    if (subs == null) {
      log.error("Null subscribes request recieved");
      return null;
    }

    if (!isConnected()) {
      return new DummyFuture();
    }

    /*
     * Creates a single JERQ command from the set, subscriptions are added
     * indivually.
     */
    final StringBuffer sb = new StringBuffer();
    sb.append("GO ");
    for (final Subscription sub : subs) {

      if (sub != null) {
       
        final String inst = sub.getInstrument();
       
        /* If we're subscribed already, add new interests, otherwise add  */
        if(subscriptions.containsKey(inst)) {
          subscriptions.get(inst).addInterests(sub.getInterests());
        } else {
          subscriptions.put(inst, sub);
        }
       
        sb.append(subscriptions.get(inst).subscribe() + ",");
      }
    }
    return writeAsync(sb.toString());
  }

  @Override
  public Future<Boolean> subscribe(final Subscription sub) {

    //TODO Should these just return DummyFutures? NULL seems bad
    if (sub == null) {
      log.error("Null subscribe request recieved");
      return null;
    }

    /* If we're subscribed already, add new interests, otherwise add */
    final String inst = sub.getInstrument();
    if(subscriptions.containsKey(inst)) {
      subscriptions.get(inst).addInterests(sub.getInterests());
    } else {
      subscriptions.put(inst, sub);
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }

    /* Request subscription from JERQ and return the future */
    return writeAsync("GO " + sub.subscribe());
  }

  @Override
  public Future<Boolean> unsubscribe(final Set<Subscription> subs) {

    if (subs == null) {
      log.error("Null subscribes request recieved");
      return null;
    }

    if (!isConnected()) {
      return new DummyFuture();
    }

    /*
     * Creates a single JERQ command from the set. Subscriptions are removed
     * individually.
     */
    final StringBuffer sb = new StringBuffer();
    sb.append("STOP ");
    for (final Subscription sub : subs) {

      if (sub != null) {
        subscriptions.remove(sub.getInstrument());
        sb.append(sub.unsubscribe() + ",");
      }
    }
    return writeAsync(sb.toString());
  }

  @Override
  public Future<Boolean> unsubscribe(final Subscription sub) {

    if (sub == null) {
      log.error("Null subscribe request recieved");
      return null;
    }

    subscriptions.remove(sub.getInstrument());

    if (!isConnected()) {
      return new DummyFuture();
    }

    /* Request subscription from JERQ and return the future */
    return writeAsync("STOP " + sub.unsubscribe());
  }

  @Override
  public synchronized void bindMessageListener(
      final DDF_MessageListener handler) {
    this.msgListener = handler;
  }

  @Override
  public synchronized void bindStateListener(
      final FeedStateListener stateListener) {
    feedListeners.add(stateListener);
  }

  private volatile Thread loginThread = null;

  int i = 0;

  private class LoginHandler {

    private boolean enabled = true;

    void enableLogins() {
      enabled = true;
    }

    void disableLogins() {
      enabled = false;
    }

    boolean isLoginActive() {
      return loginThread != null && loginThread.isAlive();
    }

    void interruptLogin() {
      if (isLoginActive()) {
        loginThread.interrupt();
      }
    }

    synchronized void login(final int delay) {

      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      log.warn(
          "login called in LoginHandler. login enabled = {} isLoginActive = {} ",
          enabled, isLoginActive() + " reconnect attempt count = "
              + i++);

      if (proxySettings != null) {

        startUpProxy();

        return;
      }

      if (enabled && !isLoginActive()) {

        log.warn("Setting feed state to attempting login");

        updateFeedStateListeners(FeedState.ATTEMPTING_LOGIN);

        loginThread = new Thread(new LoginRunnable(delay),
            "# DDF Login " + i++);

        // use .start() not executor..

        loginThread.start();

      }
    }

  }

  private void updateFeedStateListeners(final FeedState state) {
    for (final FeedStateListener listener : feedListeners) {
      listener.stateUpdate(state);
    }
  }

  /* Runnable which handles connection, login, and initializaion */
  class LoginRunnable implements Runnable {

    @SuppressWarnings("unused")
    private final int delay;

    public LoginRunnable(final int delay) {
      this.delay = delay;
    }

    @Override
    public void run() {

      log.info("starting LoginRunnable "
          + Thread.currentThread().getName());

      terminate();

      log.warn("sleeping for 2000 ms after terminate()");

      try {
        Thread.sleep(2000);
      } catch (InterruptedException e1) {
      }

      initialize();

      log.info("trying to connect to setting service...");

      /* Attempt to get current data server settings */
      DDF_Settings settings = null;
      try {
        settings = DDF_SettingsService.newSettings(username, password);
        if (!settings.isValidLogin()) {
          log.error("Posting SETTINGS_RETRIEVAL_FAILURE");
          postEvent(DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE);

          return;
        }
      } catch (final Exception e) {
        log.error("Posting SETTINGS_RETRIEVAL_FAILURE");
        postEvent(DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE);

        return;
      }

      log.debug("got settings from settings service");

      final DDF_Server server = settings.getServer(serverType);
      final String primary = server.getPrimary();
      final String secondary = server.getSecondary();

      log.debug("trying primary server login " + primary);

      /* Attempt to connect and login to primary server */
      final DDF_FeedEvent eventOne = login(primary, PORT);

      if (eventOne == DDF_FeedEvent.LOGIN_SENT) {
        log.debug("Posting LOGIN_SENT for primary server");
        postEvent(DDF_FeedEvent.LOGIN_SENT);

        return;
      }

      log.warn("failed to connect to primary server " + primary);

      log.debug("trying secondary server login " + secondary);

      /* Attempt to connect and login to secondary server */
      final DDF_FeedEvent eventTwo = login(secondary, PORT);

      if (eventTwo == DDF_FeedEvent.LOGIN_SENT) {
        log.debug("Posting LOGIN_SENT for secondary server");
        postEvent(DDF_FeedEvent.LOGIN_SENT);

        return;
      }

      /*
       * For simplicity, we only return the error message from the primary
       * server in the event both logins fail.
       */
      log.error("Failed to connect to both servers , Posting {}",
          eventOne.name());

      postEvent(eventOne);

      return;
    }

    /* Handles the login for an individual server */
    private DDF_FeedEvent login(final String host, final int port) {
      final InetSocketAddress address = new InetSocketAddress(host, port);

      ChannelFuture futureConnect = null;

      /* Netty attempt to connect to server */
      futureConnect = boot.connect(address);

      channel = futureConnect.getChannel();

      if (!futureConnect.awaitUninterruptibly(TIMEOUT, TIME_UNIT)) {
        log.error("channel connect timeout; {}:{} ", host, port);
        return DDF_FeedEvent.CHANNEL_CONNECT_TIMEOUT;
      }

      /* Handle connection attempt errors */
      if (!futureConnect.isDone()) {
        log.error("channel connect timeout; {}:{} ", host, port);
        return DDF_FeedEvent.CHANNEL_CONNECT_TIMEOUT;
      }

      if (!futureConnect.isSuccess()) {
        log.error("channel connect unsuccessful; {}:{} ", host, port);
        return DDF_FeedEvent.CHANNEL_CONNECT_FAILURE;
      }

      /* Send login command to JERQ */
      DDF_FeedEvent writeEvent = blockingWrite(FeedDDF.tcpLogin(username,
          password));

      if (writeEvent == DDF_FeedEvent.COMMAND_WRITE_FAILURE) {
        return DDF_FeedEvent.COMMAND_WRITE_FAILURE;
      }

      /* Send VERSION 3 command to JERQ */
      writeEvent = blockingWrite(FeedDDF.tcpVersion(FeedDDF.VERSION_3));

      if (writeEvent == DDF_FeedEvent.COMMAND_WRITE_FAILURE) {
        return DDF_FeedEvent.COMMAND_WRITE_FAILURE;
      }

      /* Send timestamp command to JERQ */
      writeEvent = blockingWrite(FeedDDF.tcpGo(FeedDDF.SYMBOL_TIMESTAMP));

      if (writeEvent == DDF_FeedEvent.COMMAND_WRITE_FAILURE) {
        return DDF_FeedEvent.COMMAND_WRITE_FAILURE;
      }

      return DDF_FeedEvent.LOGIN_SENT;
    }

  }

  private final RunnerDDF heartbeatTask = new RunnerDDF() {

    private long delta;

    @Override
    public void runCore() {

      Thread.currentThread().setName("# ddf-heartbeat listener");

      log.warn("starting # ddf-heartbeat listener");

      try {
        while (!Thread.currentThread().isInterrupted()) {
          checkTime();
          Thread.sleep(2000); // This must be less than
                    // HEARTBEAT_TIMEOUT
        }

      } catch (final InterruptedException e) {
        log.warn("# ddf-heartbeat listener thread InterruptedException");
        return;

      } catch (final Exception e) {

        log.warn("# ddf-heartbeat exception: {}", e);
        return;

      }

      log.warn("# ddf-heartbeat listener thread death");

    }

    private void checkTime() {

      /*
       * If not currently logged in, keep the last heart beat updated so
       * when we do query it, it will be fresh.
       */

      if (loginHandler.isLoginActive() || !isConnected()) {
        lastHeartbeat.set(System.currentTimeMillis());
      } else {
        delta = System.currentTimeMillis() - lastHeartbeat.get();

        /*
         * Close channel if time delta is greater than threshold and
         * reset last heart beat.
         */
        if (delta > HEARTBEAT_TIMEOUT) {
          log.error("Heartbeat check failed - calling hardRestart()");
          log.error("Heartbeat delta: " + delta);

          // any calls here will happen in this thread

          executor.execute(new Thread(new Disconnector()));

          lastHeartbeat.set(System.currentTimeMillis());
        }

      }
    }

  };

  private class Disconnector implements Runnable {

    @Override
    public void run() {

      hardRestart();

    }

  }

  // change how this is done

  public void setProxiedChannel(ChannelHandlerContext ctx, MessageEvent e,
      boolean success) {

    if (success) {
      this.channel = e.getChannel();

      // post ddf link connect
      postEvent(DDF_FeedEvent.LINK_CONNECT);

      final SimpleChannelHandler ddfHandler = new ChannelHandlerDDF(
          eventQueue, messageQueue);

      channel.getPipeline().addLast("ddf frame decoder",
          new MsgDeframerDDF());

      channel.getPipeline().addLast("ddf message decoder",
          new MsgDecoderDDF());

      // ### Encoders ###

      channel.getPipeline().addLast("ddf command encoder",
          new MsgEncoderDDF());

      channel.getPipeline().addLast("ddf data feed client", ddfHandler);

      socksConnectResult.add(true);
    } else {
      socksConnectResult.add(false);
    }
  }

  boolean connecting = false;

  @Override
  public void startUpProxy() {

    if (connecting == true) {
      log.error("Still connecting");
      return;
    }

    connecting = true;

    log.warn("startUpProxy() - connecting...");

    if (proxySettings == null) {
      log.error("Poxysettings are null, starting direct connect");
      startup();
      return;
    }

    /* Attempt to get current data server settings */
    DDF_Settings ddf_settings = null;
    try {
      ddf_settings = DDF_SettingsService.newSettings(username, password);
      if (!ddf_settings.isValidLogin()) {
        log.error("Posting SETTINGS_RETRIEVAL_FAILURE");
        postEvent(DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE);

        connecting = false;
        return;
      }
    } catch (final Exception e) {
      log.error("Posting SETTINGS_RETRIEVAL_FAILURE");
      postEvent(DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE);

      connecting = false;
      return;
    }

    final DDF_Server server = ddf_settings.getServer(serverType);

    loginProxy(username, password, server);

    log.warn("startUpProxy() done connecting...");

    connecting = false;

  }

}
TOP

Related Classes of com.barchart.feed.ddf.datalink.provider.FeedClientDDF

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.