/**
   * Starts the consumer which will register a queue or topic listener with Hazelcast and enable
   * message consumption (push or pull). If the consumer is already active, this method does
   * nothing.
   */
  void start() {
    if (active) {
      return;
    }

    receiveLock.lock();
    try {
      // Start listening for events. We currently always listen for events even
      // if we don't have a message listener. If this has a performance impact
      // on Hazelcast we may want to only listen if there is a registered
      // message listener that we need to notify.
      IQueue<byte[]> queue = hazelcastMQContext.resolveQueue(destination);
      if (queue != null) {
        // Get the raw queue outside of any transactional context so we can add
        // an item listener.
        queue = config.getHazelcastInstance().getQueue(queue.getName());
        queueListener = new HzQueueListener(queue);
      }

      // If we are a consumer on a topic, immediately start listening for events
      // so we can buffer them for (a)synchronous consumption.
      ITopic<byte[]> topic = hazelcastMQContext.resolveTopic(destination);
      if (topic != null) {
        topicListener = new HzTopicListener(topic);
      }

      active = true;

      if (messageListener != null) {
        // We have a message listener, so tell the context to drain the dispatch
        // ready queues.
        hazelcastMQContext.onConsumerDispatchReady(id);
      } else {
        // Signal that any receive requests can continue.
        receiveCondition.signalAll();
      }
    } finally {
      receiveLock.unlock();
    }
  }
  /**
   * Attempts to receive a message using the given strategy. The method will continue to attempt to
   * receive until either {@link ReceiveStrategy#isRetryable()} returns false, a message is
   * received, or the consumer is stopped.
   *
   * @param strategy the strategy to use for receiving the message and determining retries
   * @return the message or null if no message was received
   */
  private HazelcastMQMessage doReceive(ReceiveStrategy strategy) {

    HazelcastMQMessage msg = null;

    do {
      receiveLock.lock();
      try {

        IQueue<byte[]> queue = hazelcastMQContext.resolveQueue(destination);

        if (queue == null && topicListener == null) {
          throw new HazelcastMQException(
              format("Destination cannot be resolved [%s].", destination));
        } else if (queue == null) {
          queue = topicListener.getQueue();
        }

        byte[] msgData = strategy.receive(queue);
        if (msgData != null) {
          msg = config.getMessageConverter().toMessage(msgData);
        }

        // Check for message expiration if we have a message with expiration
        // time.
        if (msg != null && msg.getHeaders().get(Headers.EXPIRATION) != null) {
          long expirationTime = Long.parseLong(msg.getHeaders().get(Headers.EXPIRATION));

          if (expirationTime != 0 && expirationTime <= System.currentTimeMillis()) {
            log.info("Dropping message [{}] because it has expired.", msg.getId());
            msg = null;
          }
        }
      } finally {
        receiveLock.unlock();
      }
    } while (msg == null && !closed && strategy.isRetryable());

    return msg;
  }