Package com.linkedin.databus.client

Source Code of com.linkedin.databus.client.TestDatabusHttpClient

package com.linkedin.databus.client;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/


import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.BinaryEncoder;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.handler.codec.http.DefaultHttpChunk;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpServerCodec;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLogLevel;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.logging.Log4JLoggerFactory;
import org.jboss.netty.util.HashedWheelTimer;
import org.jboss.netty.util.Timer;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.linkedin.databus.client.DatabusHttpClientImpl.RuntimeConfigBuilder;
import com.linkedin.databus.client.consumer.AbstractDatabusCombinedConsumer;
import com.linkedin.databus.client.consumer.LoggingConsumer;
import com.linkedin.databus.client.netty.NettyHttpDatabusRelayConnection;
import com.linkedin.databus.client.netty.NettyHttpDatabusRelayConnectionInspector;
import com.linkedin.databus.client.netty.NettyTestUtils;
import com.linkedin.databus.client.pub.ConsumerCallbackResult;
import com.linkedin.databus.client.pub.DatabusClientException;
import com.linkedin.databus.client.pub.DatabusStreamConsumer;
import com.linkedin.databus.client.pub.DbusEventDecoder;
import com.linkedin.databus.client.pub.SCN;
import com.linkedin.databus.client.pub.ServerInfo;
import com.linkedin.databus.client.pub.ServerInfo.ServerInfoBuilder;
import com.linkedin.databus.core.Checkpoint;
import com.linkedin.databus.core.DbusEvent;
import com.linkedin.databus.core.DbusEventBuffer;
import com.linkedin.databus.core.DbusEventBuffer.AllocationPolicy;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.DbusEventInfo;
import com.linkedin.databus.core.DbusEventInternalWritable;
import com.linkedin.databus.core.DbusEventKey;
import com.linkedin.databus.core.DbusEventV1Factory;
import com.linkedin.databus.core.DbusOpcode;
import com.linkedin.databus.core.InternalDatabusEventsListener;
import com.linkedin.databus.core.data_model.DatabusSubscription;
import com.linkedin.databus.core.monitoring.mbean.DbusEventsStatisticsCollector;
import com.linkedin.databus.core.util.IdNamePair;
import com.linkedin.databus.core.util.InvalidConfigException;
import com.linkedin.databus.core.util.RngUtils;
import com.linkedin.databus.core.util.Utils;
import com.linkedin.databus2.core.container.request.RegisterResponseEntry;
import com.linkedin.databus2.schemas.utils.SchemaHelper;
import com.linkedin.databus2.test.ConditionCheck;
import com.linkedin.databus2.test.TestUtil;
import com.linkedin.databus2.test.container.MockServerChannelHandler;
import com.linkedin.databus2.test.container.SimpleObjectCaptureHandler;
import com.linkedin.databus2.test.container.SimpleTestServerConnection;

public class TestDatabusHttpClient
{
  public static final Logger LOG = Logger.getLogger("TestDatabusHttpClient");
  static final Schema SOURCE1_SCHEMA =
      Schema.parse("{\"name\":\"source1\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}");
  static final String SOURCE1_SCHEMA_STR = SOURCE1_SCHEMA.toString();
  static final byte[] SOURCE1_SCHEMAID = SchemaHelper.getSchemaId(SOURCE1_SCHEMA_STR);
  static final ExecutorService BOSS_POOL = Executors.newCachedThreadPool();
  static final ExecutorService IO_POOL = Executors.newCachedThreadPool();
  static final int[] RELAY_PORT = {14467, 14468, 14469};
  static final Timer NETWORK_TIMER = new HashedWheelTimer(10, TimeUnit.MILLISECONDS);
  static final ChannelGroup TEST_CHANNELS_GROUP = new DefaultChannelGroup();
  static final long DEFAULT_READ_TIMEOUT_MS = 10000;
  static final long DEFAULT_WRITE_TIMEOUT_MS = 10000;
  static final String SOURCE1_NAME = "test.event.source1";
  static final String SOURCES_REQUEST_REGEX = "^/sources[?]protocolVersion=[0-9]";
  static SimpleTestServerConnection[] _dummyServer = new SimpleTestServerConnection[RELAY_PORT.length];
  static DbusEventBuffer.StaticConfig _bufCfg;
  static DatabusHttpClientImpl.StaticConfig _stdClientCfg;
  static DatabusHttpClientImpl.Config _stdClientCfgBuilder;
  static final DbusEventFactory _eventFactory = new DbusEventV1Factory();

  @BeforeClass
  public void setUpClass() throws InvalidConfigException
  {
    //set up logging
    TestUtil.setupLoggingWithTimestampedFile(true,  "/tmp/TestDatabusHttpClient_", ".log", Level.INFO);
    InternalLoggerFactory.setDefaultFactory(new Log4JLoggerFactory());

    //initialize relays
    for (int relayN = 0; relayN < RELAY_PORT.length; ++relayN)
    {
      _dummyServer[relayN] = new SimpleTestServerConnection(_eventFactory.getByteOrder(),
                                                            SimpleTestServerConnection.ServerType.NIO);
      _dummyServer[relayN].setPipelineFactory(new ChannelPipelineFactory() {
          @Override
          public ChannelPipeline getPipeline() throws Exception {
              return Channels.pipeline(new LoggingHandler(InternalLogLevel.DEBUG),
                                       new HttpServerCodec(),
                                       new LoggingHandler(InternalLogLevel.DEBUG),
                                       new SimpleObjectCaptureHandler());
          }
      });
      _dummyServer[relayN].start(RELAY_PORT[relayN]);
    }

    //create standard client config
    DatabusHttpClientImpl.Config clientCfgBuilder = new DatabusHttpClientImpl.Config();
    clientCfgBuilder.getContainer().setHttpPort(0);
    clientCfgBuilder.getContainer().getJmx().setRmiEnabled(false);
    clientCfgBuilder.getContainer().setReadTimeoutMs(10000000);
    clientCfgBuilder.getConnectionDefaults().getPullerRetries().setInitSleep(10);
    clientCfgBuilder.getRuntime().getBootstrap().setEnabled(false);
    clientCfgBuilder.getCheckpointPersistence().setClearBeforeUse(true);
    for (int i = 0; i < RELAY_PORT.length; ++i)
    {
      clientCfgBuilder.getRuntime().getRelay(Integer.toString(i)).setHost("localhost");
      clientCfgBuilder.getRuntime().getRelay(Integer.toString(i)).setPort(RELAY_PORT[i]);
      clientCfgBuilder.getRuntime().getRelay(Integer.toString(i)).setSources(SOURCE1_NAME);
    }

    _stdClientCfgBuilder = clientCfgBuilder;
    _stdClientCfg = clientCfgBuilder.build();

    //create standard relay buffer config
    DbusEventBuffer.Config bufCfgBuilder = new DbusEventBuffer.Config();
    bufCfgBuilder.setAllocationPolicy(AllocationPolicy.HEAP_MEMORY.toString());
    bufCfgBuilder.setMaxSize(100000);
    bufCfgBuilder.setScnIndexSize(128);
    bufCfgBuilder.setAverageEventSize(1);

    _bufCfg = bufCfgBuilder.build();
  }

  private ServerInfo registerRelay(int id, String name, InetSocketAddress addr, String sources,
                             DatabusHttpClientImpl client)
          throws InvalidConfigException
  {
    RuntimeConfigBuilder rtConfigBuilder =
        (RuntimeConfigBuilder)client.getClientConfigManager().getConfigBuilder();

    ServerInfoBuilder relayConfigBuilder = rtConfigBuilder.getRelay(Integer.toString(id));
    relayConfigBuilder.setName(name);
    relayConfigBuilder.setHost(addr.getHostName());
    relayConfigBuilder.setPort(addr.getPort());
    relayConfigBuilder.setSources(sources);
    ServerInfo si = relayConfigBuilder.build();
    client.getClientConfigManager().setNewConfig(rtConfigBuilder.build());
    return si;
  }

  @Test
  public void testListGenerics() throws Exception
  {
    Map<List<String>, List<String>> a = new HashMap<List<String>, List<String>>();
    List<String> l1 = Arrays.asList("S1", "S2");
    List<String> v1 = Arrays.asList("localhost:8080");
    a.put(l1, v1);
    assertEquals("CompareSize", 1, safeListSize(a.get(l1)));

    Map<List<DatabusSubscription>, List<String>> b = new HashMap<List<DatabusSubscription>, List<String>>();
      List<DatabusSubscription> ls1 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S2"));
      List<DatabusSubscription> ls2 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S3"));
      b.put(ls1, l1);
    assertEquals(2, safeListSize(b.get(ls1)));
    assertEquals(0, safeListSize(b.get(ls2)));
  }

    @Test
  public void testRegisterDatabusStreamListener() throws Exception
  {
    DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
    clientConfig.getContainer().getJmx().setRmiEnabled(false);
      clientConfig.getContainer().setHttpPort(12003);
    DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

      registerRelay(1, "relay1", new InetSocketAddress("localhost", 8888), "S1,S2", client);
      registerRelay(2, "relay2", new InetSocketAddress("localhost", 7777), "S1,S3", client);
      registerRelay(3, "relay1.1", new InetSocketAddress("localhost", 8887), "S1,S2", client);
      registerRelay(4, "relay3", new InetSocketAddress("localhost", 6666), "S3,S4,S5", client);

      DummyStreamConsumer listener1 = new DummyStreamConsumer("consumer1");
      client.registerDatabusStreamListener(listener1 , null, "S1");

      List<DatabusSubscription> ls1 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S2"));
      List<DatabusSubscription> ls2 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S3"));
      List<DatabusSubscription> ls3 = DatabusSubscription.createSubscriptionList(Arrays.asList("S3", "S4", "S5"));

      assertEquals("expect one consumer in (S1,S2) or (S1,S3)", 1,
                   safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) +
                   safeListSize(client.getRelayGroupStreamConsumers().get(ls2)));

