Example #1
0
/**
 * Processes pin requests.
 *
 * <p>A pin request goes through several steps to pin a file on a pool:
 *
 * <p>- Create DB entry in state PINNING - Optionally read the name space entry - Select a read pool
 * (which may involve staging) - Update DB entry with the pool name - Create sticky flag on pool -
 * Update DB entry to state PINNED
 *
 * <p>If during any step the entry is no longer in PINNING then the operation is aborted.
 *
 * <p>If a DB error occurs it is considered fatal and the pinning operation is not completed. The DB
 * entry will stay in PINNING until either explicitly unpinned or it expires.
 *
 * <p>Database operations are blocking. Communication with PoolManager and pools is asynchronous.
 */
public class PinRequestProcessor implements CellMessageReceiver {
  private static final Logger _log = LoggerFactory.getLogger(PinRequestProcessor.class);

  /** The delay we use after a pin request failed and before retrying the request. */
  private static final long RETRY_DELAY = SECONDS.toMillis(30);

  /**
   * The delay we use after transient failures that should be retried immediately. The small delay
   * prevents tight retry loops.
   */
  private static final long SMALL_DELAY = MILLISECONDS.toMillis(10);

  /** Safety margin added to the lifetime of the sticky bit to account for clock drift. */
  private static final long CLOCK_DRIFT_MARGIN = MINUTES.toMillis(30);

  private static final Set<FileAttribute> REQUIRED_ATTRIBUTES =
      PoolMgrSelectReadPoolMsg.getRequiredAttributes();

  private ScheduledExecutorService _executor;
  private PinDao _dao;
  private CellStub _poolStub;
  private CellStub _pnfsStub;
  private CellStub _poolManagerStub;
  private CheckStagePermission _checkStagePermission;
  private long _maxLifetime;
  private TimeUnit _maxLifetimeUnit;

  private PoolMonitor _poolMonitor;

  @Required
  public void setExecutor(ScheduledExecutorService executor) {
    _executor = executor;
  }

  @Required
  public void setDao(PinDao dao) {
    _dao = dao;
  }

  @Required
  public void setPoolStub(CellStub stub) {
    _poolStub = stub;
  }

  @Required
  public void setPnfsStub(CellStub stub) {
    _pnfsStub = stub;
  }

  @Required
  public void setPoolManagerStub(CellStub stub) {
    _poolManagerStub = stub;
  }

  @Required
  public void setStagePermission(CheckStagePermission checker) {
    _checkStagePermission = checker;
  }

  @Required
  public void setMaxLifetime(long maxLifetime) {
    _maxLifetime = maxLifetime;
  }

  @Required
  public void setPoolMonitor(PoolMonitor poolMonitor) {
    _poolMonitor = poolMonitor;
  }

  public long getMaxLifetime() {
    return _maxLifetime;
  }

  @Required
  public void setMaxLifetimeUnit(TimeUnit unit) {
    _maxLifetimeUnit = unit;
  }

  public TimeUnit getMaxLifetimeUnit() {
    return _maxLifetimeUnit;
  }

  private void enforceLifetimeLimit(PinManagerPinMessage message) {
    if (_maxLifetime > -1) {
      long millis = _maxLifetimeUnit.toMillis(_maxLifetime);
      long requestedLifetime = message.getLifetime();
      if (requestedLifetime == -1) {
        message.setLifetime(millis);
      } else {
        message.setLifetime(Math.min(millis, requestedLifetime));
      }
    }
  }

  public MessageReply<PinManagerPinMessage> messageArrived(PinManagerPinMessage message)
      throws CacheException {
    MessageReply<PinManagerPinMessage> reply = new MessageReply<>();

    enforceLifetimeLimit(message);

    PinTask task = createTask(message, reply);
    if (task != null) {
      if (!task.getFileAttributes().isDefined(REQUIRED_ATTRIBUTES)) {
        rereadNameSpaceEntry(task);
      } else {
        selectReadPool(task);
      }
    }

    return reply;
  }

  protected EnumSet<RequestContainerV5.RequestState> checkStaging(PinTask task) {
    try {
      Subject subject = task.getSubject();
      StorageInfo info = task.getFileAttributes().getStorageInfo();
      return _checkStagePermission.canPerformStaging(subject, info)
          ? RequestContainerV5.allStates
          : RequestContainerV5.allStatesExceptStage;
    } catch (PatternSyntaxException | IOException ex) {
      _log.error("Failed to check stage permission: " + ex);
    }
    return RequestContainerV5.allStatesExceptStage;
  }

