Package com.linkedin.databus.client

Source Code of com.linkedin.databus.client.DatabusHttpClientImpl$BootstrapClientRuntimeConfigBuilder

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 java.io.IOException;
import java.net.URISyntaxException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.log4j.Logger;

import com.linkedin.databus.client.DatabusHttpClientImpl.CheckpointPersistenceStaticConfig.ProviderType;
import com.linkedin.databus.client.consumer.DatabusV2ConsumerRegistration;
import com.linkedin.databus.client.consumer.LoggingConsumer;
import com.linkedin.databus.client.consumer.SelectingDatabusCombinedConsumer;
import com.linkedin.databus.client.consumer.SelectingDatabusCombinedConsumerFactory;
import com.linkedin.databus.client.netty.NettyHttpConnectionFactory;
import com.linkedin.databus.client.pub.CheckpointPersistenceProvider;
import com.linkedin.databus.client.pub.ClusterCheckpointPersistenceProvider;
import com.linkedin.databus.client.pub.DatabusBootstrapConsumer;
import com.linkedin.databus.client.pub.DatabusClient;
import com.linkedin.databus.client.pub.DatabusClientException;
import com.linkedin.databus.client.pub.DatabusClientGroupMember;
import com.linkedin.databus.client.pub.DatabusClientNode;
import com.linkedin.databus.client.pub.DatabusCombinedConsumer;
import com.linkedin.databus.client.pub.DatabusRegistration;
import com.linkedin.databus.client.pub.DatabusRegistration.RegistrationState;
import com.linkedin.databus.client.pub.DatabusStreamConsumer;
import com.linkedin.databus.client.pub.DatabusV3Registration;
import com.linkedin.databus.client.pub.DbusClusterConsumerFactory;
import com.linkedin.databus.client.pub.DbusClusterInfo;
import com.linkedin.databus.client.pub.DbusPartitionListener;
import com.linkedin.databus.client.pub.DbusServerSideFilterFactory;
import com.linkedin.databus.client.pub.FileSystemCheckpointPersistenceProvider;
import com.linkedin.databus.client.pub.RegistrationId;
import com.linkedin.databus.client.pub.ServerInfo;
import com.linkedin.databus.client.pub.ServerInfo.ServerInfoBuilder;
import com.linkedin.databus.client.pub.ServerInfo.ServerInfoSetBuilder;
import com.linkedin.databus.client.pub.SharedCheckpointPersistenceProvider;
import com.linkedin.databus.client.pub.mbean.ConsumerCallbackStats;
import com.linkedin.databus.client.pub.mbean.UnifiedClientStats;
import com.linkedin.databus.client.registration.ClusterRegistrationConfig;
import com.linkedin.databus.client.registration.ClusterRegistrationStaticConfig;
import com.linkedin.databus.client.registration.DatabusMultiPartitionRegistration;
import com.linkedin.databus.client.registration.DatabusV2ClusterRegistrationImpl;
import com.linkedin.databus.client.registration.DatabusV2RegistrationImpl;
import com.linkedin.databus.client.registration.RegistrationIdGenerator;
import com.linkedin.databus.client.request.ClientStateRequestProcessor;
import com.linkedin.databus.client.request.ClientStatsRequestProcessor;
import com.linkedin.databus.core.DatabusComponentStatus;
import com.linkedin.databus.core.DbusEventBuffer;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.DbusEventV2Factory;
import com.linkedin.databus.core.data_model.DatabusSubscription;
import com.linkedin.databus.core.monitoring.mbean.AggregatedDbusEventsStatisticsCollector;
import com.linkedin.databus.core.monitoring.mbean.DbusEventsStatisticsCollector;
import com.linkedin.databus.core.monitoring.mbean.StatsCollectors;
import com.linkedin.databus.core.util.ConfigApplier;
import com.linkedin.databus.core.util.ConfigBuilder;
import com.linkedin.databus.core.util.ConfigLoader;
import com.linkedin.databus.core.util.ConfigManager;
import com.linkedin.databus.core.util.InvalidConfigException;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.core.container.monitoring.mbean.DatabusComponentAdmin;
import com.linkedin.databus2.core.container.monitoring.mbean.HttpStatisticsCollector;
import com.linkedin.databus2.core.container.netty.ServerContainer;
import com.linkedin.databus2.core.filter.DbusKeyCompositeFilterConfig;


/**
*/
public class DatabusHttpClientImpl extends ServerContainer implements DatabusClient
{
  /**
   * Public instance variables
   */

  public static final String MODULE = DatabusHttpClientImpl.class.getName();
  public static final Logger LOG = Logger.getLogger(MODULE);
  public static final int GLOBAL_STATS_MERGE_INTERVAL_MS=500;

  /**
   * Protected instance variables
   */

  /** Map from source list to relays that can provide the list */
  protected Map<List<DatabusSubscription>, Set<ServerInfo>> _relayGroups;

  /** Map from source list to relays that can provide the list */
  protected Map<List<DatabusSubscription>, Set<ServerInfo>> _bootstrapGroups;

  /** All relay connections and bootstrap connections */
  protected final List<DatabusSourcesConnection> _relayConnections;

  /** Static configuration */
  protected final StaticConfig _clientStaticConfig;
  /** Runtime config manager */
  protected final ConfigManager<RuntimeConfig> _configManager;
  /** Checkpoint persistence provider */
  protected final CheckpointPersistenceProvider _checkpointPersistenceProvider;
  /** Statistics collector about databus events */
  protected final HttpStatisticsCollector _httpStatsCollector;
  protected final LoggingConsumer _loggingConsumer;  // this instance is not really used, TODO get rid of it
  protected final DatabusRelayConnectionFactory _relayConnFactory;
  protected final DatabusBootstrapConnectionFactory _bootstrapConnFactory;

  protected final DbusEventsStatisticsCollector _bootstrapEventsStatsCollector;
  protected final StatsCollectors<DbusEventsStatisticsCollector> _bootstrapEventsStats;

  protected final StatsCollectors<ConsumerCallbackStats> _consumerStatsCollectors;
  protected final StatsCollectors<ConsumerCallbackStats> _bsConsumerStatsCollectors;
  protected final StatsCollectors<UnifiedClientStats> _unifiedClientStatsCollectors;

  protected DatabusHttpClientStatus _clientStatus;
  /** node that will join a cluster */
  protected final DatabusClientNode _clientNode;
  /** convenience handle to cluster /group */
  protected final DatabusClientGroupMember _groupMember;

  /**last write time tracker for DSC */
  protected LastWriteTimeTrackerImpl _writeTimeTracker = null;
  protected DatabusClientDSCUpdater _dscUpdater = null;

  protected final DbusEventFactory _eventFactory;

  /**
   * Private instance variables
   */

  /** Default client-relay protocol version.  @see getProtocolVersion() for details. */
  // TODO:  There's another copy of this in CheckpointPersistenceProviderAbstract; unify.  DDSDBUS-2107 item 14
  private static final int DEFAULT_PROTOCOL_VERSION = 2;

  /** max event version understood by this client */
  private static final int DEFAULT_MAX_EVENT_VERSION = DbusEventFactory.DBUS_EVENT_V2;


  /** Map from source list to stream consumers that will use those */
  private final Map<List<DatabusSubscription>, List<DatabusV2ConsumerRegistration>>
          _relayGroupStreamConsumers;

  /** Map from source list to bootstrap consumers that will use those */
  private final Map<List<DatabusSubscription>, List<DatabusV2ConsumerRegistration>>
          _relayGroupBootstrapConsumers;

  /**
   * A flag to indicate if the client is cluster manager enabled
   */
  protected boolean _cmEnabled = false;

  /**
   * List of registrations for this client instance
   */
  private final List<DatabusRegistration> _regList = new CopyOnWriteArrayList<DatabusRegistration>();

  /**
   * Set of clusters that have been registered.
   */
  private final Set<String> _activeClusters = new HashSet<String>();

  /**
   * Returns the version of the relay-client protocol we're using.  DatabusHttpV3ClientImpl overrides this.
   * Reference wiki:  Databus+v2.0++Protocol
   *
   * <ul>
   *   <li> 2 - client specifies sources rather than subscriptions and uses single-source checkpoints;
   *            /register returns list of source schemas only </li>
   *   <li> 3 - client specifies subscriptions and uses multi-source checkpoints;
   *            /register returns list of source schemas only </li>
   *   <li> 4 - as with v3, but /register returns map containing schemas for keys and metadata in
   *            addition to sources </li>
   * </ul>
   */
  protected int getProtocolVersion()
  {
    return DEFAULT_PROTOCOL_VERSION;
  }


  protected int getMaxEventVersion()
  {
    return DEFAULT_MAX_EVENT_VERSION;
  }

  public boolean isCMEnabled()
  {
    return _cmEnabled;
  }

  public Map<String, String> printClientInfo()
  throws InvalidConfigException
  {
    throw new UnsupportedOperationException("Not supported in this version of client");
  }

  /**
   * Reset all the relay connections, and pick new relays to connect to.
   */
  public void resetRelayConnections()
  {
    for (DatabusSourcesConnection conn : _relayConnections)
    {
      conn.getRelayPullThread().killConnection();
    }
  }

  /**
   * Creates a client with a configuration based on specified properties.
   * @param  propertyPrefix   the prefix for all property names for this client
   * @param  props            the configuration properties
   */
  public DatabusHttpClientImpl(String propertyPrefix, Properties props)
        throws InvalidConfigException, IOException, DatabusException
  {
    this(createConfigBuilder(propertyPrefix, props).build());
  }

  public DatabusHttpClientImpl() throws InvalidConfigException, IOException, DatabusException
  {
    this(new Config());
  }

  public DatabusHttpClientImpl(Config config) throws InvalidConfigException, IOException,
                                                     DatabusException
  {
    this(config.build());
  }

  public DatabusHttpClientImpl(StaticConfig config) throws InvalidConfigException, IOException,
                                                           DatabusException
  {
    this(config, ByteOrder.BIG_ENDIAN)// default order is big-endian
  }

  public DatabusHttpClientImpl(StaticConfig config, ByteOrder byteOrder)
  throws IOException, InvalidConfigException, DatabusException
  {
    super(config.getContainer(), byteOrder);

    _eventFactory = new DbusEventV2Factory(byteOrder);

    _clientStaticConfig = config;

    _bootstrapEventsStatsCollector = new AggregatedDbusEventsStatisticsCollector(getContainerStaticConfig().getId(),
                                                         "eventsBootstrap",
                                                         true,
                                                         true,
                                                         getMbeanServer());
    //create a meta collector across physical sources ; pass in existing collector for backward compat
    _bootstrapEventsStats = new StatsCollectors<DbusEventsStatisticsCollector>(_bootstrapEventsStatsCollector);
    _consumerStatsCollectors = new StatsCollectors<ConsumerCallbackStats>();
    _bsConsumerStatsCollectors = new StatsCollectors<ConsumerCallbackStats>();
    _unifiedClientStatsCollectors = new StatsCollectors<UnifiedClientStats>();

    _relayGroups = new HashMap<List<DatabusSubscription>, Set<ServerInfo>>(100);
    _bootstrapGroups = new HashMap<List<DatabusSubscription>, Set<ServerInfo>>(100);
    _relayGroupStreamConsumers =
        new HashMap<List<DatabusSubscription>,
                    List<DatabusV2ConsumerRegistration>>(100);
    _relayGroupBootstrapConsumers =
        new HashMap<List<DatabusSubscription>,
                    List<DatabusV2ConsumerRegistration>>(100);
    _relayConnections = new ArrayList<DatabusSourcesConnection>(20);


    if (config.getCluster().isEnabled())
    {
      _clientNode = new DatabusClientNode(config.getCluster());
      _groupMember = _clientNode.isEnabled() ?  _clientNode.getMember(_clientStaticConfig.getCluster().getDomain(),
                                                            _clientStaticConfig.getCluster().getGroup(),
                                                            _clientStaticConfig.getCluster().getName()) : null;
    }
    else
    {
      _clientNode = null;
      _groupMember = null;
    }
    _checkpointPersistenceProvider = _clientStaticConfig.getCheckpointPersistence()
            .getOrCreateCheckpointPersistenceProvider(_groupMember);
    HttpStatisticsCollector httpStatsColl = _clientStaticConfig.getHttpStatsCollector()
                                            .getExistingStatsCollector();
    if (null == httpStatsColl)
    {
      httpStatsColl = new HttpStatisticsCollector(getContainerStaticConfig().getId(),
                                                  "httpOutbound",
                                                  _clientStaticConfig.getRuntime().getHttpStatsCollector().isEnabled(),
                                                  true,
                                                  getMbeanServer());
    }
    _httpStatsCollector = httpStatsColl;

    _loggingConsumer = new LoggingConsumer(_clientStaticConfig.getLoggingListener());

    _clientStaticConfig.getRuntime().setManagedInstance(this);
    _configManager = new ConfigManager<RuntimeConfig>(_clientStaticConfig.getRuntimeConfigPrefix(),
                                                      _clientStaticConfig.getRuntime());

    NettyHttpConnectionFactory defaultConnFactory =
        new NettyHttpConnectionFactory(getBossExecutorService(), getIoExecutorService() ,
                                       getContainerStatsCollector(),
                                       getNetworkTimeoutTimer(),
                                       _clientStaticConfig.getContainer().getWriteTimeoutMs(),
                                       _clientStaticConfig.getContainer().getReadTimeoutMs(),
                                       _clientStaticConfig.getContainer().getBstReadTimeoutMs(),
                                       getProtocolVersion(),
                                       getMaxEventVersion(),
                                       getHttpChannelGroup());
    _relayConnFactory = defaultConnFactory;
    _bootstrapConnFactory = defaultConnFactory;

    initializeClientCommandProcessors();
    //_inBoundStatsCollectors is merged in ServerContainer
    getGlobalStatsMerger().registerStatsCollector(_bootstrapEventsStats);
    //_consumerStatsCollectors and _bsConsumerStatsCollectors (and _unifiedClientStatsCollectors?)
    // are not meaningful at a global level
  }

