/**
   * Retrieves acknowledgement information for a given outbound (and inbound) sequence
   *
   * @param outboundSequenceId outbound sequence identifier
   * @return acknowledgement information for a given outbound sequence
   * @throws UnknownSequenceException if no such sequence exits for a given sequence identifier
   */
  public AcknowledgementData getAcknowledgementData(String outboundSequenceId)
      throws UnknownSequenceException {
    assert sequenceManager != null;

    AcknowledgementData.Builder ackDataBuilder = AcknowledgementData.getBuilder();
    Sequence inboundSequence = sequenceManager.getBoundSequence(outboundSequenceId);
    if (inboundSequence != null) {
      /**
       * If inbound sequence exists, we are not checking if inboundSequence.isAckRequested() is
       * true. Instead, we are allways attaching inbound sequence acknowledegements (even if not
       * requested by the other side) This is to avoid potential locks in InOrder
       * delivery/redelivery scenarios.
       *
       * <p>For example, following could happen on the client side with InOrder enabled if we
       * strictly checked for inboundSequence.isAckRequested() to be true:
       *
       * <p>0. response to a previous client request arrives, endpoint is waiting for an
       * acknowledgement 1. client request is put to delivery queue 2. acknowledgements are attached
       * to the client request and ackRequested flag is cleared on inbound sequence 3. client
       * request gets lost. 4. client request is scheduled for a resend 5. client request is put to
       * delivery queue 6. this time, ackRequested flag is clear, so we will not append any
       * acknowledgements 7. client request is processed on the endpoint and response is put to the
       * endpoint's source delivery queue 8. since there was no acknowledgement of the previous
       * response, the new response is blocekd in the delivery queue forever
       *
       * <p>After step 8., communication between client and endpoint might freeze in a deadlock
       * unless another means of communicating the sequence acknowledgements from client to the
       * endpoint are established.
       */
      ackDataBuilder.acknowledgements(
          inboundSequence.getId(),
          inboundSequence.getAcknowledgedMessageNumbers(),
          inboundSequence.isClosed());
      inboundSequence.clearAckRequestedFlag();
    }
    // outbound sequence ack requested flag
    final Sequence outboundSequence = sequenceManager.getSequence(outboundSequenceId);
    if (outboundSequence.hasUnacknowledgedMessages()) {
      ackDataBuilder.ackReqestedSequenceId(outboundSequenceId);
      outboundSequence.updateLastAcknowledgementRequestTime();
    }
    final AcknowledgementData acknowledgementData = ackDataBuilder.build();
    return acknowledgementData;
  }
  /**
   * Registers outgoing message with the provided outbound sequence and sets sequenceId and
   * messageNumber properties on the outgoing message.
   *
   * <p>Once the message is registered and properties are set, the message is placed into a delivery
   * queue and delivery callback is invoked.
   *
   * @throws UnknownSequenceException if no such sequence exits for a given sequence identifier
   */
  public void registerMessage(
      @NotNull ApplicationMessage outMessage, @NotNull String outboundSequenceId)
      throws DuplicateMessageRegistrationException, UnknownSequenceException {
    assert sequenceManager != null;
    assert outMessage != null;
    assert outboundSequenceId != null;

    final Sequence outboundSequence = sequenceManager.getSequence(outboundSequenceId);
    outboundSequence.registerMessage(
        outMessage, true); // TODO it may not be needed to store message if AtMostOnce delivery
  }
  public void putToDeliveryQueue(ApplicationMessage message) throws RxRuntimeException {
    assert sequenceManager != null;

    if (LOGGER.isLoggable(Level.FINER)) {
      LOGGER.finer(
          String.format(
              "Putting a message with number [ %d ] to the delivery queue of a sequence [ %s ]",
              message.getMessageNumber(), message.getSequenceId()));
    }
    sequenceManager.getSequence(message.getSequenceId()).getDeliveryQueue().put(message);
  }