      DummyStreamConsumer listener2 = new DummyStreamConsumer("consumer2");
      client.registerDatabusStreamListener(listener2 , null, "S1");
      int consumersNum = safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) +
      safeListSize(client.getRelayGroupStreamConsumers().get(ls2));
      assertEquals("expect two consumers in (S1,S2) or (S1,S3)", 2, consumersNum);

      DatabusStreamConsumer listener3 = new LoggingConsumer(clientConfig.getLoggingListener());
      client.registerDatabusStreamListener(listener3, null, "S5");
      assertEquals("expect one consumer in (S3,S4,S5)", 1,
                   safeListSize(client.getRelayGroupStreamConsumers().get(ls3)));


  }

    @Test(expectedExceptions=DatabusClientException.class)
    public void testRegisterDatabusStreamListenerMissingSource() throws Exception
    {
      DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
      clientConfig.getContainer().getJmx().setRmiEnabled(false);
      clientConfig.getContainer().setHttpPort(10001);
      DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

      registerRelay(1, "relay1", new InetSocketAddress("localhost", 8888), "S1, S2", client);
      registerRelay(2, "relay2", new InetSocketAddress("localhost", 7777), "S1,S3", client);
      registerRelay(3, "relay1.1", new InetSocketAddress("localhost", 8887), "S1,S2", client);
      registerRelay(4, "relay3", new InetSocketAddress("localhost", 6666), "S3,S4,S5", client);

      DummyStreamConsumer listener1 = new DummyStreamConsumer("consumer1");
      client.registerDatabusStreamListener(listener1 , null, "S10");
  }

  @Test(expectedExceptions=DatabusClientException.class)
  public void testRegisterDatabusStreamListenerWrongSourceOrder() throws Exception {
      DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
      clientConfig.getContainer().getJmx().setRmiEnabled(false);
      clientConfig.getContainer().setHttpPort(10002);
      DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

      registerRelay(1, "relay1", new InetSocketAddress("localhost", 8888), "S1,S2", client);
      registerRelay(2, "relay2", new InetSocketAddress("localhost", 7777), "S1,S3", client);
      registerRelay(3, "relay1.1", new InetSocketAddress("localhost", 8887), "S1,S2", client);
      registerRelay(4, "relay3", new InetSocketAddress("localhost", 6666), "S3,S5", client);

      DummyStreamConsumer listener1 = new DummyStreamConsumer("consumer1");
      client.registerDatabusStreamListener(listener1 , null, "S5", "S3");
  }

  @Test
  public void testUnregisterDatabusStreamListener() throws Exception
  {
      DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
      clientConfig.getContainer().getJmx().setRmiEnabled(false);
      clientConfig.getContainer().setHttpPort(13403);
      DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

      registerRelay(1, "relay1", new InetSocketAddress("localhost", 8888), "S1,S2", client);
      registerRelay(2, "relay2", new InetSocketAddress("localhost", 7777), "S1,S3", client);
      registerRelay(3, "relay1.1", new InetSocketAddress("localhost", 8887), "S1,S2", client);
      registerRelay(4, "relay3", new InetSocketAddress("localhost", 6666), "S3,S4,S5", client);

      DummyStreamConsumer listener1 = new DummyStreamConsumer("consumer1");
      client.registerDatabusStreamListener(listener1 , null, "S1");
      client.registerDatabusStreamListener(listener1 , null, "S2");

      List<DatabusSubscription> ls1 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S2"));
      List<DatabusSubscription> ls2 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S3"));

      int consumersNum1 = safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) +
          safeListSize(client.getRelayGroupStreamConsumers().get(ls2));
      assertEquals("expect two consumers in (S1,S2) or (S1,S3)", 2, consumersNum1);
      assertTrue("expect at least one consumer in (S1,S2)",
                 safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) >= 1);

      DummyStreamConsumer listener2 = new DummyStreamConsumer("consumer2");
      client.registerDatabusStreamListener(listener2 , null, "S1");
      int consumersNum2 = safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) +
      safeListSize(client.getRelayGroupStreamConsumers().get(ls2));
      assertEquals("expect three consumers in (S1,S2) or (S1,S3)", 3, consumersNum2);

      client.unregisterDatabusStreamListener(listener1);
      int consumersNum3 = safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) +
          safeListSize(client.getRelayGroupStreamConsumers().get(ls2));
      //assertEquals("one consumer + 1-2 logging consumer in (S1,S2) or (S1,S3)", consumersNum2 - 2,
      //             consumersNum3);

      client.unregisterDatabusStreamListener(listener1);
      int consumersNum4 = safeListSize(client.getRelayGroupStreamConsumers().get(ls1)) +
          safeListSize(client.getRelayGroupStreamConsumers().get(ls2));
      assertEquals("expect one consumer in (S1,S2) or (S1,S3)", consumersNum3, consumersNum4);

      client.unregisterDatabusStreamListener(listener2);
  }

  @Test
  public void testRegisterRelay() throws Exception
  {
      DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
      clientConfig.getContainer().getJmx().setRmiEnabled(false);
      clientConfig.getContainer().setHttpPort(10004);
      DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

      List<DatabusSubscription> ls1 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S2"));
      List<DatabusSubscription> sl1 = DatabusSubscription.createSubscriptionList(Arrays.asList("S2", "S1"));
      List<DatabusSubscription> ls2 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1", "S3"));
      List<DatabusSubscription> ls3 = DatabusSubscription.createSubscriptionList(Arrays.asList("S1"));
      List<DatabusSubscription> ls4 = DatabusSubscription.createSubscriptionList(Arrays.asList("S3", "S4", "S5"));
      List<DatabusSubscription> ls5 = DatabusSubscription.createSubscriptionList(Arrays.asList("S3", "S4"));

      registerRelay(1, "relay1", new InetSocketAddress("localhost", 8880), "S1,S2", client);
      assertEquals("one relay group", 1, client.getRelayGroups().size());
      assertEquals("one relay", 1, client.getRelays().size());
      assertEquals("relay group S1,S2", true,
                   client.getRelayGroups().containsKey(ls1));
      assertEquals("no relay group S2,S1", false,
                   client.getRelayGroups().containsKey(sl1));

      registerRelay(2, "relay2", new InetSocketAddress("localhost", 7777), "S1,S3", client);
      assertEquals("two relay groups", 2, client.getRelayGroups().size());
      assertEquals("two relays", 2, client.getRelays().size());
      assertEquals("relay group S1,S2", true, client.getRelayGroups().containsKey(ls1));
      assertEquals("relay group S1,S3", true, client.getRelayGroups().containsKey(ls2));
      assertEquals("no relay group S1", false, client.getRelayGroups().containsKey(ls3));

      registerRelay(3, "relay1.1", new InetSocketAddress("localhost", 8887), "S1,S2", client);
      assertEquals("two relay groups", 2, client.getRelayGroups().size());
      assertEquals("three relays", 3, client.getRelays().size());
      assertEquals("S1,S2 has two relays", 2, client.getRelayGroups().get(ls1).size());

      registerRelay(4, "relay3", new InetSocketAddress("localhost", 6666), "S3,S4,S5", client);
      assertEquals("three relay groups", 3, client.getRelayGroups().size());
      assertEquals("four relays", 4, client.getRelays().size());
      assertEquals("relay group S1,S2", true, client.getRelayGroups().containsKey(ls1));
      assertEquals("relay group S1,S3", true, client.getRelayGroups().containsKey(ls2));
      assertEquals("relay group S3,S4,S5", true, client.getRelayGroups().containsKey(ls4));
      assertEquals("no relay group S3,S4", false, client.getRelayGroups().containsKey(ls5));
  }

  @Test
  public void testDatabusSources() throws Exception
  {
    DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
    clientConfig.getContainer().getJmx().setRmiEnabled(false);
      clientConfig.getContainer().setHttpPort(10100);
    DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

      ServerInfo s1 = registerRelay(1, "relay1", new InetSocketAddress("localhost", 8888), "S1,S2", client);
      @SuppressWarnings("unused")
      ServerInfo s2 = registerRelay(2, "relay2", new InetSocketAddress("localhost", 7777), "S1,S3", client);
      ServerInfo s11 = registerRelay(3, "relay1.1", new InetSocketAddress("localhost", 8887), "S1,S2", client);

      DummyStreamConsumer listener1 = new DummyStreamConsumer("consumer1");
      client.registerDatabusStreamListener(listener1 , null, "S2");

      try
      {
        client.start();
      } catch (Exception e){}

      assertEquals("Num connections must be 1", 1, client.getRelayConnections().size());

      DatabusSourcesConnection dsc2 = client.getRelayConnections().get(0);
      Set<ServerInfo> relaysInClient2 = dsc2.getRelays();
      Set<ServerInfo> ssi2 = new HashSet<ServerInfo>();
      ssi2.add(s1); ssi2.add(s11);
      assertTrue(relaysInClient2.equals(ssi2));

      client.shutdown();
  }

  @Test
  public void testPullerRetriesExhausted() throws Exception
  {
      final Logger log = Logger.getLogger("TestDatabusHttpClient.testPullerRetriesExhausted");
      log.info("start");
    DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
    clientConfig.getConnectionDefaults().getPullerRetries().setMaxRetryNum(1);
    clientConfig.getContainer().getJmx().setRmiEnabled(false);
    clientConfig.getContainer().setHttpPort(10100);
    final DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

    int port = Utils.getAvailablePort(8888);
    @SuppressWarnings("unused")
    ServerInfo s1 = registerRelay(1, "relay1", new InetSocketAddress("localhost", port), "S1", client);
    final DummySuccessfulErrorCountingConsumer listener1 =
        new DummySuccessfulErrorCountingConsumer("consumer1", false);
    client.registerDatabusStreamListener(listener1 , null, "S1");

    Thread startThread = new Thread(new Runnable()
    {
      @Override
      public void run()
      {
        client.start();
      }
    }, "client start thread");
    startThread.start();
    TestUtil.assertWithBackoff(new ConditionCheck()
        {
          @Override
          public boolean check()
          {
            return client.getRelayConnections().size() == 1;
          }
        }, "waiting for client to start", 1000, log);

    DatabusSourcesConnection dsc = client.getRelayConnections().get(0);

    RelayDispatcher rd = (RelayDispatcher) dsc.getRelayDispatcher();
    Assert.assertEquals(true, null != dsc);

    List<String> sources = new ArrayList<String>();
    Map<Long, IdNamePair> sourcesMap = new HashMap<Long, IdNamePair>();
    for (int i = 1; i <= 3; ++i)
    {
      IdNamePair sourcePair = new IdNamePair((long)i, "source" + i);
      sources.add(sourcePair.getName());
      sourcesMap.put(sourcePair.getId(), sourcePair);
    }

    HashMap<Long, List<RegisterResponseEntry>> schemaMap =
        new HashMap<Long, List<RegisterResponseEntry>>();

    List<RegisterResponseEntry> l1 = new ArrayList<RegisterResponseEntry>();
    List<RegisterResponseEntry> l2 = new ArrayList<RegisterResponseEntry>();
    List<RegisterResponseEntry> l3 = new ArrayList<RegisterResponseEntry>();

    final String SOURCE1_SCHEMA_STR = "{\"name\":\"source1\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}";
    final String SOURCE2_SCHEMA_STR = "{\"name\":\"source2\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}";;
    final String SOURCE3_SCHEMA_STR = "{\"name\":\"source3\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}";;

    l1.add(new RegisterResponseEntry(1L, (short)1,SOURCE1_SCHEMA_STR));
    l2.add(new RegisterResponseEntry(2L, (short)1,SOURCE2_SCHEMA_STR));
    l3.add(new RegisterResponseEntry(3L, (short)1,SOURCE3_SCHEMA_STR));

    schemaMap.put(1L, l1);
    schemaMap.put(2L, l2);
    schemaMap.put(3L, l3);

        rd.enqueueMessage(SourcesMessage.createSetSourcesIdsMessage(sourcesMap.values()));
        rd.enqueueMessage(SourcesMessage.createSetSourcesSchemasMessage(schemaMap));

        TestUtil.assertWithBackoff(new ConditionCheck()
        {
          @Override
          public boolean check()
          {
            return listener1._errorCount == 1;
          }
        }, "wait for error", 1000, log);
    client.shutdown();
    log.info("done");
  }

  @Test
  public void testDispatcherRetriesExhausted() throws Exception
  {
      final Logger log = Logger.getLogger("TestDatabusHttpClient.testDispatcherRetriesExhausted");
      log.info("start");
    DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
    clientConfig.getConnectionDefaults().getPullerRetries().setMaxRetryNum(3);
    clientConfig.getConnectionDefaults().getDispatcherRetries().setMaxRetryNum(1);
    clientConfig.getContainer().getJmx().setRmiEnabled(false);
    int port = Utils.getAvailablePort(10100);
    clientConfig.getContainer().setHttpPort(0);
    final DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

    port = Utils.getAvailablePort(8898);
    @SuppressWarnings("unused")
    ServerInfo s1 = registerRelay(1, "relay1", new InetSocketAddress("localhost", port), "S1", client);
    //DummyStreamConsumer listener1 = new DummyStreamConsumer("consumer1");
    final DummySuccessfulErrorCountingConsumer listener1 =
        new DummySuccessfulErrorCountingConsumer("consumer1", true);
    client.registerDatabusStreamListener(listener1 , null, "S1");

    Thread startThread = new Thread(new Runnable()
    {
      @Override
      public void run()
      {
        client.start();
      }
    }, "client start thread");
    startThread.start();

    TestUtil.assertWithBackoff(new ConditionCheck()
        {
          @Override
          public boolean check()
          {
            return client.getRelayConnections().size() == 1;
          }
        }, "client started", 1000, log);

    DatabusSourcesConnection dsc = client.getRelayConnections().get(0);

    RelayDispatcher rd = (RelayDispatcher) dsc.getRelayDispatcher();
    Assert.assertEquals(true, null != dsc);

    List<String> sources = new ArrayList<String>();
    Map<Long, IdNamePair> sourcesMap = new HashMap<Long, IdNamePair>();
    for (int i = 1; i <= 3; ++i)
    {
      IdNamePair sourcePair = new IdNamePair((long)i, "source" + i);
      sources.add(sourcePair.getName());
      sourcesMap.put(sourcePair.getId(), sourcePair);
    }

    HashMap<Long, List<RegisterResponseEntry>> schemaMap =
        new HashMap<Long, List<RegisterResponseEntry>>();

    List<RegisterResponseEntry> l1 = new ArrayList<RegisterResponseEntry>();
    List<RegisterResponseEntry> l2 = new ArrayList<RegisterResponseEntry>();
    List<RegisterResponseEntry> l3 = new ArrayList<RegisterResponseEntry>();

    final String SOURCE1_SCHEMA_STR = "{\"name\":\"source1\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}";
    final String SOURCE2_SCHEMA_STR = "{\"name\":\"source2\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}";;
    final String SOURCE3_SCHEMA_STR = "{\"name\":\"source3\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}";;

    l1.add(new RegisterResponseEntry(1L, (short)1,SOURCE1_SCHEMA_STR));
    l2.add(new RegisterResponseEntry(2L, (short)1,SOURCE2_SCHEMA_STR));
    l3.add(new RegisterResponseEntry(3L, (short)1,SOURCE3_SCHEMA_STR));

    schemaMap.put(1L, l1);
    schemaMap.put(2L, l2);
    schemaMap.put(3L, l3);

        int source1EventsNum = 2;
        int source2EventsNum = 2;

        Hashtable<Long, AtomicInteger> keyCounts = new Hashtable<Long, AtomicInteger>();
        Hashtable<Short, AtomicInteger> srcidCounts = new Hashtable<Short, AtomicInteger>();

    // Send dummy e
        DbusEventBuffer eventsBuf = dsc.getDataEventsBuffer();
        eventsBuf.start(0);
        eventsBuf.startEvents();
        initBufferWithEvents(eventsBuf, 1, source1EventsNum, (short)1, keyCounts, srcidCounts);
        initBufferWithEvents(eventsBuf, 1 + source1EventsNum, source2EventsNum, (short)2, keyCounts, srcidCounts);
        eventsBuf.endEvents(100L,null);

        rd.enqueueMessage(SourcesMessage.createSetSourcesIdsMessage(sourcesMap.values()));
        rd.enqueueMessage(SourcesMessage.createSetSourcesSchemasMessage(schemaMap));

        TestUtil.assertWithBackoff(new ConditionCheck()
        {
          @Override
          public boolean check()
          {
            return listener1._errorCount == 1;
          }
        }, "error callback", 1000, log);
    client.shutdown();
        log.info("done");
  }

    private void initBufferWithEvents(DbusEventBuffer eventsBuf,
            long keyBase,
            int numEvents,
            short srcId,
            Hashtable<Long, AtomicInteger> keyCounts,
            Hashtable<Short, AtomicInteger> srcidCounts)
    {
        if (null != srcidCounts) srcidCounts.put(srcId, new AtomicInteger(0));

        for (long i = 0; i < numEvents; ++i)
        {
            String value = "{\"s\":\"value" + i + "\"}";
            eventsBuf.appendEvent(new DbusEventKey(keyBase + i), (short)0, (short)1, (short)0, srcId,
                    new byte[16], value.getBytes(Charset.defaultCharset()), false);
            if (null != keyCounts) keyCounts.put(keyBase + i, new AtomicInteger(0));
        }
    }

    /**
     * Tests the logic of the client to disconnect from a relay while still processing
     * data from the relay. We want to make sure that ChunkedBodyReadableByteChannel
     * and thus the StreamHttpResponseProcessor do net get blocked with unprocessed data.
     * One way to trigger such an error is to insert some invalid data inside the
     * response and then some more data ( > 4MB which is the current buffer size). */
    @Test
    public void testInStreamError() throws Exception
    {
        final Logger log = Logger.getLogger("TestDatabusHttpClient.testInStreamError");
        //log.setLevel(Level.DEBUG);
        final int eventsNum = 20;
        DbusEventInfo[] eventInfos = createSampleSchema1Events(eventsNum);

        //simulate relay buffers
        DbusEventBuffer relayBuffer = new DbusEventBuffer(_bufCfg);
        relayBuffer.start(0);
        writeEventsToBuffer(relayBuffer, eventInfos, 4);

        //prepare stream response
        Checkpoint cp = Checkpoint.createFlexibleCheckpoint();
        final DbusEventsStatisticsCollector stats =
            new DbusEventsStatisticsCollector(1, "test1", true, false, null);
        ChannelBuffer streamResPrefix =
            NettyTestUtils.streamToChannelBuffer(relayBuffer, cp, 20000, stats);
        final StringBuilder alotOfDeadbeef = new StringBuilder();
        for (int i = 0; i < 1024 * 1024; ++i) {
          alotOfDeadbeef.append("DEADBEEF ");
        }
        ChannelBuffer streamResSuf =
            ChannelBuffers.wrappedBuffer(alotOfDeadbeef.toString().getBytes("UTF-8"));
        final ChannelBuffer streamRes = ChannelBuffers.wrappedBuffer(streamResPrefix, streamResSuf);

        //create client
        _stdClientCfgBuilder.getContainer().setReadTimeoutMs(DEFAULT_READ_TIMEOUT_MS);

        final DatabusHttpClientImpl client = new DatabusHttpClientImpl(_stdClientCfgBuilder.build());

        final TestConsumer consumer = new TestConsumer();
        client.registerDatabusStreamListener(consumer, null, SOURCE1_NAME);

        client.start();
        try
        {
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

          final DatabusSourcesConnection clientConn = client._relayConnections.get(0);
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection present", 100, log);

          final NettyHttpDatabusRelayConnection relayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector relayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(relayConn);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && relayConnInsp.getChannel().isConnected();
            }
          }, "client connected", 200, log);

          //figure out the connection to the relay
          Channel clientChannel = relayConnInsp.getChannel();
          InetSocketAddress relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          SocketAddress clientAddr = clientChannel.getLocalAddress();
          int relayPort = relayAddr.getPort();
          log.info("relay selected: " + relayPort);

          SimpleTestServerConnection relay = null;
          for (int i = 0; i < RELAY_PORT.length; ++i)
          {
            if (relayPort == RELAY_PORT[i]) relay = _dummyServer[i];
          }
          assertTrue(null != relay);

          final SocketAddress testClientAddr = clientAddr;
          final SimpleTestServerConnection testRelay = relay;
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != testRelay.getChildChannel(testClientAddr);
            }
          }, "relay detects new connection", 1000, log);

          Channel serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          ChannelPipeline serverPipeline = serverChannel.getPipeline();
          SimpleObjectCaptureHandler objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          HttpResponse sourcesResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                             HttpResponseStatus.OK);
          sourcesResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          sourcesResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          HttpChunk body =
              new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                   SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          log.debug("process the /register request");
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          log.debug("send back the /register response");
          RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
          String responseStr = NettyTestUtils.generateRegisterResponse(entry);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.debug("make sure the client processes the response /register correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          log.debug("process /stream call and return a response with garbled suffix");
          NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*", 1000);
          objCapture.clear();

          final HttpResponse streamResp =
              new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
          streamResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          streamResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);

          //send the response asynchronously in case the client blocks
          final Thread streamRespThread = new Thread(new Runnable()
          {

            @Override
            public void run()
            {
              NettyTestUtils.sendServerResponses(testRelay, testClientAddr, streamResp,
                                                 new DefaultHttpChunk(streamRes),
                                                 60000);
            }
          }, "send /stream resp");
          streamRespThread.setDaemon(true);
          streamRespThread.start();

          log.debug("make sure the client disconnects and recovers from the /stream response");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && !relayConnInsp.getChannel().isConnected();
            }
          }, "client disconnected", 30000, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return clientConn.getRelayPullThread().getLastOpenConnection() != relayConn;
            }
          }, "new netty connection", 30000, log);

          //make sure the relay send thread is dead
          streamRespThread.join(100);
          Assert.assertTrue(!streamRespThread.isAlive());

          log.debug("PHASE 2: make sure the client has fully recovered and able to connect to another relay");

          final NettyHttpDatabusRelayConnection newRelayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector newRelayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(newRelayConn);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != newRelayConnInsp.getChannel() && newRelayConnInsp.getChannel().isConnected();
            }
          }, "client connected to new relay", 200, log);

          //figure out the connection to the relay
          clientChannel = newRelayConnInsp.getChannel();
          relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          clientAddr = clientChannel.getLocalAddress();
          relayPort = relayAddr.getPort();
          log.info("new relay selected: " + relayPort);

          relay = null;
          int relayIdx = 0;
          for (; relayIdx < RELAY_PORT.length; ++relayIdx)
          {
            if (relayPort == RELAY_PORT[relayIdx]) relay = _dummyServer[relayIdx];
          }
          assertTrue(null != relay);

          serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          serverPipeline = serverChannel.getPipeline();
          objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          body = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                      SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          //process the /register request
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          //send back the /register response
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          //process /stream call and return a partial window
          Matcher streamMatcher =
              NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*checkPoint=([^&]*)&.*",
                                                1000);
          String cpString = streamMatcher.group(1);
          log.debug("/stream checkpoint: " + cpString);
          objCapture.clear();

          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamResPrefix));

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("lastWrittenScn=" + clientConn.getDataEventsBuffer().lastWrittenScn());
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 20;
            }
          }, "client receives /stream response", 1100, log);


          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", 11000, log);
        }
        finally {
          client.shutdown();
        }
    }

    /**
     * Tests the logic of the client to handle Timeout that comes while processing stream request.
     *  the script:
     *     setup client and connect to one of the servers
     *     wait for /sources and register call and replay
     *     save the 'future' of the write operation for the /stream call. Replace this future down the stream with the fake one,
     *       so the notification of write completion will never come
     *     make server send only headers info first
     *     make server send data, but intercept the message before it reaches the client. At this moment fire WriteTimeout
     *        exception from a separate thread.
     *     Make sure PullerThread doesn't get two error messages (and as a result tries to setup up two new connections)
     */
    @Test
    public void testInStreamTimeOut() throws Exception
    {
        final Logger log = Logger.getLogger("TestDatabusHttpClient.testInStreamTimeout");
        //log.setLevel(Level.DEBUG);
        final int eventsNum = 20;
        DbusEventInfo[] eventInfos = createSampleSchema1Events(eventsNum);

        //simulate relay buffers
        DbusEventBuffer relayBuffer = new DbusEventBuffer(_bufCfg);
        relayBuffer.start(0);
        writeEventsToBuffer(relayBuffer, eventInfos, 4);

        //prepare stream response
        Checkpoint cp = Checkpoint.createFlexibleCheckpoint();
        final DbusEventsStatisticsCollector stats =
            new DbusEventsStatisticsCollector(1, "test1", true, false, null);

        // create ChunnelBuffer and fill it with events from relayBuffer
        ChannelBuffer streamResPrefix =
            NettyTestUtils.streamToChannelBuffer(relayBuffer, cp, 20000, stats);

        //create client
        _stdClientCfgBuilder.getContainer().setReadTimeoutMs(DEFAULT_READ_TIMEOUT_MS);

        final DatabusHttpClientImpl client = new DatabusHttpClientImpl(_stdClientCfgBuilder.build());

        final TestConsumer consumer = new TestConsumer();
        client.registerDatabusStreamListener(consumer, null, SOURCE1_NAME);

        client.start(); // connect to a relay created in SetupClass (one out of three)

        // wait until a connection made
        try
        {
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

          //get the connection
          final DatabusSourcesConnection clientConn = client._relayConnections.get(0);
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection present", 100, log);

          // figure out connection details
          final NettyHttpDatabusRelayConnection relayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector relayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(relayConn);

          // wait until client is connected
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && relayConnInsp.getChannel().isConnected();
            }
          }, "client connected", 200, log);

          //figure out which port we got connected to on the server side
          Channel clientChannel = relayConnInsp.getChannel();
          InetSocketAddress relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          int relayPort = relayAddr.getPort();
          log.info("relay selected: " + relayPort);

          // add our handler to the client's pipeline which will generate the timeout
          MockServerChannelHandler mock = new MockServerChannelHandler();
          clientChannel.getPipeline().addBefore("inflater", "mockServer", mock);
          Map<String, ChannelHandler> map = clientChannel.getPipeline().toMap();
          boolean handlerFound = false;
          for(Map.Entry<String, ChannelHandler> m : map.entrySet()) {
            if(LOG.isDebugEnabled())
              LOG.debug(m.getKey() + "=>" + m.getValue());
            if(m.getKey().equals("mockServer"))
              handlerFound = true;
          }
          Assert.assertTrue(handlerFound, "handler added");

          SimpleTestServerConnection relay = null;
          // Find the relay's object
          for (int i = 0; i < RELAY_PORT.length; ++i)
          {
            if (relayPort == RELAY_PORT[i]) relay = _dummyServer[i];
          }
          assertTrue(null != relay);

          SocketAddress clientAddr = clientChannel.getLocalAddress();
          final SocketAddress testClientAddr = clientAddr;

          final SimpleTestServerConnection testRelay = relay;
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != testRelay.getChildChannel(testClientAddr);
            }
          }, "relay detects new connection", 1000, log);

          Channel serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          ChannelPipeline serverPipeline = serverChannel.getPipeline();
          SimpleObjectCaptureHandler objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          HttpResponse sourcesResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                             HttpResponseStatus.OK);
          sourcesResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          sourcesResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          HttpChunk body =
              new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                   SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          log.debug("process the /register request");
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          String msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog();
          log.debug("MSG HISTORY before: " + msgHistory);

          // make sure our handler will save the 'future' of the next write operation - 'stream'
          mock.enableSaveTheFuture(true);

          log.debug("send back the /register response");
          RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
          String responseStr = NettyTestUtils.generateRegisterResponse(entry);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.debug("make sure the client processes the response /register correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          log.debug("process /stream call and return a response");
          NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*", 1000);
          objCapture.clear();

          //disable save future as it should be saved by now
          mock.enableSaveTheFuture(false);

          final HttpResponse streamResp =
              new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
          streamResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          streamResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);

          // timeout for local netty calls (in test only)
          int timeout = 1000;
          // send header info
          relay.sendServerResponse(clientAddr, sourcesResp, timeout);
          TestUtil.sleep(1000);

          // when write data arrives from the server - we want to simulate/throw WriteTimeoutException
          mock.enableThrowWTOException(true);

          // send data
          relay.sendServerResponse(clientAddr, new DefaultHttpChunk(streamResPrefix), timeout);
          relay.sendServerResponse(clientAddr, HttpChunk.LAST_CHUNK, timeout);

          // make sure close channel event and future failure are propagated
          TestUtil.sleep(3000);
          // get the history and validate it
          String expectedHistory =  "[START, PICK_SERVER, REQUEST_SOURCES, SOURCES_RESPONSE_SUCCESS, REQUEST_REGISTER, REGISTER_RESPONSE_SUCCESS, REQUEST_STREAM, STREAM_REQUEST_SUCCESS, STREAM_RESPONSE_DONE, REQUEST_STREAM, STREAM_REQUEST_ERROR, PICK_SERVER, REQUEST_SOURCES]".trim();
          msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog().trim();
          LOG.info("MSG HISTORY: " + msgHistory);
          Assert.assertEquals(msgHistory, expectedHistory, "Puller thread message history doesn't match");

        }
        finally {
          client.shutdown();
        }
    }

    /**
     * same as above, but server doesn't send any data, and WriteComplete comes between WriteTimeout
     * and channel close
     * @throws Exception
     */
    @Test
    public void testInStreamTimeOut3() throws Exception
    {
        final Logger log = Logger.getLogger("TestDatabusHttpClient.testInStreamTimeout3");
        Level debugLevel = Level.DEBUG;
        log.setLevel(debugLevel);
        //Logger.getRootLogger().setLevel(Level.DEBUG);
        MockServerChannelHandler.LOG.setLevel(debugLevel);
        final int eventsNum = 20;
        DbusEventInfo[] eventInfos = createSampleSchema1Events(eventsNum);

        //simulate relay buffers
        DbusEventBuffer relayBuffer = new DbusEventBuffer(_bufCfg);
        relayBuffer.start(0);
        writeEventsToBuffer(relayBuffer, eventInfos, 4);

        //prepare stream response ??????????????//
        Checkpoint cp = Checkpoint.createFlexibleCheckpoint();
        final DbusEventsStatisticsCollector stats =
            new DbusEventsStatisticsCollector(1, "test1", true, false, null);

        // create ChunnelBuffer and fill it with events from relayBuffer
        ChannelBuffer streamResPrefix =
            NettyTestUtils.streamToChannelBuffer(relayBuffer, cp, 20000, stats);

        //create client
        _stdClientCfgBuilder.getContainer().setReadTimeoutMs(DEFAULT_READ_TIMEOUT_MS);

        final DatabusHttpClientImpl client = new DatabusHttpClientImpl(_stdClientCfgBuilder.build());

        final TestConsumer consumer = new TestConsumer();
        client.registerDatabusStreamListener(consumer, null, SOURCE1_NAME);

        client.start(); // connect to a relay created in SetupClass (one out of three)

        // wait until a connection made
        try
        {
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

          //get the connection
          final DatabusSourcesConnection clientConn = client._relayConnections.get(0);
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection present", 100, log);

          // figure out connection details
          final NettyHttpDatabusRelayConnection relayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector relayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(relayConn);
          relayConnInsp.getHandler().getLog().setLevel(debugLevel);

          // wait until client is connected
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && relayConnInsp.getChannel().isConnected();
            }
          }, "client connected", 200, log);

          //figure out which port we got connected to on the server side
          Channel clientChannel = relayConnInsp.getChannel();
          InetSocketAddress relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          int relayPort = relayAddr.getPort();
          log.info("relay selected: " + relayPort);

          // add our handler to the client's pipeline which will generate the timeout
          MockServerChannelHandler mock = new MockServerChannelHandler();
          clientChannel.getPipeline().addBefore("inflater", "mockServer", mock);
          // verify it is there
          Map<String, ChannelHandler> map = clientChannel.getPipeline().toMap();
          boolean handlerFound = false;
          for(Map.Entry<String, ChannelHandler> m : map.entrySet()) {
            if(LOG.isDebugEnabled())
              LOG.debug(m.getKey() + "=>" + m.getValue());
            if(m.getKey().equals("mockServer"))
              handlerFound = true;
          }
          Assert.assertTrue(handlerFound, "handler added");

          SimpleTestServerConnection relay = null;
          // Find the relay's object
          for (int i = 0; i < RELAY_PORT.length; ++i)
          {
            if (relayPort == RELAY_PORT[i]) relay = _dummyServer[i];
          }
          assertTrue(null != relay);

          SocketAddress clientAddr = clientChannel.getLocalAddress();
          final SocketAddress testClientAddr = clientAddr;

          final SimpleTestServerConnection testRelay = relay;
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != testRelay.getChildChannel(testClientAddr);
            }
          }, "relay detects new connection", 1000, log);

          Channel serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          ChannelPipeline serverPipeline = serverChannel.getPipeline();
          SimpleObjectCaptureHandler objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          HttpResponse httpResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                             HttpResponseStatus.OK);
          httpResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          httpResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          HttpChunk body =
              new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                   SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, httpResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          log.debug("process the /register request");
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          String msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog();
          log.debug("MSG HISTORY before: " + msgHistory);

          // make sure our handler will save the 'future' of the next write operation - 'stream'
          mock.enableSaveTheFuture(true);
          // delay write complete. insert Timeout exception before that
          mock.delayWriteComplete(true);

          log.debug("send back the /register response");
          RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
          String responseStr = NettyTestUtils.generateRegisterResponse(entry);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));

          NettyTestUtils.sendServerResponses(relay, clientAddr, httpResp, body);

          log.debug("make sure the client processes the response /register correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          LOG.info("*************>Message state after write complete is " + relayConnInsp.getResponseHandlerMessageState().toString());
          msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog();
          log.debug("MSG HISTORY after: " + msgHistory);
          Assert.assertEquals(countOccurencesOfWord(msgHistory, "_ERROR"), 1); //should be one error only
//////////////////////////////////////////////////////////////////////////////////////////////////
          /*
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

          //get the connection
          final DatabusSourcesConnection clientConn1 = client._relayConnections.get(0);
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn1.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection1 present", 100, log);

          // figure out connection details
          final NettyHttpDatabusRelayConnection relayConn1 =
              (NettyHttpDatabusRelayConnection)clientConn1.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector relayConnInsp1 =
              new NettyHttpDatabusRelayConnectionInspector(relayConn1);
          relayConnInsp1.getHandler().getLog().setLevel(debugLevel);

          // wait until client is connected
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp1.getChannel() && relayConnInsp1.getChannel().isConnected();
            }
          }, "client connected", 200, log);

          //figure out which port we got connected to on the server side
          Channel clientChannel1 = relayConnInsp1.getChannel();
          InetSocketAddress relayAddr1 = (InetSocketAddress)clientChannel1.getRemoteAddress();
          relayPort = relayAddr1.getPort();
          log.info("relay selected: " + relayPort);


          // do it again - no errors
          //  process the /sources request
          captureAndReplySourcesRequest(objCapture, relay, clientAddr, clientConn1, log);

          captureAndReplyRegisterRequest(objCapture, relay, clientAddr, clientConn1, log);

          log.debug("process /stream call and return a response");
          captureAndReplyStreamRequest(objCapture, relay, clientAddr, clientConn1, streamResPrefix, log);


          LOG.info("*************>Message state after write complete is " + relayConnInsp1.getResponseHandlerMessageState().toString());
          msgHistory = clientConn1.getRelayPullThread().getMessageHistoryLog();
          log.debug("MSG HISTORY after: " + msgHistory);
          Assert.assertEquals(countOccurencesOfWord(msgHistory, "_ERROR"), 1); //should be one error only



          // make sure close channel event and future failure are propagated
          TestUtil.sleep(3000);
          // get the history and validate it
          String expectedHistory =  "[START, PICK_SERVER, REQUEST_SOURCES, SOURCES_RESPONSE_SUCCESS, REQUEST_REGISTER, REGISTER_RESPONSE_SUCCESS, REQUEST_STREAM, STREAM_REQUEST_SUCCESS, STREAM_RESPONSE_DONE, REQUEST_STREAM, STREAM_REQUEST_ERROR, PICK_SERVER, REQUEST_SOURCES]".trim();
          msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog().trim();
          LOG.info("MSG HISTORY: " + msgHistory);
          Assert.assertEquals(msgHistory, expectedHistory, "Puller thread message history doesn't match");
*/
        }
        finally {
          client.shutdown();
        }
    }

    private void captureAndReplySourcesRequest(SimpleObjectCaptureHandler objCapture,
                                                SimpleTestServerConnection relay,
                                                SocketAddress clientAddr,
                                                final DatabusSourcesConnection clientConn,
                                                final Logger log) {
      NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
      objCapture.clear();

      //send back the /sources response
      HttpResponse sourcesResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                         HttpResponseStatus.OK);
      sourcesResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
      sourcesResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
      HttpChunk body =
          new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                               SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
      NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

    //make sure the client processes the response correctly
      TestUtil.assertWithBackoff(new ConditionCheck()
      {
        @Override
        public boolean check()
        {
          String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
          return "1".equals(idListString);
        }
      }, "client processes /sources response", 100, log);
    }

    private void captureAndReplyRegisterRequest(SimpleObjectCaptureHandler objCapture,
                                            SimpleTestServerConnection relay,
                                            SocketAddress clientAddr,
                                            final DatabusSourcesConnection clientConn,
                                            Logger log)
      throws JsonGenerationException, JsonMappingException, IOException {

      log.debug("process the /register request");
      NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
      objCapture.clear();

      log.debug("send back the /register response");
      RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
      String responseStr = NettyTestUtils.generateRegisterResponse(entry);
      HttpResponse registerResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                         HttpResponseStatus.OK);
      HttpChunk body = new DefaultHttpChunk(
                                  ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
      NettyTestUtils.sendServerResponses(relay, clientAddr, registerResp, body);

      log.debug("make sure the client processes the response /register correctly");
      TestUtil.assertWithBackoff(new ConditionCheck()
      {
        @Override
        public boolean check()
        {
          DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
          return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
        }
      }, "client processes /register response", 100, log);
    }

    private void captureAndReplyStreamRequest(SimpleObjectCaptureHandler objCapture,
                                                SimpleTestServerConnection relay,
                                                SocketAddress clientAddr,
                                                final DatabusSourcesConnection clientConn,
                                                ChannelBuffer streamResPrefix,
                                                Logger log) {
      NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*", 1000);
      objCapture.clear();

      final HttpResponse streamResp =
          new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
      streamResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
      streamResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);

      // timeout for local netty calls (in test only)
      int timeout = 1000;
      // send header info
      relay.sendServerResponse(clientAddr, streamResp, timeout);
      TestUtil.sleep(1000);

      // send data
      relay.sendServerResponse(clientAddr, new DefaultHttpChunk(streamResPrefix), timeout);
      relay.sendServerResponse(clientAddr, HttpChunk.LAST_CHUNK, timeout);

    }

    @Test
    public void testCountOccurnces() {
      String hay =  "[STARTSTARTT, START, PICK_SERVER, REQUEST_SOURCES, SOURCES_RESPONSE_SUCCESS, REQUEST_REGISTER, REGISTER_RESPONSE_SUCCESS, REQUEST_STREAM, STREAM_REQUEST_SUCCESS, STREAM_RESPONSE_DONE, REQUEST_STREAM, STREAM_REQUEST_ERROR, PICK_SERVER, REQUEST_SOURCES]";
      Assert.assertEquals(countOccurencesOfWord(hay, "START"), 1);
      Assert.assertEquals(countOccurencesOfWord(hay, "STARTING"), 0);
      Assert.assertEquals(countOccurencesOfWord(hay, "REQUEST_STREAM"), 2);
      Assert.assertEquals(countOccurencesOfWord(hay, "REQUEST_SOURCES"), 2);

    }
    private int countOccurencesOfWord(String hay, String needle) {
      Pattern pattern = Pattern.compile(needle+"[,?|\\]]");
      Matcher m = pattern.matcher(hay);
      int count = 0;

      while(m.find()) {
        count ++;
      }
      return count;
    }

    /**
     * Tests the logic of the client to handle Timeout that comes while processing stream request.
     *  the script:
     *     setup client and connect to one of the servers
     *     wait for /sources and register call and replay
     *     save the 'future' of the write operation for the /stream call. Replace this future down the stream with the fake one,
     *       so the notification of write completion will never come
     *     make server send only headers info first
     *     make server send data, but intercept the message before it reaches the client. At this moment fire WriteTimeout
     *        exception from a separate thread.
     *     Make sure PullerThread doesn't get two error messages (and as a result tries to setup up two new connections)
     */
    @Test
    public void testInStreamTimeOut2() throws Exception
    {
        final Logger log = Logger.getLogger("TestDatabusHttpClient.testInStreamTimeout2");
        MockServerChannelHandler.LOG.setLevel(Level.DEBUG);
        //log.setLevel(Level.);
        final int eventsNum = 20;
        DbusEventInfo[] eventInfos = createSampleSchema1Events(eventsNum);

        //simulate relay buffers
        DbusEventBuffer relayBuffer = new DbusEventBuffer(_bufCfg);
        relayBuffer.start(0);
        writeEventsToBuffer(relayBuffer, eventInfos, 4);

        //prepare stream response
        Checkpoint cp = Checkpoint.createFlexibleCheckpoint();
        final DbusEventsStatisticsCollector stats =
            new DbusEventsStatisticsCollector(1, "test1", true, false, null);

        // create ChunnelBuffer and fill it with events from relayBuffer
        ChannelBuffer streamResPrefix =
            NettyTestUtils.streamToChannelBuffer(relayBuffer, cp, 20000, stats);

        //create client
        _stdClientCfgBuilder.getContainer().setReadTimeoutMs(DEFAULT_READ_TIMEOUT_MS);

        final DatabusHttpClientImpl client = new DatabusHttpClientImpl(_stdClientCfgBuilder.build());

        final TestConsumer consumer = new TestConsumer();
        client.registerDatabusStreamListener(consumer, null, SOURCE1_NAME);

        client.start(); // connect to a relay created in SetupClass (one out of three)

        // wait until a connection made
        try
        {
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

          //get the connection
          final DatabusSourcesConnection clientConn = client._relayConnections.get(0);
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection present", 100, log);

          // figure out connection details
          final NettyHttpDatabusRelayConnection relayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector relayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(relayConn);

          // wait until client is connected
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && relayConnInsp.getChannel().isConnected();
            }
          }, "client connected", 200, log);

          //figure out which port we got connected to on the server side
          Channel clientChannel = relayConnInsp.getChannel();
          InetSocketAddress relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          int relayPort = relayAddr.getPort();
          log.info("relay selected: " + relayPort);

          // add our handler to the client's pipeline which will generate the timeout
          MockServerChannelHandler mock = new MockServerChannelHandler();
          clientChannel.getPipeline().addBefore("inflater", "mockServer", mock);
          Map<String, ChannelHandler> map = clientChannel.getPipeline().toMap();
          boolean handlerFound = false;
          for(Map.Entry<String, ChannelHandler> m : map.entrySet()) {
            if(LOG.isDebugEnabled())
              LOG.debug(m.getKey() + "=>" + m.getValue());
            if(m.getKey().equals("mockServer"))
              handlerFound = true;
          }
          Assert.assertTrue(handlerFound, "handler added");

          SimpleTestServerConnection relay = null;
          // Find the relay's object
          for (int i = 0; i < RELAY_PORT.length; ++i)
          {
            if (relayPort == RELAY_PORT[i]) relay = _dummyServer[i];
          }
          assertTrue(null != relay);

          SocketAddress clientAddr = clientChannel.getLocalAddress();
          final SocketAddress testClientAddr = clientAddr;

          final SimpleTestServerConnection testRelay = relay;
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != testRelay.getChildChannel(testClientAddr);
            }
          }, "relay detects new connection", 1000, log);

          Channel serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          ChannelPipeline serverPipeline = serverChannel.getPipeline();
          SimpleObjectCaptureHandler objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          HttpResponse sourcesResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                             HttpResponseStatus.OK);
          sourcesResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          sourcesResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          HttpChunk body =
              new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                   SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          log.info("process the /register request");
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          String msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog();
          log.info("MSG HISTORY before: " + msgHistory);

          // make sure our handler will save the 'future' of the next write operation - 'stream'
          mock.enableSaveTheFuture(true);

          log.info("send back the /register response");
          RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
          String responseStr = NettyTestUtils.generateRegisterResponse(entry);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.info("make sure the client processes the response /register correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);
          mock.disableWriteComplete(true);
          log.info("process /stream call and return a response");
          NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*", 1000);
          objCapture.clear();
          log.info("***1");
          //disable save future as it should be saved by now
          mock.enableSaveTheFuture(false);
          log.info("***2");
          final HttpResponse streamResp =
              new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
          streamResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          streamResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          log.info("***3");
          // timeout for local netty calls (in test only)
          int timeout = 1000;
          // send header info
          relay.sendServerResponse(clientAddr, sourcesResp, timeout);
          TestUtil.sleep(1000);
          log.info("***4");
          // when write data arrives from the server - we want to simulate/throw WriteTimeoutException
          mock.enableThrowWTOException(true);

          // send data
          relay.sendServerResponse(clientAddr, new DefaultHttpChunk(streamResPrefix), timeout);
          relay.sendServerResponse(clientAddr, HttpChunk.LAST_CHUNK, timeout);
          log.info("***5");
          // make sure close channel event and future failure are propagated
          TestUtil.sleep(3000);
          // get the history and validate it
          String expectedHistory =  "[START, PICK_SERVER, REQUEST_SOURCES, SOURCES_RESPONSE_SUCCESS, REQUEST_REGISTER, REGISTER_RESPONSE_SUCCESS, REQUEST_STREAM, STREAM_REQUEST_SUCCESS, STREAM_RESPONSE_DONE, REQUEST_STREAM, STREAM_REQUEST_ERROR, PICK_SERVER, REQUEST_SOURCES]".trim();
          msgHistory = clientConn.getRelayPullThread().getMessageHistoryLog().trim();
          log.info("***6");
          LOG.info("MSG HISTORY: " + msgHistory);
          Assert.assertEquals(msgHistory, expectedHistory, "Puller thread message history doesn't match");
          log.info("***7");

        }
        catch (Exception e2){
          log.info("Got exception" + e2 );
        }
        finally {
          client.shutdown();
        }
    }

  @Test
  /**
   * R1 EVB Boundaries ------------------------------------------------------------------------------
   *                   ^                        ^                          ^                    ^
   *                   0                        30                         60                   90
   * R2 EVB Boundaries --------------------------------------------------------------------------------
   *                   ^               ^                   ^               ^            ^
   *                   0               20                  40              60           80
   *
   * R3 EVB Boundaries --------------------------------------------------------------------------------
   *                   ^      ^        ^         ^         ^       ^       ^      ^     ^        ^
   *                   0      10       20        30        40      50      60     70    80       90
   *
   *
     *  Client switches from R1 to R2 on server close and from R2 to R3 on timeout while in the middle of the windows.
   *
   *  Switch from R1 -> R2 happens when windowScn = 30 and WindowOffset = 8th event
   *  Switch from R2 -> R3 happens when windowScn = 40 and windowOffset = 5th event
   *
   * @throws Exception
   */
  public void testRelayFailoverPartialWindow2() throws Exception
  {
      final Logger log = Logger.getLogger("TestDatabusHttpClient.testRelayFailoverPartialWindow2");
      //log.setLevel(Level.DEBUG);
      final int eventsNum = 200;
      DbusEventInfo[] eventInfos = createSampleSchema1Events(eventsNum);

      //simulate relay buffers
      DbusEventBuffer[] relayBuffer = new DbusEventBuffer[RELAY_PORT.length];
      List<List<Integer>> eventOfs = new ArrayList<List<Integer>>(3);
      List<List<DbusEventKey>> eventKeys = new ArrayList<List<DbusEventKey>>(3);

      for (int i = 0; i < RELAY_PORT.length; ++i)
      {
        relayBuffer[i] = new DbusEventBuffer(_bufCfg);
        relayBuffer[i].start(0);
        WriteEventsResult wrRes = writeEventsToBuffer(relayBuffer[i], eventInfos, (RELAY_PORT.length - i) * 10);
        List<Integer> ofs = wrRes.getOffsets();
        eventOfs.add(ofs);
        eventKeys.add(wrRes.getKeys());
      }

      List<DbusEventKey> key = eventKeys.get(0);
      for (int i = 1; i < RELAY_PORT.length; ++i)
      {
        assertEquals(key, eventKeys.get(i));
        key = eventKeys.get(i);
      }
      //figure out an event offset inside a window
    int resp1EnfOfs = eventOfs.get(0).get(8);

      //create client
      _stdClientCfgBuilder.getContainer().setReadTimeoutMs(DEFAULT_READ_TIMEOUT_MS);

      final DatabusHttpClientImpl client = new DatabusHttpClientImpl(_stdClientCfgBuilder.build());

      final TestConsumer consumer = new TestConsumer();
      client.registerDatabusStreamListener(consumer, null, SOURCE1_NAME);

      client.start();
      try
      {
        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

        final DatabusSourcesConnection clientConn = client._relayConnections.get(0);
        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection present", 100, log);

        final NettyHttpDatabusRelayConnection relayConn =
            (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
        final NettyHttpDatabusRelayConnectionInspector relayConnInsp =
            new NettyHttpDatabusRelayConnectionInspector(relayConn);

        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && relayConnInsp.getChannel().isConnected();
            }
          }, "client connected", 200, log);

        //figure out the connection to the relay
        Channel clientChannel = relayConnInsp.getChannel();
        InetSocketAddress relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
        SocketAddress clientAddr = clientChannel.getLocalAddress();
        int relayPort = relayAddr.getPort();
        log.info("relay selected: " + relayPort);

        SimpleTestServerConnection relay = null;
        for (int i = 0; i < RELAY_PORT.length; ++i)
        {
          if (relayPort == RELAY_PORT[i]) relay = _dummyServer[i];
        }
        assertTrue(null != relay);

        final SocketAddress testClientAddr = clientAddr;
        final SimpleTestServerConnection testRelay = relay;
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != testRelay.getChildChannel(testClientAddr);
            }
          }, "relay detects new connection", 1000, log);

        Channel serverChannel = relay.getChildChannel(clientAddr);
        assertTrue(null != serverChannel);
        ChannelPipeline serverPipeline = serverChannel.getPipeline();
        SimpleObjectCaptureHandler objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

        //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
        objCapture.clear();

        //send back the /sources response
        HttpResponse sourcesResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                           HttpResponseStatus.OK);
        sourcesResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        sourcesResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
        HttpChunk body =
            new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                 SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
        NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

        //make sure the client processes the response correctly
        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          //process the /register request
        NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          //send back the /register response
          RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
          String responseStr = NettyTestUtils.generateRegisterResponse(entry);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          //process /stream call and return a partial window
          NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*", 1000);
          objCapture.clear();

          //send back the /stream response
          final DbusEventsStatisticsCollector stats =
              new DbusEventsStatisticsCollector(1, "test1", true, false, null);
          Checkpoint cp = Checkpoint.createFlexibleCheckpoint();
          ChannelBuffer streamRes = NettyTestUtils.streamToChannelBuffer(relayBuffer[0], cp,
                                                                          resp1EnfOfs, stats);
          HttpResponse streamResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                             HttpResponseStatus.OK);
          streamResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          streamResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamRes));

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("LastWritten SCN:" + clientConn.getDataEventsBuffer().lastWrittenScn() );
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 30;
            }
          }, "client receives /stream response", 1100, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.error("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", 110000, log);
          assertEquals(-1, consumer.getRollbackScn());
          int rollbackNum = 0;
          assertEquals(stats.getTotalStats().getNumSysEvents() + 1 + rollbackNum,
                       consumer.getWinNum());

          List<DbusEventKey> expKeys = eventKeys.get(0).subList(0, (int)stats.getTotalStats().getNumDataEvents());
          List<Long> expSeqs = new ArrayList<Long>();
          for (int i = 0; i < stats.getTotalStats().getNumDataEvents(); i++)
            expSeqs.add(30L);

          long numEvents = stats.getTotalStats().getNumDataEvents();

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          numEvents = stats.getTotalStats().getNumDataEvents();

          //now kill the relay and wait for a failover
          serverChannel.close();

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && !relayConnInsp.getChannel().isConnected();
            }
          }, "client disconnected", 200, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return clientConn.getRelayPullThread().getLastOpenConnection() != relayConn;
            }
          }, "new netty connection", 200, log);

          /////////// FAKING CONNECTION TO NEW RELAY

          final NettyHttpDatabusRelayConnection newRelayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector newRelayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(newRelayConn);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != newRelayConnInsp.getChannel() && newRelayConnInsp.getChannel().isConnected();
            }
          }, "client connected to new relay", 200, log);

          //figure out the connection to the relay
          clientChannel = newRelayConnInsp.getChannel();
          relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          clientAddr = clientChannel.getLocalAddress();
          relayPort = relayAddr.getPort();
          log.info("new relay selected: " + relayPort);

          relay = null;
          int relayIdx = 0;
          for (; relayIdx < RELAY_PORT.length; ++relayIdx)
          {
            if (relayPort == RELAY_PORT[relayIdx]) relay = _dummyServer[relayIdx];
          }
          assertTrue(null != relay);

          serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          serverPipeline = serverChannel.getPipeline();
          objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          body = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                      SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          //process the /register request
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          //send back the /register response
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          //process /stream call and return a partial window
          Matcher streamMatcher =
              NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*checkPoint=([^&]*)&.*",
                                                1000);
          String cpString = streamMatcher.group(1);
          objCapture.clear();

          int respStartOfs = eventOfs.get(1).get(1);
          int respEndOfs = eventOfs.get(1).get(26);

          cp = new Checkpoint(cpString);
          assertTrue(cp.getWindowOffset() > 0); //last window read was partial
          streamRes = NettyTestUtils.streamToChannelBuffer(relayBuffer[1], cp,
                                                           respEndOfs - respStartOfs,
                                                           stats);
          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamRes));

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("lastWrittenScn=" + clientConn.getDataEventsBuffer().lastWrittenScn());
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 40;
            }
          }, "client receives /stream response", 1100, log);


          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", 11000, log);
          assertEquals(20, consumer.getRollbackScn());
          //add one more onStartDataEventsSequence after the rollback()
          ++rollbackNum;
          assertEquals(stats.getTotalStats().getNumSysEvents() + 1 + rollbackNum, consumer.getWinNum());
          expKeys.addAll(eventKeys.get(1).subList(0, (int)(stats.getTotalStats().getNumDataEvents() - numEvents)));
          for (int i = 0; i < (stats.getTotalStats().getNumDataEvents() - numEvents); i++)
            expSeqs.add((i/20)*20 + 20L);

          log.info("Expected NumKeys :" + expKeys.size() + ", Got NumKeys :" + consumer.getKeys().size());
          for(int i = 0; i < expKeys.size(); i++)
          {
            if (! consumer.getKeys().contains(expKeys.get(i)))
              log.error(i + " Key :" + expKeys.get(i) + " missing !!");
            else
              log.info(i + " Key :" + expKeys.get(i) + " present !!");
          }

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          numEvents = stats.getTotalStats().getNumDataEvents();

          assertEquals(clientConn.getRelayPullThread().getConnectionState().getDataEventsBuffer().isSCNRegress(), false);
          ///////////////////////////////////
          //simulate a timeout on the server; the client would have sent a /stream call and there
          //will be no response from the server, so eventually it should time out and switch servers
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("Channel :" + newRelayConnInsp.getChannel());
              return (null != newRelayConnInsp.getChannel()) && (!newRelayConnInsp.getChannel().isConnected());
            }
          }, "waiting for a reconnect", (long)(DEFAULT_READ_TIMEOUT_MS * 1.5), log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return clientConn.getRelayPullThread().getLastOpenConnection() != relayConn;
            }
          }, "new netty connection", 200, log);

          final NettyHttpDatabusRelayConnection new2RelayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector new2RelayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(new2RelayConn);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != new2RelayConnInsp.getChannel() && new2RelayConnInsp.getChannel().isConnected();
            }
          }, "client connected to third relay", 200, log);

          //figure out the connection to the relay
          clientChannel = new2RelayConnInsp.getChannel();
          relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          clientAddr = clientChannel.getLocalAddress();
          relayPort = relayAddr.getPort();
          log.info("third relay selected: " + relayPort);


          relay = null;
          relayIdx = 0;
          for (; relayIdx < RELAY_PORT.length; ++relayIdx)
          {
            if (relayPort == RELAY_PORT[relayIdx]) relay = _dummyServer[relayIdx];
          }
          assertTrue(null != relay);

          serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          serverPipeline = serverChannel.getPipeline();
          objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          //process the /sources request
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          //send back the /sources response
          body = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                      SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          //process the /register request
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          //send back the /register response
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /sources response", 100, log);

          //process /stream call and return a partial window
          streamMatcher =
              NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*checkPoint=([^&]*)&.*",
                                                1000);
          cpString = streamMatcher.group(1);
          objCapture.clear();

          respStartOfs = eventOfs.get(2).get(20);
          respEndOfs = eventOfs.get(2).get(89);

          log.debug("Checkpoint String is :" + cpString);


          cp = new Checkpoint(cpString);
          //last window read was partial. So the client would have reset the windowOffset
          assertTrue("Is WindowOffset Cleared",cp.getWindowOffset() == -1);
          assertEquals( "WindowSCN == PrevSCN. Ckpt :" + cp, cp.getWindowScn(), cp.getPrevScn());
          streamRes = NettyTestUtils.streamToChannelBuffer(relayBuffer[2], cp,
                                                           respEndOfs - respStartOfs,
                                                           stats);
          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamRes));

          //make sure the client processes the response correctly
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("lastWrittenScn=" + clientConn.getDataEventsBuffer().lastWrittenScn());
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 90;
            }
          }, "client receives /stream response, Sequence :" + consumer.getSequences(), 10100, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", 110000, log);

          assertEquals(30, consumer.getRollbackScn());
          //add one more onStartDataEventsSequence because of the rollback
          ++rollbackNum;
          assertEquals(stats.getTotalStats().getNumSysEvents() + 1 + rollbackNum, consumer.getWinNum());

          expKeys.addAll(eventKeys.get(2).subList(20, (int)(stats.getTotalStats().getNumDataEvents() - numEvents + 20)));
          for (int i = 0; i < (stats.getTotalStats().getNumDataEvents() - numEvents); i++)
            expSeqs.add((i/10)*10 + 30L);

          numEvents = stats.getTotalStats().getNumDataEvents();

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences Size ", expSeqs.size(), consumer.getSequences().size());
          for (int i = 0; i <expSeqs.size(); i++ )
          {
            System.out.println(i + " Exp : " + expSeqs.get(i) + ", Got :" + consumer.getSequences().get(i));
            if ( expSeqs.get(i) != consumer.getSequences().get(i))
               throw new RuntimeException("Failed at "+ i);
          }
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          numEvents = stats.getTotalStats().getNumDataEvents();
          assertEquals(clientConn.getRelayPullThread().getConnectionState().getDataEventsBuffer().isSCNRegress(), false);

          // Expect none of the keys streamed to be missed.
          expKeys.clear();
          cp = clientConn.getRelayPullThread().getConnectionState().getCheckpoint();
          int offset = (int)((cp.getWindowOffset() < 0) ? 0 : cp.getWindowOffset());
          LOG.info("Client ckpt is :" + cp);
          expKeys.addAll(eventKeys.get(2).subList(0, 80 + offset));
          for(int i = 0; i < expKeys.size(); i++)
          {
            if (! consumer.getKeys().contains(expKeys.get(i)))
            {
              log.error(i + " Key :" + expKeys.get(i) + " missing !!");
                 throw new RuntimeException("Failed at "+ i);
            }
            else
              log.info(i + " Key :" + expKeys.get(i) + " present !!");
          }
      } finally {
        client.shutdown();
      }
  }


  /**
   * Client switches from R1 to R2 on server close and from R2 to R3 on timeout while in the middle of the windows.
   *
   * <pre>
   * R1 EVB Boundaries --------------------------------------------------------------------------------
   *                   ^      ^        ^         ^         ^       ^       ^      ^     ^        ^
   *                   0      10       20        30        40      50      60     70    80       90
   *
   * R2 EVB Boundaries --------------------------------------------------------------------------------
   *                   ^               ^                   ^               ^            ^
   *                   0               20                  40              60           80
   *
   * R3 EVB Boundaries --------------------------------------------------------------------------------
   *                   ^                        ^                          ^                    ^
   *                   0                        30                         60                   90
   * </pre>
   *
   * Switch from R1 to R2 happens when windowScn = 10 and windowOffset = 8th event.
   * Switch from R2 to R3 happens when windowScn = 40 and windowOffset = 3rd event.
   *
   * @throws Exception
   */
  @Test
    public void testRelayFailoverPartialWindow1() throws Exception
    {
      final boolean debugOn = false;
      final Logger log = Logger.getLogger("TestDatabusHttpClient.testRelayFailoverPartialWindow1");
      log.setLevel(Level.INFO);
      final int eventsNum = 200;
      DbusEventInfo[] eventInfos = createSampleSchema1Events(eventsNum);
      final long timeoutMult = debugOn ? 100000 : 1;

      log.info("simulate relay buffers");
      DbusEventBuffer[] relayBuffer = new DbusEventBuffer[RELAY_PORT.length];
      List<List<Integer>> eventOfs = new ArrayList<List<Integer>>(3);
      List<List<DbusEventKey>> eventKeys = new ArrayList<List<DbusEventKey>>(3);

      for (int i = 0; i < RELAY_PORT.length; ++i)
      {
        relayBuffer[i] = new DbusEventBuffer(_bufCfg);
        relayBuffer[i].start(0);
        WriteEventsResult wrRes = writeEventsToBuffer(relayBuffer[i], eventInfos, (i + 1) * 10);
        List<Integer> ofs = wrRes.getOffsets();
        eventOfs.add(ofs);
        eventKeys.add(wrRes.getKeys());
      }

      List<DbusEventKey> key = eventKeys.get(0);
      for (int i = 1; i < RELAY_PORT.length; ++i)
      {
        assertEquals(" For Size index : " + i,key.size(), eventKeys.get(i).size());
        assertEquals(" For index : " + i,key, eventKeys.get(i));
        key = eventKeys.get(i);
      }

      int resp1EnfOfs = eventOfs.get(0).get(8);
        log.info("figure out an event offset inside a window:" + resp1EnfOfs);

      log.info("create client");
      _stdClientCfgBuilder.getContainer().setReadTimeoutMs(DEFAULT_READ_TIMEOUT_MS);

      final DatabusHttpClientImpl client = new DatabusHttpClientImpl(_stdClientCfgBuilder.build());

      final TestConsumer consumer = new TestConsumer();
      client.registerDatabusStreamListener(consumer, null, SOURCE1_NAME);

      client.start();
      try
      {
        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return client._relayConnections.size() == 1;
            }
          }, "sources connection present", 100, log);

        final DatabusSourcesConnection clientConn = client._relayConnections.get(0);
        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != clientConn.getRelayPullThread().getLastOpenConnection();
            }
          }, "relay connection present", 100, log);

        final NettyHttpDatabusRelayConnection relayConn =
            (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
        final NettyHttpDatabusRelayConnectionInspector relayConnInsp =
            new NettyHttpDatabusRelayConnectionInspector(relayConn);

        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && relayConnInsp.getChannel().isConnected();
            }
          }, "client connected", 200, log);

        log.info("figure out the connection to the relay");
        Channel clientChannel = relayConnInsp.getChannel();
        InetSocketAddress relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
        SocketAddress clientAddr = clientChannel.getLocalAddress();
        int relayPort = relayAddr.getPort();
        log.info("relay selected: " + relayPort);

        SimpleTestServerConnection relay = null;
        for (int i = 0; i < RELAY_PORT.length; ++i)
        {
          if (relayPort == RELAY_PORT[i]) relay = _dummyServer[i];
        }
        assertTrue(null != relay);

        final SocketAddress testClientAddr = clientAddr;
        final SimpleTestServerConnection testRelay = relay;
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != testRelay.getChildChannel(testClientAddr);
            }
          }, "relay detects new connection", 1000, log);

        Channel serverChannel = relay.getChildChannel(clientAddr);
        assertTrue(null != serverChannel);
        ChannelPipeline serverPipeline = serverChannel.getPipeline();
        SimpleObjectCaptureHandler objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          log.info("process the /sources request");
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
        objCapture.clear();

        log.info("send back the /sources response");
        HttpResponse sourcesResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                           HttpResponseStatus.OK);
        sourcesResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        sourcesResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
        HttpChunk body =
            new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                 SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
        NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

        log.info("make sure the client processes the response correctly");
        TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

        log.info("process the /register request");
        NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          log.info("send back the /register response");
          RegisterResponseEntry entry = new RegisterResponseEntry(1L, (short)1, SOURCE1_SCHEMA_STR);
          String responseStr = NettyTestUtils.generateRegisterResponse(entry);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.info("make sure the client processes the response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          log.info("process /stream call and return a partial window");
          NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*", 1000);
          objCapture.clear();

          log.info("send back the /stream response");
          final DbusEventsStatisticsCollector stats =
              new DbusEventsStatisticsCollector(1, "test1", true, false, null);
          Checkpoint cp = Checkpoint.createFlexibleCheckpoint();
          ChannelBuffer streamRes = NettyTestUtils.streamToChannelBuffer(relayBuffer[0], cp,
                                                                          resp1EnfOfs, stats);
          HttpResponse streamResp = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                             HttpResponseStatus.OK);
          streamResp.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
          streamResp.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamRes));

          log.info("make sure the client processes the /stream response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("LastWritten SCN:" + clientConn.getDataEventsBuffer().lastWrittenScn() );
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 10;
            }
          }, "client receives /stream response", 1100, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", 110000, log);
          assertEquals(-1, consumer.getRollbackScn());
          int rollbackNum = 0;
          assertEquals(stats.getTotalStats().getNumSysEvents() + 1 + rollbackNum, consumer.getWinNum());

          List<DbusEventKey> expKeys = eventKeys.get(0).subList(0, (int)stats.getTotalStats().getNumDataEvents());
          List<Long> expSeqs = new ArrayList<Long>();
          for (int i = 0; i < stats.getTotalStats().getNumDataEvents(); i++)
            expSeqs.add(10L);

          long numEvents = stats.getTotalStats().getNumDataEvents();

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          log.info("now kill the relay and wait for a failover");
          serverChannel.close();

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != relayConnInsp.getChannel() && !relayConnInsp.getChannel().isConnected();
            }
          }, "client disconnected", 200, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return clientConn.getRelayPullThread().getLastOpenConnection() != relayConn;
            }
          }, "new netty connection", 200, log);

          log.info("/////////// FAKING CONNECTION TO NEW RELAY //////////////");

          final NettyHttpDatabusRelayConnection newRelayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector newRelayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(newRelayConn);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != newRelayConnInsp.getChannel() && newRelayConnInsp.getChannel().isConnected();
            }
          }, "client connected to new relay", 200, log);

          log.info("figure out the connection to the relay");
          clientChannel = newRelayConnInsp.getChannel();
          relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          clientAddr = clientChannel.getLocalAddress();
          relayPort = relayAddr.getPort();
          log.info("new relay selected: " + relayPort);

          relay = null;
          int relayIdx = 0;
          for (; relayIdx < RELAY_PORT.length; ++relayIdx)
          {
            if (relayPort == RELAY_PORT[relayIdx]) relay = _dummyServer[relayIdx];
          }
          assertTrue(null != relay);

          serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          serverPipeline = serverChannel.getPipeline();
          objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          log.info("process the /sources request");
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          log.info("send back the /sources response");
          body = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                      SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.info("make sure the client processes the response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          log.info("process the /register request");
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          log.info("send back the /register response");
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.info("make sure the client processes the /register response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", 100, log);

          log.info("process /stream call and return a partial window");
          Matcher streamMatcher =
              NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*checkPoint=([^&]*)&.*",
                                                1000);
          String cpString = streamMatcher.group(1);
          objCapture.clear();

          int respStartOfs = eventOfs.get(1).get(1);
          int respEndOfs = eventOfs.get(1).get(34);

          cp = new Checkpoint(cpString);
          assertTrue(cp.getWindowOffset() > 0); //last window read was partial
          streamRes = NettyTestUtils.streamToChannelBuffer(relayBuffer[1], cp,
                                                           respEndOfs - respStartOfs,
                                                           stats);
          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamRes));

          log.info("make sure the client processes the response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("lastWrittenScn=" + clientConn.getDataEventsBuffer().lastWrittenScn());
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 40;
            }
          }, "client receives /stream response", 1100, log);


          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", 11000, log);
          assertEquals(20, consumer.getRollbackScn());

          log.info("one more onStartDataEventSequence because of the rolback");
          ++rollbackNum;
          assertEquals(stats.getTotalStats().getNumSysEvents() + 1 + rollbackNum, consumer.getWinNum());

          assertEquals(clientConn.getRelayPullThread().getConnectionState().getDataEventsBuffer().isSCNRegress(), false);
          expKeys.addAll(eventKeys.get(1).subList(0, (int)(stats.getTotalStats().getNumDataEvents() - numEvents)));
          for (int i = 0; i < stats.getTotalStats().getNumDataEvents() - numEvents; i++)
            expSeqs.add((i/20)*20 + 20L);

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          numEvents = stats.getTotalStats().getNumDataEvents();

          ///////////////////////////////////
          //simulate a timeout on the server; the client would have sent a /stream call and there
          //will be no response from the server, so eventually it should time out and switch servers
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("Channel :" + newRelayConnInsp.getChannel());
              return (null != newRelayConnInsp.getChannel()) && (!newRelayConnInsp.getChannel().isConnected());
            }
          }, "waiting for a reconnect", (long)(DEFAULT_READ_TIMEOUT_MS * 1.5), log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return clientConn.getRelayPullThread().getLastOpenConnection() != relayConn;
            }
          }, "new netty connection", 200, log);

          final NettyHttpDatabusRelayConnection new2RelayConn =
              (NettyHttpDatabusRelayConnection)clientConn.getRelayPullThread().getLastOpenConnection();
          final NettyHttpDatabusRelayConnectionInspector new2RelayConnInsp =
              new NettyHttpDatabusRelayConnectionInspector(new2RelayConn);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              return null != new2RelayConnInsp.getChannel() && new2RelayConnInsp.getChannel().isConnected();
            }
          }, "client connected to third relay", 200, log);

          log.info("figure out the connection to the relay");
          clientChannel = new2RelayConnInsp.getChannel();
          relayAddr = (InetSocketAddress)clientChannel.getRemoteAddress();
          clientAddr = clientChannel.getLocalAddress();
          relayPort = relayAddr.getPort();
          log.info("third relay selected: " + relayPort);


          relay = null;
          relayIdx = 0;
          for (; relayIdx < RELAY_PORT.length; ++relayIdx)
          {
            if (relayPort == RELAY_PORT[relayIdx]) relay = _dummyServer[relayIdx];
          }
          assertTrue(null != relay);

          serverChannel = relay.getChildChannel(clientAddr);
          assertTrue(null != serverChannel);
          serverPipeline = serverChannel.getPipeline();
          objCapture = (SimpleObjectCaptureHandler)serverPipeline.get("3");

          log.info("process the /sources request");
          NettyTestUtils.waitForHttpRequest(objCapture, SOURCES_REQUEST_REGEX, 1000);
          objCapture.clear();

          log.info("send back the /sources response");
          body = new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(("[{\"id\":1,\"name\":\"" +
                                      SOURCE1_NAME + "\"}]").getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.info("make sure the client processes the response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              String idListString = clientConn.getRelayPullThread()._currentState.getSourcesIdListString();
              return "1".equals(idListString);
            }
          }, "client processes /sources response", 100, log);

          log.info("process the /register request");
          NettyTestUtils.waitForHttpRequest(objCapture, "/register.*", 1000);
          objCapture.clear();

          log.info("SEND BACK THE /register RESPONSE");
          //clientConn.getRelayDispatcher().getLog().setLevel(Level.DEBUG);
          //RangeBasedReaderWriterLock.LOG.setLevel(Level.DEBUG);
          body = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(responseStr.getBytes(Charset.defaultCharset())));
          NettyTestUtils.sendServerResponses(relay, clientAddr, sourcesResp, body);

          log.info("make sure the client processes the response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              DispatcherState dispState = clientConn.getRelayDispatcher().getDispatcherState();
              return null != dispState.getSchemaMap() && 1 == dispState.getSchemaMap().size();
            }
          }, "client processes /register response", timeoutMult * 100, log);

          log.info("PROCESS the /stream CALL AND RETURN A PARTIAL WINDOW");
          streamMatcher =
              NettyTestUtils.waitForHttpRequest(objCapture, "/stream.*checkPoint=([^&]*)&.*",
                                                1000);
          cpString = streamMatcher.group(1);
          objCapture.clear();

          respStartOfs = eventOfs.get(2).get(1);
          respEndOfs = eventOfs.get(2).get(84);

          log.debug("Checkpoint String is :" + cpString);

          cp = new Checkpoint(cpString);
          log.info("last window read was partial. So the client would have reset the windowOffset");
          assertTrue("Is WindowOffset Cleared",cp.getWindowOffset() == -1);
          assertEquals( "WindowSCN == PrevSCN. Ckpt :" + cp, cp.getWindowScn(), cp.getPrevScn());
          streamRes = NettyTestUtils.streamToChannelBuffer(relayBuffer[2], cp,
                                                           respEndOfs - respStartOfs,
                                                           stats);
          NettyTestUtils.sendServerResponses(relay, clientAddr, streamResp,
                                             new DefaultHttpChunk(streamRes));

          log.debug("NumEvents already seen :" + numEvents);

          log.info("make sure the client processes the response correctly");
          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("lastWrittenScn=" + clientConn.getDataEventsBuffer().lastWrittenScn() + ", NumEvents :" + stats.getTotalStats().getNumDataEvents() );
              return clientConn.getDataEventsBuffer().lastWrittenScn() == 90;
            }
          }, "client receives /stream response, Sequences :" + consumer.getSequences(),
             timeoutMult * 1100, log);

          TestUtil.assertWithBackoff(new ConditionCheck()
          {
            @Override
            public boolean check()
            {
              log.debug("events num=" + consumer.getEventNum());
              return stats.getTotalStats().getNumDataEvents() == consumer.getEventNum();
            }
          }, "client processes /stream response", timeoutMult * 1100, log);

          log.info("one more onStartDataEventSequence because of the rollback");
          assertEquals(30, consumer.getRollbackScn());
          ++rollbackNum;
          assertEquals(stats.getTotalStats().getNumSysEvents() + 1 + rollbackNum, consumer.getWinNum());

          expKeys.addAll(eventKeys.get(2).subList(0, (int)(stats.getTotalStats().getNumDataEvents() - numEvents)));
          for (int i = 0; i < stats.getTotalStats().getNumDataEvents() - numEvents; i++)
            expSeqs.add((i/30)*30 + 30L);

          assertEquals("Keys", expKeys, consumer.getKeys());
          assertEquals("Sequences", expSeqs, consumer.getSequences());

          numEvents = stats.getTotalStats().getNumDataEvents();

          assertEquals(clientConn.getRelayPullThread().getConnectionState().getDataEventsBuffer().isSCNRegress(), false);
      }
      finally
      {
        client.shutdown();
      }
    }

  @Test
  public void testStartNoConsumers() throws Exception
  {
    DatabusHttpClientImpl.Config clientConfig = new DatabusHttpClientImpl.Config();
    clientConfig.getContainer().getJmx().setRmiEnabled(false);
    clientConfig.getContainer().setHttpPort(10004);
    final DatabusHttpClientImpl client = new DatabusHttpClientImpl(clientConfig);

    Thread startThread = new Thread(new Runnable()
    {
      @Override
      public void run()
      {
         client.start();
      }
    }, "client start thread");
    startThread.setDaemon(true);
    startThread.start();

    try
    {
      startThread.join(5000);
    }
    catch (InterruptedException e)
    {
      LOG.warn("interrupted");
    }

    assertTrue(!startThread.isAlive());
  }

  private static <T>int safeListSize(List<T> l)
  {
    return null == l ? 0 : l.size();
  }

  static DbusEventInfo[] createSampleSchema1Events(int eventsNum) throws IOException
  {
    Random rng = new Random();
    DbusEventInfo[] result = new DbusEventInfo[eventsNum];
    GenericDatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(SOURCE1_SCHEMA);
    for (int i = 0; i < eventsNum; ++i)
    {
      GenericRecord r = new GenericData.Record(SOURCE1_SCHEMA);
      String s = RngUtils.randomString(rng.nextInt(100));
      r.put("s", s);
      ByteArrayOutputStream baos = new ByteArrayOutputStream(s.length() + 100);
      BinaryEncoder out = new BinaryEncoder(baos);
      try
      {
        writer.write(r, out);
        out.flush();

        result[i] = new DbusEventInfo(DbusOpcode.UPSERT, 1, (short)1, (short)1,
                                      System.nanoTime(),
                                      (short)1, SOURCE1_SCHEMAID,
                                      baos.toByteArray(), false, true);
        result[i].setEventSerializationVersion(_eventFactory.getVersion());
      }
      finally
      {
        baos.close();
      }
    }

    return result;
  }

  public static class WriteEventsResult
  {
    private final List<Integer> offsets;
    private final List<DbusEventKey> keys;

    public WriteEventsResult(List<Integer> offsets, List<DbusEventKey> keys) {
      super();
      this.offsets = offsets;
      this.keys = keys;
    }

    public List<Integer> getOffsets() {
      return offsets;
    }

    public List<DbusEventKey> getKeys() {
      return keys;
    }
  }

  static WriteEventsResult writeEventsToBuffer(DbusEventBuffer buf, DbusEventInfo[] eventInfos, int winSize)
         throws UnsupportedEncodingException
  {
    Random rng = new Random(100);
    final List<Integer> eventOfs = new ArrayList<Integer>(eventInfos.length);
    final List<DbusEventKey> eventKeys = new ArrayList<DbusEventKey>(eventInfos.length);

    InternalDatabusEventsListener ofsTracker = new InternalDatabusEventsListener()
    {
      @Override
      public void close() throws IOException {}

      @Override
      public void onEvent(DbusEvent event, long offset, int size)
      {
        DbusEventInternalWritable e = (DbusEventInternalWritable)event;
        assertTrue("endEvents() gave us an invalid event! (offset = " + offset + ", size = " + size + ")",
                   e.isValid(true));
        eventOfs.add(e.getRawBytes().position() - e.size())// FIXME: is this correct?  position() == offset; why subtract size?
      }
    };
    buf.addInternalListener(ofsTracker);

    for (int i = 0; i < eventInfos.length; ++i)
    {
      if (i % winSize == 0)
      {
        if (i > 0) buf.endEvents(i);
        buf.startEvents();
      }
      DbusEventKey key =
          new DbusEventKey(RngUtils.randomString(rng, rng.nextInt(50)).getBytes("UTF-8"));
      buf.appendEvent(key, eventInfos[i], null);
      eventKeys.add(key);
    }
    buf.endEvents(eventInfos.length);

    buf.removeInternalListener(ofsTracker);

    return new WriteEventsResult(eventOfs, eventKeys);
  }
}