  /**
   * Creates a client configuration builder from a properties collection
   * @param  propertyPrefix   the prefix for all property names for this client
   * @param  props            the configuration properties
   */
  public static Config createConfigBuilder(String propertyPrefix, Properties props)
          throws InvalidConfigException
  {
    Config configBuilder = new Config();
    ConfigLoader<StaticConfig> confLoader =
        new ConfigLoader<StaticConfig>(propertyPrefix, configBuilder);
    confLoader.loadConfig(props);

    return configBuilder;
  }

  public StaticConfig getClientStaticConfig()
  {
    return _clientStaticConfig;
  }

  /**
   * Registers a consumer for streaming events for the given sources
   * @param listener        the consumer callback object
   * @param filterConfig    configuration for filtering events (can be null)
   * @param sources         the list of sources to listen to (order is significant)
   */
  @Override
  public void registerDatabusStreamListener(DatabusStreamConsumer listener, DbusKeyCompositeFilterConfig filterConfig, String ... sources)
                                    throws DatabusClientException
  {
    registerDatabusStreamListener(listener, Arrays.asList(sources), filterConfig);
  }

  @Override
  public void registerDatabusStreamListener(DatabusStreamConsumer[] listeners, DbusKeyCompositeFilterConfig filterConfig, String ... sources)
                                    throws DatabusClientException
  {
    registerDatabusStreamListener(listeners, Arrays.asList(sources), filterConfig);
  }

  @Override
  public void registerDatabusBootstrapListener(DatabusBootstrapConsumer listener, DbusKeyCompositeFilterConfig filterConfig, String ... sources)
                                               throws DatabusClientException
  {
    registerDatabusBootstrapListener(listener, Arrays.asList(sources), filterConfig);
  }

  @Override
  public void registerDatabusBootstrapListener(DatabusBootstrapConsumer[] listeners, DbusKeyCompositeFilterConfig filterConfig, String ... sources)
  throws DatabusClientException
  {
    registerDatabusBootstrapListener(listeners, Arrays.asList(sources), filterConfig);
  }

  /**
   *
   * @param listener
   * @param sources
   * @param filterConfig
   * @throws DatabusClientException
   */
  public void registerDatabusStreamListener(DatabusStreamConsumer listener,
                                            List<String> sources,
                                            DbusKeyCompositeFilterConfig filterConfig)
                                            throws DatabusClientException
  {
    DatabusStreamConsumer[] listeners = { listener };
    registerDatabusStreamListener(listeners, sources, filterConfig);
  }

  /**
   *
   * @param listeners
   * @param sources
   * @param filterConfig
   * @throws DatabusClientException
   */
  public synchronized void registerDatabusStreamListener(DatabusStreamConsumer[] listeners,
                                                         List<String> sources,
                                                         DbusKeyCompositeFilterConfig filterConfig)
  throws DatabusClientException
  {
    List<DatabusStreamConsumer> listenersList = Arrays.asList(listeners);
    List<SelectingDatabusCombinedConsumer> sdccListenersList =
        SelectingDatabusCombinedConsumerFactory.convertListOfStreamConsumers(listenersList);
    List<DatabusCombinedConsumer> dccListenersList = new ArrayList<DatabusCombinedConsumer>();
    for(SelectingDatabusCombinedConsumer sdcc: sdccListenersList)
    {
      dccListenersList.add(sdcc);
    }

    DatabusV2ConsumerRegistration consumerReg =
        new DatabusV2ConsumerRegistration(dccListenersList, sources, filterConfig);

    registerDatabusListener(consumerReg, _relayGroups, getRelayGroupStreamConsumers(),
                            DatabusSubscription.createSubscriptionList(sources));
  }

  /**
   *
   * @param listener
   * @param sources
   * @param filterConfig
   * @throws DatabusClientException
   */
  public void registerDatabusBootstrapListener(DatabusBootstrapConsumer listener,
                                               List<String> sources,
                                               DbusKeyCompositeFilterConfig filterConfig)
                                              throws DatabusClientException
  {
    DatabusBootstrapConsumer[] listeners = { listener };
    registerDatabusBootstrapListener(listeners, sources, filterConfig);
  }

  /**
   *
   * @param listeners
   * @param sources
   * @param filter
   * @throws DatabusClientException
   */
  public synchronized void registerDatabusBootstrapListener(DatabusBootstrapConsumer[] listeners,
                                               List<String> sources,
                                               DbusKeyCompositeFilterConfig filter)
                                              throws DatabusClientException
  {
    List<DatabusBootstrapConsumer> listenersList = Arrays.asList(listeners);
    List<SelectingDatabusCombinedConsumer> sdccListenersList =
        SelectingDatabusCombinedConsumerFactory.convertListOfBootstrapConsumers(listenersList);
    List<DatabusCombinedConsumer> dccListenersList = new ArrayList<DatabusCombinedConsumer>();
    for(SelectingDatabusCombinedConsumer sdcc: sdccListenersList)
    {
      dccListenersList.add(sdcc);
    }

    DatabusV2ConsumerRegistration consumerReg =
        new DatabusV2ConsumerRegistration(dccListenersList, sources, filter);

    registerDatabusListener(consumerReg, _relayGroups, getRelayGroupBootstrapConsumers(),
                            DatabusSubscription.createSubscriptionList(sources));
  }

  /**
   *
   * @param listener
   * @throws DatabusClientException
   */
  public synchronized void unregisterDatabusStreamListener(DatabusStreamConsumer listener)
                                      throws DatabusClientException
  {
      for (List<DatabusSubscription> relayGroup: getRelayGroupStreamConsumers().keySet())
      {
        while (getRelayGroupStreamConsumers().get(relayGroup).remove(listener));
      }
  }

  /**
   *
   * @param listener
   * @throws DatabusClientException
   */
  public synchronized void unregisterDatabusBootstrapListener(DatabusBootstrapConsumer listener)
                                                 throws DatabusClientException
  {
      for (List<DatabusSubscription> relayGroup: getRelayGroupBootstrapConsumers().keySet())
      {
        while (getRelayGroupBootstrapConsumers().get(relayGroup).remove(listener));
      }
  }

  public synchronized void doRegisterBootstrapService(ServerInfo serverInfo)
  {
      LOG.info("Registering bootstrap: " + serverInfo.toString());
      List<DatabusSubscription> subList = DatabusSubscription.createSubscriptionList(serverInfo.getSources());
      Set<ServerInfo> sourceBootstrapServers = _bootstrapGroups.get(subList);
      if (null == sourceBootstrapServers)
      {
        sourceBootstrapServers = new HashSet<ServerInfo>();
        _bootstrapGroups.put(subList, sourceBootstrapServers);
      }
      sourceBootstrapServers.add(serverInfo);
  }

  public synchronized void doUnregisterBootstrapService(ServerInfo serverInfo)
  {
      Set<ServerInfo> sourceBootstrapServers = _bootstrapGroups.get(DatabusSubscription.createSubscriptionList(serverInfo.getSources()));
      if (null != sourceBootstrapServers)
      {
        LOG.info("Unregistering bootstrap: " + serverInfo.toString());
        sourceBootstrapServers.remove(serverInfo);
      }
  }

  /**
   *
   * @return
   */
  public Map<List<DatabusSubscription>, Set<ServerInfo>> getRelayGroups() {
    return _relayGroups;
  }

  /**
   *
   * @return
   */
  public List<ServerInfo> getRelays() {
    RuntimeConfig runtimeConfig = getClientConfigManager().getReadOnlyConfig();
    return runtimeConfig.getRelays();
  }

  /**
   *
   * Returns *both* the relay and bootstrap connections
   * @return
   */
  public synchronized List<DatabusSourcesConnection> getRelayConnections() {
    return _relayConnections;
  }

  /**
   *
   * @return
   */
  public synchronized List<ServerInfo> getBootstrapServices() {
    RuntimeConfig runtimeConfig = getClientConfigManager().getReadOnlyConfig();
    return runtimeConfig.getBootstrap().getServices();
  }

  /**
   *
   * @return
   */
  public synchronized Map<List<DatabusSubscription>, List<DatabusV2ConsumerRegistration>>
  getRelayGroupStreamConsumers()
  {
    return _relayGroupStreamConsumers;
  }

  /**
   *
   * @return
   */
  public synchronized Map<List<DatabusSubscription>, List<DatabusV2ConsumerRegistration>>
  getRelayGroupBootstrapConsumers()
  {
    return _relayGroupBootstrapConsumers;
  }

  /**
   *
   * @return
   */
  public ConfigManager<RuntimeConfig> getClientConfigManager()
  {
    return _configManager;
  }

  /**
   *
   * @return
   */
  public CheckpointPersistenceProvider getCheckpointPersistenceProvider()
  {
    return _checkpointPersistenceProvider;
  }

  /**
   *
   * @return
   */
  public HttpStatisticsCollector getHttpStatsCollector()
  {
    return _httpStatsCollector;
  }

  /**
   *
   * @return
   */
  // TODO:  deprecate this?  returned instance is useless, but RuntimeConfigBuilder needs a managed instance...
  public LoggingConsumer getLoggingListener()
  {
    return _loggingConsumer;
  }

  /**
   *
   * @return
   */
  public DatabusRelayConnectionFactory getRelayConnFactory()
  {
    return _relayConnFactory;
  }

  /**
   *
   * @return
   */
  public DatabusBootstrapConnectionFactory getBootstrapConnFactory()
  {
    return _bootstrapConnFactory;
  }

  /**
   * @return set of bootstrap events collectors
   */
  public StatsCollectors<DbusEventsStatisticsCollector> getBootstrapEventsStats()
  {
    return _bootstrapEventsStats;
  }

  /**
   * @return set of relay consumer stats collectors
   */
  public StatsCollectors<ConsumerCallbackStats> getRelayConsumerStatsCollectors()
  {
    return _consumerStatsCollectors;
  }

  /**
   * @return set of bootstrap consumer stats collectors
   */
  public StatsCollectors<ConsumerCallbackStats> getBootstrapConsumerStatsCollectors()
  {
    return _bsConsumerStatsCollectors;
  }

  /**
   * @return set of unified client (consumer) stats collectors.
   */
  public StatsCollectors<UnifiedClientStats> getUnifiedClientStatsCollectors()
  {
    return _unifiedClientStatsCollectors;
  }

  /**
   *
   * @return
   */
  public DbusEventsStatisticsCollector getBootstrapEventsStatsCollector()
  {
    return _bootstrapEventsStats.getStatsCollector();
  }



  /**
   *
   * @param writeTimeTracker
   */
  public void setWriteTimeTracker(LastWriteTimeTrackerImpl writeTimeTracker)
  {
    _writeTimeTracker = writeTimeTracker;
  }