  private void retry(final PinTask task, long delay) {
    if (!task.isValidIn(delay)) {
      fail(task, CacheException.TIMEOUT, "Pin request TTL exceeded");
    } else {
      _executor.schedule(
          new Runnable() {
            @Override
            public void run() {
              try {
                rereadNameSpaceEntry(task);
              } catch (CacheException e) {
                fail(task, e.getRc(), e.getMessage());
              } catch (RuntimeException e) {
                fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString());
              }
            }
          },
          delay,
          MILLISECONDS);
    }
  }

  private void fail(PinTask task, int rc, String error) {
    try {
      task.fail(rc, error);
      clearPin(task);
    } catch (RuntimeException e) {
      _log.error(e.toString());
    }
  }

  private void rereadNameSpaceEntry(final PinTask task) throws CacheException {
    /* Ensure that task is still valid and stays valid for the
     * duration of the name space lookup.
     */
    refreshTimeout(task, getExpirationTimeForNameSpaceLookup());

    /* We allow the set of provided attributes to be incomplete
     * and thus add attributes required by pool manager.
     */
    Set<FileAttribute> attributes = EnumSet.noneOf(FileAttribute.class);
    attributes.addAll(task.getFileAttributes().getDefinedAttributes());
    attributes.addAll(PoolMgrSelectReadPoolMsg.getRequiredAttributes());

    _pnfsStub.send(
        new PnfsGetFileAttributes(task.getPnfsId(), attributes),
        PnfsGetFileAttributes.class,
        new AbstractMessageCallback<PnfsGetFileAttributes>() {
          @Override
          public void success(PnfsGetFileAttributes msg) {
            try {
              task.setFileAttributes(msg.getFileAttributes());

              /* Ensure that task is still valid
               * and stays valid for the duration
               * of the pool selection.
               */
              refreshTimeout(task, getExpirationTimeForPoolSelection());
              selectReadPool(task);
            } catch (CacheException e) {
              fail(task, e.getRc(), e.getMessage());
            } catch (RuntimeException e) {
              fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString());
            }
          }

          @Override
          public void failure(int rc, Object error) {
            fail(task, rc, error.toString());
          }

          @Override
          public void noroute(CellPath path) {
            /* PnfsManager is unreachable. We
             * expect this to be a transient
             * problem and retry in a moment.
             */
            retry(task, RETRY_DELAY);
          }

          @Override
          public void timeout(CellPath path) {
            /* PnfsManager did not respond. We
             * expect this to be a transient
             * problem and retry in a moment.
             */
            retry(task, SMALL_DELAY);
          }
        });
  }

  private void selectReadPool(final PinTask task) throws CacheException {
    try {
      PoolSelector poolSelector =
          _poolMonitor.getPoolSelector(task.getFileAttributes(), task.getProtocolInfo(), null);

      PoolInfo pool = poolSelector.selectPinPool();
      setPool(task, pool.getName());
      setStickyFlag(task, pool.getName(), pool.getAddress());
    } catch (FileNotOnlineCacheException e) {
      askPoolManager(task);
    }
  }

  private void askPoolManager(final PinTask task) {
    PoolMgrSelectReadPoolMsg msg =
        new PoolMgrSelectReadPoolMsg(
            task.getFileAttributes(),
            task.getProtocolInfo(),
            task.getReadPoolSelectionContext(),
            checkStaging(task));
    msg.setSubject(task.getSubject());
    msg.setSkipCostUpdate(true);
    _poolManagerStub.send(
        msg,
        PoolMgrSelectReadPoolMsg.class,
        new AbstractMessageCallback<PoolMgrSelectReadPoolMsg>() {
          @Override
          public void success(PoolMgrSelectReadPoolMsg msg) {
            try {
              /* Pool manager expects us
               * to keep some state
               * between retries.
               */
              task.setReadPoolSelectionContext(msg.getContext());

              /* Store the pool name in
               * the DB so we know what to
               * clean up if something
               * fails.
               */
              String poolName = msg.getPoolName();
              CellAddressCore poolAddress = msg.getPoolAddress();
              task.getFileAttributes().getLocations().add(poolName);
              setPool(task, poolName);

              setStickyFlag(task, poolName, poolAddress);
            } catch (CacheException e) {
              fail(task, e.getRc(), e.getMessage());
            } catch (RuntimeException e) {
              fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString());
            }
          }

          @Override
          public void failure(int rc, Object error) {
            /* Pool manager expects us to
             * keep some state between
             * retries.
             */
            task.setReadPoolSelectionContext(getReply().getContext());
            switch (rc) {
              case CacheException.OUT_OF_DATE:
                /* Pool manager asked for a
                 * refresh of the request.
                 * Retry right away.
                 */
                retry(task, 0);
                break;
              case CacheException.FILE_NOT_IN_REPOSITORY:
              case CacheException.PERMISSION_DENIED:
                fail(task, rc, error.toString());
                break;
              default:
                /* Ideally we would delegate the retry to the door,
                 * but for the time being the retry is dealed with
                 * by pin manager.
                 */
                retry(task, RETRY_DELAY);
                break;
            }
          }

          @Override
          public void noroute(CellPath path) {
            /* Pool manager is
             * unreachable. We expect this
             * to be transient and retry in
             * a moment.
             */
            retry(task, RETRY_DELAY);
          }

          @Override
          public void timeout(CellPath path) {
            /* Pool manager did not
             * respond. We expect this to be
             * transient and retry in a
             * moment.
             */
            retry(task, SMALL_DELAY);
          }
        });
  }

  private void setStickyFlag(
      final PinTask task, final String poolName, CellAddressCore poolAddress) {
    /* The pin lifetime should be from the moment the file is
     * actually pinned. Due to staging and pool to pool transfers
     * this may be much later than when the pin was requested.
     */
    Date pinExpiration = task.freezeExpirationTime();

    /* To allow for some drift in clocks we add a safety margin to
     * the lifetime of the sticky bit.
     */
    long poolExpiration =
        (pinExpiration == null) ? -1 : pinExpiration.getTime() + CLOCK_DRIFT_MARGIN;

    PoolSetStickyMessage msg =
        new PoolSetStickyMessage(
            poolName, task.getPnfsId(), true, task.getSticky(), poolExpiration);
    _poolStub.send(
        new CellPath(poolAddress),
        msg,
        PoolSetStickyMessage.class,
        new AbstractMessageCallback<PoolSetStickyMessage>() {
          @Override
          public void success(PoolSetStickyMessage msg) {
            try {
              setToPinned(task);
              task.success();
            } catch (CacheException e) {
              fail(task, e.getRc(), e.getMessage());
            } catch (RuntimeException e) {
              fail(task, CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString());
            }
          }

          @Override
          public void failure(int rc, Object error) {
            switch (rc) {
              case CacheException.POOL_DISABLED:
                /* Pool manager had outdated
                 * information about the pool. Give
                 * it a chance to be updated and
                 * then retry.
                 */
                retry(task, RETRY_DELAY);
                break;
              case CacheException.FILE_NOT_IN_REPOSITORY:
                /* Pnfs manager had stale location
                 * information. The pool clears
                 * this information as a result of
                 * this error, so we retry in a
                 * moment.
                 */
                retry(task, SMALL_DELAY);
                break;
              default:
                fail(task, rc, error.toString());
                break;
            }
          }

          @Override
          public void noroute(CellPath path) {
            /* The pool must have gone down. Give
             * pool manager a moment to notice this
             * and then retry.
             */
            retry(task, RETRY_DELAY);
          }

          @Override
          public void timeout(CellPath path) {
            /* No response from pool. Typically this is
             * because the pool is overloaded.
             */
            fail(task, CacheException.TIMEOUT, "No reply from " + path);
          }
        });
  }

  private Date getExpirationTimeForNameSpaceLookup() {
    long now = System.currentTimeMillis();
    long timeout = _pnfsStub.getTimeoutInMillis();
    return new Date(now + 2 * (timeout + RETRY_DELAY));
  }

  private Date getExpirationTimeForPoolSelection() {
    long now = System.currentTimeMillis();
    long timeout = _poolManagerStub.getTimeoutInMillis();
    return new Date(now + 2 * (timeout + RETRY_DELAY));
  }

  private Date getExpirationTimeForSettingFlag() {
    long now = System.currentTimeMillis();
    long timeout = _poolStub.getTimeoutInMillis();
    return new Date(now + 2 * timeout);
  }

  @Transactional
  protected PinTask createTask(
      PinManagerPinMessage message, MessageReply<PinManagerPinMessage> reply) {
    PnfsId pnfsId = message.getFileAttributes().getPnfsId();

    if (message.getRequestId() != null) {
      Pin pin = _dao.getPin(pnfsId, message.getRequestId());
      if (pin != null) {
        /* In this case the request is a resubmission. If the
         * previous pin completed then use it. Otherwise abort the
         * previous pin and create a new one.
         */
        if (pin.getState() == PINNED) {
          message.setPin(pin);
          reply.reply(message);
          return null;
        }

        pin.setState(UNPINNING);
        pin.setRequestId(null);
        _dao.storePin(pin);
      }
    }

    Pin pin = new Pin(message.getSubject(), pnfsId);
    pin.setRequestId(message.getRequestId());
    pin.setSticky("PinManager-" + UUID.randomUUID().toString());
    pin.setExpirationTime(getExpirationTimeForPoolSelection());

    return new PinTask(message, reply, _dao.storePin(pin));
  }

  /**
   * Load the pin belonging to the PinTask.
   *
   * @throw CacheException if the pin no longer exists or is no longer in PINNING.
   */
  protected Pin loadPinBelongingTo(PinTask task) throws CacheException {
    Pin pin = _dao.getPin(task.getPinId(), task.getSticky(), PINNING);
    if (pin == null) {
      throw new CacheException("Operation was aborted");
    }
    return pin;
  }

  @Transactional(isolation = REPEATABLE_READ)
  protected void refreshTimeout(PinTask task, Date date) throws CacheException {
    Pin pin = loadPinBelongingTo(task);
    pin.setExpirationTime(date);
    task.setPin(_dao.storePin(pin));
  }

  @Transactional(isolation = REPEATABLE_READ)
  protected void setPool(PinTask task, String pool) throws CacheException {
    Pin pin = loadPinBelongingTo(task);
    pin.setExpirationTime(getExpirationTimeForSettingFlag());
    pin.setPool(pool);
    task.setPin(_dao.storePin(pin));
  }

  @Transactional(isolation = REPEATABLE_READ)
  protected void setToPinned(PinTask task) throws CacheException {
    Pin pin = loadPinBelongingTo(task);
    pin.setExpirationTime(task.getExpirationTime());
    pin.setState(PINNED);
    task.setPin(_dao.storePin(pin));
  }

  @Transactional
  protected void clearPin(PinTask task) {
    if (task.getPool() != null) {
      /* If the pin record expired or the pin was explicitly
       * unpinned, then the unpin processor may already have
       * submitted a request to the pool to clear the sticky
       * flag. Although out of order delivery of messages is
       * unlikely, if it would happen then we have a race
       * between the set sticky and clear sticky messages. To
       * cover this case we delete the old record and create a
       * fresh one in UNPINNING.
       */
      _dao.deletePin(task.getPin());
      Pin pin = new Pin(task.getSubject(), task.getPnfsId());
      pin.setState(UNPINNING);
      _dao.storePin(pin);
    } else {
      /* We didn't create a sticky flag yet, so there is no
       * reason to keep the record. It will expire by itself,
       * but we delete the record now to avoid that we get
       * tickets from admins wondering why they have records
       * staying in PINNING.
       */
      _dao.deletePin(task.getPin());
    }
  }
}
/** Convenience "factory" class to facilitate opening a {@link Connection} to an AMQP broker. */
public class ConnectionFactory implements Cloneable {