class DummyStreamConsumer implements DatabusStreamConsumer
{
  private final String _name;
  public int _errorCount = 0;

  public DummyStreamConsumer(String name) {
    super();
    _name = name;
  }

  @Override
  public ConsumerCallbackResult onCheckpoint(SCN checkpointScn) {
    return ConsumerCallbackResult.ERROR;
  }

  @Override
  public ConsumerCallbackResult onDataEvent(DbusEvent e, DbusEventDecoder eventDecoder) {
    return ConsumerCallbackResult.ERROR;
  }

  @Override
  public ConsumerCallbackResult onEndDataEventSequence(SCN endScn) {
    return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onEndSource(String source, Schema sourceSchema) {
      return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onRollback(SCN startScn) {
      return ConsumerCallbackResult.ERROR;
  }

  @Override
  public ConsumerCallbackResult onStartDataEventSequence(SCN startScn) {
      return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onStartSource(String source, Schema sourceSchema) {
      return ConsumerCallbackResult.SUCCESS;
  }

  public String getName() {
    return _name;
  }

  @Override
  public ConsumerCallbackResult onStartConsumption()
  {
    return ConsumerCallbackResult.ERROR;
  }

  @Override
  public ConsumerCallbackResult onStopConsumption()
  {
    return ConsumerCallbackResult.ERROR;
  }

  @Override
  public ConsumerCallbackResult onError(Throwable err)
  {
  _errorCount++;
    return ConsumerCallbackResult.SUCCESS;
  }
}

class TestConsumer extends AbstractDatabusCombinedConsumer
{
  public static final String MODULE = TestConsumer.class.getName();
  public static final Logger LOG = Logger.getLogger(MODULE);

  private int _eventNum;
  private int _winNum;
  private long _rollbackScn;

  private final List<DbusEventKey> keys = new ArrayList<DbusEventKey>();
  private final List<Long> sequences = new ArrayList<Long>();

  public TestConsumer()
  {
    resetCounters();
  }

  public void resetCounters()
  {
    _eventNum = 0;
    _winNum = 0;
    _rollbackScn = -1;

  }

  public void resetEvents()
  {
    keys.clear();
    sequences.clear();
  }

  public List<DbusEventKey> getKeys() {
  return keys;
}

public List<Long> getSequences() {
  return sequences;
}

protected int getEventNum()
  {
    return _eventNum;
  }

  protected int getWinNum()
  {
    return _winNum;
  }

  @Override
  public ConsumerCallbackResult onDataEvent(DbusEvent e, DbusEventDecoder eventDecoder)
  {
    ++ _eventNum;
    if ( (! e.isCheckpointMessage()) && (!e.isControlMessage()))
    {
      sequences.add(e.sequence());
      if ( e.isKeyNumber())
        keys.add(new DbusEventKey(e.key()));
      else
        keys.add(new DbusEventKey(e.keyBytes()));
    }
    LOG.info("TestConsumer: OnDataEvent : Sequence : " + e.sequence());
    return super.onDataEvent(e, eventDecoder);
  }

  @Override
  public ConsumerCallbackResult onStartDataEventSequence(SCN startScn)
  {
    ++ _winNum;
    return super.onStartDataEventSequence(startScn);
  }

  @Override
  public ConsumerCallbackResult onRollback(SCN startScn)
  {
    if (startScn instanceof SingleSourceSCN)
    {
      SingleSourceSCN s = (SingleSourceSCN)startScn;
      _rollbackScn = s.getSequence();
    } else {
      throw new RuntimeException("SCN not instance of SingleSourceSCN");
    }
    return super.onRollback(startScn);
  }

  protected long getRollbackScn()
  {
    return _rollbackScn;
  }

}

/**
* All callbacks succeed for this consumer
*
* @author pganti
*
*/
class DummySuccessfulErrorCountingConsumer implements DatabusStreamConsumer
{
  private final String _name;
  public int _errorCount;
  private final boolean _returnErrorsOnDataEvent;

  public DummySuccessfulErrorCountingConsumer(String name, boolean returnErrorsOnDataEvent) {
    super();
    _name = name;
    _errorCount = 0;
    _returnErrorsOnDataEvent = returnErrorsOnDataEvent;
  }

  @Override
  public ConsumerCallbackResult onCheckpoint(SCN checkpointScn) {
    return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onDataEvent(DbusEvent e, DbusEventDecoder eventDecoder) {
    if (_returnErrorsOnDataEvent)
      return ConsumerCallbackResult.ERROR;
    else
      return ConsumerCallbackResult.SUCCESS;

  }

  @Override
  public ConsumerCallbackResult onEndDataEventSequence(SCN endScn) {
    return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onEndSource(String source, Schema sourceSchema) {
      return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onRollback(SCN startScn) {
      return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onStartDataEventSequence(SCN startScn) {
      return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onStartSource(String source, Schema sourceSchema) {
      return ConsumerCallbackResult.SUCCESS;
  }

  public String getName() {
    return _name;
  }

  @Override
  public ConsumerCallbackResult onStartConsumption()
  {
    return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onStopConsumption()
  {
    return ConsumerCallbackResult.SUCCESS;
  }

  @Override
  public ConsumerCallbackResult onError(Throwable err)
  {
  _errorCount++;
    return ConsumerCallbackResult.SUCCESS;
  }
}
TOP

Related Classes of com.linkedin.databus.client.TestDatabusHttpClient

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.