  /**
   *
   * @return
   */
  public LastWriteTimeTrackerImpl getWriteTimeTracker()
  {
    return _writeTimeTracker;
  }

  /**
   *
   * @return
   */
  public DatabusClientDSCUpdater getDSCUpdater()
  {
    return _dscUpdater ;
  }

  /**
   * Returns the set of client clusters that are currently "active". A cluster is considered active, if it has atleast one registration in
   * one of the following states (REGISTERED,  REGISTERED, STARTED, PAUSED, RESUMED, SUSPENDED_ON_ERROR). The states which are not considered
   * to be active are INIT, DEREGISTERED states.
   *
   * This is applicable for load-balanced mode only in both V2(Oracle) and V3(Espresso).
   */
  public Set<String> getActiveClientClusters()
  {
    return _activeClusters;
  }

  @Override
  public DatabusRegistration register(DatabusCombinedConsumer consumer,
                                      String... sources)
               throws DatabusClientException
  {
    if ((null == consumer))
      throw new DatabusClientException("No consumer callback has been specified.");

    if ((null == sources) || (sources.length == 0))
      throw new DatabusClientException("Please specify Databus sources to be consumed: register(consumer, source1, source2, ...");

    RegistrationId regId =
        RegistrationIdGenerator.generateNewId(consumer.getClass().getSimpleName(),
                                              DatabusSubscription.createSubscriptionList(Arrays.asList(sources)));
    DatabusV2RegistrationImpl reg = new DatabusV2RegistrationImpl(regId,
                                                                  this,
                                                                  getCheckpointPersistenceProvider());
    List<DatabusCombinedConsumer> consumers = new ArrayList<DatabusCombinedConsumer>();
    consumers.add(consumer);
    reg.addDatabusConsumers(consumers);
    reg.addSubscriptions(sources);
    _regList.add(reg);
    reg.onRegister();
    return reg;
  }

