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.CountDownLatch;
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.AtomicInteger;
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.channel.socket.nio.NioWorkerPool;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.logging.Slf4JLoggerFactory;
import org.jboss.netty.util.HashedWheelTimer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.barchart.feed.api.connection.Connection;
import com.barchart.feed.base.sub.SubCommand;
import com.barchart.feed.base.sub.SubCommand.Type;
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.enums.DDF_FeedEvent;
import com.barchart.feed.ddf.message.api.DDF_BaseMessage;
import com.barchart.feed.ddf.message.api.DDF_ControlTimestamp;
import com.barchart.feed.ddf.message.provider.CodecHelper;
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.ClockDDF;
import com.barchart.feed.ddf.util.FeedClock;
import com.barchart.feed.ddf.util.FeedDDF;

class FeedClientDDF implements DDF_FeedClient {

  private static final String VERSION = FeedDDF.VERSION_4;
  private static final int PORT = 7500;

  private static final int DEFAULT_IO_THREADS =
      Runtime.getRuntime().availableProcessors() * 2;
 
  /** 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, DDF_Subscription> subscriptions =
      new ConcurrentHashMap<String, DDF_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<Connection.Monitor> feedListeners =
      new CopyOnWriteArrayList<Connection.Monitor>();

  //

  private ClientBootstrap boot;

  private ChannelFactory channelFactory;
  private Channel channel;
  private HashedWheelTimer timer = null;
 
  //

  private String username;
  private String password;
  private final DDF_ServerType serverType = DDF_ServerType.STREAM;
  private Executor executor;
 
  //private ExecutorService localExecutor = Executors.newFixedThreadPool(100);

  // 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;

    timer = new HashedWheelTimer();
   
    channelFactory = new NioClientSocketChannelFactory(
        executor, 1, new NioWorkerPool(executor, DEFAULT_IO_THREADS), timer);

    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(DDF_FeedEvent event) {
          /* Do nothing */
        }
      });
    }

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

    eventPolicy.put(DDF_FeedEvent.LINK_DISCONNECT, reconnectionPolicy);

    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());

  }

  private final DefaultReloginPolicy reconnectionPolicy = new DefaultReloginPolicy();

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

    terminate();

    initialize();

    // do socks connection

    log.info("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.info("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 # command to JERQ */
    writeEvent = blockingWrite(FeedDDF.tcpVersion(VERSION));

    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(DDF_FeedEvent event) {
      if (subscriptions.size() > 0) {
        log.debug("Requesting current subscriptions");
        final Set<SubCommand> subs = new HashSet<SubCommand>();
        for(final Entry<String, DDF_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(DDF_FeedEvent event) {
      executor.execute(new Thread(new Disconnector(event.name())));
    }
  }

  /*
   * 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(DDF_FeedEvent event) {
      lastHeartbeat.set(System.currentTimeMillis());
    }
  }

  private volatile AtomicInteger eventTaskNumber = new AtomicInteger();
  private volatile AtomicInteger messageTaskNumber = new AtomicInteger();
  private volatile AtomicInteger heartbeatTaskNumber = new AtomicInteger();

  private final RunnerDDF eventTask = new RunnerDDF() {

    @Override
    protected void runCore() {

      final int threadNumber = eventTaskNumber.getAndIncrement();

      Thread.currentThread().setName("# DDF EVENT TASK " + threadNumber);

      log.warn("# started DDF-EventTask {}", threadNumber);

      startupLatch.countDown();

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

        try {

          final DDF_FeedEvent event = eventQueue.take();

          if (DDF_FeedEvent.isConnectionError(event)) {

            log.info("Setting feed state to logged out");
            updateFeedStateListeners(Connection.State.DISCONNECTED);

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

            log.info("Login success, feed state updated");
            updateFeedStateListeners(Connection.State.CONNECTED);

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

            log.info("Setting feed state to logged out");
            updateFeedStateListeners(Connection.State.DISCONNECTED);

          }

          log.trace("Enacting policy for :{}", event.name());

          eventPolicy.get(event).newEvent(event);

        } catch (final InterruptedException e) {

          log.warn("# DDF-EventTask InterruptedException {}",
              threadNumber);

          log.info("Setting feed state to logged out");
          updateFeedStateListeners(Connection.State.DISCONNECTED);

          Thread.currentThread().interrupt();

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

      log.error("# DDF-EventTask death {}", threadNumber);
    }
  };

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

      final int threadNumber = messageTaskNumber.getAndIncrement();

      Thread.currentThread().setName("# DDF MESSAGE TASK " + threadNumber);

      log.warn("# started DDF-MessageTask {}", threadNumber);

      startupLatch.countDown();

      while (!Thread.currentThread().isInterrupted()) {
        try {
          final DDF_BaseMessage message = messageQueue.take();
          if (msgListener != null) {
           
            /* We set the clock by the timestamp messages, however,
             * we also set the clock by market messages, but this has to
             * happen in the message decoding */
            if (message instanceof DDF_ControlTimestamp) {
              ClockDDF.clock.set(((DDF_ControlTimestamp) message)
                  .getStampUTC().asMillisUTC());
            }
           
            //// #######################
            // log.debug(message.toString());
            // #######################
           
            msgListener.handleMessage(message);
          }
        } catch (final InterruptedException e) {

          log.warn("# DDF-MessageTask InterruptedException {}", threadNumber);

          Thread.currentThread().interrupt();

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

      log.error("# DDF-MessageTask death {}", threadNumber);
    }
  };
 
 

  @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");
    }
  }

  private volatile CountDownLatch startupLatch = new CountDownLatch(3);

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

    startupLatch = new CountDownLatch(3);

    log.warn("# initialize start");

    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);

      try {
        executor.execute(new Thread(new Disconnector(
            "DDF_Heartbeat Thread Start Exception")));
      } catch (Exception e1) {
      }

      return;
    }

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

      try {
        executor.execute(new Thread(new Disconnector(
            "DDF_Event Thread Start Exception")));
      } catch (Exception e1) {
      }

      return;
    }

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

      try {
        executor.execute(new Thread(new Disconnector(
            "DDF_Message Thread Start Exception")));
      } catch (Exception e1) {
      }

      return;
    }

    try {
      startupLatch.await();
    } catch (InterruptedException e1) {
      log.warn("# initialize interrupted, returning");
      return;
    }

    log.warn("# initialize complete");

  }

  private void terminate() {

    log.warn("## terminate start");
   
    eventQueue.clear();
    messageQueue.clear();

    /* Interrupts login thread if login is active */

    loginHandler.disableLogins();
    loginHandler.interruptLogin();

    // kill all threads
    // blocking order with join()

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

      log.warn("# terminate: DDF-heartbeat killed");

    }

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

      log.warn("# terminate: DDF-MesssageTask killed");

    }

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

      log.warn("# terminate DDF-EventTask killed");

    }

    log.warn("# terminate: closing channel.");

    if (channel != null) {

      final ChannelFuture cf = channel.close();

      try {
        cf.await();
      } catch (final InterruptedException e) {
        log.warn("# terminate: channel.close() channel interrupted");
      } finally {
        log.warn("# terminate: channel.close() complete");
        channel = null;
      }

    }
   
    log.warn("## terminate complete");

  }

  /**
   * blocks
   *
   */
  private synchronized void hardRestart(String caller) {
    log.warn("#### hardRestart called by: " + caller);
    log.warn("#### hardRestart: starting");

    log.warn("#### hardRestart: interupt logins");

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

    log.warn("#### hardRestart: calling terminate");

    terminate();

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

    log.warn("#### hardRestart: complete");

    log.warn("#### hardRestart: starting login");

    // login

    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);

    terminate();
   
    killTimer();
   
  }
 
  private void killTimer() {
   
    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
   
    for(final Thread t : threadSet) {
      if(t.getName().startsWith("Hashed wheel timer")) {
        while(t.isAlive()) {
          t.stop();
        }
      }
    }
   
  }
 
  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<SubCommand> subs) {

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

    final Set<SubCommand> insts = new HashSet<SubCommand>();
    final Set<SubCommand> exch = new HashSet<SubCommand>();
   
    for(final SubCommand sub : subs) {
      switch(sub.type()) {
      default:
        throw new IllegalStateException("Subscription type cannot be null");
      case INSTRUMENT:
        insts.add(sub);
        break;
      case EXCHANGE:
        exch.add(sub);
        break;
      }
    }
   
    if(!insts.isEmpty()) {
     
      if(exch.isEmpty()) {
        return subInsts(insts);
      } else {
        subInsts(insts);
        return subExcs(exch);
      }
     
    }
   
    if(!exch.isEmpty()) {
      return subExcs(exch);
    } else {
      return new DummyFuture();
    }
   
  }
 
  private Future<Boolean> subInsts(final Set<SubCommand> subs) {
   
    /*
     * Creates a single JERQ command from the set, subscriptions are added
     * indivually.
     */
    final StringBuffer sb = new StringBuffer();
    sb.append("GO ");
    for (final SubCommand sub : subs) {

      if (sub != null) {
       
        final String interest = sub.interest();
       
        /* If we're subscribed already, add new interests, otherwise add  */
        if(subscriptions.containsKey(interest)) {
          subscriptions.get(interest).addTypes(sub.types());
        } else {
          subscriptions.put(interest, new DDF_Subscription(sub, Type.INSTRUMENT));
        }
       
        sb.append(subscriptions.get(interest).encode() + ",");
      }
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    return writeAsync(sb.toString());
   
  }
 
  private Future<Boolean> subExcs(final Set<SubCommand> subs) {
   
    final StringBuffer sb = new StringBuffer();
   
    sb.append("STR L ");
    for(final SubCommand sub : subs) {
     
      if(sub != null) {
     
        final String interest = sub.interest();
       
        if(!subscriptions.containsKey(interest)) {
         
          subscriptions.put(interest, new DDF_Subscription(sub, Type.EXCHANGE));
         
        }
       
        sb.append(interest + ";");
       
      }
     
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    return writeAsync(sb.toString());
  }
 
  @Override
  public Future<Boolean> subscribe(final SubCommand sub) {

    //TODO Should these just return DummyFutures? NULL is bad
    if (sub == null) {
      log.error("Null subscribe request recieved");
      return null;
    }
   
    if(sub.type() == Type.EXCHANGE) {
      return subExc(sub);
    } else {
      return subInst(sub);
    }
   
  }

  private Future<Boolean> subInst(final SubCommand sub) {
   
    /* If we're subscribed already, add new interests, otherwise add */
    final String inst = sub.interest();
    if(subscriptions.containsKey(inst)) {
      subscriptions.get(inst).addTypes(sub.types());
    } else {
      subscriptions.put(inst, new DDF_Subscription(sub, Type.INSTRUMENT));
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    /* Request subscription from JERQ and return the future */
    return writeAsync("GO " + sub.encode());
   
  }
 
  private Future<Boolean> subExc(final SubCommand sub) {
   
    final String interest = sub.interest();
   
    if(!subscriptions.containsKey(interest)) {
      subscriptions.put(interest, new DDF_Subscription(sub, Type.EXCHANGE));
    } else {
      return new DummyFuture();
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    return writeAsync("STR L " + sub.interest());
  }
 
  @Override
  public Future<Boolean> unsubscribe(final Set<SubCommand> subs) {

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

    final Set<SubCommand> insts = new HashSet<SubCommand>();
    final Set<SubCommand> exch = new HashSet<SubCommand>();
   
    for(final SubCommand sub : subs) {
      switch(sub.type()) {
      case INSTRUMENT:
        insts.add(sub);
        break;
      case EXCHANGE:
        exch.add(sub);
        break;
      }
    }
   
    if(!insts.isEmpty()) {
     
      if(exch.isEmpty()) {
        return unsubInsts(insts);
      } else {
        unsubInsts(insts);
        return unsubExchs(exch);
      }
     
    }
   
    if(!exch.isEmpty()) {
      return unsubExchs(exch);
    } else {
      return new DummyFuture();
    }
   
  }

  private Future<Boolean> unsubInsts(final Set<SubCommand> subs) {
   
    /*
     * Creates a single JERQ command from the set. Subscriptions are removed
     * individually.
     */
    final StringBuffer sb = new StringBuffer();
    sb.append("STOP ");
    for (final SubCommand sub : subs) {

      if (sub != null) {
        subscriptions.remove(sub.interest());
        sb.append(sub.interest() + ",");
      }
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    return writeAsync(sb.toString());
  }
 
  private Future<Boolean> unsubExchs(final Set<SubCommand> subs) {
   
    for(final SubCommand sub : subs) {
      subscriptions.remove(sub.interest());
    }
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    /* Have to unsub from everything and resub */
    writeAsync("STOP");
   
    final Set<SubCommand> resubs = new HashSet<SubCommand>();
    for(final Entry<String, DDF_Subscription> e : subscriptions.entrySet()) {
      resubs.add(e.getValue());
    }
   
    return subscribe(resubs);
  }
 
  @Override
  public Future<Boolean> unsubscribe(final SubCommand sub) {

    if (sub == null) {
      log.error("Null subscribe request recieved");
      return null;
    }
   
    if(sub.type() == Type.EXCHANGE) {
      return unsubExc(sub);
    } else {
      return unsubInst(sub);
    }

  }
 
  private Future<Boolean> unsubInst(final SubCommand sub) {
   
    subscriptions.remove(sub.interest());

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

    /* Request subscription from JERQ and return the future */
    return writeAsync("STOP " + sub.interest());
   
  }
 
  private Future<Boolean> unsubExc(final SubCommand sub) {
   
    subscriptions.remove(sub.interest());
   
    if (!isConnected()) {
      return new DummyFuture();
    }
   
    /* Have to unsub from everything and resub */
    writeAsync("STOP");
   
    final Set<SubCommand> subs = new HashSet<SubCommand>();
    for(final Entry<String, DDF_Subscription> e : subscriptions.entrySet()) {
      subs.add(e.getValue());
    }
   
    return subscribe(subs);
   
  }

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

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

  private volatile Thread loginThread = null;

  private volatile AtomicInteger loginThreadNumber = new AtomicInteger();
  private volatile boolean isLoggingIn = false;

  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();
        log.warn("# LoginHandler - login thread killed.");
      }
    }

    synchronized void login(final int delay) {

      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        log.error("# LoginHandler interrupted while sleeping");
        return;
      }

      final int threadNumber = loginThreadNumber.getAndIncrement();

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

      if (proxySettings != null) {

        startUpProxy();

        return;
      }

      if (enabled && !isLoginActive()) {

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

        updateFeedStateListeners(Connection.State.CONNECTING);

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

        // use .start() not executor..

        loginThread.start();

      }
    }

  }

  private void updateFeedStateListeners(final Connection.State state) {
    for (final Connection.Monitor listener : feedListeners) {
      // FIXME Monitor not implemented yet, so this needs to be a Monitor
      listener.handle(state, null);
    }
  }

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

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

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

    @Override
    public void run() {

      isLoggingIn = true;

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

      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);
          isLoggingIn = false;
          return;
        }
      } catch (final Exception e) {
        log.error("Posting SETTINGS_RETRIEVAL_FAILURE");
        postEvent(DDF_FeedEvent.SETTINGS_RETRIEVAL_FAILURE);
        isLoggingIn = false;
        return;
      }

      log.info("received settings from settings service\n{}", settings);

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

      log.info("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.info("Posting LOGIN_SENT for primary server");
        postEvent(DDF_FeedEvent.LOGIN_SENT);
        isLoggingIn = false;
        return;
      }

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

      log.warn("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.info("Posting LOGIN_SENT for secondary server");
        postEvent(DDF_FeedEvent.LOGIN_SENT);
        isLoggingIn = false;
        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());

      isLoggingIn = false;

      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(VERSION));

      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() {

      final int threadNumber = heartbeatTaskNumber.getAndIncrement();

      Thread.currentThread().setName(
          "# DDF HEARTBEAT TASK " + threadNumber);

      log.warn("started # DDF-heartbeat task {} ", threadNumber);

      startupLatch.countDown();

      try {

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

          checkTime();
          Thread.sleep(2000); // This must be less than
                    // HEARTBEAT_TIMEOUT
        }

      } catch (final InterruptedException e) {

        log.warn("# DDF-heartbeat task InterruptedException {}",
            threadNumber);

        Thread.currentThread().interrupt();

      } catch (final Exception e) {

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

      }

      log.error("# DDF-heartbeat task death {}", threadNumber);

    }

    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
          // ...so we will start new thread so this one can die
          executor.execute(new Thread(new Disconnector(
              "HEARTBEAT TIMEOUT")));

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

      }
    }

  };

  private class Disconnector implements Runnable {

    final String caller;

    public Disconnector(final String caller) {
      this.caller = caller;
    }

    @Override
    public void run() {
      if (isLoggingIn) {
        log.warn("## "
            + caller
            + " is trying to call hardRestart, but we are still logging in.");
        return;
      }

      hardRestart(caller);

    }
  }

  // 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.