/**
   * Cancel a previous subscription.
   *
   * @param destinationType The destination type (TOPIC or QUEUE).
   * @param destinationName The destination name (e.g., /topic/.*).
   * @param acceptRequest An AcceptRequest object used handling Accept messages.
   */
  public void unsubscribe(
      NetAction.DestinationType destinationType,
      String destinationName,
      AcceptRequest acceptRequest)
      throws Throwable {
    if ((StringUtils.isNotBlank(destinationName)) && (destinationType != null)) {
      NetUnsubscribe unsubs = new NetUnsubscribe(destinationName, destinationType);
      if (acceptRequest != null) {
        unsubs.setActionId(acceptRequest.getActionId());
        PendingAcceptRequestsManager.addAcceptRequest(acceptRequest);
      }
      NetAction action = new NetAction(ActionType.UNSUBSCRIBE);
      action.setUnsbuscribeMessage(unsubs);

      NetMessage message = buildMessage(action);

      getNetHandler().sendMessage(message);

      Map<String, BrokerAsyncConsumer> subscriptions =
          _consumerList.get(
              destinationType.equals(DestinationType.TOPIC)
                  ? DestinationType.TOPIC
                  : DestinationType.QUEUE);
      subscriptions.remove(destinationName);
    } else {
      throw new IllegalArgumentException("Mal-formed Unsubscribe request");
    }
  }
  /**
   * Publishes a message to a queue.
   *
   * @param brokerMessage The Broker message containing the payload.
   * @param destinationName The destination name (e.g. /queue/foo).
   * @param acceptRequest An AcceptRequest object used handling Accept messages.
   */
  public void enqueueMessage(
      NetBrokerMessage brokerMessage, String destinationName, AcceptRequest acceptRequest) {

    if ((brokerMessage != null) && (StringUtils.isNotBlank(destinationName))) {
      NetPublish publish =
          new NetPublish(
              destinationName, pt.com.broker.types.NetAction.DestinationType.QUEUE, brokerMessage);
      if (acceptRequest != null) {
        publish.setActionId(acceptRequest.getActionId());
        PendingAcceptRequestsManager.addAcceptRequest(acceptRequest);
      }

      NetAction action = new NetAction(ActionType.PUBLISH);
      action.setPublishMessage(publish);

      NetMessage msg = buildMessage(action, brokerMessage.getHeaders());

      try {
        getNetHandler().sendMessage(msg);
      } catch (Throwable t) {
        log.error("Failed to deliver message.", t);
      }
    } else {
      throw new IllegalArgumentException("Mal-formed Enqueue request");
    }
  }
  /**
   * Publish a message to a topic.
   *
   * @param brokerMessage The Broker message containing the payload.
   * @param destination The destination name (e.g. /topic/foo).
   * @param acceptRequest An AcceptRequest object used handling Accept messages.
   */
  public void publishMessage(
      NetBrokerMessage brokerMessage, String destination, AcceptRequest acceptRequest) {
    if ((brokerMessage != null) && (StringUtils.isNotBlank(destination))) {
      NetPublish publish =
          new NetPublish(
              destination, pt.com.broker.types.NetAction.DestinationType.TOPIC, brokerMessage);
      if (acceptRequest != null) {
        publish.setActionId(acceptRequest.getActionId());
        PendingAcceptRequestsManager.addAcceptRequest(acceptRequest);
      }
      NetAction action = new NetAction(ActionType.PUBLISH);
      action.setPublishMessage(publish);

      NetMessage msg = buildMessage(action, brokerMessage.getHeaders());

      try {
        getNetHandler().sendMessage(msg);
      } catch (Throwable e) {
        log.error("Could not publish message, messageId:");
        log.error(e.getMessage(), e);
      }
    } else {
      throw new IllegalArgumentException("Mal-formed Publish request");
    }
  }
  /**
   * Checks agent's liveness by sending a Ping message. Waits synchronously by the response.
   *
   * @return A <code>Pong</code> message or <code>null</code> if the agent doesn't answer in 2
   *     seconds;
   */
  public NetPong checkStatus() throws Throwable {
    String actionId = UUID.randomUUID().toString();
    NetPing ping = new NetPing(actionId);

    NetAction action = new NetAction(ActionType.PING);
    action.setPingMessage(ping);

    NetMessage message = buildMessage(action);

    getNetHandler().sendMessage(message);

    long timeout = System.currentTimeMillis() + (2 * 1000);
    NetPong pong = null;

    do {
      synchronized (_bstatus) {
        Sleep.time(500);
        if (System.currentTimeMillis() > timeout) return null;
        pong = _bstatus.peek();
        if (pong == null) continue;
        if (!pong.getActionId().equals(NetPong.getUniversalActionId())
            && !pong.getActionId().equals(actionId)) {
          pong = null;
        }
        _bstatus.remove();
      }
    } while (pong == null);

    return pong;
  }
  /**
   * Acknowledges a received message.
   *
   * @param notification The received notification message
   * @param acceptRequest An AcceptRequest object used handling Accept messages.
   */
  public void acknowledge(NetNotification notification, AcceptRequest acceptRequest)
      throws Throwable {

    if ((notification != null)
        && (notification.getMessage() != null)
        && (StringUtils.isNotBlank(notification.getMessage().getMessageId()))) {
      NetBrokerMessage brkMsg = notification.getMessage();

      if (notification.getDestinationType() == DestinationType.TOPIC) {
        return;
      }

      String ackDestination = notification.getSubscription();

      NetAcknowledge ackMsg = new NetAcknowledge(ackDestination, brkMsg.getMessageId());
      if (acceptRequest != null) {
        ackMsg.setActionId(acceptRequest.getActionId());
        PendingAcceptRequestsManager.addAcceptRequest(acceptRequest);
      }

      NetAction action = new NetAction(ActionType.ACKNOWLEDGE);
      action.setAcknowledgeMessage(ackMsg);
      NetMessage msg = buildMessage(action);

      getNetHandler().sendMessage(msg);

    } else {
      throw new IllegalArgumentException("Can't acknowledge invalid message.");
    }
  }
  /**
   * Obtain a queue message synchronously.
   *
   * @param queueName Name of the queue from where to retrieve a message
   * @param timeout Timeout, in milliseconds. When timeout is reached a TimeoutException is thrown.
   *     Zero means that the client wants to wait forever. A negative value means that the client
   *     doesn't want to wait if there are no messages is local agent's queue.
   * @param reserveTime Message reserve time, in milliseconds. Polled messages are reserved, by
   *     default, for 15 minutes. If clients prefer a different reserve time , bigger or small, they
   *     can specify it.
   * @param acceptRequest An AcceptRequest object used handling Accept messages.
   * @return A notification containing the queue message. Or null if timeout was a negative value
   *     and there was no message in local agent's queue.
   */
  public NetNotification poll(
      String queueName, long timeout, long reserveTime, AcceptRequest acceptRequest)
      throws Throwable {
    if (StringUtils.isBlank(queueName))
      throw new IllegalArgumentException("Mal-formed Poll request. queueName is blank.");

    NetPoll poll = new NetPoll(queueName, timeout);
    NetAction action = new NetAction(ActionType.POLL);
    action.setPollMessage(poll);

    NetMessage message = buildMessage(action);

    SynchronousQueue<NetMessage> synQueue = new SynchronousQueue<NetMessage>();

    synchronized (_syncSubscriptions) {
      if (_syncSubscriptions.containsKey(queueName))
        throw new IllegalArgumentException("Queue " + queueName + " has already a poll runnig.");
      _syncSubscriptions.put(queueName, message);

      pendingPolls.put(queueName, synQueue);
    }

    if (reserveTime > 0) {
      message.getHeaders().put(Headers.RESERVE_TIME, reserveTime + "");
    }

    if (acceptRequest != null) {
      poll.setActionId(acceptRequest.getActionId());
      PendingAcceptRequestsManager.addAcceptRequest(acceptRequest);
    }

    getNetHandler().sendMessage(message);

    NetMessage receivedMsg = synQueue.take();

    synchronized (_syncSubscriptions) {
      _syncSubscriptions.remove(queueName);
      pendingPolls.remove(queueName);
    }

    if (receivedMsg == BrokerProtocolHandler.TimeoutUnblockNotification)
      throw new TimeoutException();
    if (receivedMsg == BrokerProtocolHandler.NoMessageUnblockNotification) return null;

    NetNotification m = null;
    if (receivedMsg.getAction().getActionType().equals(ActionType.NOTIFICATION)) {
      m = receivedMsg.getAction().getNotificationMessage();
    } else {
      log.error(
          "Poll unbloqued by a message that wasn't of any of the expeceted error nor a notification.");
      return null;
    }

    return m;
  }
  protected void sendSubscriptions() throws Throwable {
    for (DestinationType desType : _consumerList.keySet()) {
      Map<String, BrokerAsyncConsumer> subscriptions = _consumerList.get(desType);
      for (BrokerAsyncConsumer aconsumer : subscriptions.values()) {
        NetSubscribe subscription = aconsumer.getSubscription();

        NetAction netAction = new NetAction(ActionType.SUBSCRIBE);
        netAction.setSubscribeMessage(subscription);

        NetMessage msg = buildMessage(netAction, subscription.getHeaders());

        getNetHandler().sendMessage(msg);
        log.info("Reconnected async consumer for '{}'", subscription.getDestination());
      }
    }
    synchronized (_syncSubscriptions) {
      for (String queueName : _syncSubscriptions.keySet()) {
        getNetHandler().sendMessage(_syncSubscriptions.get(queueName));
      }
    }
  }
  /**
   * Create a new asynchronous subscription.
   *
   * @param subscribe A subscription message.
   * @param listener A BrokerListener instance.
   * @param acceptRequest An AcceptRequest object used handling Accept messages.
   */
  public void addAsyncConsumer(
      NetSubscribe subscribe, BrokerListener listener, AcceptRequest acceptRequest)
      throws Throwable {
    if ((subscribe != null) && (StringUtils.isNotBlank(subscribe.getDestination()))) {

      Map<String, BrokerAsyncConsumer> subscriptions =
          _consumerList.get(
              subscribe.getDestinationType().equals(DestinationType.TOPIC)
                  ? DestinationType.TOPIC
                  : DestinationType.QUEUE);

      BrokerAsyncConsumer previous =
          subscriptions.put(
              subscribe.getDestination(), new BrokerAsyncConsumer(subscribe, listener));

      if (previous != null) {
        // A subscription for the given destination already existed. Set it again and
        subscriptions.put(subscribe.getDestination(), previous);

        throw new IllegalStateException("A listener for that Destination already exists");
      }

      if (acceptRequest != null) {
        subscribe.setActionId(acceptRequest.getActionId());
        PendingAcceptRequestsManager.addAcceptRequest(acceptRequest);
      }

      NetAction netAction = new NetAction(ActionType.SUBSCRIBE);
      netAction.setSubscribeMessage(subscribe);

      NetMessage msg = buildMessage(netAction, subscribe.getHeaders());

      getNetHandler().sendMessage(msg);

      log.info("Created new async consumer for '{}'", subscribe.getDestination());
    } else {
      throw new IllegalArgumentException("Mal-formed Notification request");
    }
  }