  @Override
  public DatabusRegistration register(
               Collection<DatabusCombinedConsumer> consumers,
               String... sources)
        throws DatabusClientException
  {
    if ((null == consumers|| (consumers.isEmpty()))
      throw new DatabusClientException("No consumer callbacks have been specified.");

    if ((null == sources) || (sources.length == 0))
      throw new DatabusClientException("Please specify Databus sources to be consumed: register(consumer, source1, source2, ...");

    RegistrationId regId =
        RegistrationIdGenerator.generateNewId(consumers.iterator().next().getClass().getSimpleName(),
                                              DatabusSubscription.createSubscriptionList(Arrays.asList(sources)));
    DatabusV2RegistrationImpl reg = new DatabusV2RegistrationImpl(regId,
                                                                  this,
                                                                  getCheckpointPersistenceProvider());
    reg.addDatabusConsumers(consumers);
    reg.addSubscriptions(sources);
    _regList.add(reg);
    reg.onRegister();
    return reg;
  }

  @Override
  public DatabusRegistration registerCluster(String cluster,
                                             DbusClusterConsumerFactory consumerFactory,
                                             DbusServerSideFilterFactory filterFactory,
                                             DbusPartitionListener partitionListener, String... sources)
  throws DatabusClientException
  {
    if ((null == sources) || (sources.length == 0))
      throw new DatabusClientException("Sources is empty !!");

    if ( _activeClusters.contains(cluster))
      throw new DatabusClientException("Cluster :" + cluster + " has already been registed to this client instance." +
                                       " Only one registration per cluster is allowed for a databus client instance !!");

    ClusterRegistrationStaticConfig c = _clientStaticConfig.getClientCluster(cluster);

    if ( null == c )
      throw new DatabusClientException("Cluster Configuration for cluster (" + cluster + ") not provided !!");

    if ( null == consumerFactory)
      throw new DatabusClientException("Consumer Factory is null !!");

    ClusterCheckpointPersistenceProvider.StaticConfig ckptPersistenceProviderConfig =
        new ClusterCheckpointPersistenceProvider.StaticConfig(c.getZkAddr(),c.getClusterName(),c.getMaxCkptWritesSkipped(),c.getCheckpointIntervalMs());

    DbusClusterInfo clusterInfo = new DbusClusterInfo(c.getClusterName(), c.getNumPartitions(), c.getQuorum());

    RegistrationId regId = RegistrationIdGenerator.generateNewId(c.getClusterName());

    DatabusV2ClusterRegistrationImpl reg =
        new DatabusV2ClusterRegistrationImpl(regId, this, ckptPersistenceProviderConfig, clusterInfo, consumerFactory, filterFactory, partitionListener, sources);
    _regList.add(reg);
    reg.onRegister();
    _activeClusters.add(cluster);
    return reg;
  }

  public boolean deregister(DatabusRegistration reg)
  {
    return _regList.remove(reg);
  }

  public Collection<DatabusMultiPartitionRegistration> getAllClientClusterRegistrations()
  {
    List<DatabusMultiPartitionRegistration> regs = new ArrayList<DatabusMultiPartitionRegistration>();
    for (DatabusRegistration reg : _regList)
    {
      if (reg instanceof DatabusV2ClusterRegistrationImpl)
        regs.add((DatabusV2ClusterRegistrationImpl)reg);
    }
    return regs;
  }

  public Collection<DatabusRegistration> getAllRegistrations()
  {
    return Collections.unmodifiableCollection(_regList);
  }

  /**
   * @return
   */
  public boolean isClusterEnabled()
  {
    return (_clientNode != null) && (_clientNode.isEnabled());
  }

  @Override
  public void pause()
  {
    _clientStatus.pause();
  }

  @Override
  public void resume()
  {
    _clientStatus.resume();
  }

  @Override
  public void suspendOnError(Throwable cause)
  {
    _clientStatus.suspendOnError(cause);
  }

  /**
   *
   * @param args
   * @throws Exception
   */
  public static void main(String[] args) throws Exception
  {
    Config staticConfigBuilder = new Config();
    DatabusHttpClientImpl client = createFromCli(args, staticConfigBuilder);
    client.startAndBlock();
  }

  /**
   * Starts the databus client library. Meant to be used from the command line.
   * @param   args                   the command line arguments
   * @param   defaultConfigBuilder   default values for the Databus client library configuration; can
   *                                 be null
   */
  public static DatabusHttpClientImpl createFromCli(String[] args,
                                                    Config defaultConfigBuilder) throws Exception
  {
    Properties startupProps =  ServerContainer.processCommandLineArgs(args);
    if (null == defaultConfigBuilder) defaultConfigBuilder = new Config();

    ConfigLoader<StaticConfig> staticConfigLoader =
        new ConfigLoader<StaticConfig>("databus.client.", defaultConfigBuilder);

    StaticConfig staticConfig = staticConfigLoader.loadConfig(startupProps);

    DatabusHttpClientImpl dbusHttpClient = new DatabusHttpClientImpl(staticConfig);

    return dbusHttpClient;
  }

  @Override
  public void start()
  {
    super.start();
  }

  @Override
  protected void doStart()
  {
    super.doStart();

    LOG.info("relay consumers: " + getRelayGroupStreamConsumers().size());
    LOG.info("bootstrap consumers: " + getRelayGroupBootstrapConsumers().size());

    if (_clientNode != null && _clientNode.isEnabled())
    {
      if (!waitForLeadership())
      {
        LOG.error("Error acquiring leadership! Not starting client");
        return;
      }
    }

    _httpStatsCollector.registerMastershipStatus(1);
    if (!getClientStaticConfig().usesDynamicRelayConfiguration())
    {
      initializeRelayConnections();
      for (DatabusRegistration r : _regList)
      {
        if (!r.getState().isRunning())
        {
          try
          {
            LOG.info("Registration (" + r.getRegistrationId() + ") has not been started yet !! Starting !!");
            r.start();
          }
          catch (DatabusClientException e)
          {
            LOG.error("Got exception while starting registration !!", e);
            throw new RuntimeException(e);
          }
        }
      }
    }
    else
    {
      // The connections are made during registerDatabusListener call and not when the client is started
      LOG.info("Client with dynamically configured relays: relay connections will be initialized during register call");
    }
    if ((_dscUpdater != null) && _dscUpdater.isRunning())
    {
      _dscUpdater.stop();
    }
  }

  @Override
  protected void doShutdown()
  {
    LOG.info(getClass().getSimpleName() + ": shutting down...");
    unregisterMbeans();
    for (DatabusSourcesConnection relayConn: _relayConnections)
    {
      relayConn.stop();
    }

    for(DatabusRegistration reg : _regList)
    {
      LOG.info("Shutting down registration: " + reg.getRegistrationId());
      try
      {
        if (reg.getState() != RegistrationState.SHUTDOWN)
          reg.shutdown();
      } catch (Exception ex) {
        LOG.error("Unable to shut down registration: " + reg.getRegistrationId(),ex);
      }
    }

    //shut down dsc updater thread if running; it might write to zk, so leave zk after this
    if ((_dscUpdater != null) && _dscUpdater.isRunning())
    {
      _dscUpdater.stop();
      _dscUpdater.awaitShutdown();
    }

    //leave group;
    if (_groupMember != null)
    {
      _groupMember.leave();
    }
    //close zk connection
    if (_clientNode != null)
    {
      _clientNode.close();
    }
    _httpStatsCollector.registerMastershipStatus(0);

    super.doShutdown();
    LOG.info(getClass().getSimpleName() +": shutdown complete.");
  }

  /**
   *
   * @throws DatabusException
   */
  protected void initializeClientCommandProcessors() throws DatabusException
  {
    _processorRegistry.register(ClientStatsRequestProcessor.COMMAND_NAME,
                               new ClientStatsRequestProcessor(null, this));
    _processorRegistry.register(ClientStateRequestProcessor.COMMAND_NAME,
                            new ClientStateRequestProcessor(null, this));
  }

  /**
   *
   */
  protected boolean waitForLeadership()
  {
    DatabusClientGroupMember member = _groupMember;
    if (member != null)
    {
      if (member.join())
      {
        LOG.info("Waiting for leadership: " + member.toString());
      //lauch dscUpdater thread
        if (_dscUpdater != null)
        {
          Thread t = new Thread(_dscUpdater, "DscUpdater");
          t.setDaemon(true);
          t.start();
        }
        boolean acquiredLeadership = member.waitForLeaderShip();
        LOG.info("Acquired leadership=  " + acquiredLeadership + " member=" +  member.toString());
        return acquiredLeadership;
      }
    }
    LOG.info("Started Failed! Non availability of group/cluster handler! http relay connection for sources:" + getRelayGroupStreamConsumers());
    return false;
  }

  /**
   *
   */
  protected void unregisterMbeans()
  {
      getHttpStatsCollector().unregisterMBeans();
      getBootstrapEventsStatsCollector().unregisterMBeans();
      for (DbusEventsStatisticsCollector b: _bootstrapEventsStats.getStatsCollectors())
      {
        b.unregisterMBeans();
      }
      for (DatabusSourcesConnection conn: _relayConnections)
      {
        conn.unregisterMbeans();
      }
  }

  /**
   *
   * @throws DatabusClientException
   */
  protected ServerInfo getRandomRelay(Map<List<DatabusSubscription>,
                                      Set<ServerInfo>> groupsServers,
                                      List<DatabusSubscription> sources)
  throws DatabusClientException
  {
    List<ServerInfo> candidateServers = findServers(groupsServers, sources);
    if (0 == candidateServers.size())
    {

      throw new DatabusClientException("Unable to find servers to support sources: " + sources);
    }
    Random rng = new Random();
    ServerInfo randomRelay = candidateServers.get(rng.nextInt(candidateServers.size()));
    return randomRelay;
  }

  protected List<DatabusV2ConsumerRegistration> registerDatabusListener(
      DatabusV2ConsumerRegistration listener,
      Map<List<DatabusSubscription>, Set<ServerInfo>> groupsServers,
      Map<List<DatabusSubscription>, List<DatabusV2ConsumerRegistration>> groupsListeners,
      List<DatabusSubscription> sources)
  throws DatabusClientException
  {
    List<DatabusSubscription> subsSources = null;
    ServerInfo randomRelay = getRandomRelay(groupsServers, sources);
    if ( null == randomRelay )
    {
      // Possible only in dynamic case (formerly "v3"), where a registration succeeds
      // even if there is no relay available to serve it immediately.
      assert getClientStaticConfig().usesDynamicRelayConfiguration() :
             "Client relay(s) configured statically but no relays available at listener registration";
      subsSources = sources;
    }
    else
    {
      try
      {
        subsSources = DatabusSubscription.createFromUriList(randomRelay.getSources());
      }
      catch (DatabusException e)
      {
        throw new DatabusClientException("source list decode error:" + e.getMessage(), e);
      }
      catch (URISyntaxException e)
      {
        throw new DatabusClientException("source list decode error:" + e.getMessage(), e);
      }
    }
    List<DatabusV2ConsumerRegistration> consumers = getListOfConsumerRegsFromSubList(groupsListeners, subsSources);
    if (null == consumers)
    {
      consumers = new CopyOnWriteArrayList<DatabusV2ConsumerRegistration>();
      groupsListeners.put(subsSources, consumers);
    }
    consumers.add(listener);
    return consumers;
  }

  /*
   * Given a mapping between a List of subscriptions to List of registrations, and a list of interested subscriptions,
   * returns a corresponding list of registrations.
   *
   * The semantics of this vary for V2 and V3 clients currently. This method is overridden in V3 with its semantics
   */
  protected static List<DatabusV2ConsumerRegistration> getListOfConsumerRegsFromSubList(
      Map<List<DatabusSubscription>,
      List<DatabusV2ConsumerRegistration>> groupsListeners,
      List<DatabusSubscription> subsSources)
  {
    return groupsListeners.get(subsSources);
  }

  @Override
  protected DatabusComponentAdmin createComponentAdmin()
  {
    return new DatabusComponentAdmin(this, getMbeanServer(), DatabusHttpClientImpl.class.getSimpleName());
  }

  @Override
  protected DatabusComponentStatus createComponentStatus()
  {
    _clientStatus = new DatabusHttpClientStatus();
    return _clientStatus;
  }

  protected synchronized void doRegisterRelay(ServerInfo serverInfo)
  {
    LOG.info("Registering relay: " + serverInfo.toString());

    List<DatabusSubscription> subList = DatabusSubscription.createSubscriptionList(serverInfo.getSources());
    Set<ServerInfo> sourceRelays = _relayGroups.get(subList);
    if (null == sourceRelays)
    {
      sourceRelays = new HashSet<ServerInfo>(5);
      _relayGroups.put(subList, sourceRelays);
    }
    sourceRelays.add(serverInfo);
  }


  /**
   * Should be only used by unittest
   */
  public void setCMEnabled(boolean enabled)
  {
    _cmEnabled = enabled;
  }

  protected synchronized void doUnregisterRelay(ServerInfo serverInfo)
  {
      Set<ServerInfo> sourceRelays = _relayGroups.get(DatabusSubscription.createSubscriptionList(serverInfo.getSources()));
      if (null != sourceRelays)
      {
        LOG.info("Unregistering relay: " + serverInfo.toString());
        sourceRelays.remove(serverInfo);
      }
  }

  /** generate a stats object name for a sources connection object */
  String generateSubsStatsName(List<String> sourcesStrList)
  {
    int subsListSize = sourcesStrList.size();
    String lastSubs = (subsListSize != 0) ? sourcesStrList.get(subsListSize-1) : "null" ;
    String[] cmpt =  lastSubs.split("\\.");
    String name =  (cmpt.length >= 4) ? cmpt[3] : lastSubs;
    LOG.info("sourcename= " + name);
    return name;
  }

  private synchronized void initializeRelayConnections()
  {
    for(List<DatabusSubscription> subsList: _relayGroups.keySet())
    {
      List<String> sourcesStrList = DatabusSubscription.getStrList(subsList);
      List<DatabusV2ConsumerRegistration> relayConsumers =
          getRelayGroupStreamConsumers().get(subsList);
      //nothing to do
      if (null == relayConsumers || 0 == relayConsumers.size()) continue;
      try
      {
        DatabusSourcesConnection.StaticConfig connConfig =
            getClientStaticConfig().getConnection(sourcesStrList);
        if (null == connConfig)
        {
          connConfig = getClientStaticConfig().getConnectionDefaults();
        }

        // Disabling SCN index works only with the BLOCK_ON_WRITE policy. If Scn index is disabled,
        // make sure we have the right policy.
        if (!connConfig.getEventBuffer().isEnableScnIndex() &&
          connConfig.getEventBuffer().getQueuePolicy() != DbusEventBuffer.QueuePolicy.BLOCK_ON_WRITE)
        {
          throw new InvalidConfigException("If SCN index is disabled, queue policy must be BLOCK_ON_WRITE");
        }
        CheckpointPersistenceProvider cpPersistenceProvder = getCheckpointPersistenceProvider();
        if (null != cpPersistenceProvder && getClientStaticConfig().getCheckpointPersistence().isClearBeforeUse())
        {
          cpPersistenceProvder.removeCheckpoint(sourcesStrList);
        }

        ServerInfo server0 = _relayGroups.get(subsList).iterator().next();

        ArrayList<DatabusV2ConsumerRegistration> bstConsumersRegs =
            new ArrayList<DatabusV2ConsumerRegistration>();
        for (List<DatabusSubscription> bstSubSourcesList: getRelayGroupBootstrapConsumers().keySet())
        {
          List<DatabusV2ConsumerRegistration> bstRegsistrations
          = getRelayGroupBootstrapConsumers().get(bstSubSourcesList);
          for (DatabusV2ConsumerRegistration bstConsumerReg: bstRegsistrations)
          {
            if (server0.supportsSources(bstConsumerReg.getSources()))
            {
              bstConsumersRegs.add(bstConsumerReg);
            }
          }
        }

        DbusEventBuffer eventBuffer = connConfig.getEventBuffer().getOrCreateEventBuffer(_eventFactory);
        eventBuffer.setDropOldEvents(true);
        eventBuffer.start(0);


        DbusEventBuffer bootstrapBuffer = null;
        // create bootstrap only if it is enabled
        if(_clientStaticConfig.getRuntime().getBootstrap().isEnabled()) {
          bootstrapBuffer = new DbusEventBuffer(connConfig.getEventBuffer());
          bootstrapBuffer.setDropOldEvents(false);
          bootstrapBuffer.start(0);
        }

        LOG.info("The sourcesList is " + sourcesStrList);
        LOG.info("The relayGroupStreamConsumers is " + getRelayGroupStreamConsumers().get(subsList));

        Set<ServerInfo> relays = _relayGroups.get(subsList);
        Set<ServerInfo> bootstrapServices = _bootstrapGroups.get(subsList);

        String statsCollectorName = generateSubsStatsName(sourcesStrList);
        int ownerId = getContainerStaticConfig().getId();

        _bootstrapEventsStats.addStatsCollector(
            statsCollectorName,
            new DbusEventsStatisticsCollector(ownerId,
                                              statsCollectorName + ".inbound.bs",
                                              true,
                                              false,
                                              getMbeanServer()));

        _inBoundStatsCollectors.addStatsCollector(
            statsCollectorName,
            new DbusEventsStatisticsCollector(ownerId,
                                              statsCollectorName + ".inbound",
                                              true,
                                              false,
                                              getMbeanServer()));

        _outBoundStatsCollectors.addStatsCollector(
            statsCollectorName,
            new DbusEventsStatisticsCollector(ownerId,
                                              statsCollectorName + ".outbound",
                                              true,
                                              false,
                                              getMbeanServer()));

        _consumerStatsCollectors.addStatsCollector(
            statsCollectorName,
            new ConsumerCallbackStats(ownerId,
                                      statsCollectorName + ".inbound.cons",
                                      statsCollectorName + ".inbound.cons",
                                      true,
                                      false,
                                      null,
                                      getMbeanServer()));

        _bsConsumerStatsCollectors.addStatsCollector(
            statsCollectorName,
            new ConsumerCallbackStats(ownerId,
                                      statsCollectorName + ".inbound.bs.cons" ,
                                      statsCollectorName + ".inbound.bs.cons",
                                      true,
                                      false,
                                      null,
                                      getMbeanServer()));

        _unifiedClientStatsCollectors.addStatsCollector(
            statsCollectorName,
            new UnifiedClientStats(ownerId,
                                   statsCollectorName + ".inbound.unified.cons",
                                   statsCollectorName + ".inbound.unified.cons",
                                   true,
                                   false,
                                   _clientStaticConfig.getPullerThreadDeadnessThresholdMs(),
                                   null,
                                   getMbeanServer()));

        ConnectionStateFactory connStateFactory = new ConnectionStateFactory(DatabusSubscription.getStrList(subsList));
        DatabusSourcesConnection newConn =
            new DatabusSourcesConnection(
                connConfig,
                subsList,
                relays,
                bootstrapServices,
                relayConsumers,
                //_relayGroupBootstrapConsumers.get(sourcesList),
                bstConsumersRegs,
                eventBuffer,
                bootstrapBuffer,
                getDefaultExecutorService(),
                getContainerStatsCollector(),
                _inBoundStatsCollectors.getStatsCollector(statsCollectorName),
                _bootstrapEventsStats.getStatsCollector(statsCollectorName),
                _consumerStatsCollectors.getStatsCollector(statsCollectorName),
                _bsConsumerStatsCollectors.getStatsCollector(statsCollectorName),
                _unifiedClientStatsCollectors.getStatsCollector(statsCollectorName),
                getCheckpointPersistenceProvider(),
                getRelayConnFactory(),
                getBootstrapConnFactory(),
                getHttpStatsCollector(),
                null,
                this,
                _eventFactory,
                connStateFactory);

        newConn.start();
        _relayConnections.add(newConn);
      }
      catch (Exception e)
      {
        LOG.error("connection initialization issue for source(s):" + subsList +
            "; please check your configuration", e);
      }
    }

    if (0 == _relayConnections.size())
    {
      LOG.warn("No connections specified");
    }
  }

  protected static List<ServerInfo> findServers(Map<List<DatabusSubscription>, Set<ServerInfo>> groups,
                                                 List<DatabusSubscription> subs)
  {
    boolean debugEnabled = LOG.isDebugEnabled();
    ArrayList<ServerInfo> result = new ArrayList<ServerInfo>(10);
    for(Map.Entry<List<DatabusSubscription>, Set<ServerInfo>> entry : groups.entrySet())
    {
      List<DatabusSubscription> serverSubs = entry.getKey();
      if (ServerInfo.checkSubsequenceSubsV3(subs, serverSubs))
      {
        result.addAll(entry.getValue());
      }
      if (debugEnabled) LOG.debug("Log an individual entry in group " + entry);
    }

    return result;
  }

  private static List<ServerInfo> parseServerInfosMap(Map<String, ServerInfoBuilder> map)
                          throws InvalidConfigException
  {
    ArrayList<ServerInfo> infos = new ArrayList<ServerInfo>((int)(map.size() * 1.3));
    for (Map.Entry<String, ServerInfoBuilder> entry : map.entrySet())
    {
      LOG.info("parseServerInfo: " + entry.toString());
      ServerInfoBuilder builder = entry.getValue();
      boolean added = infos.add(builder.build());
      LOG.info("added=" + added);
    }
    LOG.info("info size=" + infos.size());
    return infos;
  }

  /**
   * Not applicable in V2.
   */
  public Map<RegistrationId, DatabusV3Registration> getRegistrationIdMap()
  {
    return null;
  }

  /**
   * Return DatabusSourcesConnection corresponding to this regId
   */
  public DatabusSourcesConnection getDatabusSourcesConnection(String regIdStr)
  {
    return null;
  }

  /**
   * Runtime configuration for bootstrapping of the consumers
   */
  public static class BootstrapClientRuntimeConfig implements ConfigApplier<BootstrapClientRuntimeConfig>
  {
    private final boolean _enabled;
    private final List<ServerInfo> _services;

    public BootstrapClientRuntimeConfig(boolean enabled, List<ServerInfo> bootstrapServices)
    {
      super();
      _enabled = enabled;
      _services = bootstrapServices;
    }

    /** A flag that indicates if bootstrapping is enabled. */
    public boolean isEnabled()
    {
      return _enabled;
    }

    /** Server configuration for the bootstrap servers */
    public List<ServerInfo> getServices()
    {
      return _services;
    }

    public Set<ServerInfo> getServicesSet(){
      return new HashSet<ServerInfo>(_services);
    }

    @Override
    public void applyNewConfig(BootstrapClientRuntimeConfig oldConfig)
    {
      //Nothing to do
    }

    @Override
    public boolean equals(Object otherConfig)
    {
      if (null == otherConfig || ! (otherConfig instanceof BootstrapClientRuntimeConfig)) return false;
      return equalsConfig((BootstrapClientRuntimeConfig)otherConfig);
    }

    @Override
    public boolean equalsConfig(BootstrapClientRuntimeConfig otherConfig)
    {
      if (null == otherConfig) return false;
      return isEnabled() == otherConfig.isEnabled() &&
             getServices().equals(otherConfig.getServices());
    }

    @Override
    public int hashCode()
    {
      return (_enabled ? 0xFFFFFFFF : 0) ^ _services.hashCode();
    }
  }

  public static class BootstrapClientRuntimeConfigBuilder
                      implements ConfigBuilder<BootstrapClientRuntimeConfig>
  {
    private boolean _enabled;
    private final Map<String, ServerInfoBuilder> _services;
    private DatabusHttpClientImpl _managedInstance;
    private String _servicesList = "";

    public BootstrapClientRuntimeConfigBuilder()
    {
      _enabled = false;
      _services = new HashMap<String, ServerInfoBuilder>();
    }

    public boolean isEnabled()
    {
      return _enabled;
    }

    public void setEnabled(boolean enabled)
    {
      _enabled = enabled;
    }

    public void setService(String id, ServerInfoBuilder serverInfo)
    {
      _services.put(id, serverInfo);
    }

    public ServerInfoBuilder getService(String id)
    {
      ServerInfoBuilder bootstrapService = _services.get(id);
      if (null == bootstrapService)
      {
        bootstrapService = new ServerInfoBuilder();
        _services.put(id, bootstrapService);
      }

      return bootstrapService;
    }

    @Override
    public BootstrapClientRuntimeConfig build() throws InvalidConfigException
    {
      if (null == _managedInstance)
      {
        throw new InvalidConfigException("No managed databus client");
      }

      List<ServerInfo> bootstrapServices = parseServerInfosMap(_services);
      if (null != _servicesList && _servicesList.length() > 0) bootstrapServices = RuntimeConfigBuilder.parseServerInfoList(_servicesList, bootstrapServices);

      return new BootstrapClientRuntimeConfig(isEnabled(), bootstrapServices);
    }

    public DatabusHttpClientImpl getManagedInstance()
    {
      return _managedInstance;
    }

    public void setManagedInstance(DatabusHttpClientImpl managedInstance)
    {
      _managedInstance = managedInstance;
    }

    public String getServicesList()
    {
      return _servicesList;
    }

    public void setServicesList(String servicesList)
    {
      _servicesList = servicesList;
    }

  }

  public static class CheckpointPersistenceRuntimeConfig
               implements ConfigApplier<CheckpointPersistenceRuntimeConfig>
  {
    private final FileSystemCheckpointPersistenceProvider.RuntimeConfig _fileSystem;
    private final SharedCheckpointPersistenceProvider.RuntimeConfig _shared;

    public CheckpointPersistenceRuntimeConfig(FileSystemCheckpointPersistenceProvider.RuntimeConfig fileSystem)
    {
      super();
      _fileSystem = fileSystem;
      _shared= null;
    }

    public CheckpointPersistenceRuntimeConfig(SharedCheckpointPersistenceProvider.RuntimeConfig shared)
    {
      super();
      _shared = shared;
      _fileSystem = null;
    }


    /** Runtime configuration for the file-system-based checkpoint persistence provider */
    public FileSystemCheckpointPersistenceProvider.RuntimeConfig getFileSystem()
    {
      return _fileSystem;
    }

    public SharedCheckpointPersistenceProvider.RuntimeConfig getShared()
    {
      return _shared;
    }

    @Override
    public void applyNewConfig(CheckpointPersistenceRuntimeConfig oldConfig)
    {
      if (_fileSystem != null)
      {
        if (null == oldConfig || !getFileSystem().equals(oldConfig.getFileSystem()))
        {
          getFileSystem().applyNewConfig(null != oldConfig ? oldConfig.getFileSystem() : null);
        }
      }
      else if (_shared != null)
      {
        if (null == oldConfig || !getShared().equals(oldConfig.getShared()))
        {
          getShared().applyNewConfig(null != oldConfig ? oldConfig.getShared() : null);
        }
      }
    }

    @Override
    public boolean equals(Object otherConfig)
    {
      if (null == otherConfig || !(otherConfig instanceof CheckpointPersistenceRuntimeConfig)) return false;
      return equalsConfig((CheckpointPersistenceRuntimeConfig)otherConfig);
    }

    @Override
    public boolean equalsConfig(CheckpointPersistenceRuntimeConfig otherConfig)
    {
      if (null == otherConfig) return false;
      if (_fileSystem!=null)
      {
        return getFileSystem().equals(otherConfig.getFileSystem());
      }
      else if (_shared != null)
      {
        return getShared().equals(otherConfig.getShared());
      }
      return false;
    }

    @Override
    public int hashCode()
    {
      return (_fileSystem != null) ? _fileSystem.hashCode() : _shared.hashCode();
    }


  }

  public static class CheckpointPersistenceRuntimeConfigBuilder
                      implements ConfigBuilder<CheckpointPersistenceRuntimeConfig>
  {
    private FileSystemCheckpointPersistenceProvider.RuntimeConfigBuilder _fileSystem;
    private SharedCheckpointPersistenceProvider.RuntimeConfigBuilder _shared;
    private DatabusHttpClientImpl _managedInstance = null;

    public CheckpointPersistenceRuntimeConfigBuilder()
    {
      _fileSystem = new FileSystemCheckpointPersistenceProvider.RuntimeConfigBuilder();
      _shared =  new SharedCheckpointPersistenceProvider.RuntimeConfigBuilder();
    }

    public FileSystemCheckpointPersistenceProvider.RuntimeConfigBuilder getFileSystem()
    {
      return _fileSystem;
    }


    public void setFileSystem(FileSystemCheckpointPersistenceProvider.RuntimeConfigBuilder fileSystem)
    {
      _fileSystem = fileSystem;
    }

    public DatabusHttpClientImpl getManagedInstance()
    {
      return _managedInstance;
    }

    public void setManagedInstance(DatabusHttpClientImpl managedInstance)
    {
      _managedInstance = managedInstance;
      if (null != _managedInstance)
      {
        CheckpointPersistenceProvider persistenceProvider =
            _managedInstance.getCheckpointPersistenceProvider();
        if (persistenceProvider instanceof FileSystemCheckpointPersistenceProvider)
        {
          _fileSystem.setManagedInstance((FileSystemCheckpointPersistenceProvider)persistenceProvider);
        }
        else if (persistenceProvider instanceof SharedCheckpointPersistenceProvider)
        {
          _shared.setManagedInstance((SharedCheckpointPersistenceProvider)persistenceProvider);
        }
      }
    }

    @Override
    public CheckpointPersistenceRuntimeConfig build() throws InvalidConfigException
    {
      if (null == _managedInstance)
      {
        throw new InvalidConfigException("No associated client for runtime config");
      }
      if (_fileSystem.getManagedInstance()!= null) {
        return new CheckpointPersistenceRuntimeConfig(_fileSystem.build());
      } else if (_shared.getManagedInstance()!=null) {
        return new CheckpointPersistenceRuntimeConfig(_shared.build());
      }
      return null;
    }

    public SharedCheckpointPersistenceProvider.RuntimeConfigBuilder getShared()
    {
      return _shared;
    }

    public void setShared(SharedCheckpointPersistenceProvider.RuntimeConfigBuilder sharedState)
    {
      _shared = sharedState;
    }
  }

  public static class CheckpointPersistenceStaticConfig
  {
    public static enum ProviderType
    {
      FILE_SYSTEM,
      EXISTING,
      NONE,
      SHARED
    }

    private final ProviderType _type;
    private final FileSystemCheckpointPersistenceProvider.StaticConfig _fileSystem;
    private final SharedCheckpointPersistenceProvider.StaticConfig _sharedState;
    private final CheckpointPersistenceProvider _existing;
    private final CheckpointPersistenceRuntimeConfigBuilder _runtime;
    private final String _runtimeConfigPrefix;
    private final boolean _clearBeforeUse;
    private final int _protocolVersion;


    @Override
    public String toString()
    {
      return "CheckpointPersistenceStaticConfig [_type=" + _type
             + ", _fileSystem=" + _fileSystem + ", _sharedState="
             + _sharedState + ", _existing=" + _existing + ", _runtime="
             + _runtime + ", _runtimeConfigPrefix=" + _runtimeConfigPrefix
             + ", _clearBeforeUse=" + _clearBeforeUse + "]";
    }

    public CheckpointPersistenceStaticConfig(ProviderType type,
                                             FileSystemCheckpointPersistenceProvider.StaticConfig fileSystem,
                                             SharedCheckpointPersistenceProvider.StaticConfig sharedState,
                                             CheckpointPersistenceProvider existing,
                                             CheckpointPersistenceRuntimeConfigBuilder runtime,
                                             String runtimeConfigPrefix,
                                             boolean clearBeforeUse,
                                             int protocolVersion)
    {
      super();
      _type = type;
      _fileSystem = fileSystem;
      _sharedState = sharedState;
      _existing = existing;
      _runtime = runtime;
      _runtimeConfigPrefix = runtimeConfigPrefix;
      _clearBeforeUse = clearBeforeUse;
      _protocolVersion = protocolVersion;
    }

    /** The type of the checkpoint persistence provider.
     *
     * <ul>
     <li> FILE_SYSTEM specifies to store the checkpoints in a directory on a local file system</li>
     <li> EXISTING specifies to use a Spring-wired checkpoint-persistence provider </li>
     <li> SHARED specifies to store the checkpoints in a shared location as distributed state </li>
     <li> NONE specifies not to persist checkpoints (<b>Use at your own risk!</b>) </li>
     * </ul>
     **/
    public ProviderType getType()
    {
      return _type;
    }

    /**
     * The static configuration for the file-system-based checkpoint persistence provider.
     * {@link #getType()} needs to be {@link ProviderType}.FILE_SYSTEM for this property to have an
     * effect. */
    public FileSystemCheckpointPersistenceProvider.StaticConfig getFileSystem()
    {
      return _fileSystem;
    }

    /**
     * The static configuration for the shared-state based checkpoint persistence provider.
     * {@link #getType()} needs to be {@link ProviderType} SHARED for this property to have an
     * effect. */
    public SharedCheckpointPersistenceProvider.StaticConfig getSharedState()
    {
      return _sharedState;
    }

    /**
     * Obtain the existing checkpoint persistence provider if one has been specified.
     *
     * NOTE: We don't use the standard getXXX() naming convention to avoid attempts
     * to serialize it.
     **/
    public CheckpointPersistenceProvider existing()
    {
      return ProviderType.EXISTING == _type ? _existing : null;
    }

    public String getRuntimeConfigPrefix()
    {
      return _runtimeConfigPrefix;
    }

    /** The runtime configuration for the checkpoint persistence provider */
    public CheckpointPersistenceRuntimeConfigBuilder getRuntime()
    {
      return _runtime;
    }

    /**
     * A flag that indicates whether to clear any existing persisted checkpoints on first use.
     *
     * NOTE: This should be used mostly for testing where we want to make sure that no state from
     * previous tests is used.
     * */
    public boolean isClearBeforeUse()
    {
      return _clearBeforeUse;
    }

    public CheckpointPersistenceProvider getOrCreateCheckpointPersistenceProvider()
    throws InvalidConfigException
    {
      return getOrCreateCheckpointPersistenceProvider(null);
    }

    public CheckpointPersistenceProvider getOrCreateCheckpointPersistenceProvider(DatabusClientGroupMember groupMember)
           throws InvalidConfigException
    {
      CheckpointPersistenceProvider cpPersistenceProvider = null;
      switch (getType())
      {
        case FILE_SYSTEM:
        LOG.info("Creating file-system checkpoint persistence provider");
        cpPersistenceProvider =
            new FileSystemCheckpointPersistenceProvider(getFileSystem(), _protocolVersion);
        break;
        case SHARED:
        LOG.info("Creating shared ZooKeeper-based checkpoint persistence provider");
        cpPersistenceProvider = new SharedCheckpointPersistenceProvider(groupMember, getSharedState());
        break;

        case EXISTING: cpPersistenceProvider = _existing; break;
        case NONE: cpPersistenceProvider = null; break;
        default: throw new InvalidConfigException("Unable to create persistence provider of type: " +
                                       getType());
      }

      return cpPersistenceProvider;
    }
  }

  public static class CheckpointPersistenceStaticConfigBuilder
                      implements ConfigBuilder<CheckpointPersistenceStaticConfig>
  {
    private String _type = ProviderType.FILE_SYSTEM.toString();
    private FileSystemCheckpointPersistenceProvider.Config _fileSystem;
    private SharedCheckpointPersistenceProvider.Config _sharedState;
    private CheckpointPersistenceProvider _existing  = null;
    private CheckpointPersistenceRuntimeConfigBuilder _runtime;
    private String _runtimeConfigPrefix;
    private boolean _clearBeforeUse;
    private int _protocolVersion;

    public CheckpointPersistenceStaticConfigBuilder()
    {
      _fileSystem = new FileSystemCheckpointPersistenceProvider.Config();
      _runtime = new CheckpointPersistenceRuntimeConfigBuilder();
      _runtime.setFileSystem(_fileSystem.getRuntime());
      setRuntimeConfigPrefix("databus.checkpointPersistence.");
      _sharedState = new SharedCheckpointPersistenceProvider.Config();
      _clearBeforeUse = false;
      _protocolVersion = DEFAULT_PROTOCOL_VERSION;
    }

    public void setProtocolVersion(int protocolVersion)
    {
      _protocolVersion = protocolVersion;
    }

    public String getType()
    {
      return _type;
    }

    public void setType(String type)
    {
      _type = type;
    }

    public FileSystemCheckpointPersistenceProvider.Config getFileSystem()
    {
      return _fileSystem;
    }

    public void setFileSystem(FileSystemCheckpointPersistenceProvider.Config fileSystem)
    {
      _fileSystem = fileSystem;
    }

    public CheckpointPersistenceProvider getExisting()
    {
      return _existing;
    }

    /** Sets a spring-wired existing checkpoint persistence provider. {@link #getType()} needs to be
     * {@link ProviderType}.EXISTING for this property to have an effect. */
    public void setExisting(CheckpointPersistenceProvider existing)
    {
      _existing = existing;
    }

    public String getRuntimeConfigPrefix()
    {
      return _runtimeConfigPrefix;
    }

    public void setRuntimeConfigPrefix(String runtimeConfigPrefix)
    {
      _runtimeConfigPrefix = runtimeConfigPrefix;
      _fileSystem.setRuntimeConfigPrefix(_runtimeConfigPrefix + ".fileSystem");
    }

    public CheckpointPersistenceRuntimeConfigBuilder getRuntime()
    {
      return _runtime;
    }

    public void setRuntime(CheckpointPersistenceRuntimeConfigBuilder runtime)
    {
      _runtime = runtime;
    }

    @Override
    public CheckpointPersistenceStaticConfig build() throws InvalidConfigException
    {
      CheckpointPersistenceStaticConfig.ProviderType providerType = null;

      try
      {
        providerType = CheckpointPersistenceStaticConfig.ProviderType.valueOf(_type.toUpperCase());
      }
      catch (Exception e)
      {
        throw new InvalidConfigException("invalid cp3 type:" + _type, e);
      }

      if (CheckpointPersistenceStaticConfig.ProviderType.EXISTING == providerType &&
          null == getExisting())
      {
        throw new InvalidConfigException("no existing checkpoint persistence provider specified");
      }

      LOG.info("checkpoint persistence type: " + _type);
      LOG.info("clear before use: " + _clearBeforeUse);
      LOG.info("client-relay protocol version: " + _protocolVersion);

      return new CheckpointPersistenceStaticConfig(providerType,
                                                   getFileSystem().build(),
                                                   getSharedState().build(),
                                                   getExisting(), getRuntime(),
                                                   getRuntimeConfigPrefix(),
                                                   isClearBeforeUse(),
                                                   _protocolVersion);
    }

    public boolean isClearBeforeUse()
    {
      return _clearBeforeUse;
    }

    public void setClearBeforeUse(boolean clearBeforeUse)
    {
      _clearBeforeUse = clearBeforeUse;
    }

    public SharedCheckpointPersistenceProvider.Config getSharedState()
    {
      return _sharedState;
    }

    public void setSharedState(SharedCheckpointPersistenceProvider.Config sharedState)
    {
      _sharedState = sharedState;
    }
  }

  public class RuntimeConfig implements ConfigApplier<RuntimeConfig>
  {
    private final ServerContainer.RuntimeConfig _container;
    private final CheckpointPersistenceRuntimeConfig _checkpointPersistence;
    private final List<ServerInfo> _relays;
    private final HttpStatisticsCollector.RuntimeConfig _httpStatsCollector;
    private final LoggingConsumer.RuntimeConfig _loggingListener;
    private final BootstrapClientRuntimeConfig _bootstrap;

    public RuntimeConfig(ServerContainer.RuntimeConfig container,
                         CheckpointPersistenceRuntimeConfig checkpoint,
                         List<ServerInfo> relays,
                         HttpStatisticsCollector.RuntimeConfig httpStatsCollector,
                         LoggingConsumer.RuntimeConfig loggingListener,
                         BootstrapClientRuntimeConfig bootstrap)
    {
      super();
      _container = container;
      _checkpointPersistence = checkpoint;
      _relays = relays;
      _httpStatsCollector = httpStatsCollector;
      _loggingListener = loggingListener;
      _bootstrap = bootstrap;
    }

    /** Runtime configuration for the Netty serving container */
    public ServerContainer.RuntimeConfig getContainer()
    {
      return _container;
    }

    /** Runtime configuration for the checkpoint persistence provider */
    public CheckpointPersistenceRuntimeConfig getCheckpointPersistence()
    {
      return _checkpointPersistence;
    }

    /** Server configuration for the available relays */
    public List<ServerInfo> getRelays()
    {
      return _relays;
    }

    public Set<ServerInfo> getRelaysSet(){
      return new HashSet<ServerInfo>(_relays);
    }

    /** Add a relay to set of relays */
    public void addRelay(ServerInfo si)
    {
      if (! _cmEnabled) {
        LOG.error("Supported only when Helix Integration is enabled (i.e., V3 client + CM enabled");
      }
      LOG.info("Adding relay with name " + si.getName() + " " + si.getAddress().getHostName() + " " + si.getAddress().getPort());
      _relays.add(si);
      return;
    }

    /** Remove a relay to set of relays */
    public void removeRelay(ServerInfo si)
    {
      if (! _cmEnabled) {
        LOG.error("Supported only when Helix Integration is enabled (i.e., V3 client + CM enabled");
      }
      LOG.info("Removing relay with name " + si.getName() + " " + si.getAddress().getHostName() + " " + si.getAddress().getPort());
      _relays.remove(si);
      return;
    }

    /** Remove a relay to set of relays */
    public void updateRelaySet(Set<ServerInfo> ssi)
    {
      if (! _cmEnabled) {
        LOG.error("Supported only when Helix Integration is enabled (i.e., V3 client + CM enabled");
      }
      LOG.info("Updating relay set ");
      for (ServerInfo si: ssi)
      {
        LOG.info(si.getName() + " " + si.getAddress().getHostName() + " " + si.getAddress().getPort());
      }
      _relays.addAll(ssi);
      return;
    }

    /** Runtime configuration for bootstrapping */
    public BootstrapClientRuntimeConfig getBootstrap()
    {
      return _bootstrap;
    }

    /** Runtime configuration for statistics collector for the HTTP calls to the relays */
    public HttpStatisticsCollector.RuntimeConfig getHttpStatsCollector()
    {
      return _httpStatsCollector;
    }

    /** Runtime configuration for the logging consumer */
    public LoggingConsumer.RuntimeConfig getLoggingListener()
    {
      return _loggingListener;
    }

    @Override
    public void applyNewConfig(RuntimeConfig oldConfig)
    {
      LOG.info("Applying runtime client config");
      if (null == oldConfig || ! getContainer().equals(oldConfig.getContainer()))
      {
        getContainer().applyNewConfig(null != oldConfig ? oldConfig.getContainer() : null);
      }
      if (getCheckpointPersistence() != null) {
        if (null == oldConfig || !getCheckpointPersistence().equals(oldConfig.getCheckpointPersistence()))
        {
          getCheckpointPersistence().applyNewConfig(
              null != oldConfig ? oldConfig.getCheckpointPersistence() : null);
        }
      }
      if (null == oldConfig || !getRelays().equals(oldConfig.getRelays()))
      {
        if (null != oldConfig)
        {
          for (ServerInfo serverInfo: oldConfig.getRelays())
          {
            if (! getRelays().contains(serverInfo))
            {
              doUnregisterRelay(serverInfo);
            }
          }
        }

        for (ServerInfo serverInfo: getRelays())
        {
          if (null == oldConfig || ! oldConfig.getRelays().contains(serverInfo))
          {
            doRegisterRelay(serverInfo);
          }
        }

      }

      if (null == oldConfig || !getBootstrapServices().equals(oldConfig.getBootstrap().getServices()))
      {
        if (null != oldConfig)
        {
          for (ServerInfo serverInfo : oldConfig.getBootstrap().getServices())
          {
            if (! getBootstrapServices().contains(serverInfo))
            {
              doUnregisterBootstrapService(serverInfo);
            }
          }
        }

        for (ServerInfo serverInfo: getBootstrap().getServices())
        {
          if (null == oldConfig || ! oldConfig.getBootstrap().getServices().contains(serverInfo))
          {
            doRegisterBootstrapService(serverInfo);
          }
        }
      }
    }

    @Override
    public boolean equals(Object otherConfig)
    {
      if (null == otherConfig || !(otherConfig instanceof RuntimeConfig)) return false;
      return equalsConfig((RuntimeConfig)otherConfig);
    }

    @Override
    public boolean equalsConfig(RuntimeConfig otherConfig)
    {
      if (null == otherConfig) return false;
      return getContainer().equals(otherConfig.getContainer()) &&
             getCheckpointPersistence().equals(otherConfig.getCheckpointPersistence());
    }

    @Override
    public int hashCode()
    {
      return _container.hashCode() ^ _checkpointPersistence.hashCode();
    }

  }

  public static class RuntimeConfigBuilder implements ConfigBuilder<RuntimeConfig>
  {
    private ServerContainer.RuntimeConfigBuilder _container;
    private CheckpointPersistenceRuntimeConfigBuilder _checkpointPersistence;
    private DatabusHttpClientImpl _managedInstance = null;
    private final Map<String, ServerInfoBuilder> _relays;
    private HttpStatisticsCollector.RuntimeConfigBuilder _httpStatsCollector;
    private LoggingConsumer.RuntimeConfigBuilder _loggingListener;
    private BootstrapClientRuntimeConfigBuilder _bootstrap;
    private String _relaysList = "";

    public RuntimeConfigBuilder()
    {
      _container = new ServerContainer.RuntimeConfigBuilder();
      _checkpointPersistence = new CheckpointPersistenceRuntimeConfigBuilder();
      _relays = new HashMap<String, ServerInfoBuilder>();
      _httpStatsCollector = new HttpStatisticsCollector.RuntimeConfigBuilder();
      _loggingListener = new LoggingConsumer.RuntimeConfigBuilder();
      _bootstrap = new BootstrapClientRuntimeConfigBuilder();
    }

    public ServerContainer.RuntimeConfigBuilder getContainer()
    {
      return _container;
    }

    public void setContainer(ServerContainer.RuntimeConfigBuilder container)
    {
      _container = container;
    }

    public CheckpointPersistenceRuntimeConfigBuilder getCheckpointPersistence()
    {
      return _checkpointPersistence;
    }

    public void setCheckpointPersistence(CheckpointPersistenceRuntimeConfigBuilder checkpointPersistence)
    {
      _checkpointPersistence = checkpointPersistence;
    }

    public DatabusHttpClientImpl getManagedInstance()
    {
      return _managedInstance;
    }

    public void setManagedInstance(DatabusHttpClientImpl managedInstance)
    {
      _managedInstance = managedInstance;
      if (null != _managedInstance)
      {
        _container.setManagedInstance(_managedInstance);
        _checkpointPersistence.setManagedInstance(_managedInstance);
        _httpStatsCollector.setManagedInstance(_managedInstance.getHttpStatsCollector());
        _loggingListener.managedInstance(_managedInstance.getLoggingListener());
        _bootstrap.setManagedInstance(_managedInstance);
      }
    }

    public ServerInfoBuilder getRelay(String id)
    {
      ServerInfoBuilder relay = getRelays().get(id);
      if (null == relay)
      {
        relay = new ServerInfoBuilder();
        setRelay(id, relay);
      }

      return relay;
    }

    public void setRelay(String id, ServerInfoBuilder serverInfo)
    {
      _relays.put(id, serverInfo);
    }

    public Map<String, ServerInfoBuilder> getRelays()
    {
      return _relays;
    }

    @Override
    public RuntimeConfig build() throws InvalidConfigException
    {
      if (null == _managedInstance)
      {
        throw new InvalidConfigException("No associated http client for runtime config");
      }

      List<ServerInfo> relays = parseServerInfosMap(getRelays());
      LOG.info("Relays size=" + relays.size());
      if (null != _relaysList && _relaysList.length() > 0) relays = parseServerInfoList(_relaysList, relays);
      LOG.info("Relays size=" + relays.size());

      return _managedInstance.new RuntimeConfig(getContainer().build(),
                                                getCheckpointPersistence().build(),
                                                relays,
                                                getHttpStatsCollector().build(),
                                                getLoggingListener().build(),
                                                getBootstrap().build());
    }

    static List<ServerInfo> parseServerInfoList(String serversList, List<ServerInfo> servers)
            throws InvalidConfigException
    {
      if (null == servers) servers = new ArrayList<ServerInfo>(10);
      ServerInfoSetBuilder builder = new ServerInfoSetBuilder();
      builder.setServers(serversList);
      servers.addAll(builder.build());

      return servers;
    }

    public HttpStatisticsCollector.RuntimeConfigBuilder getHttpStatsCollector()
    {
      return _httpStatsCollector;
    }

    public void setHttpStatsCollector(HttpStatisticsCollector.RuntimeConfigBuilder httpStatsCollector)
    {
      _httpStatsCollector = httpStatsCollector;
    }

    public LoggingConsumer.RuntimeConfigBuilder getLoggingListener()
    {
      return _loggingListener;
    }

    public void setLoggingListener(LoggingConsumer.RuntimeConfigBuilder loggingListener)
    {
      _loggingListener = loggingListener;
    }

    public BootstrapClientRuntimeConfigBuilder getBootstrap()
    {
      return _bootstrap;
    }

    public void setBootstrap(BootstrapClientRuntimeConfigBuilder bootstrap)
    {
      _bootstrap = bootstrap;
    }

    public String getRelaysList()
    {
      return _relaysList;
    }

    public void setRelaysList(String relaysList)
    {
      _relaysList = relaysList;
    }

  }

  public static class StaticConfig
  {
    private final CheckpointPersistenceStaticConfig _checkpointPersistence;
    private final ServerContainer.StaticConfig _container;
    private final RuntimeConfigBuilder _runtime;
    private final String _runtimeConfigPrefix;
    private final DatabusSourcesConnection.StaticConfig _connectionDefaults;
    private final Map<List<String>, DatabusSourcesConnection.StaticConfig> _connections;
    private final HttpStatisticsCollector.StaticConfig _httpStatsCollector;
    private final LoggingConsumer.StaticConfig _loggingListener;
    private final DatabusClientNode.StaticConfig _cluster;
    private final long _dscUpdateIntervalMs;
    private final long _pullerThreadDeadnessThresholdMs;
    private final int _pullerBufferUtilizationPct;
    private final boolean _enableReadLatestOnRelayFallOff;
    private final boolean _enablePerConnectionStats;
    private final boolean _usesDynamicRelayConfiguration;
    private final Map<String, ClusterRegistrationStaticConfig> _clientClusters;

    public StaticConfig(CheckpointPersistenceStaticConfig checkpointPersistence,
                        ServerContainer.StaticConfig container,
                        RuntimeConfigBuilder runtime,
                        String runtimeConfigPrefix,
                        DatabusSourcesConnection.StaticConfig connectionDefaults,
                        Map<List<String>, DatabusSourcesConnection.StaticConfig> connections,
                        HttpStatisticsCollector.StaticConfig httpStatsCollector,
                        LoggingConsumer.StaticConfig loggingListener,
                        DatabusClientNode.StaticConfig cluster,
                        long dscUpdateIntervalMs,
                        long pullerThreadDeadnessThresholdMs,
                        int pullerBufferUtilizationPct,
                        boolean enableReadLatestOnRelayFallOff,
                        boolean enablePerConnectionStats,
                        boolean usesDynamicRelayConfiguration,
                        Map<String, ClusterRegistrationStaticConfig> clientClusters)
    {
      super();
      _checkpointPersistence = checkpointPersistence;
      _container = container;
      _runtime = runtime;
      _runtimeConfigPrefix = runtimeConfigPrefix;
      _connections = connections;
      _connectionDefaults = connectionDefaults;
      _httpStatsCollector = httpStatsCollector;
      _loggingListener = loggingListener;
      _cluster = cluster;
      _dscUpdateIntervalMs = dscUpdateIntervalMs;
      _pullerThreadDeadnessThresholdMs = pullerThreadDeadnessThresholdMs;
      _pullerBufferUtilizationPct = pullerBufferUtilizationPct;
      _enableReadLatestOnRelayFallOff = enableReadLatestOnRelayFallOff;
      _enablePerConnectionStats = enablePerConnectionStats;
      _usesDynamicRelayConfiguration = usesDynamicRelayConfiguration;
      _clientClusters = clientClusters;
    }

    // NOT USED
    public long getDSCUpdateIntervalMs()
    {
      return _dscUpdateIntervalMs;
    }

    public long getPullerThreadDeadnessThresholdMs()
    {
      return _pullerThreadDeadnessThresholdMs;
    }

    /** The checkpoint persistent provider static configuration */
    public CheckpointPersistenceStaticConfig getCheckpointPersistence()
    {
      return _checkpointPersistence;
    }

    /** The netty container static configuration */
    public ServerContainer.StaticConfig getContainer()
    {
      return _container;
    }

    /** The client runtime configuration */
    public RuntimeConfigBuilder getRuntime()
    {
      return _runtime;
    }

    public DatabusClientNode.StaticConfig getCluster()
    {
      return _cluster;
    }

    public String getRuntimeConfigPrefix()
    {
      return _runtimeConfigPrefix;
    }

    /** All Databus source connections static configurations */
    public Map<List<String>, DatabusSourcesConnection.StaticConfig> getConnections()
    {
      return _connections;
    }

    /** The Databus source connections static configuration */
    public DatabusSourcesConnection.StaticConfig getConnection(List<String> id)
    {
      return _connections.get(id);
    }

    /** The client cluster configuration */
    public ClusterRegistrationStaticConfig getClientCluster(String clusterName) {
      return _clientClusters.get(clusterName);
    }

    /** The default static configuration for all Databus source connections */
    public DatabusSourcesConnection.StaticConfig getConnectionDefaults()
    {
      return _connectionDefaults;
    }

    /** The static configuration for statistics collector for all HTTP calls to the relays */
    public HttpStatisticsCollector.StaticConfig getHttpStatsCollector()
    {
      return _httpStatsCollector;
    }

    /** Static configuration for the logging consumer */
    public LoggingConsumer.StaticConfig getLoggingListener()
    {
      return _loggingListener;
    }

    public int getPullerBufferUtilizationPct()
    {
      return _pullerBufferUtilizationPct;
    }

    public boolean isReadLatestScnOnErrorEnabled()
    {
      return _enableReadLatestOnRelayFallOff;
    }

    public boolean isEnablePerConnectionStats()
    {
      return _enablePerConnectionStats;
    }

    /**
     * The relays used by the client can be configured either statically or, with a ZooKeeper
     * setup, dynamically.  The v2 codebase (this) supports only static configs, but the v3
     * codebase (@see DatabusHttpV3ClientImpl) supports both.  Note that this setting is
     * independent of the v2 vs. v3 (vs. v4) <em>protocol</em>; static configuration may be
     * desirable even for clients using the latest protocol version.  Also note that while
     * "v2" and "v3" are often conflated with Oracle and Espresso source databases, respectively,
     * this is more or less an accidental and temporary state of affairs.
     */
    public boolean usesDynamicRelayConfiguration()
    {
      return _usesDynamicRelayConfiguration;
    }

    @Override
    public String toString()
    {
      return "StaticConfig ["
             + "_checkpointPersistence=" + _checkpointPersistence
             + ", _container=" + _container
             + ", _runtime=" + _runtime
             + ", _runtimeConfigPrefix=" + _runtimeConfigPrefix
             + ", _connectionDefaults=" + _connectionDefaults
             + ", _connections=" + _connections
             + ", _httpStatsCollector=" + _httpStatsCollector
             + ", _loggingListener=" + _loggingListener
             + ", _cluster=" + _cluster
             + ", _dscUpdateIntervalMs=" + _dscUpdateIntervalMs
             + ", _pullerThreadDeadnessThresholdMs=" + _pullerThreadDeadnessThresholdMs
             + ", _pullerBufferUtilizationPct=" + _pullerBufferUtilizationPct
             + ", _enableReadLatestOnRelayFallOff=" + _enableReadLatestOnRelayFallOff
             + ", _enablePerConnectionStats=" + _enablePerConnectionStats
             + ", _usesDynamicRelayConfiguration=" + _usesDynamicRelayConfiguration
             + ", _clientClusters=" + _clientClusters
             + "]";
    }
  }

  public static class StaticConfigBuilderBase
  {
    protected CheckpointPersistenceStaticConfigBuilder _checkpointPersistence;
    protected ServerContainer.Config _container;
    protected RuntimeConfigBuilder _runtime;
    protected String _runtimeConfigPrefix = "databus.client.";
    protected DatabusSourcesConnection.Config _connectionDefaults;
    protected Map<String, DatabusSourcesConnection.Config> _connections;
    protected HttpStatisticsCollector.Config _httpStatsCollector;
    protected LoggingConsumer.Config _loggingListener;
    protected DatabusClientNode.Config _cluster;
    protected long _dscUpdateIntervalMs;
    protected long _pullerThreadDeadnessThresholdMs;
    protected int _pullerBufferUtilizationPct;
    protected boolean _enablePerConnectionStats = false;
    protected Map<String, ClusterRegistrationConfig> _clientClusters;

    // Flag to do streamFromLastScn on SCNNotFoundException
    protected boolean _enableReadLatestOnRelayFallOff;

    protected HashMap<List<String>, DatabusSourcesConnection.StaticConfig> _connConfigs;

    public StaticConfigBuilderBase()
    {
      _dscUpdateIntervalMs = 5*1000;
      _pullerThreadDeadnessThresholdMs = UnifiedClientStats.DEFAULT_DEADNESS_THRESHOLD_MS;
      _pullerBufferUtilizationPct = 100;
      // Flag to do streamFromLastScn on SCNNotFoundException
      _enableReadLatestOnRelayFallOff = false;
      _runtime = new RuntimeConfigBuilder();
      setCheckpointPersistence(new CheckpointPersistenceStaticConfigBuilder());

      //make sure the default client settings do not conflict with relay's settings
      ServerContainer.Config containerCfg = new ServerContainer.Config();
      containerCfg.setHttpPort(containerCfg.getHttpPort() + 1);
      containerCfg.getJmx().setJmxServicePort(containerCfg.getJmx().getJmxServicePort() + 1);

      setContainer(containerCfg);
      _connectionDefaults = new DatabusSourcesConnection.Config();
      _connections = new HashMap<String, DatabusSourcesConnection.Config>(5);
      _clientClusters = new HashMap<String, ClusterRegistrationConfig>();
      _httpStatsCollector = new HttpStatisticsCollector.Config();
      _loggingListener = new LoggingConsumer.Config();
      _cluster= new DatabusClientNode.Config();
    }

    protected void verifyConfig() throws InvalidConfigException
    {
      if (_pullerBufferUtilizationPct <= 0 || _pullerBufferUtilizationPct > 100)
      {
        throw new InvalidConfigException("invalid puller buffer utilization percentage:" +
            _pullerBufferUtilizationPct);
      }
    }

    public CheckpointPersistenceStaticConfigBuilder getCheckpointPersistence()
    {
      return _checkpointPersistence;
    }

    public void setCheckpointPersistence(CheckpointPersistenceStaticConfigBuilder checkpointPersistence)
    {
      _checkpointPersistence = checkpointPersistence;
      _checkpointPersistence.setRuntimeConfigPrefix(_runtimeConfigPrefix + "checkpoint.");
      _runtime.setCheckpointPersistence(checkpointPersistence.getRuntime());
    }

    public ServerContainer.Config getContainer()
    {
      return _container;
    }

    public void setContainer(ServerContainer.Config container)
    {
      _container = container;
      _container.setRuntimeConfigPropertyPrefix(_runtimeConfigPrefix + "runtime.");
      _runtime.setContainer(container.getRuntime());
    }

    public RuntimeConfigBuilder getRuntime()
    {
      return _runtime;
    }

    public void setRuntime(RuntimeConfigBuilder runtime)
    {
      _runtime = runtime;
    }

    public String getRuntimeConfigPrefix()
    {
      return _runtimeConfigPrefix;
    }

    public void setRuntimeConfigPrefix(String runtimeConfigPrefix)
    {
      _runtimeConfigPrefix = runtimeConfigPrefix;
      _container.setRuntimeConfigPropertyPrefix(_runtimeConfigPrefix + "runtime.");
      _checkpointPersistence.setRuntimeConfigPrefix(_runtimeConfigPrefix + "checkpoint.");
    }

    public int getPullerBufferUtilizationPct()
    {
      return _pullerBufferUtilizationPct;
    }

    public void setPullerBufferUtilizationPct(int pullerBufferUtilizationPct)
    {
      _pullerBufferUtilizationPct = pullerBufferUtilizationPct;
    }

    public boolean isEnableReadLatestOnRelayFallOff()
    {
      return _enableReadLatestOnRelayFallOff;
    }


    public void setEnableReadLatestOnRelayFallOff(boolean enableReadLatestOnRelayFallOff)
    {
      this._enableReadLatestOnRelayFallOff = enableReadLatestOnRelayFallOff;
    }

    public DatabusSourcesConnection.Config getConnection(String id)
    {
      DatabusSourcesConnection.Config conn = _connections.get(id);
      if (null == conn)
      {
        conn = new DatabusSourcesConnection.Config(_connectionDefaults);
        _connections.put(id, conn);
      }

      return conn;
    }

    public void setConnection(String id, DatabusSourcesConnection.Config conn)
    {
      _connections.put(id, conn);
    }

    public ClusterRegistrationConfig getClientCluster(String id)
    {
      ClusterRegistrationConfig cluster = _clientClusters.get(id);
      if (null == cluster)
      {
        cluster = new ClusterRegistrationConfig();
        _clientClusters.put(id, cluster);
      }

      return cluster;
    }

    public void setClientCluster(String id, ClusterRegistrationConfig cluster)
    {
      _clientClusters.put(id, cluster);
    }

    public HttpStatisticsCollector.Config getHttpStatsCollector()
    {
      return _httpStatsCollector;
    }

    public void setHttpStatsCollector(HttpStatisticsCollector.Config httpStatsCollector)
    {
      _httpStatsCollector = httpStatsCollector;
    }

    public LoggingConsumer.Config getLoggingListener()
    {
      return _loggingListener;
    }

    public void setLoggingListener(LoggingConsumer.Config loggingListener)
    {
      _loggingListener = loggingListener;
    }

    // NOT USED
    public long getDscUpdateIntervalMs()
    {
      return _dscUpdateIntervalMs;
    }

    // NOT USED
    public void setDscUpdateIntervalMs(long dscUpdateIntervalMs)
    {
      _dscUpdateIntervalMs = dscUpdateIntervalMs;
    }

    public long getPullerThreadDeadnessThresholdMs()
    {
      return _pullerThreadDeadnessThresholdMs;
    }

    public void setPullerThreadDeadnessThresholdMs(long pullerThreadDeadnessThresholdMs)
    {
      _pullerThreadDeadnessThresholdMs = pullerThreadDeadnessThresholdMs;
    }


    public DatabusClientNode.Config getCluster()
    {
      return _cluster;
    }

    public void setCluster(DatabusClientNode.Config cluster)
    {
      _cluster = cluster;
    }
    public DatabusSourcesConnection.Config getConnectionDefaults()
    {
      return _connectionDefaults;
    }

    public void setConnectionDefaults(DatabusSourcesConnection.Config connectionDefaults)
    {
      _connectionDefaults = connectionDefaults;
    }

    public boolean isEnablePerConnectionStats()
    {
      return _enablePerConnectionStats;
    }

    public void setEnablePerConnectionStats(boolean enablePerConnectionStats)
    {
      _enablePerConnectionStats = enablePerConnectionStats;
    }

    /**
     * The relays used by the client can be configured either statically or, with a ZooKeeper
     * setup, dynamically.  The v2 codebase (this) supports only static configs, but the v3
     * codebase (@see DatabusHttpV3ClientImpl) supports both.  Note that this setting is
     * independent of the v2 vs. v3 (vs. v4) <em>protocol</em>; static configuration may be
     * desirable even for clients using the latest protocol version.  Also note that while
     * "v2" and "v3" are often conflated with Oracle and Espresso source databases, respectively,
     * that is more or less an accidental and temporary state of affairs.
     */
    public boolean usesDynamicRelayConfiguration()
    {
      return false;
    }
  }

  public static class Config extends StaticConfigBuilderBase
  implements ConfigBuilder<StaticConfig>
  {
    public Config()
    {
      super();
    }

    @Override
    public StaticConfig build() throws InvalidConfigException
    {
      verifyConfig();
      //the default connection configs inherit from clientConfigs; primarily for backward compatibility
      getConnectionDefaults().setConsumeCurrent(!getRuntime().getBootstrap().isEnabled());
      getConnectionDefaults().setPullerBufferUtilizationPct(_pullerBufferUtilizationPct);
      getConnectionDefaults().setReadLatestScnOnError(_enableReadLatestOnRelayFallOff);
      ServerContainer.StaticConfig sconfig = getContainer().build();
      getConnectionDefaults().setId(sconfig.getId());

      //per connection configs: retain explicit settings or defaults of SourcesConnection
      _connConfigs =  new HashMap<List<String>, DatabusSourcesConnection.StaticConfig>((int)(_connections.size() * 1.3));
      for (String connKey: _connections.keySet() )
      {
        String[] keySources = connKey.split("[,]");
        for (int i = 0; i < keySources.length; ++i) keySources[i] = keySources[i].trim();
        DatabusSourcesConnection.Config confBuilder = _connections.get(connKey);
        confBuilder.setId(sconfig.getId());
        _connConfigs.put(Arrays.asList(keySources), confBuilder.build());
      }

      Map<String, ClusterRegistrationStaticConfig> clientClusterStaticConfigs = new HashMap<String, ClusterRegistrationStaticConfig>();

      for (Entry<String, ClusterRegistrationConfig> e : _clientClusters.entrySet())
      {
        String clusterName = e.getValue().getClusterName();

        if (clientClusterStaticConfigs.containsKey(clusterName))
            throw new InvalidConfigException("Duplicate configuration for client cluster :" + clusterName);

        ClusterRegistrationStaticConfig c = e.getValue().build();

        clientClusterStaticConfigs.put(clusterName, c);
      }

      return new StaticConfig(getCheckpointPersistence().build(), sconfig,
                              getRuntime(), getRuntimeConfigPrefix(),
                              getConnectionDefaults().build(),
                              _connConfigs,
                              getHttpStatsCollector().build(),
                              getLoggingListener().build(),
                              getCluster().build(),
                              _dscUpdateIntervalMs,
                              _pullerThreadDeadnessThresholdMs,
                              _pullerBufferUtilizationPct,
                              _enableReadLatestOnRelayFallOff,
                              _enablePerConnectionStats,
                              usesDynamicRelayConfiguration(),
                              clientClusterStaticConfigs);
    }

  }

  /**
   * TODO: (DDSDBUS-93) the design needs to be re-visit - when there are multiple connections,
   * what status shall we return on behaver of the client? For now, we just return
   * the first one.
   */
  protected class DatabusHttpClientStatus extends DatabusComponentStatus
  {
    public DatabusHttpClientStatus()
    {
      super("DatabusHttpClientImpl");
    }

    @Override
    public Status getStatus()
    {
      if ( !_relayConnections.isEmpty())
        return _relayConnections.get(0).getConnectionStatus().getStatus();
      else if ( ! _regList.isEmpty())
        return _regList.get(0).getStatus().getStatus();

      return null;
    }

    @Override
    public String getMessage()
    {
      if ( !_relayConnections.isEmpty())
        return _relayConnections.get(0).getConnectionStatus().getMessage();
      else if ( ! _regList.isEmpty())
        return _regList.get(0).getStatus().getMessage();

      return null;
    }

    @Override
    public void pause()
    {
      super.pause();

      for (DatabusSourcesConnection relayConn: _relayConnections)
      {
        relayConn.getConnectionStatus().pause();
      }

      for(DatabusRegistration reg : _regList)
      {
        LOG.info("Pausing registration :" + reg.getRegistrationId());
        try
        {
          if (reg.getState().isRunning())
            reg.pause();
        } catch (Exception ex) {
          LOG.error("Unable to pause registration :" + reg.getRegistrationId(),ex);
        }
      }
    }

    @Override
    public void resume()
    {
      for (DatabusSourcesConnection relayConn: _relayConnections)
      {
        relayConn.getConnectionStatus().resume();
      }

      for(DatabusRegistration reg : _regList)
      {
        LOG.info("Resuming registration :" + reg.getRegistrationId());
        try
        {
          if (reg.getState() == RegistrationState.PAUSED)
            reg.resume();
        } catch (Exception ex) {
          LOG.error("Unable to resume registration :" + reg.getRegistrationId(),ex);
        }
      }

      super.resume();
    }

    @Override
    public void suspendOnError(Throwable cause)
    {
      super.suspendOnError(cause);

      for (DatabusSourcesConnection relayConn: _relayConnections)
      {
        relayConn.getConnectionStatus().suspendOnError(cause);
      }

      for(DatabusRegistration reg : _regList)
      {
        LOG.info("Suspending registration :" + reg.getRegistrationId());
        try
        {
          if (reg.getState().isRunning())
            reg.suspendOnError(cause);
        } catch (Exception ex) {
          LOG.error("Unable to suspend registration :" + reg.getRegistrationId(),ex);
        }
      }
    }

    @Override
    public void shutdown()
    {
      for (DatabusSourcesConnection relayConn: _relayConnections)
      {
        relayConn.getConnectionStatus().shutdown();
      }

      for(DatabusRegistration reg : _regList)
      {
        LOG.info("Shutting down registration :" + reg.getRegistrationId());
        try
        {
          if (reg.getState() != RegistrationState.SHUTDOWN)
            reg.shutdown();
        } catch (Exception ex) {
          LOG.error("Unable to shutdown registration :" + reg.getRegistrationId(),ex);
        }
      }

      super.shutdown();
    }

    public Map<List<DatabusSubscription>,  Set<ServerInfo>> getRelayGroups()
    {
      return _relayGroups;
    }
  }

  public DatabusV3Registration getRegistration(RegistrationId rid)
  {
    throw new UnsupportedOperationException();
  }

  public DbusEventFactory getEventFactory()
  {
    return _eventFactory;
  }

  /**
   * Fetch all the client clusters which have been registered in this client instance keyed by their
   * registrationIds. This has been overridden by V3 client to provide both V2 and V3 clusters.
   *
   * Only a copy of the registration ids are returned. Hence modifying the registration ids should not
   * affect the global Registration Id map.
   * @return Client clusters registered in this client instance keyed by their registration ids.
   */
  public Map<RegistrationId, DbusClusterInfo> getAllClientClusters()
  {
    Map<RegistrationId, DbusClusterInfo> clusters = new HashMap<RegistrationId, DbusClusterInfo>();

    Collection<DatabusMultiPartitionRegistration> regs =
        getAllClientClusterRegistrations();
    for (DatabusMultiPartitionRegistration reg : regs)
    {
      if (reg instanceof DatabusV2ClusterRegistrationImpl)
      {
        DatabusV2ClusterRegistrationImpl r = (DatabusV2ClusterRegistrationImpl) reg;
        clusters.put(new RegistrationId(r.getRegistrationId().getId()),r.getClusterInfo());
      }
    }
    return clusters;
  }
}
TOP

Related Classes of com.linkedin.databus.client.DatabusHttpClientImpl$BootstrapClientRuntimeConfigBuilder

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.