  /** Default user name */
  public static final String DEFAULT_USER = "******";
  /** Default password */
  public static final String DEFAULT_PASS = "******";
  /** Default virtual host */
  public static final String DEFAULT_VHOST = "/";
  /** Default maximum channel number; zero for unlimited */
  public static final int DEFAULT_CHANNEL_MAX = 0;
  /** Default maximum frame size; zero means no limit */
  public static final int DEFAULT_FRAME_MAX = 0;
  /** Default heart-beat interval; 60 seconds */
  public static final int DEFAULT_HEARTBEAT = 60;
  /** The default host */
  public static final String DEFAULT_HOST = "localhost";
  /** 'Use the default port' port */
  public static final int USE_DEFAULT_PORT = -1;
  /** The default non-ssl port */
  public static final int DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT;
  /** The default ssl port */
  public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671;
  /** The default TCP connection timeout: 60 seconds */
  public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
  /**
   * The default AMQP 0-9-1 connection handshake timeout. See DEFAULT_CONNECTION_TIMEOUT for TCP
   * (socket) connection timeout.
   */
  public static final int DEFAULT_HANDSHAKE_TIMEOUT = 10000;
  /** The default shutdown timeout; zero means wait indefinitely */
  public static final int DEFAULT_SHUTDOWN_TIMEOUT = 10000;

  /** The default continuation timeout for RPC calls in channels: 10 minutes */
  public static final int DEFAULT_CHANNEL_RPC_TIMEOUT = (int) MINUTES.toMillis(10);

  private static final String PREFERRED_TLS_PROTOCOL = "TLSv1.2";

  private static final String FALLBACK_TLS_PROTOCOL = "TLSv1";

  private String username = DEFAULT_USER;
  private String password = DEFAULT_PASS;
  private String virtualHost = DEFAULT_VHOST;
  private String host = DEFAULT_HOST;
  private int port = USE_DEFAULT_PORT;
  private int requestedChannelMax = DEFAULT_CHANNEL_MAX;
  private int requestedFrameMax = DEFAULT_FRAME_MAX;
  private int requestedHeartbeat = DEFAULT_HEARTBEAT;
  private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
  private int handshakeTimeout = DEFAULT_HANDSHAKE_TIMEOUT;
  private int shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT;
  private Map<String, Object> _clientProperties = AMQConnection.defaultClientProperties();
  private SocketFactory factory = SocketFactory.getDefault();
  private SaslConfig saslConfig = DefaultSaslConfig.PLAIN;
  private ExecutorService sharedExecutor;
  private ThreadFactory threadFactory = Executors.defaultThreadFactory();
  // minimises the number of threads rapid closure of many
  // connections uses, see rabbitmq/rabbitmq-java-client#86
  private ExecutorService shutdownExecutor;
  private ScheduledExecutorService heartbeatExecutor;
  private SocketConfigurator socketConf = new DefaultSocketConfigurator();
  private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();

  private boolean automaticRecovery = true;
  private boolean topologyRecovery = true;

  // long is used to make sure the users can use both ints
  // and longs safely. It is unlikely that anybody'd need
  // to use recovery intervals > Integer.MAX_VALUE in practice.
  private long networkRecoveryInterval = 5000;

  private MetricsCollector metricsCollector;

  private boolean nio = false;
  private FrameHandlerFactory frameHandlerFactory;
  private NioParams nioParams = new NioParams();

  private SSLContext sslContext;

  /**
   * Continuation timeout on RPC calls.
   *
   * @since 4.1.0
   */
  private int channelRpcTimeout = DEFAULT_CHANNEL_RPC_TIMEOUT;

  /** @return the default host to use for connections */
  public String getHost() {
    return host;
  }

  /** @param host the default host to use for connections */
  public void setHost(String host) {
    this.host = host;
  }

  public static int portOrDefault(int port, boolean ssl) {
    if (port != USE_DEFAULT_PORT) return port;
    else if (ssl) return DEFAULT_AMQP_OVER_SSL_PORT;
    else return DEFAULT_AMQP_PORT;
  }

  /** @return the default port to use for connections */
  public int getPort() {
    return portOrDefault(port, isSSL());
  }

  /**
   * Set the target port.
   *
   * @param port the default port to use for connections
   */
  public void setPort(int port) {
    this.port = port;
  }

  /**
   * Retrieve the user name.
   *
   * @return the AMQP user name to use when connecting to the broker
   */
  public String getUsername() {
    return this.username;
  }

  /**
   * Set the user name.
   *
   * @param username the AMQP user name to use when connecting to the broker
   */
  public void setUsername(String username) {
    this.username = username;
  }

  /**
   * Retrieve the password.
   *
   * @return the password to use when connecting to the broker
   */
  public String getPassword() {
    return this.password;
  }

  /**
   * Set the password.
   *
   * @param password the password to use when connecting to the broker
   */
  public void setPassword(String password) {
    this.password = password;
  }

  /**
   * Retrieve the virtual host.
   *
   * @return the virtual host to use when connecting to the broker
   */
  public String getVirtualHost() {
    return this.virtualHost;
  }

  /**
   * Set the virtual host.
   *
   * @param virtualHost the virtual host to use when connecting to the broker
   */
  public void setVirtualHost(String virtualHost) {
    this.virtualHost = virtualHost;
  }

  /**
   * Convenience method for setting the fields in an AMQP URI: host, port, username, password and
   * virtual host. If any part of the URI is ommited, the ConnectionFactory's corresponding variable
   * is left unchanged.
   *
   * @param uri is the AMQP URI containing the data
   */
  public void setUri(URI uri)
      throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
    if ("amqp".equals(uri.getScheme().toLowerCase())) {
      // nothing special to do
    } else if ("amqps".equals(uri.getScheme().toLowerCase())) {
      setPort(DEFAULT_AMQP_OVER_SSL_PORT);
      useSslProtocol();
    } else {
      throw new IllegalArgumentException("Wrong scheme in AMQP URI: " + uri.getScheme());
    }

    String host = uri.getHost();
    if (host != null) {
      setHost(host);
    }

    int port = uri.getPort();
    if (port != -1) {
      setPort(port);
    }

    String userInfo = uri.getRawUserInfo();
    if (userInfo != null) {
      String userPass[] = userInfo.split(":");
      if (userPass.length > 2) {
        throw new IllegalArgumentException("Bad user info in AMQP " + "URI: " + userInfo);
      }

      setUsername(uriDecode(userPass[0]));
      if (userPass.length == 2) {
        setPassword(uriDecode(userPass[1]));
      }
    }

    String path = uri.getRawPath();
    if (path != null && path.length() > 0) {
      if (path.indexOf('/', 1) != -1) {
        throw new IllegalArgumentException("Multiple segments in " + "path of AMQP URI: " + path);
      }

      setVirtualHost(uriDecode(uri.getPath().substring(1)));
    }
  }

  /**
   * Convenience method for setting the fields in an AMQP URI: host, port, username, password and
   * virtual host. If any part of the URI is ommited, the ConnectionFactory's corresponding variable
   * is left unchanged. Note that not all valid AMQP URIs are accepted; in particular, the hostname
   * must be given if the port, username or password are given, and escapes in the hostname are not
   * permitted.
   *
   * @param uriString is the AMQP URI containing the data
   */
  public void setUri(String uriString)
      throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
    setUri(new URI(uriString));
  }

  private String uriDecode(String s) {
    try {
      // URLDecode decodes '+' to a space, as for
      // form encoding.  So protect plus signs.
      return URLDecoder.decode(s.replace("+", "%2B"), "US-ASCII");
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Retrieve the requested maximum channel number
   *
   * @return the initially requested maximum channel number; zero for unlimited
   */
  public int getRequestedChannelMax() {
    return this.requestedChannelMax;
  }

  /**
   * Set the requested maximum channel number
   *
   * @param requestedChannelMax initially requested maximum channel number; zero for unlimited
   */
  public void setRequestedChannelMax(int requestedChannelMax) {
    this.requestedChannelMax = requestedChannelMax;
  }

  /**
   * Retrieve the requested maximum frame size
   *
   * @return the initially requested maximum frame size, in octets; zero for unlimited
   */
  public int getRequestedFrameMax() {
    return this.requestedFrameMax;
  }

  /**
   * Set the requested maximum frame size
   *
   * @param requestedFrameMax initially requested maximum frame size, in octets; zero for unlimited
   */
  public void setRequestedFrameMax(int requestedFrameMax) {
    this.requestedFrameMax = requestedFrameMax;
  }

  /**
   * Retrieve the requested heartbeat interval.
   *
   * @return the initially requested heartbeat interval, in seconds; zero for none
   */
  public int getRequestedHeartbeat() {
    return this.requestedHeartbeat;
  }

  /**
   * Set the TCP connection timeout.
   *
   * @param timeout connection TCP establishment timeout in milliseconds; zero for infinite
   */
  public void setConnectionTimeout(int timeout) {
    if (timeout < 0) {
      throw new IllegalArgumentException("TCP connection timeout cannot be negative");
    }
    this.connectionTimeout = timeout;
  }

  /**
   * Retrieve the TCP connection timeout.
   *
   * @return the TCP connection timeout, in milliseconds; zero for infinite
   */
  public int getConnectionTimeout() {
    return this.connectionTimeout;
  }

  /**
   * Retrieve the AMQP 0-9-1 protocol handshake timeout.
   *
   * @return the AMQP0-9-1 protocol handshake timeout, in milliseconds
   */
  public int getHandshakeTimeout() {
    return handshakeTimeout;
  }

  /**
   * Set the AMQP0-9-1 protocol handshake timeout.
   *
   * @param timeout the AMQP0-9-1 protocol handshake timeout, in milliseconds
   */
  public void setHandshakeTimeout(int timeout) {
    if (timeout < 0) {
      throw new IllegalArgumentException("handshake timeout cannot be negative");
    }
    this.handshakeTimeout = timeout;
  }

  /**
   * Set the shutdown timeout. This is the amount of time that Consumer implementations have to
   * continue working through deliveries (and other Consumer callbacks) <b>after</b> the connection
   * has closed but before the ConsumerWorkService is torn down. If consumers exceed this timeout
   * then any remaining queued deliveries (and other Consumer callbacks, <b>including</b> the
   * Consumer's handleShutdownSignal() invocation) will be lost.
   *
   * @param shutdownTimeout shutdown timeout in milliseconds; zero for infinite; default 10000
   */
  public void setShutdownTimeout(int shutdownTimeout) {
    this.shutdownTimeout = shutdownTimeout;
  }

  /**
   * Retrieve the shutdown timeout.
   *
   * @return the shutdown timeout, in milliseconds; zero for infinite
   */
  public int getShutdownTimeout() {
    return shutdownTimeout;
  }

  /**
   * Set the requested heartbeat timeout. Heartbeat frames will be sent at about 1/2 the timeout
   * interval. If server heartbeat timeout is configured to a non-zero value, this method can only
   * be used to lower the value; otherwise any value provided by the client will be used.
   *
   * @param requestedHeartbeat the initially requested heartbeat timeout, in seconds; zero for none
   * @see <a href="http://rabbitmq.com/heartbeats.html">RabbitMQ Heartbeats Guide</a>
   */
  public void setRequestedHeartbeat(int requestedHeartbeat) {
    this.requestedHeartbeat = requestedHeartbeat;
  }

  /**
   * Retrieve the currently-configured table of client properties that will be sent to the server
   * during connection startup. Clients may add, delete, and alter keys in this table. Such changes
   * will take effect when the next new connection is started using this factory.
   *
   * @return the map of client properties
   * @see #setClientProperties
   */
  public Map<String, Object> getClientProperties() {
    return _clientProperties;
  }

  /**
   * Replace the table of client properties that will be sent to the server during subsequent
   * connection startups.
   *
   * @param clientProperties the map of extra client properties
   * @see #getClientProperties
   */
  public void setClientProperties(Map<String, Object> clientProperties) {
    _clientProperties = clientProperties;
  }

  /**
   * Gets the sasl config to use when authenticating
   *
   * @return the sasl config
   * @see com.rabbitmq.client.SaslConfig
   */
  public SaslConfig getSaslConfig() {
    return saslConfig;
  }

  /**
   * Sets the sasl config to use when authenticating
   *
   * @param saslConfig
   * @see com.rabbitmq.client.SaslConfig
   */
  public void setSaslConfig(SaslConfig saslConfig) {
    this.saslConfig = saslConfig;
  }

  /** Retrieve the socket factory used to make connections with. */
  public SocketFactory getSocketFactory() {
    return this.factory;
  }

  /**
   * Set the socket factory used to make connections with. Can be used to enable SSL connections by
   * passing in a javax.net.ssl.SSLSocketFactory instance.
   *
   * @see #useSslProtocol
   */
  public void setSocketFactory(SocketFactory factory) {
    this.factory = factory;
  }

  /**
   * Get the socket configurator.
   *
   * @see #setSocketConfigurator(SocketConfigurator)
   */
  @SuppressWarnings("unused")
  public SocketConfigurator getSocketConfigurator() {
    return socketConf;
  }

  /**
   * Set the socket configurator. This gets a chance to "configure" a socket before it has been
   * opened. The default socket configurator disables Nagle's algorithm.
   *
   * @param socketConfigurator the configurator to use
   */
  public void setSocketConfigurator(SocketConfigurator socketConfigurator) {
    this.socketConf = socketConfigurator;
  }

  /**
   * Set the executor to use for consumer operation dispatch by default for newly created
   * connections. All connections that use this executor share it.
   *
   * <p>It's developer's responsibility to shut down the executor when it is no longer needed.
   *
   * @param executor executor service to be used for consumer operation
   */
  public void setSharedExecutor(ExecutorService executor) {
    this.sharedExecutor = executor;
  }

  /**
   * Set the executor to use for connection shutdown. All connections that use this executor share
   * it.
   *
   * <p>It's developer's responsibility to shut down the executor when it is no longer needed.
   *
   * @param executor executor service to be used for connection shutdown
   */
  public void setShutdownExecutor(ExecutorService executor) {
    this.shutdownExecutor = executor;
  }

  /**
   * Set the executor to use to send heartbeat frames. All connections that use this executor share
   * it.
   *
   * <p>It's developer's responsibility to shut down the executor when it is no longer needed.
   *
   * @param executor executor service to be used to send heartbeat
   */
  public void setHeartbeatExecutor(ScheduledExecutorService executor) {
    this.heartbeatExecutor = executor;
  }

  /**
   * Retrieve the thread factory used to instantiate new threads.
   *
   * @see ThreadFactory
   */
  public ThreadFactory getThreadFactory() {
    return threadFactory;
  }

  /**
   * Set the thread factory used to instantiate new threads.
   *
   * @see ThreadFactory
   */
  public void setThreadFactory(ThreadFactory threadFactory) {
    this.threadFactory = threadFactory;
  }

  /**
   * Get the exception handler.
   *
   * @see com.rabbitmq.client.ExceptionHandler
   */
  public ExceptionHandler getExceptionHandler() {
    return exceptionHandler;
  }

  /**
   * Set the exception handler to use for newly created connections.
   *
   * @see com.rabbitmq.client.ExceptionHandler
   */
  public void setExceptionHandler(ExceptionHandler exceptionHandler) {
    if (exceptionHandler == null) {
      throw new IllegalArgumentException("exception handler cannot be null!");
    }
    this.exceptionHandler = exceptionHandler;
  }

  public boolean isSSL() {
    return getSocketFactory() instanceof SSLSocketFactory;
  }

  /**
   * Convenience method for setting up a SSL socket factory/engine, using the DEFAULT_SSL_PROTOCOL
   * and a trusting TrustManager.
   */
  public void useSslProtocol() throws NoSuchAlgorithmException, KeyManagementException {
    useSslProtocol(
        computeDefaultTlsProcotol(
            SSLContext.getDefault().getSupportedSSLParameters().getProtocols()));
  }

  /**
   * Convenience method for setting up a SSL socket factory/engine, using the supplied protocol and
   * a very trusting TrustManager.
   */
  public void useSslProtocol(String protocol)
      throws NoSuchAlgorithmException, KeyManagementException {
    useSslProtocol(protocol, new NullTrustManager());
  }

  /**
   * Convenience method for setting up an SSL socket factory/engine. Pass in the SSL protocol to
   * use, e.g. "TLSv1" or "TLSv1.2".
   *
   * @param protocol SSL protocol to use.
   */
  public void useSslProtocol(String protocol, TrustManager trustManager)
      throws NoSuchAlgorithmException, KeyManagementException {
    SSLContext c = SSLContext.getInstance(protocol);
    c.init(null, new TrustManager[] {trustManager}, null);
    useSslProtocol(c);
  }

  /**
   * Convenience method for setting up an SSL socket factory/engine. Pass in an initialized
   * SSLContext.
   *
   * @param context An initialized SSLContext
   */
  public void useSslProtocol(SSLContext context) {
    setSocketFactory(context.getSocketFactory());
    this.sslContext = context;
  }

  public static String computeDefaultTlsProcotol(String[] supportedProtocols) {
    if (supportedProtocols != null) {
      for (String supportedProtocol : supportedProtocols) {
        if (PREFERRED_TLS_PROTOCOL.equalsIgnoreCase(supportedProtocol)) {
          return supportedProtocol;
        }
      }
    }
    return FALLBACK_TLS_PROTOCOL;
  }

  /**
   * Returns true if <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, false otherwise
   *
   * @return true if automatic connection recovery is enabled, false otherwise
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public boolean isAutomaticRecoveryEnabled() {
    return automaticRecovery;
  }

  /**
   * Enables or disables <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic
   * connection recovery</a>.
   *
   * @param automaticRecovery if true, enables connection recovery
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public void setAutomaticRecoveryEnabled(boolean automaticRecovery) {
    this.automaticRecovery = automaticRecovery;
  }

  /**
   * Returns true if topology recovery is enabled, false otherwise
   *
   * @return true if topology recovery is enabled, false otherwise
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  @SuppressWarnings("unused")
  public boolean isTopologyRecoveryEnabled() {
    return topologyRecovery;
  }

  /**
   * Enables or disables topology recovery
   *
   * @param topologyRecovery if true, enables topology recovery
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public void setTopologyRecoveryEnabled(boolean topologyRecovery) {
    this.topologyRecovery = topologyRecovery;
  }

  public void setMetricsCollector(MetricsCollector metricsCollector) {
    this.metricsCollector = metricsCollector;
  }

  public MetricsCollector getMetricsCollector() {
    return metricsCollector;
  }

  protected synchronized FrameHandlerFactory createFrameHandlerFactory() throws IOException {
    if (nio) {
      if (this.frameHandlerFactory == null) {
        if (this.nioParams.getNioExecutor() == null && this.nioParams.getThreadFactory() == null) {
          this.nioParams.setThreadFactory(getThreadFactory());
        }
        this.frameHandlerFactory =
            new SocketChannelFrameHandlerFactory(connectionTimeout, nioParams, isSSL(), sslContext);
      }
      return this.frameHandlerFactory;
    } else {
      return new SocketFrameHandlerFactory(
          connectionTimeout, factory, socketConf, isSSL(), this.shutdownExecutor);
    }
  }

  /**
   * Create a new broker connection, picking the first available address from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param addrs an array of known broker addresses (hostname/port pairs) to try in order
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(Address[] addrs) throws IOException, TimeoutException {
    return newConnection(this.sharedExecutor, Arrays.asList(addrs), null);
  }

  /**
   * Create a new broker connection, picking the first available address from the list provided by
   * the {@link AddressResolver}.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address provided by the {@link
   * AddressResolver}.
   *
   * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to
   *     connect to
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(AddressResolver addressResolver)
      throws IOException, TimeoutException {
    return newConnection(this.sharedExecutor, addressResolver, null);
  }

  /**
   * Create a new broker connection with a client-provided name, picking the first available address
   * from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param addrs an array of known broker addresses (hostname/port pairs) to try in order
   * @param clientProvidedName application-specific connection name, will be displayed in the
   *     management UI if RabbitMQ server supports it. This value doesn't have to be unique and
   *     cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed
   *     to be human-readable.
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(Address[] addrs, String clientProvidedName)
      throws IOException, TimeoutException {
    return newConnection(this.sharedExecutor, Arrays.asList(addrs), clientProvidedName);
  }

  /**
   * Create a new broker connection, picking the first available address from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param addrs a List of known broker addresses (hostname/port pairs) to try in order
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(List<Address> addrs) throws IOException, TimeoutException {
    return newConnection(this.sharedExecutor, addrs, null);
  }

  /**
   * Create a new broker connection with a client-provided name, picking the first available address
   * from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param addrs a List of known broker addresses (hostname/port pairs) to try in order
   * @param clientProvidedName application-specific connection name, will be displayed in the
   *     management UI if RabbitMQ server supports it. This value doesn't have to be unique and
   *     cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed
   *     to be human-readable.
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(List<Address> addrs, String clientProvidedName)
      throws IOException, TimeoutException {
    return newConnection(this.sharedExecutor, addrs, clientProvidedName);
  }

  /**
   * Create a new broker connection, picking the first available address from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param executor thread execution service for consumers on the connection
   * @param addrs an array of known broker addresses (hostname/port pairs) to try in order
   * @return an interface to the connection
   * @throws java.io.IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(ExecutorService executor, Address[] addrs)
      throws IOException, TimeoutException {
    return newConnection(executor, Arrays.asList(addrs), null);
  }

  /**
   * Create a new broker connection with a client-provided name, picking the first available address
   * from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param executor thread execution service for consumers on the connection
   * @param addrs an array of known broker addresses (hostname/port pairs) to try in order
   * @param clientProvidedName application-specific connection name, will be displayed in the
   *     management UI if RabbitMQ server supports it. This value doesn't have to be unique and
   *     cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed
   *     to be human-readable.
   * @return an interface to the connection
   * @throws java.io.IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(
      ExecutorService executor, Address[] addrs, String clientProvidedName)
      throws IOException, TimeoutException {
    return newConnection(executor, Arrays.asList(addrs), clientProvidedName);
  }

  /**
   * Create a new broker connection, picking the first available address from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param executor thread execution service for consumers on the connection
   * @param addrs a List of known broker addrs (hostname/port pairs) to try in order
   * @return an interface to the connection
   * @throws java.io.IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(ExecutorService executor, List<Address> addrs)
      throws IOException, TimeoutException {
    return newConnection(executor, addrs, null);
  }

  /**
   * Create a new broker connection, picking the first available address from the list provided by
   * the {@link AddressResolver}.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address provided by the {@link
   * AddressResolver}.
   *
   * @param executor thread execution service for consumers on the connection
   * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to
   *     connect to
   * @return an interface to the connection
   * @throws java.io.IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(ExecutorService executor, AddressResolver addressResolver)
      throws IOException, TimeoutException {
    return newConnection(executor, addressResolver, null);
  }

  /**
   * Create a new broker connection with a client-provided name, picking the first available address
   * from the list.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address from the provided list.
   *
   * @param executor thread execution service for consumers on the connection
   * @param addrs a List of known broker addrs (hostname/port pairs) to try in order
   * @param clientProvidedName application-specific connection name, will be displayed in the
   *     management UI if RabbitMQ server supports it. This value doesn't have to be unique and
   *     cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed
   *     to be human-readable.
   * @return an interface to the connection
   * @throws java.io.IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(
      ExecutorService executor, List<Address> addrs, String clientProvidedName)
      throws IOException, TimeoutException {
    return newConnection(executor, createAddressResolver(addrs), clientProvidedName);
  }

  /**
   * Create a new broker connection with a client-provided name, picking the first available address
   * from the list provided by the {@link AddressResolver}.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Future reconnection attempts will pick a random accessible address provided by the {@link
   * AddressResolver}.
   *
   * @param executor thread execution service for consumers on the connection
   * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to
   *     connect to
   * @param clientProvidedName application-specific connection name, will be displayed in the
   *     management UI if RabbitMQ server supports it. This value doesn't have to be unique and
   *     cannot be used as a connection identifier e.g. in HTTP API requests. This value is supposed
   *     to be human-readable.
   * @return an interface to the connection
   * @throws java.io.IOException if it encounters a problem
   * @see <a href="http://www.rabbitmq.com/api-guide.html#recovery">Automatic Recovery</a>
   */
  public Connection newConnection(
      ExecutorService executor, AddressResolver addressResolver, String clientProvidedName)
      throws IOException, TimeoutException {
    if (this.metricsCollector == null) {
      this.metricsCollector = new NoOpMetricsCollector();
    }
    // make sure we respect the provided thread factory
    FrameHandlerFactory fhFactory = createFrameHandlerFactory();
    ConnectionParams params = params(executor);
    // set client-provided via a client property
    if (clientProvidedName != null) {
      Map<String, Object> properties = new HashMap<String, Object>(params.getClientProperties());
      properties.put("connection_name", clientProvidedName);
      params.setClientProperties(properties);
    }

    if (isAutomaticRecoveryEnabled()) {
      // see com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory#newConnection
      AutorecoveringConnection conn =
          new AutorecoveringConnection(params, fhFactory, addressResolver, metricsCollector);

      conn.init();
      return conn;
    } else {
      List<Address> addrs = addressResolver.getAddresses();
      IOException lastException = null;
      for (Address addr : addrs) {
        try {
          FrameHandler handler = fhFactory.create(addr);
          AMQConnection conn = new AMQConnection(params, handler, metricsCollector);
          conn.start();
          this.metricsCollector.newConnection(conn);
          return conn;
        } catch (IOException e) {
          lastException = e;
        }
      }
      throw (lastException != null) ? lastException : new IOException("failed to connect");
    }
  }

  public ConnectionParams params(ExecutorService consumerWorkServiceExecutor) {
    ConnectionParams result = new ConnectionParams();

    result.setUsername(username);
    result.setPassword(password);
    result.setConsumerWorkServiceExecutor(consumerWorkServiceExecutor);
    result.setVirtualHost(virtualHost);
    result.setClientProperties(getClientProperties());
    result.setRequestedFrameMax(requestedFrameMax);
    result.setRequestedChannelMax(requestedChannelMax);
    result.setShutdownTimeout(shutdownTimeout);
    result.setSaslConfig(saslConfig);
    result.setNetworkRecoveryInterval(networkRecoveryInterval);
    result.setTopologyRecovery(topologyRecovery);
    result.setExceptionHandler(exceptionHandler);
    result.setThreadFactory(threadFactory);
    result.setHandshakeTimeout(handshakeTimeout);
    result.setRequestedHeartbeat(requestedHeartbeat);
    result.setShutdownExecutor(shutdownExecutor);
    result.setHeartbeatExecutor(heartbeatExecutor);
    result.setChannelRpcTimeout(channelRpcTimeout);
    return result;
  }

  /**
   * Create a new broker connection.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Reconnection attempts will always use the address configured on {@link ConnectionFactory}.
   *
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection() throws IOException, TimeoutException {
    return newConnection(
        this.sharedExecutor, Collections.singletonList(new Address(getHost(), getPort())));
  }

  /**
   * Create a new broker connection.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Reconnection attempts will always use the address configured on {@link ConnectionFactory}.
   *
   * @param connectionName arbitrary sring for connection name client property
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(String connectionName) throws IOException, TimeoutException {
    return newConnection(
        this.sharedExecutor,
        Collections.singletonList(new Address(getHost(), getPort())),
        connectionName);
  }

  /**
   * Create a new broker connection.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Reconnection attempts will always use the address configured on {@link ConnectionFactory}.
   *
   * @param executor thread execution service for consumers on the connection
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(ExecutorService executor) throws IOException, TimeoutException {
    return newConnection(executor, Collections.singletonList(new Address(getHost(), getPort())));
  }

  /**
   * Create a new broker connection.
   *
   * <p>If <a href="http://www.rabbitmq.com/api-guide.html#recovery">automatic connection
   * recovery</a> is enabled, the connection returned by this method will be {@link Recoverable}.
   * Reconnection attempts will always use the address configured on {@link ConnectionFactory}.
   *
   * @param executor thread execution service for consumers on the connection
   * @param connectionName arbitrary sring for connection name client property
   * @return an interface to the connection
   * @throws IOException if it encounters a problem
   */
  public Connection newConnection(ExecutorService executor, String connectionName)
      throws IOException, TimeoutException {
    return newConnection(
        executor, Collections.singletonList(new Address(getHost(), getPort())), connectionName);
  }

  protected AddressResolver createAddressResolver(List<Address> addresses) {
    if (addresses.size() == 1) {
      return new DnsRecordIpAddressResolver(addresses.get(0), isSSL());
    } else {
      return new ListAddressResolver(addresses);
    }
  }

  @Override
  public ConnectionFactory clone() {
    try {
      return (ConnectionFactory) super.clone();
    } catch (CloneNotSupportedException e) {
      throw new Error(e);
    }
  }

  /**
   * Returns automatic connection recovery interval in milliseconds.
   *
   * @return how long will automatic recovery wait before attempting to reconnect, in ms; default is
   *     5000
   */
  public long getNetworkRecoveryInterval() {
    return networkRecoveryInterval;
  }

  /**
   * Sets connection recovery interval. Default is 5000.
   *
   * @param networkRecoveryInterval how long will automatic recovery wait before attempting to
   *     reconnect, in ms
   */
  public void setNetworkRecoveryInterval(int networkRecoveryInterval) {
    this.networkRecoveryInterval = networkRecoveryInterval;
  }

  /**
   * Sets connection recovery interval. Default is 5000.
   *
   * @param networkRecoveryInterval how long will automatic recovery wait before attempting to
   *     reconnect, in ms
   */
  public void setNetworkRecoveryInterval(long networkRecoveryInterval) {
    this.networkRecoveryInterval = networkRecoveryInterval;
  }

  /**
   * Sets the parameters when using NIO.
   *
   * @param nioParams
   * @see NioParams
   */
  public void setNioParams(NioParams nioParams) {
    this.nioParams = nioParams;
  }

  /**
   * Use non-blocking IO (NIO) for communication with the server. With NIO, several connections
   * created from the same {@link ConnectionFactory} can use the same IO thread.
   *
   * <p>A client process using a lot of not-so-active connections can benefit from NIO, as it would
   * use fewer threads than with the traditional, blocking IO mode.
   *
   * <p>Use {@link NioParams} to tune NIO and a {@link SocketChannelConfigurator} to configure the
   * underlying {@link java.nio.channels.SocketChannel}s for connections.
   *
   * @see NioParams
   * @see SocketChannelConfigurator
   * @see java.nio.channels.SocketChannel
   * @see java.nio.channels.Selector
   */
  public void useNio() {
    this.nio = true;
  }

  /**
   * Use blocking IO for communication with the server. With blocking IO, each connection creates
   * its own thread to read data from the server.
   */
  public void useBlockingIo() {
    this.nio = false;
  }

  /**
   * Set the continuation timeout for RPC calls in channels. Default is 10 minutes. 0 means no
   * timeout.
   *
   * @param channelRpcTimeout
   */
  public void setChannelRpcTimeout(int channelRpcTimeout) {
    if (channelRpcTimeout < 0) {
      throw new IllegalArgumentException("Timeout cannot be less than 0");
    }
    this.channelRpcTimeout = channelRpcTimeout;
  }

  /**
   * Get the timeout for RPC calls in channels.
   *
   * @return
   */
  public int getChannelRpcTimeout() {
    return channelRpcTimeout;
  }
}